diff options
Diffstat (limited to 'chip')
-rw-r--r-- | chip/stm32/build.mk | 1 | ||||
-rw-r--r-- | chip/stm32/registers.h | 1 | ||||
-rw-r--r-- | chip/stm32/usb_pd_phy.c | 495 |
3 files changed, 497 insertions, 0 deletions
diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk index bb837b19e7..aa8e08b32c 100644 --- a/chip/stm32/build.mk +++ b/chip/stm32/build.mk @@ -37,3 +37,4 @@ chip-$(HAS_TASK_POWERLED)+=power_led.o chip-$(CONFIG_FLASH)+=flash-$(FLASH_FAMILY).o chip-$(CONFIG_ADC)+=adc-$(CHIP_FAMILY).o chip-$(CONFIG_PWM)+=pwm.o +chip-$(CONFIG_USB_POWER_DELIVERY)+=usb_pd_phy.o diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index 4507d68d71..d3485824c8 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -670,6 +670,7 @@ typedef volatile struct stm32_spi_regs stm32_spi_regs_t; #define STM32_SPI_CR1_MSTR (1 << 2) #define STM32_SPI_CR2_RXDMAEN (1 << 0) #define STM32_SPI_CR2_TXDMAEN (1 << 1) +#define STM32_SPI_CR2_DATASIZE(n) (((n) - 1) << 8) /* --- Debug --- */ diff --git a/chip/stm32/usb_pd_phy.c b/chip/stm32/usb_pd_phy.c new file mode 100644 index 0000000000..f325c1be05 --- /dev/null +++ b/chip/stm32/usb_pd_phy.c @@ -0,0 +1,495 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "adc.h" +#include "clock.h" +#include "common.h" +#include "console.h" +#include "dma.h" +#include "gpio.h" +#include "hwtimer.h" +#include "hooks.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "util.h" +#include "usb_pd.h" +#include "usb_pd_config.h" + +#ifdef CONFIG_COMMON_RUNTIME +#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) +#else +#define CPRINTF(format, args...) +#endif + +#define PD_DATARATE 300000 /* Hz */ + +/* + * Maximum size of a Power Delivery packet (in bits on the wire) : + * 16-bit header + 0..7 32-bit data objects (+ 4b5b encoding) + * 64-bit preamble + SOP (4x 5b) + message in 4b5b + 32-bit CRC + EOP (1x 5b) + * = 64 + 4*5 + 16 * 5/4 + 7 * 32 * 5/4 + 32 * 5/4 + 5 + */ +#define PD_BIT_LEN 429 + +#define PD_MAX_RAW_SIZE (PD_BIT_LEN*2) + +/* maximum number of consecutive similar bits with Biphase Mark Coding */ +#define MAX_BITS 2 + +/* alternating bit sequence used for packet preamble : 00 10 11 01 00 .. */ +#define PD_PREAMBLE 0xB4B4B4B4 /* starts with 0, ends with 1 */ + +#define TX_CLOCK_DIV ((clock_get_freq() / (2*PD_DATARATE))) + +/* threshold for 1 300-khz period */ +#define PERIOD 4 +#define NB_PERIOD(from, to) ((((to) - (from) + (PERIOD/2)) & 0xFF) / PERIOD) +#define PERIOD_THRESHOLD ((PERIOD + 2*PERIOD) / 2) + +/* Timers used for TX and RX clocking */ +#define TIM_TX TIM_CLOCK_PD_TX +#define TIM_RX TIM_CLOCK_PD_RX + +#include "crc.h" + +/* samples for the PD messages */ +static uint32_t raw_samples[DIV_ROUND_UP(PD_MAX_RAW_SIZE, sizeof(uint32_t))]; + +/* state of the bit decoder */ +static int d_toggle; +static int d_lastlen; +static uint32_t d_last; + +void *pd_init_dequeue(void) +{ + /* preamble ends with 1 */ + d_toggle = 0; + d_last = 0; + d_lastlen = 0; + + return raw_samples; +} + +static int wait_bits(int nb) +{ + int avail; + stm32_dma_chan_t *rx = dma_get_channel(DMAC_TIM_RX); + + avail = dma_bytes_done(rx, PD_MAX_RAW_SIZE); + if (avail < nb) { /* no received yet ... */ + timestamp_t deadline = get_time(); + deadline.val += 4 * MAX_BITS * (nb - avail); + while ((dma_bytes_done(rx, PD_MAX_RAW_SIZE) < nb) + && get_time().val < deadline.val) + ; /* optimized for latency, not CPU usage ... */ + if (dma_bytes_done(rx, PD_MAX_RAW_SIZE) < nb) { + CPRINTF("TMOUT RX %d/%d\n", + dma_bytes_done(rx, PD_MAX_RAW_SIZE), nb); + return -1; + } + } + return nb; +} + +int pd_dequeue_bits(void *ctxt, int off, int len, uint32_t *val) +{ + int w; + uint8_t cnt = 0xff; + uint8_t *samples = ctxt; + + while ((d_lastlen < len) && (off < PD_MAX_RAW_SIZE - 1)) { + w = wait_bits(off + 2); + if (w < 0) + goto stream_err; + cnt = samples[off] - samples[off-1]; + if (!cnt || (cnt > 3*PERIOD)) + goto stream_err; + off++; + if (cnt <= PERIOD_THRESHOLD) { + /* + w = wait_bits(off + 1); + if (w < 0) + goto stream_err; + */ + cnt = samples[off] - samples[off-1]; + if (cnt > PERIOD_THRESHOLD) + goto stream_err; + off++; + } + + /* enqueue the bit of the last period */ + d_last = (d_last >> 1) + | (cnt <= PERIOD_THRESHOLD ? 0x80000000 : 0); + d_lastlen++; + } + if (off < PD_MAX_RAW_SIZE) { + *val = (d_last << (d_lastlen - len)) >> (32 - len); + d_lastlen -= len; + return off; + } else { + return -1; + } +stream_err: + CPRINTF("Invalid %d @%d\n", cnt, off); + return -1; +} + +int pd_find_preamble(void *ctxt) +{ + int bit; + uint8_t *vals = ctxt; + + /* + * Detect preamble + * Alternate 1-period 1-period & 2-period. + */ + uint32_t all = 0; + stm32_dma_chan_t *rx = dma_get_channel(DMAC_TIM_RX); + + for (bit = 1; bit < PD_MAX_RAW_SIZE - 1; bit++) { + uint8_t cnt; + /* wait if the bit is not received yet ... */ + if (PD_MAX_RAW_SIZE - rx->cndtr < bit + 1) { + while ((PD_MAX_RAW_SIZE - rx->cndtr < bit + 1) && + !(STM32_TIM_SR(TIM_RX) & 4)) + ; + if (STM32_TIM_SR(TIM_RX) & 4) { + CPRINTF("TMOUT RX %d/%d\n", + PD_MAX_RAW_SIZE - rx->cndtr, bit); + return -1; + } + } + cnt = vals[bit] - vals[bit-1]; + all = (all >> 1) | (cnt <= PERIOD_THRESHOLD ? 1 << 31 : 0); + if (all == 0x36db6db6) + return bit - 1; /* should be SYNC-1 */ + if (all == 0xF33F3F3F) + return -2; /* got HARD-RESET */ + } + return -1; +} + +static int b_toggle; + +int pd_write_preamble(void *ctxt) +{ + uint32_t *msg = ctxt; + + /* 64-bit x2 preamble */ + msg[0] = PD_PREAMBLE; + msg[1] = PD_PREAMBLE; + msg[2] = PD_PREAMBLE; + msg[3] = PD_PREAMBLE; + b_toggle = 0x3FF; /* preamble ends with 1 */ + return 2*64; +} + +int pd_write_sym(void *ctxt, int bit_off, uint32_t val10) +{ + uint32_t *msg = ctxt; + int word_idx = bit_off / 32; + int bit_idx = bit_off % 32; + uint32_t val = b_toggle ^ val10; + b_toggle = val & 0x200 ? 0x3FF : 0; + if (bit_idx <= 22) { + if (bit_idx == 0) + msg[word_idx] = 0; + msg[word_idx] |= val << bit_idx; + } else { + msg[word_idx] |= val << bit_idx; + msg[word_idx+1] = val >> (32 - bit_idx); + /* side effect: clear the new word when starting it */ + } + return bit_off + 5*2; +} + +int pd_write_last_edge(void *ctxt, int bit_off) +{ + uint32_t *msg = ctxt; + int word_idx = bit_off / 32; + int bit_idx = bit_off % 32; + + if (bit_idx == 0) + msg[word_idx] = 0; + if (!b_toggle /* last bit was 0 */) { + /* transition to 1, then 0 */ + msg[word_idx] |= 1 << bit_idx; + } + /* ensure that the trailer is 0 */ + msg[word_idx+1] = 0; + + return bit_off + 2; +} + +#ifdef CONFIG_COMMON_RUNTIME +void pd_dump_packet(void *ctxt, const char *msg) +{ + uint8_t *vals = ctxt; + int bit; + + CPRINTF("ERR %s:\n000:- ", msg); + /* Packet debug output */ + for (bit = 1; bit < PD_MAX_RAW_SIZE; bit++) { + int cnt = NB_PERIOD(vals[bit-1], vals[bit]); + if ((bit & 31) == 0) + CPRINTF("\n%03d:", bit); + CPRINTF("%1d ", cnt); + } + CPRINTF("><\n"); + cflush(); + for (bit = 0; bit < PD_MAX_RAW_SIZE; bit++) { + if ((bit & 31) == 0) + CPRINTF("\n%03d:", bit); + CPRINTF("%02x ", vals[bit]); + } + CPRINTF("||\n"); + cflush(); +} +#endif /* CONFIG_COMMON_RUNTIME */ + +/* --- SPI TX operation --- */ + +static const struct dma_option dma_tx_option = { + DMAC_SPI_TX, (void *)&SPI_REGS->dr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT +}; + +void pd_start_tx(void *ctxt, int bit_len) +{ + stm32_dma_chan_t *tx = dma_get_channel(DMAC_SPI_TX); + + /* update DMA configuration */ + dma_prepare_tx(&dma_tx_option, DIV_ROUND_UP(bit_len, 8), ctxt); + /* Flush data in write buffer so that DMA can get the lastest data */ + asm volatile("dmb;"); + /* Kick off the DMA to send the data */ + dma_go(tx); + + /* disable RX detection interrupt */ + pd_rx_disable_monitoring(); + /* + * Drive the CC line from the TX block : + * - set the low level reference. + * - put SPI function on TX pin. + */ + pd_tx_enable(); + + /* Start counting at 300Khz*/ + STM32_TIM_CR1(TIM_TX) |= 1; +} + +void pd_tx_done(void) +{ + stm32_spi_regs_t *spi = SPI_REGS; + + dma_wait(DMAC_SPI_TX); + /* wait for real end of transmission */ +#ifdef CHIP_FAMILY_STM32F0 + while ((spi->sr & (3<<11))) + ; /* wait for TX FIFO empty */ +#else + while (!(spi->sr & (1<<1))) + ; /* wait for TXE == 1 */ +#endif + while (spi->sr & (1<<7)) + ; /* wait for BSY == 0 */ + /* ensure that we are not pushing out junk */ + *(uint8_t *)&spi->dr = 0; + /* Stop counting */ + STM32_TIM_CR1(TIM_TX) &= ~1; + /* clear tranfer flag */ + dma_clear_isr(DMAC_SPI_TX); + /* put TX pins and reference in Hi-Z */ + pd_tx_disable(); +} + +/* --- RX operation using comparator linked to timer --- */ + +static const struct dma_option dma_tim_option = { + DMAC_TIM_RX, (void *)&STM32_TIM_CCRx(TIM_RX, TIM_CCR_IDX), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT, +}; + +void pd_rx_start(void) +{ + /* start sampling the edges on the CC line using the RX timer */ + dma_start_rx(&dma_tim_option, PD_MAX_RAW_SIZE, raw_samples); + /* enable TIM2 DMA requests */ + STM32_TIM_EGR(TIM_RX) = 0x0001; /* reset counter / reload PSC */; + STM32_TIM_SR(TIM_RX) = 0; /* clear overflows */ + STM32_TIM_CR1(TIM_RX) |= 1; +} + +void pd_rx_complete(void) +{ + /* stop stampling TIM2 */ + STM32_TIM_CR1(TIM_RX) &= ~1; + /* stop DMA */ + dma_disable(DMAC_TIM_RX); +} + +void pd_rx_enable_monitoring(void) +{ + /* clear comparator external interrupt */ + STM32_EXTI_PR = 1 << EXTI_COMP; + /* clean up older comparator event */ + task_clear_pending_irq(IRQ_COMP); + /* re-enable comparator interrupt to detect packets */ + task_enable_irq(IRQ_COMP); +} + +void pd_rx_disable_monitoring(void) +{ + /* stop monitoring RX during sampling */ + task_disable_irq(IRQ_COMP); + /* clear comparator external interrupt */ + STM32_EXTI_PR = 1 << EXTI_COMP; +} + +/* detect an edge on the PD RX pin */ +void pd_rx_handler(void) +{ + /* start sampling */ + pd_rx_start(); + /* ignore the comparator IRQ until we are done with current message */ + pd_rx_disable_monitoring(); + /* trigger the analysis in the task */ + pd_rx_event(); +} +DECLARE_IRQ(STM32_IRQ_COMP, pd_rx_handler, 1); + +/* --- Startup initialization --- */ +void *pd_hw_init(void) +{ + stm32_spi_regs_t *spi = SPI_REGS; + + /* set 40 MHz pin speed on communication pins */ + pd_set_pins_speed(); + + /* --- SPI init --- */ + + /* Enable clocks to SPI module */ + spi_enable_clock(); + + /* Initialize TX pins and put them in Hi-Z */ + pd_tx_init(); + + /* Enable Tx DMA for our first transaction */ + spi->cr2 = STM32_SPI_CR2_TXDMAEN | STM32_SPI_CR2_DATASIZE(8); + + /* Enable the salve SPI: LSB first, force NSS, TX only */ + spi->cr1 = STM32_SPI_CR1_SPE | STM32_SPI_CR1_LSBFIRST + | STM32_SPI_CR1_SSM | STM32_SPI_CR1_BIDIMODE + | STM32_SPI_CR1_BIDIOE; + + /* configure TX DMA */ + dma_prepare_tx(&dma_tx_option, PD_MAX_RAW_SIZE, raw_samples); + + /* --- set the TX timer with updates at 600KHz (BMC frequency) --- */ + __hw_timer_enable_clock(TIM_TX, 1); + /* Timer configuration */ + STM32_TIM_CR1(TIM_TX) = 0x0000; + STM32_TIM_CR2(TIM_TX) = 0x0000; + STM32_TIM_DIER(TIM_TX) = 0x0000; + /* Auto-reload value : 600000 Khz overflow */ + STM32_TIM_ARR(TIM_TX) = TX_CLOCK_DIV; + /* 50% duty cycle on the output */ + STM32_TIM_CCR1(TIM_TX) = STM32_TIM_ARR(TIM_TX) / 2; + /* Timer CH1 output configuration */ + STM32_TIM_CCMR1(TIM_TX) = (6 << 4) | (1 << 3); + STM32_TIM_CCER(TIM_TX) = 1; + STM32_TIM_BDTR(TIM_TX) = 0x8000; + /* set prescaler to /1 */ + STM32_TIM_PSC(TIM_TX) = 0; + /* Reload the pre-scaler and reset the counter */ + STM32_TIM_EGR(TIM_TX) = 0x0001; + + /* --- set counter for RX timing : 2.4Mhz rate, free-running --- */ + __hw_timer_enable_clock(TIM_RX, 1); + /* Timer configuration */ + STM32_TIM_CR1(TIM_RX) = 0x0000; + STM32_TIM_CR2(TIM_RX) = 0x0000; + STM32_TIM_DIER(TIM_RX) = 0x0000; + /* Auto-reload value : 16-bit free running counter */ + STM32_TIM_ARR(TIM_RX) = 0xFFFF; + /* Timeout for message receive : 2.7ms */ + STM32_TIM_CCR2(TIM_RX) = clock_get_freq() / (RX_CLOCK_DIV + 1) + * 27 / 10000 /* 2.7 ms */; + /* Timer ICx input configuration */ +#if TIM_CCR_IDX == 1 + STM32_TIM_CCMR1(TIM_RX) = TIM_CCR_CS << 0; +#elif TIM_CCR_IDX == 4 + STM32_TIM_CCMR2(TIM_RX) = TIM_CCR_CS << 8; +#else +#error Unsupported RX timer capture input +#endif + STM32_TIM_CCER(TIM_RX) = 0xB << ((TIM_CCR_IDX - 1) * 4); + /* configure DMA request on CCRx update */ + STM32_TIM_DIER(TIM_RX) |= 1 << (8 + TIM_CCR_IDX); /* CCxDE */; + /* set prescaler to /26 (F=1.2Mhz, T=0.8us) */ + STM32_TIM_PSC(TIM_RX) = RX_CLOCK_DIV; + /* Reload the pre-scaler and reset the counter */ + STM32_TIM_EGR(TIM_RX) = 0x0001 | (1 << TIM_CCR_IDX) /* clear CCRx */; + /* clear update event from reloading */ + STM32_TIM_SR(TIM_RX) = 0; + + /* --- DAC configuration for comparator at 850mV --- */ +#ifdef CONFIG_PD_USE_DAC_AS_REF + /* Enable DAC interface clock. */ + STM32_RCC_APB1ENR |= (1 << 29); + /* set voltage Vout=0.850V (Vref = 3.0V) */ + STM32_DAC_DHR12RD = 850 * 4096 / 3000; + /* Start DAC channel 1 */ + STM32_DAC_CR = STM32_DAC_CR_EN1; +#endif + + /* --- COMP2 as comparator for RX vs Vmid = 850mV --- */ +#ifdef CONFIG_USB_PD_INTERNAL_COMP +#if defined(CHIP_FAMILY_STM32F0) + /* 40 MHz pin speed on PA0 and PA4 */ + STM32_GPIO_OSPEEDR(GPIO_A) |= 0x303; + /* turn on COMP/SYSCFG */ + STM32_RCC_APB2ENR |= 1 << 0; + /* currently in hi-speed mode : TODO revisit later, INM = PA0(INM6) */ + STM32_COMP_CSR = STM32_COMP_CMP1EN | STM32_COMP_CMP1MODE_LSPEED | + STM32_COMP_CMP1INSEL_INM6 | + STM32_COMP_CMP1OUTSEL_TIM1_IC1 | + STM32_COMP_CMP1HYST_HI; +#elif defined(CHIP_FAMILY_STM32L) + /* 40 MHz pin speed on PB4 */ + STM32_GPIO_OSPEEDR(GPIO_B) |= 0x300; + + STM32_RCC_APB1ENR |= 1 << 31; /* turn on COMP */ + + STM32_COMP_CSR = STM32_COMP_OUTSEL_TIM2_IC4 | STM32_COMP_INSEL_DAC_OUT1 + | STM32_COMP_SPEED_FAST; + /* route PB4 to COMP input2 through GR6_1 bit 4 (or PB5->GR6_2 bit 5) */ + STM32_RI_ASCR2 |= 1 << 4; +#else +#error Unsupported chip family +#endif +#endif /* CONFIG_USB_PD_INTERNAL_COMP */ + /* DBG */usleep(250000); + /* comparator interrupt setup */ + EXTI_XTSR |= 1 << EXTI_COMP; + STM32_EXTI_IMR |= 1 << EXTI_COMP; + task_enable_irq(IRQ_COMP); + + CPRINTF("USB PD initialized\n"); + return raw_samples; +} + +void pd_set_clock(int freq) +{ + STM32_TIM_ARR(TIM_TX) = clock_get_freq() / (2*freq); +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE +void pd_set_host_mode(int enable) +{ + gpio_set_level(GPIO_CC_HOST, enable); +} +#endif /* CONFIG_USB_PD_DUAL_ROLE */ |