blob: 8551cde8c3ef81c42ac4000500637b947185b262 [file] [log] [blame]
/* Copyright 2022 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* HyperDebug GPIO logic and console commands */
#include "adc.h"
#include "atomic.h"
#include "builtin/assert.h"
#include "clock.h"
#include "clock_chip.h"
#include "cmsis-dap.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "gpio.h"
#include "gpio_chip.h"
#include "hooks.h"
#include "hwtimer.h"
#include "panic.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"
/*
* Description of the CMSIS-DAP Google vendor extension for GPIO bitbanging.
* Requests and responses begin with a single byte (0x83). The standard
* CMSIS-DAP protocol has all requests fitting in a single 64-byte USB packet.
* Our extension does not adhere to that convention, and treats the USB
* interface as a stream of data without paying attention to packet
* boundaries.
*
* Command GPIO bitbanging (Host to Device):
*
* This request will start or continue a bitbanging waveform. The set of pins
* to operate on, and the clock rate, must have been previously specified
* using the "gpio bit-bang" console command.
*
* The waveform data bytes encode runs of samples to be clocked out, with
* optional delays in between. A run of data bytes is encoded with one byte for
* for each clock tick, all having the MSB (DELAY_BIT) equal to zero, while the
* seven least significant bits encoding values for each of up to seven pins
* (starting from the LSB). A delay between runs is encoded as one or more
* bytes with their MSB (DELAY_BIT) set to one. The low seven bits from each
* such "cluster" is to be all concatenated (least significant bits in first
* bytes), in order to form an integer number of clock ticks of delay. A delay
* of one tick is equivalent to repeating the last sample (and thus does not
* save any memory).
*
* A delay of zero ticks is invalid, so the encoding of one or more consecutive
* bytes with value 0x80, surrounded by bytes with a high bit of zero, is used
* as escape for special features. Currently the four byte sequence: [0x80 0x80
* mask pattern] is used to request an indefinite delay until sampled pins equal
* the given `pattern` for all bits which are set to one in the given `mask`.
*
* +----------------+---------------+-----------------+---------------+
* | cmsis_cmd : 1B | gpio_cmd : 1B | data count : 2B | data (>= 0B) |
* +----------------+---------------+-----------------+---------------+
*
* cmsis_cmd: DAP_GOOG_Gpio (0x83)
*
* gpio_cmd: one of:
* GPIO_REQ_BITBANG (0x10)
* GPIO_REQ_BITBANG_STREAMING (0x11)
*
* data count: 2 byte, zero based count of bytes to follow
*
* data: Up to 65535 bytes of data of bitbanging waveform (format
* described above). The caller should not send more data than
* what has been indicated to be available by the "free count"
* field of a previous response.
*
*
* Response GPIO bitbanging (Device to Host):
*
* For each byte of waveform data sent from host to device (as part of above
* command type), one byte will eventually be returned from device to host using
* this response type (possibly in the immediate response, possibly in a later
* one). The returned bytes will mirror the runs of sample and delay in the
* request stream. Each sample byte in the response will contain the values of
* the involved pins as seen just before the waveform data was applied. That
* is, the values will be shifted by one sample. push-pull pins will always
* read back the same value as from the previous byte in the waveform data,
* open-drain may or may not, and input pins will be unaffected by the value in
* the waveform data. Delay encodings are passed back unchanged.
*
* +---------------+------------+--------------+--------------+--------------+
* | cmsis_cmd: 1B | status: 1B | free cnt: 2B | data cnt: 2B | data (>= 0B) |
* +---------------+------------+--------------+--------------+--------------+
*
* cmsis_cmd: DAP_GOOG_Gpio (0x83)
*
* status: one of:
* STATUS_BITBANG_IDLE (0x00)
* STATUS_BITBANG_ONGOING (0x01)
* STATUS_ERROR_WAVEFORM (0x80)
*
* free count: 2 byte, indicates how many bytes of buffer space will be free
* after this response has been offloaded by HyperDebug. That
* is, this is the maximum number of bytes that the host can
* safely transmit in the next GPIO bitbanging command. The host
* is encouraged to send a zero-byte GPIO bitbanging command the
* first time, for the sole purpose of learning the buffer size
* of HyperDebug.
*
* data count: 2 byte, zero based count of bytes to follow
*
* data: Up to 65535 bytes of data of bitbanging waveform (format
* described above).
*
*/
/* Size of buffer used for bitbanging waveform. */
#define BITBANG_BUFFER_SIZE 16384
/* Size of buffer used for gpio monitoring. */
#define CYCLIC_BUFFER_SIZE 65536
/* Number of concurrent gpio monitoring operations supported. */
#define NUM_CYCLIC_BUFFERS 3
/*
* Declaration of registers for STM32 low power timers
*/
struct lptimer_ctlr {
unsigned int isr;
unsigned int icr;
unsigned int ier;
unsigned int cfgr;
unsigned int cr;
unsigned int cmp;
unsigned int arr;
unsigned int cnt;
unsigned int option_register;
unsigned int reserved;
unsigned int rcr;
};
/* Must be volatile, or compiler optimizes out repeated accesses */
typedef volatile struct lptimer_ctlr lptimer_ctlr_t;
struct pwm_pin_t {
void *timer_regs;
bool is_lp_timer;
uint8_t timer_no;
uint8_t channel; /* Range 1 - 4 */
uint8_t pad_alternate_function;
};
/*
* Rather arbitrarily pretend the low power timers are numbered 11 - 13 (the
* STM32L5 has a "gap" in the numbering of its non-low power timer, so this does
* not create conflicts.
*
* A single numbering scheme is required in order to index into the array
* timer_pwm_use.
*/
#define PWM_LPTIMER_1 11
#define PWM_LPTIMER_2 12
#define PWM_LPTIMER_3 13
#define PWM_TIMER(N) (void *)STM32_TIM_BASE(N), false, PWM_TIMER_##N
#define PWM_LPTIMER(N) (void *)STM32_LPTIM_BASE(N), true, PWM_LPTIMER_##N
/* Sparse array of PWM capabilities for GPIO pins. */
const struct pwm_pin_t pwm_pins[GPIO_COUNT] = {
[GPIO_CN10_31] = { PWM_TIMER(1), 1, 1 }, /* PA8, MCO */
[GPIO_CN10_4] = { PWM_TIMER(1), 1, 1 }, /* PE9 */
[GPIO_CN10_6] = { PWM_TIMER(1), 2, 1 }, /* PE11, QSPI CS */
[GPIO_NUCLEO_LED3] = { PWM_TIMER(1), 2, 1 }, /* PA9 */
[GPIO_CN12_33] = { PWM_TIMER(1), 3, 1 }, /* PA10 */
[GPIO_CN9_22] = { PWM_TIMER(3), 1, 2 }, /* PE3 */
[GPIO_CN7_11] = { PWM_TIMER(3), 1, 2 }, /* PB4 */
[GPIO_CN9_16] = { PWM_TIMER(3), 2, 2 }, /* PE4 */
[GPIO_CN9_18] = { PWM_TIMER(3), 3, 2 }, /* PE5 */
[GPIO_CN9_7] = { PWM_TIMER(3), 3, 2 }, /* PB0 */
[GPIO_CN9_20] = { PWM_TIMER(3), 4, 2 }, /* PE6 */
[GPIO_CN10_7] = { PWM_TIMER(3), 4, 2 }, /* PB1 */
[GPIO_CN9_15] = { PWM_TIMER(4), 1, 2 }, /* PB6 */
[GPIO_CN7_7] = { PWM_TIMER(4), 1, 2 }, /* PD12 */
[GPIO_NUCLEO_LED2] = { PWM_TIMER(4), 2, 2 }, /* PB7 */
[GPIO_CN12_41] = { PWM_TIMER(4), 2, 2 }, /* PD13 */
[GPIO_CN7_16] = { PWM_TIMER(4), 3, 2 }, /* PD14 */
[GPIO_CN7_18] = { PWM_TIMER(4), 4, 2 }, /* PD15 */
[GPIO_CN10_29] = { PWM_TIMER(5), 1, 2 }, /* PA0 */
[GPIO_CN11_9] = { PWM_TIMER(5), 1, 2 }, /* PF6 */
[GPIO_CN10_11] = { PWM_TIMER(5), 2, 2 }, /* PA1 */
[GPIO_CN9_26] = { PWM_TIMER(5), 2, 2 }, /* PF7 */
[GPIO_CN9_3] = { PWM_TIMER(5), 3, 2 }, /* PA2 */
[GPIO_CN9_24] = { PWM_TIMER(5), 3, 2 }, /* PF8 */
[GPIO_CN9_1] = { PWM_TIMER(5), 4, 2 }, /* PA3 */
[GPIO_CN9_28] = { PWM_TIMER(5), 4, 2 }, /* PF9 */
[GPIO_CN7_1] = { PWM_TIMER(8), 1, 3 }, /* PC6 */
[GPIO_NUCLEO_LED1] = { PWM_TIMER(8), 2, 3 }, /* PC7 */
[GPIO_CN8_2] = { PWM_TIMER(8), 3, 3 }, /* PC8 */
[GPIO_CN8_4] = { PWM_TIMER(8), 4, 3 }, /* PC9 */
[GPIO_CN12_28] = { PWM_TIMER(15), 1, 14 }, /* PB14 */
[GPIO_CN11_66] = { PWM_TIMER(15), 1, 14 }, /* PG10 */
[GPIO_CN12_26] = { PWM_TIMER(15), 2, 14 }, /* PB15 */
[GPIO_CN12_42] = { PWM_TIMER(15), 2, 14 }, /* PF10 */
[GPIO_CN10_33] = { PWM_TIMER(16), 1, 14 }, /* PE0 */
[GPIO_CN11_61] = { PWM_TIMER(17), 1, 14 }, /* PE1 */
[GPIO_CN9_13] = { PWM_LPTIMER(1), 1, 1 }, /* PB2 */
[GPIO_CN11_64] = { PWM_LPTIMER(1), 1, 1 }, /* PG15 */
[GPIO_CN7_9] = { PWM_LPTIMER(2), 1, 14 }, /* PA4 */
[GPIO_CN8_16] = { PWM_LPTIMER(3), 1, 2 }, /* PF5 */
[GPIO_CN9_5] = { PWM_LPTIMER(3), 1, 2 }, /* PC3 */
[GPIO_CN10_15] = { PWM_LPTIMER(3), 1, 2 }, /* PB10 */
};
#undef PWM_TIMER
#undef PWM_LPTIMER
struct timer_pwm_use_t {
/*
* Number of channels currently generating PWM waveform based on this
* timer. Hardware timer will be running if and only if this is nonzero.
*/
int num_channels_in_use;
/* Which pin is currently using each timer channel (GPIO_COUNT if none).
*/
int channel_pin[4];
};
struct timer_pwm_use_t timer_pwm_use[18];
struct dac_t {
uint8_t channel_no;
uint32_t enable_mask;
volatile uint32_t *data_register;
};
/* Sparse array of DAC capabilities for GPIO pins. */
const struct dac_t dac_channels[GPIO_COUNT] = {
[GPIO_CN7_9] = { 0, STM32_DAC_CR_EN1, &STM32_DAC_DHR12R1 },
[GPIO_CN7_10] = { 1, STM32_DAC_CR_EN2, &STM32_DAC_DHR12R2 },
};
/*
* A voltage measured in millivolts has to be multiplied by then divided by the
* two values below, in order to get a 12-bit value suitable for putting into
* the DAC register.
*/
int dac_multiplier = 4096, dac_divisor = 3300;
/*
* GPIO structure for keeping extra flags such as GPIO_OPEN_DRAIN, to be applied
* whenever the pin is switched into "alternate" mode.
*/
struct gpio_alt_flags {
/* Port base address */
uint32_t port;
/* Bitmask on that port (multiple bits allowed) */
uint32_t mask;
/* Flags (GPIO_*; see above). */
uint32_t flags;
};
/*
* Construct the gpio_alt_flags array, this really is just a subset of the
* columns in the gpio_alt_funcs array in common/gpio.c (which is not accessible
* from here). This array is used by extra_alternate_flags().
*/
#define ALTERNATE(pinmask, function, module, flagz) \
{ GPIO_##pinmask, .flags = (flagz) },
static __const_data const struct gpio_alt_flags gpio_alt_flags[] = {
#include "gpio.wrap"
};
#undef ALTERNATE
/*
* Which pin of the shield is the RESET signal, that should be pulled down if
* the blue user button is pressed.
*/
int shield_reset_pin = GPIO_COUNT; /* "no pin" value */
/*
* A cyclic buffer is used to record events (edges) of one or more GPIO
* signals. Each event records the time since the previous event, and the
* signal that changed (the direction of change is not explicitly recorded).
*
* So conceptually the buffer entries are pairs of (diff: u64, signal_no: u8).
* These entries are encoded as bytes in the following way: First the timestamp
* diff is shifted left by signal_bits, and the signal_no value put into the
* lower bits freed up this way. Now we have a single u64, which often will be
* a small value (or at least, when the edges happen rapidly, and the need to
* store many of them the highest, then the u64 will be a small value). This
* u64 is then stored 7 bits at a time in successive bytes, with the most
* significant bit indicating whether more bytes belong to the same entry.
*
* The chain of relative timestamps are resolved by keeping two absolute
* timestamps: tail_time is the time of the most recently inserted event, and is
* accessed and updated only by the interrupt handler. head_time is the past
* timestamp on which the diff of the oldest record in the buffer is based (the
* timestamp of the last record to be removed from the buffer), it is accessed
* and updated only from the non-interrupt code that removes records from the
* buffer.
*
* In a similar fashion, the signal level is recorded "at both ends" in for each
* monitored signal by tail_level and head_level, the former only accessed from
* the interrupt handler, and the latter only accessed from non-interrupt code.
*/
struct cyclic_buffer_header_t {
/* Time base that the oldest event is relative to. */
timestamp_t head_time;
/* Time of the most recent event, updated from interrupt context. */
volatile uint32_t tail_time;
/* Index at which new records are placed, updated from interrupt. */
uint8_t *volatile tail;
/* Index of oldest record. */
const uint8_t *head;
/*
* End of cyclic byte buffer. Here tail and head will wrap back to the
* first byte of data[].
*/
uint8_t *end;
/* Sticky bit recording if buffer overrun occurred. */
volatile uint8_t overrun;
/* Number of signals being monitored in this buffer. */
uint8_t num_signals;
/* The number of bits required to represent 0..num_signals-1. */
uint8_t signal_bits;
/* Data contents */
uint8_t data[] __attribute__((aligned(8)));
/*
* WARNING: Any change to this struct must be accompanied by
* corresponding changes in gpio_edge.S.
*/
};
/*
* The STM32L5 has 16 edge detection circuits. Each pin can only be used with
* one of them. That is, detector 0 can take its input from one of pins A0,
* B0, C0, ..., while detector 1 can choose between A1, B1, etc.
*
* Information about the current use of each detection circuit is stored in 16
* "slots" below.
*/
struct monitoring_slot_t {
/* Link to buffer recording edges of this signal. */
struct cyclic_buffer_header_t *buffer;
uint32_t gpio_base;
uint32_t gpio_pin_mask;
/* EC enum id of the signal used by this detection slot. */
int gpio_signal;
/*
* Most recently recorded level of the signal. (0: low, gpio_pin_mask:
* high).
*/
volatile uint32_t tail_level;
/*
* Level as of the current oldest end (head) of the recording. (0: low,
* gpio_pin_mask: high).
*/
uint32_t head_level;
/* The index of the signal as used in the recording buffer. */
uint8_t signal_no;
/*
* The array below will contain a copy of the interrupt handler code, to
* execute from SRAM for speed, as well as for the convenience of being
* able to access member variables above using pc-relative addressing.
*/
uint8_t code[224] __attribute__((aligned(8)));
/*
* WARNING: Any change to this struct must be accompanied by
* corresponding changes in gpio_edge.S.
*/
};
struct monitoring_slot_t monitoring_slots[16];
/*
* Memory area used for allocation of cyclic buffers.
*/
uint8_t buffer_area[NUM_CYCLIC_BUFFERS]
[sizeof(struct cyclic_buffer_header_t) + CYCLIC_BUFFER_SIZE];
static struct cyclic_buffer_header_t *allocate_cyclic_buffer(size_t size)
{
for (int i = 0; i < NUM_CYCLIC_BUFFERS; i++) {
struct cyclic_buffer_header_t *res =
(struct cyclic_buffer_header_t *)buffer_area[i];
if (res->num_signals)
continue;
if (sizeof(struct cyclic_buffer_header_t) + size >
sizeof(buffer_area[i])) {
/* Requested size exceeds the capacity of the area. */
return NULL;
}
/* Will be overwritten with another non-zero value by caller */
res->num_signals = 0xFF;
return res;
}
/* No free buffers */
return NULL;
}
static void free_cyclic_buffer(struct cyclic_buffer_header_t *buf)
{
buf->num_signals = 0;
}
/*
* Counts unacknowledged buffer overruns. Whenever non-zero, the red LED
* will flash.
*/
atomic_t num_cur_error_conditions;
/*
* Counts the number of cyclic buffers currently in existence, the green LED
* will flash whenever this is non-zero, indicating the monitoring activity.
*/
int num_cur_monitoring = 0;
__attribute__((noinline)) void overrun(struct monitoring_slot_t *slot)
{
struct cyclic_buffer_header_t *buffer_header = slot->buffer;
gpio_disable_interrupt(slot->gpio_signal);
if (!buffer_header->overrun) {
buffer_header->overrun = 1;
atomic_add(&num_cur_error_conditions, 1);
}
}
/*
* This interrupt routine is called without the usual wrapper for handling task
* re-scheduling upon entry and exit. This gives lower latency, which is
* critical when recording a sequence of GPIO edges from software as is done
* here. Task-related functions MUST NEVER be called from within this handler.
*/
void gpio_edge(enum gpio_signal signal)
{
}
void edge_int(void);
void edge_int_end(void); /* Not a real function */
struct replacement_instruction_t {
uint32_t count;
uint8_t *location, *location_end;
uint8_t *table, *table_end;
};
extern struct replacement_instruction_t load_pin_mask_replacement;
extern struct replacement_instruction_t signal_no_replacement;
extern struct replacement_instruction_t signal_bits_replacement;
/*
* The arm architecture recognizes the "thumb" 16-bit instruction set by setting
* the least significant bit of the instruction pointer. The code still is
* stored in 16-bit instructions at even addresses, but all function pointers
* has added one to the code addresses. The below macros convert between data
* pointers suitable for memcpy(), and code pointers suitable for jumping to.
* (By clearing or setting the lowest bit.)
*/
#define THUMB_CODE_TO_DATA_PTR(P) ((uint8_t *)((size_t)(P) & ~1U))
#define DATA_TO_THUMB_CODE_PTR(P) ((void (*)(void))((size_t)(P) | 1U))
__attribute__((noinline)) void
replace(struct monitoring_slot_t *slot,
const struct replacement_instruction_t *instr, size_t index)
{
size_t instruction_offset =
instr->location - THUMB_CODE_TO_DATA_PTR(&edge_int);
size_t instruction_size = instr->location_end - instr->location;
ASSERT(instr->table_end - instr->table ==
instr->count * instruction_size);
ASSERT(index < instr->count);
(void)instruction_offset;
memcpy(slot->code + instruction_offset,
THUMB_CODE_TO_DATA_PTR(instr->table) + index * instruction_size,
instruction_size);
}
/*
* Blue user button pressed, assert/deassert the user-specified reset signal.
*/
void user_button_edge(enum gpio_signal signal)
{
int pressed = gpio_get_level(GPIO_NUCLEO_USER_BTN);
if (shield_reset_pin < GPIO_COUNT)
gpio_set_level(shield_reset_pin, !pressed); /* Active low */
}
#define GPIO_IRQ_HIGHEST_PRIORITY(no) \
const struct irq_priority __keep IRQ_PRIORITY(STM32_IRQ_EXTI##no) \
__attribute__((section( \
".rodata.irqprio"))) = { STM32_IRQ_EXTI##no, 0 }
GPIO_IRQ_HIGHEST_PRIORITY(0);
GPIO_IRQ_HIGHEST_PRIORITY(1);
GPIO_IRQ_HIGHEST_PRIORITY(2);
GPIO_IRQ_HIGHEST_PRIORITY(3);
GPIO_IRQ_HIGHEST_PRIORITY(4);
GPIO_IRQ_HIGHEST_PRIORITY(5);
GPIO_IRQ_HIGHEST_PRIORITY(6);
GPIO_IRQ_HIGHEST_PRIORITY(7);
GPIO_IRQ_HIGHEST_PRIORITY(8);
GPIO_IRQ_HIGHEST_PRIORITY(9);
GPIO_IRQ_HIGHEST_PRIORITY(10);
GPIO_IRQ_HIGHEST_PRIORITY(11);
GPIO_IRQ_HIGHEST_PRIORITY(12);
GPIO_IRQ_HIGHEST_PRIORITY(13);
GPIO_IRQ_HIGHEST_PRIORITY(14);
GPIO_IRQ_HIGHEST_PRIORITY(15);
/* Usual vector table in flash memory. */
extern void (*vectors[125])(void);
/* Our copy of the vector table in a specially aligned SRAM section. */
__attribute((section(".bss.vector_table"))) void (*sram_vectors[125])(void);
#define CORTEX_VTABLE REG32(0xE000ED08)
static void (*saved_gpio_edge_vectors[16])(void);
static void enable_asm_gpio_edge_handlers(void)
{
/*
* Disable handling of the blue button while GPIO monitoring is ongoing.
*/
gpio_disable_interrupt(GPIO_NUCLEO_USER_BTN);
/*
* Update GPIO edge interrupt vectors to point directly at copies of
* edge_int(), thereby bypassing the scheduling wrapper of
* DECLARE_IRQ().
*
* This is safe because these interrupts do not cause any task to become
* runnable.
*/
for (int i = 0; i < 16; i++) {
sram_vectors[16 + STM32_IRQ_EXTI0 + i] =
DATA_TO_THUMB_CODE_PTR(&monitoring_slots[i].code);
}
}
static void disable_asm_gpio_edge_handlers(void)
{
/*
* Update GPIO edge interrupt vectors to their EC RTOS defaults.
*/
for (int i = 0; i < 16; i++) {
/* Reinstate default edge interrupt handlers. */
sram_vectors[16 + STM32_IRQ_EXTI0 + i] =
saved_gpio_edge_vectors[i];
}
/*
* Re-enable handling of the blue button as GPIO monitoring is done.
*/
gpio_clear_pending_interrupt(GPIO_NUCLEO_USER_BTN);
gpio_enable_interrupt(GPIO_NUCLEO_USER_BTN);
}
#define STM32_VREFINT_CALIBRATION REG16(0x0BFA05AA)
static void calibrate_adc(void)
{
int reading = 0;
const int num_readings = 16;
/*
* Disable the re-enable ADC, in order to trigger calibration.
*/
adc_disable();
/* Initialize the ADC by performing a fake reading */
adc_read_channel(ADC_CN9_11);
/*
* Make a number of consecutive readings of the internal voltage
* reference.
*/
for (int i = 0; i < num_readings; i++)
reading += adc_read_channel(ADC_VREFINT);
/*
* Based on the recent readings of the known voltage reference, compute
* ratio between voltage in millivolts and ADC/DAC counts in the range
* 0-4095.
*/
int supply_mv = 0;
if (reading > 0)
supply_mv = 3000 * STM32_VREFINT_CALIBRATION * num_readings /
reading;
if (supply_mv < 1000 || supply_mv > 4000) {
/*
* The reading of the bandgap reference corresponds to an
* unrealistic supply voltage, something must be wrong.
*/
ccprintf("Error: ADC calibration reading: %d\n", reading);
/* Use hardcoded values, in part to avoid division by zero. */
dac_divisor = 3300;
dac_multiplier = 4096;
} else {
dac_divisor = 3000 * STM32_VREFINT_CALIBRATION / 256;
dac_multiplier = 4096 * reading / num_readings / 256;
}
/*
* Set conversion factor for all the ADC channels (inverse of DAC
* conversion direction), excluding the VREFINT channel, which does not
* do any conversion, as we are interested in the raw reading, for
* calibration.
*/
for (int i = 0; i < ADC_CH_COUNT; i++) {
if (i == ADC_VREFINT)
continue;
adc_channels[i].factor_mul = dac_divisor;
adc_channels[i].factor_div = dac_multiplier;
}
}
/*
* Choose one of the 16 possible alternate functions for a given pin, without
* actually putting the pins in "alternate" mode (instead leaving it in GPIO
* mode). At runtime, the "gpio mode" command can be used to enable the chosen
* function.
*/
static void gpio_select_alternate_function(int gpio,
enum gpio_alternate_func func)
{
int index = GPIO_MASK_TO_NUM(gpio_list[gpio].mask);
uint32_t gpio_base = gpio_list[gpio].port;
volatile uint32_t *af_register;
if (index < 8) {
af_register = &STM32_GPIO_AFRL(gpio_base);
} else {
af_register = &STM32_GPIO_AFRH(gpio_base);
index -= 8;
}
uint32_t val = *af_register;
val &= ~(0x0000000FU << (index * 4));
val |= ((uint32_t)func) << (index * 4);
*af_register = val;
}
static void board_gpio_init(void)
{
size_t interrupt_handler_size = THUMB_CODE_TO_DATA_PTR(&edge_int_end) -
THUMB_CODE_TO_DATA_PTR(&edge_int);
ASSERT(interrupt_handler_size <= sizeof(monitoring_slots[0].code));
/* Mark every slot as unused. */
for (int i = 0; i < ARRAY_SIZE(monitoring_slots); i++)
monitoring_slots[i].gpio_signal = GPIO_COUNT;
/* Enable handling of the blue user button of Nucleo-L552ZE-Q. */
gpio_clear_pending_interrupt(GPIO_NUCLEO_USER_BTN);
gpio_enable_interrupt(GPIO_NUCLEO_USER_BTN);
/*
* Make a copy of the flash vector table in SRAM, then modify
* GPIO-related entries of the SRAM version to bypass EC-RTOS for lower
* latency. Leave the original flash table active for now, switching to
* the SRAM one only when actively performing gpio monitoring. This
* allows the above handling of presses of the blue button to operate on
* the ordinary rails, as long as no gpio monitoring is active. (Button
* presses will not be handled while gpio monitoring is ongoing.)
*/
memcpy(sram_vectors, vectors, sizeof(sram_vectors));
CORTEX_VTABLE = (uint32_t)(sram_vectors);
for (int i = 0; i < 16; i++) {
memcpy(monitoring_slots[i].code,
THUMB_CODE_TO_DATA_PTR(&edge_int),
interrupt_handler_size);
replace(&monitoring_slots[i], &load_pin_mask_replacement, i);
saved_gpio_edge_vectors[i] =
sram_vectors[16 + STM32_IRQ_EXTI0 + i];
}
/*
* Enable TIMER7 for precise JTAG bit-banging.
*/
__hw_timer_enable_clock(JTAG_TIMER, 1);
STM32_TIM_CR1(JTAG_TIMER) = STM32_TIM_CR1_CEN;
/* Prepare timer for use in GPIO bit-banging. */
__hw_timer_enable_clock(BITBANG_TIMER, 1);
task_enable_irq(IRQ_TIM(BITBANG_TIMER));
/*
* Choose PWM as the alternate function for pins below, without actually
* putting the pins in "alternate" mode (instead leaving it in GPIO
* mode). At runtime, the "gpio mode" command can be used to enable the
* PWM function for any of these pins.
*/
for (int i = 0; i < GPIO_COUNT; i++) {
if (!pwm_pins[i].timer_regs)
continue;
gpio_select_alternate_function(
i, pwm_pins[i].pad_alternate_function);
}
for (int i = 0; i < sizeof(timer_pwm_use) / sizeof(timer_pwm_use[0]);
i++) {
timer_pwm_use[i].num_channels_in_use = 0;
for (int j = 0; j < 4; j++)
timer_pwm_use[i].channel_pin[j] = GPIO_COUNT;
}
/* Enable ADC */
clock_enable_module(MODULE_ADC, 1);
/* Enable internal VREFINT voltage reference. */
STM32_ADC1_CCR |= BIT(22);
/* Perform first calibration (again on every reinit()). */
calibrate_adc();
/* Enable DAC */
STM32_RCC_APB1ENR |= STM32_RCC_APB1ENR1_DAC1EN;
}
DECLARE_HOOK(HOOK_INIT, board_gpio_init, HOOK_PRIO_DEFAULT);
static void stop_all_gpio_monitoring(void)
{
struct monitoring_slot_t *slot;
struct cyclic_buffer_header_t *buffer_header;
for (int i = 0; i < ARRAY_SIZE(monitoring_slots); i++) {
slot = monitoring_slots + i;
if (!slot->buffer)
continue;
/*
* Disable interrupts for all signals feeding into the same
* cyclic buffer, and clear `slot->buffer` to make sure they are
* not discovered by next iteration of the outer loop.
*/
buffer_header = slot->buffer;
for (int j = i; j < ARRAY_SIZE(monitoring_slots); j++) {
slot = monitoring_slots + j;
if (slot->buffer != buffer_header)
continue;
gpio_disable_interrupt(slot->gpio_signal);
slot->gpio_signal = GPIO_COUNT;
slot->buffer = NULL;
}
/* Deallocate this one cyclic buffer. */
num_cur_monitoring--;
if (buffer_header->overrun)
atomic_sub(&num_cur_error_conditions, 1);
free_cyclic_buffer(buffer_header);
}
/* Ensure handling of the blue user button of Nucleo-L552ZE-Q is
* enabled. */
disable_asm_gpio_edge_handlers();
}
/*
* Return GPIO_OPEN_DRAIN or any other special flags to apply when the given
* signal is in "alternate" mode.
*/
static uint32_t extra_alternate_flags(enum gpio_signal signal)
{
const struct gpio_info *g = gpio_list + signal;
const struct gpio_alt_flags *af;
/* Find the first ALTERNATE() declaration for the given pin. */
for (af = gpio_alt_flags;
af < gpio_alt_flags + ARRAY_SIZE(gpio_alt_flags); af++) {
if (af->port != g->port)
continue;
if (af->mask & g->mask) {
return af->flags;
}
}
/* No ALTERNATE() declaration mention the given pin. */
return 0;
}
/**
* Find a GPIO signal by name.
*
* This is copied from gpio.c unfortunately, as it is static over there.
*
* @param name Signal name to find
*
* @return the signal index, or GPIO_COUNT if no match.
*/
enum gpio_signal gpio_find_by_name(const char *name)
{
int i;
if (!name || !*name)
return GPIO_COUNT;
for (i = 0; i < GPIO_COUNT; i++)
if (gpio_is_implemented(i) &&
!strcasecmp(name, gpio_get_name(i)))
return i;
return GPIO_COUNT;
}
/*
* Set the mode of a GPIO pin: input/opendrain/pushpull/alternate.
*/
static int command_gpio_mode(int argc, const char **argv)
{
int gpio;
int flags;
uint32_t dac_enable_value = STM32_DAC_CR;
if (argc < 3)
return EC_ERROR_PARAM_COUNT;
gpio = gpio_find_by_name(argv[1]);
if (gpio == GPIO_COUNT)
return EC_ERROR_PARAM1;
flags = gpio_get_flags(gpio);
flags &= ~(GPIO_INPUT | GPIO_OUTPUT | GPIO_OPEN_DRAIN | GPIO_ANALOG);
dac_enable_value &= ~dac_channels[gpio].enable_mask;
if (strcasecmp(argv[2], "input") == 0)
flags |= GPIO_INPUT;
else if (strcasecmp(argv[2], "opendrain") == 0)
flags |= GPIO_OUTPUT | GPIO_OPEN_DRAIN;
else if (strcasecmp(argv[2], "pushpull") == 0)
flags |= GPIO_OUTPUT;
else if (strcasecmp(argv[2], "adc") == 0)
flags |= GPIO_ANALOG;
else if (strcasecmp(argv[2], "dac") == 0) {
if (dac_channels[gpio].enable_mask == 0) {
ccprintf("Error: Pin does not support dac\n");
return EC_ERROR_PARAM2;
}
dac_enable_value |= dac_channels[gpio].enable_mask;
/* Disable digital output, when DAC is overriding. */
flags |= GPIO_INPUT;
} else if (strcasecmp(argv[2], "alternate") == 0)
flags |= GPIO_ALTERNATE | extra_alternate_flags(gpio);
else
return EC_ERROR_PARAM2;
/* Update GPIO flags. */
gpio_set_flags(gpio, flags);
STM32_DAC_CR = dac_enable_value;
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND_FLAGS(
gpiomode, command_gpio_mode,
"name <input | opendrain | pushpull | adc | dac | alternate>",
"Set a GPIO mode", CMD_FLAG_RESTRICTED);
/*
* Set the weak pulling of a GPIO pin: up/down/none.
*/
static int command_gpio_pull_mode(int argc, const char **argv)
{
int gpio;
int flags;
if (argc < 3)
return EC_ERROR_PARAM_COUNT;
gpio = gpio_find_by_name(argv[1]);
if (gpio == GPIO_COUNT)
return EC_ERROR_PARAM1;
flags = gpio_get_flags(gpio);
flags = flags & ~(GPIO_PULL_UP | GPIO_PULL_DOWN);
if (strcasecmp(argv[2], "none") == 0)
;
else if (strcasecmp(argv[2], "up") == 0)
flags |= GPIO_PULL_UP;
else if (strcasecmp(argv[2], "down") == 0)
flags |= GPIO_PULL_DOWN;
else
return EC_ERROR_PARAM2;
/* Update GPIO flags. */
gpio_set_flags(gpio, flags);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND_FLAGS(gpiopullmode, command_gpio_pull_mode,
"name <none | up | down>",
"Set a GPIO weak pull mode", CMD_FLAG_RESTRICTED);
static int set_dac(int gpio, const char *value)
{
int milli_volts, dac_value;
char *e;
if (dac_channels[gpio].enable_mask == 0) {
ccprintf("Error: Pin does not support dac\n");
return EC_ERROR_PARAM6;
}
milli_volts = strtoi(value, &e, 0);
if (*e)
return EC_ERROR_PARAM6;
dac_value = milli_volts * dac_multiplier / dac_divisor;
if (dac_value <= 0)
*dac_channels[gpio].data_register = 0;
else if (dac_value >= 4096)
*dac_channels[gpio].data_register = 4095;
else
*dac_channels[gpio].data_register = dac_value;
return EC_SUCCESS;
}
/*
* Set the value in millivolts for driving the DAC of a given pin.
*/
static int command_gpio_analog_set(int argc, const char **argv)
{
int gpio;
if (argc < 4)
return EC_ERROR_PARAM_COUNT;
gpio = gpio_find_by_name(argv[2]);
if (gpio == GPIO_COUNT)
return EC_ERROR_PARAM2;
if (set_dac(gpio, argv[3]) != EC_SUCCESS)
return EC_ERROR_PARAM3;
return EC_SUCCESS;
}
/*
* Configure drive speed of a given pin, mostly useful for SPI pins if clock
* frequency is to exceed 10MHz. The STM32L5 datasheet defines four levels 0-3,
* higher numbers mean faster slew rate, default for all pins is level 0.
*/
static int command_gpio_set_speed(int argc, const char **argv)
{
if (argc < 4)
return EC_ERROR_PARAM_COUNT;
int gpio = gpio_find_by_name(argv[2]);
if (gpio == GPIO_COUNT)
return EC_ERROR_PARAM2;
char *e;
int speed = strtoi(argv[3], &e, 0);
if (*e)
return EC_ERROR_PARAM3;
if (speed < 0 || speed > 3)
return EC_ERROR_PARAM3;
int index = GPIO_MASK_TO_NUM(gpio_list[gpio].mask);
uint32_t register_value = STM32_GPIO_OSPEEDR(gpio_list[gpio].port);
register_value &= ~(3U << (index * 2));
register_value |= speed << (index * 2);
STM32_GPIO_OSPEEDR(gpio_list[gpio].port) = register_value;
return EC_SUCCESS;
}
/*
* Set multiple aspects of a GPIO pin simultaneously, that is, can switch output
* level, opendrain/pushpull, and pullup simultaneously, eliminating the risk of
* glitches.
*/
static int command_gpio_multiset(int argc, const char **argv)
{
int gpio;
int flags;
uint32_t dac_enable_value = STM32_DAC_CR;
if (argc < 4)
return EC_ERROR_PARAM_COUNT;
gpio = gpio_find_by_name(argv[2]);
if (gpio == GPIO_COUNT)
return EC_ERROR_PARAM2;
flags = gpio_get_flags(gpio);
if (argc > 3 && strcasecmp(argv[3], "-") != 0) {
flags = flags & ~(GPIO_LOW | GPIO_HIGH);
if (strcasecmp(argv[3], "0") == 0)
flags |= GPIO_LOW;
else if (strcasecmp(argv[3], "1") == 0)
flags |= GPIO_HIGH;
else
return EC_ERROR_PARAM3;
}
if (argc > 4 && strcasecmp(argv[4], "-") != 0) {
flags &= ~(GPIO_INPUT | GPIO_OUTPUT | GPIO_OPEN_DRAIN |
GPIO_ANALOG);
dac_enable_value &= ~dac_channels[gpio].enable_mask;
if (strcasecmp(argv[4], "input") == 0)
flags |= GPIO_INPUT;
else if (strcasecmp(argv[4], "opendrain") == 0)
flags |= GPIO_OUTPUT | GPIO_OPEN_DRAIN;
else if (strcasecmp(argv[4], "pushpull") == 0)
flags |= GPIO_OUTPUT;
else if (strcasecmp(argv[4], "adc") == 0)
flags |= GPIO_ANALOG;
else if (strcasecmp(argv[4], "dac") == 0) {
if (dac_channels[gpio].enable_mask == 0) {
ccprintf("Error: Pin does not support dac\n");
return EC_ERROR_PARAM2;
}
dac_enable_value |= dac_channels[gpio].enable_mask;
/* Disable digital output, when DAC is overriding. */
flags |= GPIO_INPUT;
} else if (strcasecmp(argv[4], "alternate") == 0)
flags |= GPIO_ALTERNATE | extra_alternate_flags(gpio);
else
return EC_ERROR_PARAM4;
}
if (argc > 5 && strcasecmp(argv[5], "-") != 0) {
flags = flags & ~(GPIO_PULL_UP | GPIO_PULL_DOWN);
if (strcasecmp(argv[5], "none") == 0)
;
else if (strcasecmp(argv[5], "up") == 0)
flags |= GPIO_PULL_UP;
else if (strcasecmp(argv[5], "down") == 0)
flags |= GPIO_PULL_DOWN;
else
return EC_ERROR_PARAM5;
}
if (argc > 6 && strcasecmp(argv[6], "-") != 0) {
if (set_dac(gpio, argv[6]) != EC_SUCCESS)
return EC_ERROR_PARAM6;
}
/* Update GPIO flags. */
gpio_set_flags(gpio, flags);
STM32_DAC_CR = dac_enable_value;
return EC_SUCCESS;
}
/*
* Choose the pin that should be pulled low when the blue user button is
* pressed.
*/
static int command_gpio_set_reset(int argc, const char **argv)
{
int gpio;
if (argc < 3)
return EC_ERROR_PARAM_COUNT;
if (!strcasecmp(argv[2], "none")) {
shield_reset_pin = GPIO_COUNT; /* "no pin" value */
return EC_SUCCESS;
}
gpio = gpio_find_by_name(argv[2]);
if (gpio == GPIO_COUNT)
return EC_ERROR_PARAM2;
shield_reset_pin = gpio;
return EC_SUCCESS;
}
static int command_gpio_monitoring_start(int argc, const char **argv)
{
BUILD_ASSERT(STM32_IRQ_EXTI15 < 32);
int gpios[16];
int gpio_num = argc - 3;
int i;
timestamp_t now;
int rv;
uint32_t nvic_mask;
/* Maybe configurable by parameter */
size_t cyclic_buffer_size = CYCLIC_BUFFER_SIZE;
struct cyclic_buffer_header_t *buf;
struct monitoring_slot_t *slot;
if (gpio_num <= 0 || gpio_num > 16)
return EC_ERROR_PARAM_COUNT;
for (i = 0; i < gpio_num; i++) {
gpios[i] = gpio_find_by_name(argv[3 + i]);
if (gpios[i] == GPIO_COUNT) {
rv = EC_ERROR_PARAM3 + i;
goto out_partial_cleanup;
}
slot = monitoring_slots +
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
if (slot->gpio_signal != GPIO_COUNT) {
ccprintf("Error: Monitoring of %s conflicts with %s\n",
argv[3 + i],
gpio_list[slot->gpio_signal].name);
rv = EC_ERROR_PARAM3 + i;
goto out_partial_cleanup;
}
slot->gpio_signal = gpios[i];
}
/*
* All the requested signals were available for monitoring, and their
* slots have been marked as reserved for the respective signal.
*/
buf = allocate_cyclic_buffer(cyclic_buffer_size);
if (!buf) {
rv = EC_ERROR_BUSY;
goto out_cleanup;
}
/* Disable handling of the blue user button while monitoring is ongoing.
*/
if (!num_cur_monitoring)
enable_asm_gpio_edge_handlers();
buf->head = buf->tail = buf->data;
buf->end = buf->data + cyclic_buffer_size;
buf->overrun = 0;
buf->num_signals = gpio_num;
buf->signal_bits = 0;
/* Compute how many bits are required to represent 0..gpio_num-1. */
while ((gpio_num - 1) >> buf->signal_bits)
buf->signal_bits++;
for (i = 0; i < gpio_num; i++) {
slot = monitoring_slots +
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
slot->gpio_base = gpio_list[gpios[i]].port;
slot->gpio_pin_mask = gpio_list[gpios[i]].mask;
slot->buffer = buf;
slot->signal_no = i;
replace(slot, &signal_no_replacement, i);
replace(slot, &signal_bits_replacement, buf->signal_bits);
}
/*
* The code relies on all EXTIn interrupts belonging to the same 32-bit
* NVIC register, so that multiple interrupts can be "unleashed"
* simultaneously.
*/
nvic_mask = 0;
/*
* Disable interrupts in GPIO/EXTI detection circuits (should be
* disabled already, but disabled and clear pending bit to be on the
* safe side).
*/
for (i = 0; i < gpio_num; i++) {
int gpio_num = GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
gpio_disable_interrupt(gpios[i]);
gpio_clear_pending_interrupt(gpios[i]);
nvic_mask |= BIT(STM32_IRQ_EXTI0 + gpio_num);
}
/* Also disable interrupts at NVIC (interrupt controller) level. */
CPU_NVIC_UNPEND(0) = nvic_mask;
CPU_NVIC_DIS(0) = nvic_mask;
for (i = 0; i < gpio_num; i++) {
int gpio_num = GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
slot = monitoring_slots + gpio_num;
/*
* Tell the GPIO block to start detecting rising and falling
* edges, and latch them in STM32_EXTI_RPR and STM32_EXTI_FPR
* respectively. Interrupts are still disabled in the NVIC,
* meaning that the execution will not be interrupted, yet, even
* if the GPIO block requests interrupt.
*/
gpio_enable_interrupt(gpios[i]);
slot->tail_level = slot->head_level =
gpio_get_level(gpios[i]) ? gpio_list[gpios[i]].mask : 0;
/*
* Race condition here! If three or more edges happen in
* rapid succession, we may fail to record some of them, but
* we should never over-report edges.
*
* Since edge detection was enabled before the "tail_level"
* was polled, if an edge happened between the two, then an
* interrupt is currently pending, and when handled after this
* loop, the logic in the gpio_edge interrupt handler would
* wrongly conclude that the signal must have seen two
* transitions, in order to end up at the same level as before.
* In order to avoid such over-reporting, we clear "pending"
* interrupt bit below, but only for the direction that goes
* "towards" the level measured above.
*/
if (slot->tail_level)
STM32_EXTI_RPR = BIT(gpio_num);
else
STM32_EXTI_FPR = BIT(gpio_num);
}
/*
* Now enable the handling of the set of interrupts.
*/
now = get_time();
buf->tail_time = now.le.lo;
CPU_NVIC_EN(0) = nvic_mask;
buf->head_time = now;
num_cur_monitoring++;
ccprintf(" @%lld\n", buf->head_time.val);
/*
* Dump the initial level of each input, for the convenience of the
* caller. (Allow makes monitoring useful, even if a signal has no
* transitions during the monitoring period.
*/
for (i = 0; i < gpio_num; i++) {
slot = monitoring_slots +
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
ccprintf(" %d %s %d\n", i, gpio_list[gpios[i]].name,
!!slot->head_level);
}
return EC_SUCCESS;
out_cleanup:
i = gpio_num;
out_partial_cleanup:
while (i-- > 0) {
monitoring_slots[GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask)]
.gpio_signal = GPIO_COUNT;
}
return rv;
}
static const uint8_t *
traverse_buffer(struct cyclic_buffer_header_t *buf, int gpio_signals_by_no[],
timestamp_t now, size_t limit,
void (*process_event)(uint8_t, timestamp_t, bool));
static timestamp_t traverse_until;
static void print_event(uint8_t signal_no, timestamp_t event_time, bool rising)
{
/* To conserve bandwidth, timestamps are relative to `traverse_until`.
*/
ccprintf(" %d %lld %s\n", signal_no,
event_time.val - traverse_until.val, rising ? "R" : "F");
/* Flush console to avoid truncating output */
cflush();
}
static int command_gpio_monitoring_read(int argc, const char **argv)
{
int gpios[16];
int gpio_num = argc - 3;
int i;
struct cyclic_buffer_header_t *buf = NULL;
struct monitoring_slot_t *slot;
int gpio_signals_by_no[16];
if (gpio_num <= 0 || gpio_num > 16)
return EC_ERROR_PARAM_COUNT;
for (i = 0; i < gpio_num; i++) {
gpios[i] = gpio_find_by_name(argv[3 + i]);
if (gpios[i] == GPIO_COUNT)
return EC_ERROR_PARAM3 + i; /* May overflow */
slot = monitoring_slots +
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
if (slot->gpio_signal != gpios[i]) {
ccprintf("Error: Not monitoring %s\n",
gpio_list[gpios[i]].name);
return EC_ERROR_PARAM3 + i;
}
if (slot->signal_no != i) {
ccprintf("Error: Inconsistent order at %s\n",
gpio_list[gpios[i]].name);
return EC_ERROR_PARAM3 + i;
}
if (buf == NULL) {
buf = slot->buffer;
} else if (buf != slot->buffer) {
ccprintf(
"Error: Not monitoring %s as part of same groups as %s\n",
gpio_list[gpios[i]].name,
gpio_list[gpios[0]].name);
return EC_ERROR_PARAM3 + i;
}
gpio_signals_by_no[slot->signal_no] = gpios[i];
}
if (gpio_num != buf->num_signals) {
ccprintf("Error: Not full set of signals monitored\n");
return EC_ERROR_INVAL;
}
/*
* Print at most 32 lines at a time, since `cflush()` seems to not
* prevent overflow.
*/
traverse_until = get_time();
ccprintf(" @%lld\n", traverse_until.val);
buf->head = traverse_buffer(buf, gpio_signals_by_no, traverse_until, 32,
&print_event);
if (buf->head != buf->tail)
ccprintf("Warning: more data\n");
if (buf->overrun)
ccprintf("Error: Buffer overrun\n");
return EC_SUCCESS;
}
/*
* This routine iterates through buffered entries starting from buf->head,
* stopping when there are no more entries before the `now` timestamp, or when
* having processed a certain number of entries given by `limit`, whichever
* comes first. The return value indicate a new value which the caller must put
* into buf->head. As soon as the caller does this, the traversed range is free
* to be overwritten by the interrupt handler.
*/
static const uint8_t *
traverse_buffer(struct cyclic_buffer_header_t *buf, int gpio_signals_by_no[],
timestamp_t now, size_t limit,
void (*process_event)(uint8_t, timestamp_t, bool))
{
/*
* We have read the current time, before taking a snapshot of the tail
* pointer as set by the interrupt handler. This way, we can guarantee
* that the transcript will include any edge happening at or before the
* `now` timestamp. If an interrupt happens after `now` was captured,
* but before the line below, causing our tail pointer to include an
* event that happened after "now", then it and any further entries will
* be excluded from the traversal, and remain in the cyclic buffer for
* the next invocation of `gpio monitoring read`.
*/
const uint8_t *tail = buf->tail;
int8_t signal_bits = buf->signal_bits;
const uint8_t *head = buf->head;
timestamp_t head_time = buf->head_time;
while (head != tail && limit-- > 0) {
const uint8_t *const buf_start = buf->data;
timestamp_t diff;
uint8_t byte;
uint8_t signal_no;
uint32_t mask;
int shift = 0;
const uint8_t *tentative_head = head;
struct monitoring_slot_t *slot;
diff.val = 0;
do {
byte = *tentative_head++;
if (tentative_head == buf->end)
tentative_head = buf_start;
diff.val |= (byte & 0x7F) << shift;
shift += 7;
} while (byte & 0x80);
signal_no = diff.val & (0xFF >> (8 - signal_bits));
diff.val >>= signal_bits;
if (head_time.val + diff.val > now.val) {
/*
* Do not consume this or subsequent records, which
* apparently happened after our "now" timestamp from
* earlier in the execution of this method.
*/
break;
}
head = tentative_head;
head_time.val += diff.val;
mask = gpio_list[gpio_signals_by_no[signal_no]].mask;
slot = monitoring_slots + GPIO_MASK_TO_NUM(mask);
slot->head_level ^= mask;
if (process_event)
process_event(signal_no, head_time, slot->head_level);
}
buf->head_time = head_time;
return head;
}
static int command_gpio_monitoring_stop(int argc, const char **argv)
{
int gpios[16];
int gpio_num = argc - 3;
int i;
struct cyclic_buffer_header_t *buf = NULL;
struct monitoring_slot_t *slot;
if (gpio_num <= 0 || gpio_num > 16)
return EC_ERROR_PARAM_COUNT;
for (i = 0; i < gpio_num; i++) {
gpios[i] = gpio_find_by_name(argv[3 + i]);
if (gpios[i] == GPIO_COUNT)
return EC_ERROR_PARAM3 + i; /* May overflow */
slot = monitoring_slots +
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
if (slot->gpio_signal != gpios[i]) {
ccprintf("Error: Not monitoring %s\n",
gpio_list[gpios[i]].name);
return EC_ERROR_PARAM3 + i;
}
if (buf == NULL) {
buf = slot->buffer;
} else if (buf != slot->buffer) {
ccprintf(
"Error: Not monitoring %s as part of same groups as %s\n",
gpio_list[gpios[i]].name,
gpio_list[gpios[0]].name);
return EC_ERROR_PARAM3 + i;
}
}
if (gpio_num != buf->num_signals) {
ccprintf("Error: Not full set of signals monitored\n");
return EC_ERROR_INVAL;
}
for (i = 0; i < gpio_num; i++) {
gpio_disable_interrupt(gpios[i]);
}
/*
* With no more interrupts modifying the buffer, it can be deallocated.
*/
num_cur_monitoring--;
for (i = 0; i < gpio_num; i++) {
slot = monitoring_slots +
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
slot->gpio_signal = GPIO_COUNT;
slot->buffer = NULL;
}
if (buf->overrun)
atomic_sub(&num_cur_error_conditions, 1);
/* Re-enable handling of the blue user button once monitoring is done.
*/
if (!num_cur_monitoring)
disable_asm_gpio_edge_handlers();
free_cyclic_buffer(buf);
return EC_SUCCESS;
}
static int command_gpio_monitoring(int argc, const char **argv)
{
if (argc < 3)
return EC_ERROR_PARAM_COUNT;
if (!strcasecmp(argv[2], "start"))
return command_gpio_monitoring_start(argc, argv);
if (!strcasecmp(argv[2], "read"))
return command_gpio_monitoring_read(argc, argv);
if (!strcasecmp(argv[2], "stop"))
return command_gpio_monitoring_stop(argc, argv);
return EC_ERROR_PARAM2;
}
/*
* Organization of bitbang.data. The indices move to the right, as data is
* being read/written:
*
* CMSIS reads data IRQ reads and overwrites CMSIS writes
* v v v
* +----+--------------------------+----------------------------+------------+
* | | samples to be sent to PC | waveform data from PC | |
* +----+--------------------------+----------------------------+------------+
* ^ ^ ^ ^
* bitbang.head bitbang.irq | bitbang.tail
* bitbang.irq_tail
*/
struct bitbang_state_t {
/*
* Cyclic buffer storing the waveform to output, as well as recorded
* samples.
*/
uint8_t data[BITBANG_BUFFER_SIZE];
/* Index incremented by CMSIS_DAP task when data arrives from PC. */
uint32_t tail;
/*
* Index indicating how far the interrupt handler can process, set by
* CMSIS_DAP task when data arrives from PC. Usually it will be
* identical to bitbang.tail, but may lag by a few bytes, in cases when
* a multi-byte encoding has been only partially received. We do not
* want the interrupt handler to "see" partially received instructions,
* as that would require more complicated code.
*/
volatile uint32_t irq_tail;
/*
* Index incremented by timer interrupt handler. At each tick, the
* interrupt handler will read the byte at this index, and use it do
* drive GPIO outputs, and then replace it with GPIO input levels as
* they were measured just before the output levels were applied.
*/
volatile uint32_t irq;
/* Index incremented by CMSIS_DAP task when data is sent to PC. */
uint32_t head;
/*
* For the cases where encoded data indicates a "pause" of several clock
* ticks between waveform edges, this counter is used to record how many
* future interrupts should "do nothing", before the next byte is
* applied to GPIOs.
*/
uint32_t countdown;
/*
* In case the encoded data indicates a "pause" until certain input
* trigger, this is represented by `bitbang.mask` being non-zero. Only
* once the sampled input pins match `bitbang.pattern` for all of the
* bits set in `bitbang.mask` will processing of the remaining part of
* the bitbanging waveform resume.
*/
uint8_t mask, pattern;
/*
* How many bytes used for an "ordinary" sample, that is, not a special
* pause encoding. The BITBANG_DELAY_BIT of the first byte of such a
* sample is zero, subsequent bytes of the sample may use all eight bits
* for data.
*/
uint8_t num_sample_bytes;
/*
* Space in SRAM for interrupt handler to be composed just-in-time from
* machine code snippets, based on the set of pins being manipulated.
*/
uint8_t code[512] __attribute__((aligned(4)));
};
struct bitbang_state_t bitbang;
/*
* Obtain address into bitbang_data, corresponding to given index.
*/
static inline uint8_t *bitbang_data_ptr(uint32_t idx)
{
BUILD_ASSERT(POWER_OF_TWO(BITBANG_BUFFER_SIZE));
return bitbang.data + (idx & (BITBANG_BUFFER_SIZE - 1));
}
#define BITBANG_DELAY_BIT 0x80
#define BITBANG_DATA_MASK 0x7F
/*
* Bitbanging timer interrupt one level below the GPIO edge detection
* interrupts. If more than one of the pins being bitbanged are also being
* monitored, this allows accurately recording which pin is modified first at a
* particular clock tick, as the edge interrupt would run for each iteration of
* the loop in the bitbanging interrupt handler above. Leaving them at same
* priority would mean that all edge detection interrupts would run after the
* bitbanging handler, probably in order of the pin number, which could lead to
* falsely reversing the order of e.g. edges on SDA and SCL, which would impact
* the meaning of I2C signals.
*/
const struct irq_priority __keep IRQ_PRIORITY(IRQ_TIM(BITBANG_TIMER))
__attribute__((weak, section(".rodata.irqprio"))) = {
IRQ_TIM(BITBANG_TIMER), 1
};
/*
* Returns a prescaler value such that the divisor can fit into a 16-bit
* register.
*/
static uint32_t find_suitable_prescaler(uint64_t divisor)
{
/* Find power of two for prescaling */
uint8_t prescaler_shift = 0;
while (divisor > (0x10000ULL << prescaler_shift))
prescaler_shift++;
return 1U << prescaler_shift;
}
static void stop_all_gpio_bitbanging(void)
{
/* Stop timer */
STM32_TIM_CR1(BITBANG_TIMER) = 0;
/*
* Empty the queue.
*
* CAUTION: No guard against CMSIS-DAP task simultaneously operating on
* the queue, we count on OpenTitanTool not simultaneously requesting
* big-banging via one USB endpoint and re-initialization on another.
*/
bitbang.tail = 0;
bitbang.irq = 0;
bitbang.irq_tail = 0;
bitbang.head = 0;
}
void bitbang_int_begin(void); /* Not a real function */
void bitbang_int(void);
void bitbang_int_end(void); /* Not a real function */
struct snippet_t {
uint32_t count;
uint8_t *table, *table_end;
};
extern struct snippet_t read_gpio_snippet;
extern struct snippet_t get_bit_snippet;
extern struct snippet_t align_bits_snippet;
extern struct snippet_t midway_snippet;
extern struct snippet_t set_bit_snippet;
extern struct snippet_t set_additional_bit_snippet;
extern struct snippet_t apply_gpio_snippet;
extern struct snippet_t fetch_dac_value_snippet;
extern struct snippet_t fetch_dac_value2_snippet;
extern struct snippet_t apply_dac_snippet;
extern struct snippet_t finish_snippet;
void append_snippet(uint8_t **code_ptr, const struct snippet_t *snippet,
size_t index)
{
ASSERT(index < snippet->count);
ASSERT((snippet->table_end - snippet->table) % (snippet->count * 2) ==
0);
size_t snippet_size =
(snippet->table_end - snippet->table) / snippet->count;
memcpy(*code_ptr,
THUMB_CODE_TO_DATA_PTR(snippet->table) + index * snippet_size,
snippet_size);
*code_ptr += snippet_size;
}
static int command_gpio_bit_bang(int argc, const char **argv)
{
if (argc < 4)
return EC_ERROR_PARAM_COUNT;
int gpio_num = argc - 3;
if (gpio_num > 7)
return EC_ERROR_PARAM_COUNT;
const uint32_t timer_freq = clock_get_timer_freq();
char *e;
uint64_t desired_period_ns = strtoull(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
if (desired_period_ns > 0xFFFFFFFFFFFFFFFFULL / timer_freq) {
/* Would overflow below. */
return EC_ERROR_PARAM2;
}
/*
* Calculate number of hardware timer cycles for each bit-banging
* sample.
*/
uint64_t divisor =
DIV_ROUND_NEAREST(desired_period_ns * timer_freq, 1000000000);
if (divisor > (1ULL << 32)) {
/* Would overflow the 32-bit timer. */
return EC_ERROR_PARAM2;
}
int gpios[7];
for (int i = 0; i < gpio_num; i++) {
gpios[i] = gpio_find_by_name(argv[3 + i]);
if (gpios[i] == GPIO_COUNT) {
return EC_ERROR_PARAM3 + i;
}
}
if (STM32_TIM_CR1(BITBANG_TIMER) & STM32_TIM_CR1_CEN) {
ccprintf("Error: Ongoing operation, cannot change settings.\n");
return EC_ERROR_INVAL;
}
/*
* All input valid, now record the request.
*/
bitbang.num_sample_bytes = 1;
/* Appropriate power of two for prescaling */
uint32_t prescaler = find_suitable_prescaler(divisor);
/* Set clock divisor to achieve requested tick period. */
STM32_TIM_ARR(BITBANG_TIMER) =
DIV_ROUND_NEAREST(divisor, prescaler) - 1;
/* Update prescaler. */
STM32_TIM_PSC(BITBANG_TIMER) = prescaler - 1;
/* Set up the overflow interrupt */
STM32_TIM_SR(BITBANG_TIMER) = 0;
STM32_TIM_DIER(BITBANG_TIMER) = 0x0001;
/* Make copy of initial part of interrupt routine */
size_t initial_size = &bitbang_int_end - &bitbang_int_begin;
memcpy(bitbang.code, THUMB_CODE_TO_DATA_PTR(&bitbang_int_begin),
initial_size);
uint8_t *code_ptr = bitbang.code + initial_size;
/*
* Compose code to sample levels of the particular pins.
*/
for (int i = 0; i < gpio_num; i++) {
/* Load GPIOx_IDR into CPU register. */
append_snippet(&code_ptr, &read_gpio_snippet,
(gpio_list[gpios[i]].port - STM32_GPIOA_BASE) /
(STM32_GPIOB_BASE - STM32_GPIOA_BASE));
/*
* Inpect a particular from above bit, and shift it into high
* bit of accumulator register.
*/
append_snippet(&code_ptr, &get_bit_snippet,
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask));
/*
* In case the next pins are on the same GPIO bank, no need to
* load GPIOx_IRD again, instead inspect other bits on the same
* value in CPU register, each time shifting into high bit of
* the accumulator register.
*/
while (i + 1 < gpio_num && gpio_list[gpios[i + 1]].port ==
gpio_list[gpios[i]].port) {
i++;
append_snippet(
&code_ptr, &get_bit_snippet,
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask));
}
}
/*
* Shift accumulator right, so that the `gpio_num` highest bits become
* the `gpio_num` lowest bits.
*/
append_snippet(&code_ptr, &align_bits_snippet, gpio_num - 1);
/*
* Large section of fixed logic in the interrupt handler, which will
* load a byte from the waveform data, and decides whether it encodes
* instructions to pause, in which case it returns, or whether it
* encodes ordinary samples to be output, in which case it passes
* control to the code below, after having overwritten the byte in the
* buffer with the accumulator value gathered above.
*/
append_snippet(&code_ptr, &midway_snippet, 0);
/*
* Compose code to apply levels to the particular pins.
*/
for (int i = 0; i < gpio_num; i++) {
/*
* Shift out the lower bit from an accumulator register, and
* prepare a value in another CPU register, containing a single
* bit in either the upper 16 bits or lower 16 bits, depending
* on the aforementioned bit. This value will be suitable for
* writing to the "bit set/reset" register GPIOn_BSRR, to make a
* particular pin go either low or high.
*/
append_snippet(&code_ptr, &set_bit_snippet,
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask));
/*
* In case the next pins are on the same GPIO bank, no need to
* write to GPIOn_BSRR multiple times, instead shift further
* bits out of the accumulator, and set bits in either upper or
* lower part of the CPU register.
*/
while (i + 1 < gpio_num && gpio_list[gpios[i + 1]].port ==
gpio_list[gpios[i]].port) {
i++;
append_snippet(
&code_ptr, &set_additional_bit_snippet,
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask));
}
/* Store CPU register into GPIOn_BSRR. */
append_snippet(&code_ptr, &apply_gpio_snippet,
(gpio_list[gpios[i]].port - STM32_GPIOA_BASE) /
(STM32_GPIOB_BASE - STM32_GPIOA_BASE));
}
/* Return from interrupt handler. */
append_snippet(&code_ptr, &finish_snippet, 0);
if (code_ptr > bitbang.code + sizeof(bitbang.code))
panic("Interrupt handler does not fit");
sram_vectors[16 + IRQ_TIM(BITBANG_TIMER)] = DATA_TO_THUMB_CODE_PTR(
&bitbang_int - &bitbang_int_begin + bitbang.code);
return EC_SUCCESS;
}
static int command_gpio_dac_bang(int argc, const char **argv)
{
if (argc < 4)
return EC_ERROR_PARAM_COUNT;
int gpio_num = argc - 3;
if (gpio_num > 7)
return EC_ERROR_PARAM_COUNT;
const uint32_t timer_freq = clock_get_timer_freq();
char *e;
uint64_t desired_period_ns = strtoull(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM3;
if (desired_period_ns > 0xFFFFFFFFFFFFFFFFULL / timer_freq) {
/* Would overflow below. */
return EC_ERROR_PARAM3;
}
/*
* Calculate number of hardware timer cycles for each bit-banging
* sample.
*/
uint64_t divisor =
DIV_ROUND_NEAREST(desired_period_ns * timer_freq, 1000000000);
if (divisor > (1ULL << 32)) {
/* Would overflow the 32-bit timer. */
return EC_ERROR_PARAM3;
}
int gpios[7];
for (int i = 0; i < gpio_num; i++) {
gpios[i] = gpio_find_by_name(argv[3 + i]);
if (gpios[i] == GPIO_COUNT) {
return EC_ERROR_PARAM3 + i;
}
if (dac_channels[gpios[i]].enable_mask == 0) {
ccprintf("Error: Pin %s does not support DAC\n",
gpio_list[gpios[i]].name);
return EC_ERROR_PARAM3 + i;
}
}
if (STM32_TIM_CR1(BITBANG_TIMER) & STM32_TIM_CR1_CEN) {
ccprintf("Error: Ongoing operation, cannot change settings.\n");
return EC_ERROR_INVAL;
}
/*
* All input valid, now record the request.
*/
bitbang.num_sample_bytes = 1;
/* Appropriate power of two for prescaling */
uint32_t prescaler = find_suitable_prescaler(divisor);
/* Set clock divisor to achieve requested tick period. */
STM32_TIM_ARR(BITBANG_TIMER) =
DIV_ROUND_NEAREST(divisor, prescaler) - 1;
/* Update prescaler. */
STM32_TIM_PSC(BITBANG_TIMER) = prescaler - 1;
/* Set up the overflow interrupt */
STM32_TIM_SR(BITBANG_TIMER) = 0;
STM32_TIM_DIER(BITBANG_TIMER) = 0x0001;
/* Make copy of initial part of interrupt routine */
size_t initial_size = &bitbang_int_end - &bitbang_int_begin;
memcpy(bitbang.code, THUMB_CODE_TO_DATA_PTR(&bitbang_int_begin),
initial_size);
uint8_t *code_ptr = bitbang.code + initial_size;
/*
* Large section of fixed logic in the interrupt handler, which will
* load a byte from the waveform data, and decides whether it encodes
* instructions to pause, in which case it returns, or wether it encodes
* ordinary samples to be output, in which case it passes control to the
* code below. (Unlike GPIO bit-banging, there is no sampling phase
* before this.)
*/
append_snippet(&code_ptr, &midway_snippet, 0);
/*
* Compose code to apply levels to the particular DAC channels.
*/
for (int i = 0; i < gpio_num; i++) {
if (i == 0) {
/*
* Load 12-bit value into CPU register by combining the
* 7-bit value loaded by the midway_snippet with one
* more byte fetched from the waveform data buffer.
*/
append_snippet(&code_ptr, &fetch_dac_value_snippet, 0);
bitbang.num_sample_bytes += 1;
} else {
/*
* Load 12-bit value into CPU register by fetching two
* bytes from the waveform data buffer.
*/
append_snippet(&code_ptr, &fetch_dac_value2_snippet, 0);
bitbang.num_sample_bytes += 2;
}
/* Store 12-bit value into a particular DAC output register. */
append_snippet(&code_ptr, &apply_dac_snippet,
dac_channels[gpios[i]].channel_no);
}
/* Return from interrupt handler. */
append_snippet(&code_ptr, &finish_snippet, 0);
if (code_ptr > bitbang.code + sizeof(bitbang.code))
panic("Interrupt handler does not fit");
sram_vectors[16 + IRQ_TIM(BITBANG_TIMER)] = DATA_TO_THUMB_CODE_PTR(
&bitbang_int - &bitbang_int_begin + bitbang.code);
ccprintf("Calibration: %d %d\n", dac_multiplier, dac_divisor);
return EC_SUCCESS;
}
enum timer_setup_err_t {
TIMER_SETUP_SUCCESS = 0,
TIMER_SETUP_OUT_OF_RANGE,
TIMER_SETUP_CONFLICT,
};
/*
* Turn on the timer associated with the given pin, and set it up to repeat
* every "period" clock cycles (of the peripheral clock).
*/
static enum timer_setup_err_t setup_timer(int gpio, uint32_t prescaler,
uint64_t period)
{
if (prescaler > 0x10000) {
/* Period requires too large a prescaler value. */
return TIMER_SETUP_OUT_OF_RANGE;
}
const int timer_no = pwm_pins[gpio].timer_no;
timer_ctlr_t *const tim = pwm_pins[gpio].timer_regs;
if (timer_pwm_use[timer_no].num_channels_in_use == 0) {
/* We are first user of this timer. */
/* Enable timer clock. */
__hw_timer_enable_clock(timer_no, 1);
/* Disable counter during setup (should be already). */
tim->cr1 = 0x0000;
tim->psc = prescaler - 1;
tim->arr = DIV_ROUND_NEAREST(period, prescaler) - 1;
/* Output, PWM mode 1, preload enable. */
tim->ccmr1 = (6 << 12) | BIT(11) | (6 << 4) | BIT(3);
tim->ccmr2 = (6 << 12) | BIT(11) | (6 << 4) | BIT(3);
return TIMER_SETUP_SUCCESS;
}
if (tim->psc == prescaler - 1 &&
tim->arr == DIV_ROUND_NEAREST(period, prescaler) - 1) {
/* Timer happens to already run at the period we want. */
return TIMER_SETUP_SUCCESS;
}
const int current_pin =
timer_pwm_use[timer_no]
.channel_pin[(pwm_pins[gpio].channel - 1)];
if (timer_pwm_use[timer_no].num_channels_in_use == 1 &&
gpio == current_pin) {
/*
* As the pin we have been asked to set up is currently the only
* user of this timer, we can switch timer frequency.
*/
tim->cr1 = 0x0000;
tim->psc = prescaler - 1;
tim->arr = DIV_ROUND_NEAREST(period, prescaler) - 1;
return TIMER_SETUP_SUCCESS;
}
/*
* This timer is already running at a different period (value of arr and
* prescaler) used for PWM on another pin, we cannot set up what was
* asked.
*/
return TIMER_SETUP_CONFLICT;
}
/*
* Enable PWM output for the given pin, such that the output will be high for
* "high_count" clock cycles (of the peripheral clock), and low for the
* remaining part of the timer period.
*/
static void enable_timer_output_channel(int gpio, uint32_t prescaler,
uint64_t high_count)
{
const int timer_no = pwm_pins[gpio].timer_no;
timer_ctlr_t *const tim = pwm_pins[gpio].timer_regs;
tim->ccr[pwm_pins[gpio].channel] =
DIV_ROUND_NEAREST(high_count, prescaler);
/* Output enable. Set active high/low. */
tim->ccer |= 1 << ((pwm_pins[gpio].channel - 1) * 4);
if (tim->cr1 == 0) {
/*
* Generate update event to force immediate loading of shadow
* registers, (otherwise the counter might have to run to 16-bit
* overflow before the new value of ARR took effect).
*/
tim->egr |= 1;
/* Not all timers have BDTR register. */
if (timer_no == 1 || timer_no >= 8)
tim->bdtr |= STM32_TIM_BDTR_MOE;
/* Enable auto-reload preload, start counting. */
tim->cr1 |= BIT(7) | BIT(0);
}
}
/*
* Disable PWM output for the given pin, (and turn off the timer, if no other
* outputs are currently using it).
*/
static void disable_timer_output_channel(int gpio, bool last)
{
const int timer_no = pwm_pins[gpio].timer_no;
timer_ctlr_t *const tim = pwm_pins[gpio].timer_regs;
/* Clear output enable bit for this channel. */
tim->ccer &= ~(1U << ((pwm_pins[gpio].channel - 1) * 4));
if (!last)
return;
/* Last PWM user of this timer gone, stop the timer. */
tim->cr1 = 0x0000;
/* Disable timer clock. */
__hw_timer_enable_clock(timer_no, 0);
}
/* Enable clock to low power timer. */
static void lptimer_enable_clock(int timer_no)
{
switch (timer_no) {
case PWM_LPTIMER_1:
STM32_RCC_APB1ENR |= STM32_RCC_APB1ENR1_LPTIM1EN;
break;
case PWM_LPTIMER_2:
STM32_RCC_APB1ENR2 |= STM32_RCC_APB1ENR2_LPTIM2EN;
break;
case PWM_LPTIMER_3:
STM32_RCC_APB1ENR2 |= STM32_RCC_APB1ENR2_LPTIM3EN;
break;
}
}
/* Disable clock to low power timer. */
static void lptimer_disable_clock(int timer_no)
{
switch (timer_no) {
case PWM_LPTIMER_1:
STM32_RCC_APB1ENR &= ~STM32_RCC_APB1ENR1_LPTIM1EN;
break;
case PWM_LPTIMER_2:
STM32_RCC_APB1ENR2 &= ~STM32_RCC_APB1ENR2_LPTIM2EN;
break;
case PWM_LPTIMER_3:
STM32_RCC_APB1ENR2 &= ~STM32_RCC_APB1ENR2_LPTIM3EN;
break;
}
}
/*
* Turn on the low power timer associated with the given pin, and set it up to
* repeat every "period" clock cycles (of the peripheral clock).
*/
static enum timer_setup_err_t setup_lptimer(int gpio, uint32_t prescaler,
uint64_t period)
{
const int timer_no = pwm_pins[gpio].timer_no;
lptimer_ctlr_t *const tim = pwm_pins[gpio].timer_regs;
/* Enable clock to low power timer. */
lptimer_enable_clock(timer_no);
/* Enable timer, must be done before modifying other registers. */
tim->cr = BIT(0);
int scale = 31 - __builtin_clz(prescaler);
if (scale > 7)
return TIMER_SETUP_OUT_OF_RANGE;
tim->cfgr = scale << 9 | BIT(21);
tim->arr = DIV_ROUND_NEAREST(period, prescaler) - 1;
return TIMER_SETUP_SUCCESS;
}
/*
* Enable PWM output for the given pin, such that the output will be high for
* "high_count" clock cycles (of the peripheral clock), and low for the
* remaining part of the timer period.
*/
static void enable_lptimer_output_channel(int gpio, uint32_t prescaler,
uint64_t high_count)
{
lptimer_ctlr_t *const tim = pwm_pins[gpio].timer_regs;
tim->cmp = DIV_ROUND_NEAREST(high_count, prescaler) - 1;
/* Start timer */
tim->cr |= BIT(2);
}
/*
* Disable PWM output for the given pin, (and turn off the timer, if no other
* outputs are currently using it).
*/
static void disable_lptimer_output_channel(int gpio, bool last)
{
/*
* Low power timers have only a single output channel, so disabling one
* output channel can always be safely achieved simply by shutting down
* the timer.
*/
lptimer_disable_clock(pwm_pins[gpio].timer_no);
}
static int command_gpio_pwm(int argc, const char **argv)
{
if (argc < 4)
return EC_ERROR_PARAM_COUNT;
int gpio = gpio_find_by_name(argv[2]);
if (gpio == GPIO_COUNT)
return EC_ERROR_PARAM2;
if (!pwm_pins[gpio].timer_regs) {
ccprintf("Error: Pin does not support pwm\n");
return EC_ERROR_PARAM2;
}
const int timer_no = pwm_pins[gpio].timer_no;
const int current_pin =
timer_pwm_use[timer_no]
.channel_pin[(pwm_pins[gpio].channel - 1)];
if (strcasecmp(argv[3], "off") == 0) {
if (gpio == GPIO_CN10_31) {
/* Disable MCO */
STM32_RCC_CFGR &= ~STM32_RCC_CFGR_MCOPRE_MSK &
~STM32_RCC_CFGR_MCOSEL_MSK;
}
if (current_pin != gpio)
return EC_SUCCESS;
timer_pwm_use[timer_no]
.channel_pin[(pwm_pins[gpio].channel - 1)] = GPIO_COUNT;
bool last = !--timer_pwm_use[timer_no].num_channels_in_use;
pwm_pins[gpio].is_lp_timer ?
disable_lptimer_output_channel(gpio, last) :
disable_timer_output_channel(gpio, last);
return EC_SUCCESS;
}
if (argc < 5)
return EC_ERROR_PARAM_COUNT;
const uint32_t timer_freq = pwm_pins[gpio].is_lp_timer ?
clock_get_apb_freq() :
clock_get_timer_freq();
char *e;
uint64_t desired_period_ns = strtoull(argv[3], &e, 0);
if (*e)
return EC_ERROR_PARAM3;
/* Duty cycle of the high pulse */
uint64_t desired_high_ns = strtoull(argv[4], &e, 0);
if (*e)
return EC_ERROR_PARAM4;
if (desired_high_ns > desired_period_ns)
return EC_ERROR_PARAM4;
if (desired_period_ns > 0xFFFFFFFFFFFFFFFFULL / timer_freq) {
/* Would overflow below. */
return EC_ERROR_PARAM3;
}
const uint32_t core_freq = clock_get_freq();
if (gpio == GPIO_CN10_31 &&
desired_period_ns <= 0xFFFFFFFFFFFFFFFFULL / core_freq) {
uint64_t mco_divisor = DIV_ROUND_NEAREST(
desired_period_ns * core_freq, 1000000000);
uint64_t high_count = DIV_ROUND_NEAREST(
desired_high_ns * core_freq, 1000000000);
int halvings = 31 - __builtin_clz(mco_divisor);
if (halvings <= 4 && mco_divisor == (1 << halvings) &&
(mco_divisor == 1 || high_count == (1 << (halvings - 1)))) {
/*
* Requested a duty cycle of 50% at a speed which
* equals the system clock divided by a power of two
* no larger than 16. This means that "master clock
* output" (MCO) functionality can be used instead of
* timer peripheral.
*/
STM32_RCC_CFGR =
(STM32_RCC_CFGR & ~STM32_RCC_CFGR_MCOPRE_MSK &
~STM32_RCC_CFGR_MCOSEL_MSK) |
(halvings << STM32_RCC_CFGR_MCOPRE_POS) |
(1 << STM32_RCC_CFGR_MCOSEL_POS);
gpio_select_alternate_function(gpio, 0);
return EC_SUCCESS;
} else {
/* Continue using timer PWM capability. */
gpio_select_alternate_function(
gpio, pwm_pins[gpio].pad_alternate_function);
}
}
/* Calculate number of hardware timer ticks for each full PWM period. */
uint64_t period =
DIV_ROUND_NEAREST(desired_period_ns * timer_freq, 1000000000);
/* Calculate number of hardware timer ticks with high PWM output. */
uint64_t high_count =
DIV_ROUND_NEAREST(desired_high_ns * timer_freq, 1000000000);
/* Appropriate power of two for prescaling */
uint32_t prescaler = find_suitable_prescaler(period);
if (current_pin != GPIO_COUNT && current_pin != gpio) {
ccprintf("Error: PWM on %s conflicts with %s\n", argv[2],
gpio_list[current_pin].name);
return EC_ERROR_PARAM2;
}
switch (pwm_pins[gpio].is_lp_timer ?
setup_lptimer(gpio, prescaler, period) :
setup_timer(gpio, prescaler, period)) {
case TIMER_SETUP_SUCCESS:
break;
case TIMER_SETUP_OUT_OF_RANGE:
ccprintf("Error: PWM frequency of %s not supported on %s\n",
argv[2], gpio_list[gpio].name);
return EC_ERROR_PARAM3;
case TIMER_SETUP_CONFLICT:
/*
* Cannot change timer frequency without affecting
* existing PWM on another channel of this same timer.
*/
for (int j = 0; j < 3; j++) {
int other_pin = timer_pwm_use[timer_no].channel_pin[j];
if (other_pin == GPIO_COUNT)
continue;
ccprintf(
"Error: PWM frequency of %s conflicts with %s\n",
argv[2], gpio_list[other_pin].name);
return EC_ERROR_PARAM2;
}
/*
* Loop above should have found at least one non-empty
* entry, since num_channels_in_use is non-zero.
*/
panic("PWM invariant");
}
pwm_pins[gpio].is_lp_timer ?
enable_lptimer_output_channel(gpio, prescaler, high_count) :
enable_timer_output_channel(gpio, prescaler, high_count);
if (current_pin == GPIO_COUNT) {
timer_pwm_use[timer_no]
.channel_pin[(pwm_pins[gpio].channel - 1)] = gpio;
timer_pwm_use[timer_no].num_channels_in_use++;
}
return EC_SUCCESS;
}
static int command_gpio(int argc, const char **argv)
{
if (argc < 2)
return EC_ERROR_PARAM_COUNT;
if (!strcasecmp(argv[1], "analog-set"))
return command_gpio_analog_set(argc, argv);
if (!strcasecmp(argv[1], "set-speed"))
return command_gpio_set_speed(argc, argv);
if (!strcasecmp(argv[1], "monitoring"))
return command_gpio_monitoring(argc, argv);
if (!strcasecmp(argv[1], "multiset"))
return command_gpio_multiset(argc, argv);
if (!strcasecmp(argv[1], "set-reset"))
return command_gpio_set_reset(argc, argv);
if (!strcasecmp(argv[1], "bit-bang"))
return command_gpio_bit_bang(argc, argv);
if (!strcasecmp(argv[1], "dac-bang"))
return command_gpio_dac_bang(argc, argv);
if (!strcasecmp(argv[1], "pwm"))
return command_gpio_pwm(argc, argv);
return EC_ERROR_PARAM1;
}
DECLARE_CONSOLE_COMMAND_FLAGS(
gpio, command_gpio,
"multiset name [level] [mode] [pullmode] [milli_volts]"
"\nanalog-set name milli_volts"
"\nset-speed name 0-3"
"\nset-reset name"
"\nmonitoring start name..."
"\nmonitoring read name..."
"\nmonitoring stop name..."
"\nbit-bang clock_ns name...",
"GPIO manipulation", CMD_FLAG_RESTRICTED);
static void gpio_reinit(void)
{
const struct gpio_info *g = gpio_list;
int i;
stop_all_gpio_monitoring();
stop_all_gpio_bitbanging();
/* Set all GPIOs to defaults */
for (i = 0; i < GPIO_COUNT; i++, g++) {
int flags = g->flags;
if (flags & GPIO_DEFAULT)
continue;
if (flags & GPIO_ALTERNATE)
flags |= extra_alternate_flags(i);
/* Set up GPIO based on flags */
gpio_set_flags_by_mask(g->port, g->mask, flags);
}
/* Disable any DAC (which would override GPIO function of pins) */
STM32_DAC_CR = 0;
/* Disable any PWM */
for (int gpio = 0; gpio < GPIO_COUNT; gpio++) {
if (!pwm_pins[gpio].timer_regs)
continue;
/* Disable timer clock. */
const int timer_no = pwm_pins[gpio].timer_no;
if (pwm_pins[gpio].is_lp_timer) {
lptimer_disable_clock(timer_no);
} else {
__hw_timer_enable_clock(timer_no, 0);
}
}
for (int i = 0; i < sizeof(timer_pwm_use) / sizeof(timer_pwm_use[0]);
i++) {
timer_pwm_use[i].num_channels_in_use = 0;
for (int j = 0; j < 4; j++)
timer_pwm_use[i].channel_pin[j] = GPIO_COUNT;
}
/*
* Default behavior of blue user button is to pull CN10_29 low, as that
* pin is used for RESET on both OpenTitan shield and legacy GSC
* shields.
*/
shield_reset_pin = GPIO_CN10_29;
calibrate_adc();
}
DECLARE_HOOK(HOOK_REINIT, gpio_reinit, HOOK_PRIO_DEFAULT);
static void led_tick(void)
{
/* Indicate ongoing GPIO monitoring by flashing the green LED. */
if (num_cur_monitoring) {
gpio_set_level(GPIO_NUCLEO_LED1,
!gpio_get_level(GPIO_NUCLEO_LED1));
} else {
/*
* If not flashing, leave the green LED on, to indicate that
* HyperDebug firmware is running and ready.
*/
gpio_set_level(GPIO_NUCLEO_LED1, 1);
}
/* Indicate error conditions by flashing red LED. */
if (atomic_add(&num_cur_error_conditions, 0))
gpio_set_level(GPIO_NUCLEO_LED3,
!gpio_get_level(GPIO_NUCLEO_LED3));
else {
/*
* If not flashing, leave the red LED off.
*/
gpio_set_level(GPIO_NUCLEO_LED3, 0);
}
}
DECLARE_HOOK(HOOK_TICK, led_tick, HOOK_PRIO_DEFAULT);
/*
* Declaration of header used in the binary USB protocol (Google HyperDebug
* extensions to CMSIS-DAP protocol.)
*/
struct gpio_monitoring_header_t {
/* Size of this struct, including the size field. */
uint16_t transcript_offset;
/*
* Non-zero status indicates an error processing the request, in such
* case the other fields may not be valid.
*/
uint16_t status;
/* Bitfield of the level of each of the signals at the beginning of this
* transcript. */
uint16_t start_levels;
/* Number of bytes of transcript following this struct. */
uint16_t transcript_size;
/* Time window covered by this transcript. */
uint64_t start_timestamp;
uint64_t end_timestamp;
};
/* Sub-requests */
const uint8_t GPIO_REQ_MONITORING_READ = 0x00;
const uint8_t GPIO_REQ_BITBANG = 0x10;
const uint8_t GPIO_REQ_BITBANG_STREAMING = 0x11;
/* Values for gpio_monitoring_header_t::status */
const uint16_t MON_SUCCESS = 0;
/* Specified GPIO not recognized by HyperDebug */
const uint16_t MON_UNKNOWN_GPIO = 1;
/* Specified GPIO not being monitored */
const uint16_t MON_GPIO_NOT_MONITORED = 2;
/* Specified list of GPIOs span several monitoring groups */
const uint16_t MON_GPIO_MIXED = 3;
/* Specified list of GPIOs fails to include some pins from the group */
const uint16_t MON_GPIO_MISSING = 4;
/* Buffer overrun, returned data is incomplete */
const uint16_t MON_BUFFER_OVERRUN = 5;
static void dap_goog_gpio_monitoring_read(size_t peek_c)
{
/*
* Essentially the same as console command `gpio monitoring read`, but
* with binary protocol for greatly improved efficiency.
*/
if (peek_c < 3)
return;
int gpio_num = rx_buffer[2];
int gpios[16];
struct cyclic_buffer_header_t *buf = NULL;
int gpio_signals_by_no[16];
queue_remove_units(&cmsis_dap_rx_queue, rx_buffer, 3);
for (int i = 0; i < gpio_num; i++) {
uint8_t str_len;
queue_blocking_remove(&cmsis_dap_rx_queue, &str_len, 1);
queue_blocking_remove(&cmsis_dap_rx_queue, rx_buffer, str_len);
rx_buffer[str_len] = '\0';
gpios[i] = gpio_find_by_name(rx_buffer);
}
if (cmsis_dap_unwind_requested())
return;
/*
* Start the one-byte CMSIS-DAP encapsulation header at offset 7 in
* tx_buffer, such that our header struct which follows it will be
* 8-byte aligned.
*/
struct gpio_monitoring_header_t *header =
(struct gpio_monitoring_header_t *)(tx_buffer + 8);
uint8_t *const encapsulated_header = tx_buffer + 7;
const size_t encapsulated_header_size = 1 + sizeof(*header);
encapsulated_header[0] = tx_buffer[0];
memset(header, 0, sizeof(*header));
header->transcript_offset = sizeof(*header);
header->status = MON_SUCCESS;
for (int i = 0; i < gpio_num; i++) {
if (gpios[i] == GPIO_COUNT)
header->status = MON_UNKNOWN_GPIO;
struct monitoring_slot_t *slot =
monitoring_slots +
GPIO_MASK_TO_NUM(gpio_list[gpios[i]].mask);
if (slot->gpio_signal != gpios[i]) {
header->status = MON_GPIO_NOT_MONITORED;
}
if (buf == NULL) {
buf = slot->buffer;
} else if (buf != slot->buffer) {
header->status = MON_GPIO_MIXED;
}
gpio_signals_by_no[slot->signal_no] = gpios[i];
}
if (gpio_num != buf->num_signals) {
header->status = MON_GPIO_MISSING;
}
if (header->status != 0) {
/* Report error processing the request. */
queue_add_units(&cmsis_dap_tx_queue, encapsulated_header,
encapsulated_header_size);
return;
}
uint32_t start_levels = 0;
for (uint8_t signal_no = 0; signal_no < buf->num_signals; signal_no++) {
uint32_t mask = gpio_list[gpio_signals_by_no[signal_no]].mask;
struct monitoring_slot_t *slot =
monitoring_slots + GPIO_MASK_TO_NUM(mask);
if (slot->head_level)
start_levels |= 1 << signal_no;
}
header->start_levels = start_levels;
header->start_timestamp = buf->head_time.val;
timestamp_t now = get_time();
header->end_timestamp = now.val;
const uint8_t *head =
traverse_buffer(buf, gpio_signals_by_no, now, (size_t)-1, NULL);
if (buf->overrun) {
/*
* Report overrun, but still transmit the events that we managed
* to capture.
*/
header->status = MON_BUFFER_OVERRUN;
}
/*
* Having found the byte range that corresponds to the time interval in
* the header, and having updated `head_level` and `head_time` to match
* the end of the interval, we can now transmit all the raw bytes of the
* range. If it wraps around the cyclic buffer, we need two calls to
* `queue_add_units` (in addition to first call to transmit the header).
*/
if (buf->head <= head) {
/* One contiguous range */
header->transcript_size = head - buf->head;
queue_add_units(&cmsis_dap_tx_queue, encapsulated_header,
encapsulated_header_size);
queue_blocking_add(&cmsis_dap_tx_queue, buf->head,
header->transcript_size);
} else {
/* Data wraps around */
header->transcript_size =
head - buf->data + buf->end - buf->head;
queue_add_units(&cmsis_dap_tx_queue, encapsulated_header,
encapsulated_header_size);
queue_blocking_add(&cmsis_dap_tx_queue, buf->head,
buf->end - buf->head);
queue_blocking_add(&cmsis_dap_tx_queue, buf->data,
head - buf->data);
}
buf->head = head;
}
const uint8_t STATUS_BITBANG_IDLE = 0x00;
const uint8_t STATUS_BITBANG_ONGOING = 0x01;
const uint8_t STATUS_ERROR_WAVEFORM = 0x80;
/*
* Validate data from previous bitbang_irq_tail to bitbang_tail + data_len.
* That is possibly a few bytes from the tail end of the most recent data, plus
* anything received in this request. Return non-zero on invalid data, if data
* is valid, increments bitbang_tail by exactly data_len, and sets
* bitbang_irq_tail at or a few bytes before the new bitbang_tail.
*/
static uint8_t validate_received_waveform(uint16_t data_len, bool streaming)
{
uint32_t tail_goal = bitbang.tail + data_len;
uint32_t idx = bitbang.irq_tail;
uint32_t valid_idx = idx;
while (idx != tail_goal) {
if (!(*bitbang_data_ptr(idx) & BITBANG_DELAY_BIT)) {
/*
* Sample for output. Ensure that if each sample takes
* up more than a single byte, that we have received all
* bytes for this sample, before allowing the interrupt
* handler to see and process any byte of it.
*/
idx += bitbang.num_sample_bytes;
if ((int32_t)(tail_goal - idx) >= 0) {
valid_idx = idx;
continue;
} else {
break;
}
}
uint8_t delay_scale = 0, num_bytes = 0;
bool all_zeroes = true;
while (idx != tail_goal &&
*bitbang_data_ptr(idx) & BITBANG_DELAY_BIT) {
uint8_t data = *bitbang_data_ptr(idx) &
BITBANG_DATA_MASK;
/*
* Shifting right by 32 - delay_scale effectively gives
* us the low bytes of the upper 32 bits of what we
* would have gotten by shifting left by delay_scale.
* If that is non-zero, it means that the encoded value
* would exceed 32 bits.
*/
if (data >> (32 - delay_scale)) {
return STATUS_ERROR_WAVEFORM;
}
delay_scale += 7;
num_bytes++;
if (data != 0)
all_zeroes = false;
idx++;
}
if (idx != tail_goal && all_zeroes) {
/*
* Zero-cycle delay is invalid, the encoding is used as
* escape for "special" commands.
*/
if (num_bytes == 2) {
/*
* Request to wait for particular pattern of
* input pins. Verify that required parameters
* are present.
*/
if (++idx == tail_goal)
break;
if (++idx == tail_goal)
break;
} else {
/*
* Unrecognized special request encoding.
*/
return STATUS_ERROR_WAVEFORM;
}
}
}
if (!streaming && valid_idx != bitbang.tail + data_len) {
/*
* Possibly incomplete delay encoding at end of waveform, but no
* further waveform data is expected. IRQ handler is not coded
* to be able to handle delay not followed by at least one
* waveform sample, so we have to reject this.
*/
return STATUS_ERROR_WAVEFORM;
}
bitbang.tail = tail_goal;
bitbang.irq_tail = valid_idx;
return 0;
}
/*
* Receive more bitbanging data to be inserted at bitbang.tail, then offload
* data between bitbang.head and bitbang.irq.
*/
void dap_goog_gpio_bitbang(size_t peek_c, bool streaming)
{
if (peek_c < 4)
return;
uint16_t data_len = rx_buffer[2] + (rx_buffer[3] << 8);
queue_advance_head(&cmsis_dap_rx_queue, 4);
uint8_t *tail_ptr = bitbang_data_ptr(bitbang.tail);
if (tail_ptr + data_len <= bitbang.data + sizeof(bitbang.data)) {
queue_blocking_remove(&cmsis_dap_rx_queue, tail_ptr, data_len);
} else {
uint16_t remaining_space =
bitbang.data + sizeof(bitbang.data) - tail_ptr;
queue_blocking_remove(&cmsis_dap_rx_queue, tail_ptr,
remaining_space);
queue_blocking_remove(&cmsis_dap_rx_queue, bitbang.data,
data_len - remaining_space);
}
if (cmsis_dap_unwind_requested())
return;
uint8_t status = validate_received_waveform(data_len, streaming);
if (status != 0) {
stop_all_gpio_bitbanging();
/* How much buffer space is free. */
uint16_t free_bytes =
bitbang.irq + BITBANG_BUFFER_SIZE - bitbang.tail;
tx_buffer[1] = status;
*(uint16_t *)(tx_buffer + 2) = free_bytes;
*(uint16_t *)(tx_buffer + 4) = 0;
queue_add_units(&cmsis_dap_tx_queue, tx_buffer, 6);
return;
}
uint32_t timer_cr1 = STM32_TIM_CR1(BITBANG_TIMER);
if (!(timer_cr1 & STM32_TIM_CR1_CEN) &&
bitbang.irq_tail != bitbang.irq) {
/*
* Hardware timer is not running, and we have received one or
* more byte of bitbang waveform. This means that it is time
* to start the timer, so that the next interrupt will begin
* producing the waveform.
*/
uint32_t prescaler = STM32_TIM_PSC(BITBANG_TIMER) + 1;
uint64_t divisor =
(uint64_t)(STM32_TIM32_ARR(BITBANG_TIMER) + 1) *
prescaler;
/* Number of timer increments per millisecond. */
uint32_t counts_in_1ms = clock_get_timer_freq() / 1000;
if (divisor > counts_in_1ms) {
/*
* Slow bit-banging clock. Use non-zero counter start
* value, such that the first overflow interrupt will
* happen in one millisecond, rather than waiting for a
* full clock tick delay, which could be multiple
* seconds.
*/
STM32_TIM32_CNT(BITBANG_TIMER) =
STM32_TIM32_ARR(BITBANG_TIMER) -
DIV_ROUND_UP(counts_in_1ms, prescaler);
bitbang.countdown = 0;
} else {
/*
* Fast bit-banging clock. First few interrupts may
* have higher latency. In order to avoid jitter in the
* bit-banged waveform, set up such that the first three
* timer interrupts will be skipped, before the
* requested waveform begins.
*/
STM32_TIM32_CNT(BITBANG_TIMER) = 0;
bitbang.countdown = 3;
}
bitbang.mask = 0;
/* Start counting */
STM32_TIM_CR1(BITBANG_TIMER) |= STM32_TIM_CR1_CEN;
}
/*
* At this point, the timer interrupt is clocking out data, and
* placing sampled values into the same buffer. For streaming
* requests, we want to send a reply once half of the given data has
* been processed, for non-streaming, we want to wait until all the
* data has been processed.
*
* In any case, we do not want to delay responding to the USB request
* for too long, as that could cause timeout in the handling on the host
* computer. So if necessary, we will respond with fewer bytes of data
* than indicated above, possibly no data bytes at all, in which case
* the host computer will have to issue a new USB request (probably with
* zero bytes of waveform data), in order to inquire if data has become
* available.
*/
timestamp_t start = get_time();
const uint32_t MAX_USB_RESPONSE_TIME_US = 25000;
do {
if (!streaming) {
if (!(STM32_TIM_CR1(BITBANG_TIMER) & STM32_TIM_CR1_CEN))
break;
} else {
uint16_t used_bytes = bitbang.tail - bitbang.head;
if (bitbang.irq - bitbang.head >= used_bytes / 2)
break;
}
} while (time_since32(start) < MAX_USB_RESPONSE_TIME_US);
uint32_t idx = bitbang.irq;
tx_buffer[1] = bitbang.head != bitbang.tail ? STATUS_BITBANG_ONGOING :
STATUS_BITBANG_IDLE;
if (!streaming && idx == bitbang.tail) {
/*
* No more data to process, this means that at the next timer
* interrupt, the handler will disable the timer, if not
* already. Since `command_gpio_bit_bang()` rejects new
* settings, if the timer interrupt is enabled, as very slow
* tick clock could result in the next operation being rejected,
* unless we explicitly stop the timer here.
*/
STM32_TIM_CR1(BITBANG_TIMER) &= ~STM32_TIM_CR1_CEN;
}
/* Number of data bytes to return in this response. */
data_len = idx - bitbang.head;
/* How much buffer space will be free after sending this response. */
uint16_t free_bytes = idx + BITBANG_BUFFER_SIZE - bitbang.tail;
*(uint16_t *)(tx_buffer + 2) = free_bytes;
*(uint16_t *)(tx_buffer + 4) = data_len;
uint8_t *head_ptr = bitbang_data_ptr(bitbang.head);
if (head_ptr + data_len <= bitbang.data + sizeof(bitbang.data)) {
queue_add_units(&cmsis_dap_tx_queue, tx_buffer, 6);
queue_blocking_add(&cmsis_dap_tx_queue, head_ptr, data_len);
} else {
uint16_t remaining_space =
bitbang.data + sizeof(bitbang.data) - head_ptr;
queue_add_units(&cmsis_dap_tx_queue, tx_buffer, 6);
queue_blocking_add(&cmsis_dap_tx_queue, head_ptr,
remaining_space);
queue_blocking_add(&cmsis_dap_tx_queue, bitbang.data,
data_len - remaining_space);
}
bitbang.head = idx;
}
/*
* Entry point for CMSIS-DAP vendor command for GPIO operations.
*
* CAUTION: This handler routine runs on the CMSIS-DAP task, and the code below
* may block waiting to receive/send data via USB. This has the potential to
* conflict with the console task, particularly if that one invokes a function
* like `stop_all_gpio_bitbanging()`, which modifies the same state as methods
* below.
*
* As long as clients behave, and do not simultaneously request monitoring or
* bitbanging operations though the CMSIS-DAP interface while also sending
* `reinit` console command, the one case we are worried about is a bitbanging
* or monitoring client having stopped "in the middle" of performing some
* CMSIS-DAP operation, leaving the CMSIS-DAP task stuck in one of the handler
* functions in this file. Then the next test session would presumably start by
* invoking `reinit`, which will be handled this way: In `cmsis-dap.c` a REINIT
* hook is registered with high priority, which will set
* `cmsis_dap_unwind_requested()` and will cause any blocking queue operation of
* the CMSIS-DAP task to exit. Handler functions above will respond by exiting
* immediately, even if that means possibly leaving inconsistent state (such as
* having updated `head_level` but not moved the `head` pointer to match). The
* normal priority REINIT hook in this file will then be called, which resets
* the state, such that it will be in a consistent and known initial state.
*/
void dap_goog_gpio(size_t peek_c)
{
/*
* We need to inspect sub-command on second byte below, in order to
* start decoding.
*/
if (peek_c < 2)
return;
switch (rx_buffer[1]) {
case GPIO_REQ_MONITORING_READ:
/*
* Hand off all available GPIO monitoring data so far,
* suitable for streaming.
*/
dap_goog_gpio_monitoring_read(peek_c);
break;
case GPIO_REQ_BITBANG:
/*
* Accept data for bitbanging, wait for waveform to be
* complete, and then hand back data polled during.
*/
dap_goog_gpio_bitbang(peek_c, false);
break;
case GPIO_REQ_BITBANG_STREAMING:
/*
* Accept data for bitbanging, hand back available data, while
* waveform still in process, suitable for streaming if
* invoked again before data runs out.
*/
dap_goog_gpio_bitbang(peek_c, true);
break;
}
}
/*
* The monitoring_for_falling_edge() family of functions use the SysTick timer
* for polling the GPIO from interrupts.
*/
static int gsc_ready_pin;
static volatile enum {
GSC_WAITING_FOR_HIGH_LEVEL = 0,
GSC_WAITING_FOR_FALLING_EDGE = 1,
GSC_DETECTED_FALLING_EDGE = 2,
} gsc_ready_state;
void sys_tick_handler(void)
{
if (gpio_get_level(gsc_ready_pin)) {
/* High level, prepare to detect falling edge. */
if (gsc_ready_state == GSC_WAITING_FOR_HIGH_LEVEL)
gsc_ready_state = GSC_WAITING_FOR_FALLING_EDGE;
} else {
if (gsc_ready_state == GSC_WAITING_FOR_FALLING_EDGE) {
/* Low level after above, we detected falling edge. */
gsc_ready_state = GSC_DETECTED_FALLING_EDGE;
/* No need for further timer interrupts */
CPU_NVIC_ST_CTRL = 0;
}
}
}
#define CPU_NVIC_ST_RVR CPUREG(0xE000E014)
#define CPU_NVIC_ST_CVR CPUREG(0xE000E018)
void start_monitoring_for_falling_edge(int pin)
{
gsc_ready_pin = pin;
gsc_ready_state = GSC_WAITING_FOR_HIGH_LEVEL;
/*
* SysTick interrupt every 5 us. Should be able to detect pulses as
* narrow as 10us.
*/
CPU_NVIC_ST_RVR = 5 * clock_get_freq() / 1000000 - 1;
/* Enable SysTick countdown, internal CPU clock source, interrupt. */
CPU_NVIC_ST_CTRL = ST_CLKSOURCE | ST_TICKINT | ST_ENABLE;
}
int wait_for_falling_edge(timestamp_t deadline)
{
while (gsc_ready_state != GSC_DETECTED_FALLING_EDGE) {
timestamp_t now = get_time();
if (timestamp_expired(deadline, &now)) {
/* Stop SysTick */
CPU_NVIC_ST_CTRL = 0;
return EC_ERROR_TIMEOUT;
}
}
return EC_SUCCESS;
}
void stop_monitoring_for_falling_edge(void)
{
/* Stop SysTick */
CPU_NVIC_ST_CTRL = 0;
}