743 lines
21 KiB
C
743 lines
21 KiB
C
/*
|
|
* Copyright 2013 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.
|
|
*
|
|
* SPI driver for Chrome EC.
|
|
*
|
|
* This uses DMA to handle transmission and reception.
|
|
*/
|
|
|
|
#include "chipset.h"
|
|
#include "clock.h"
|
|
#include "console.h"
|
|
#include "dma.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "link_defs.h"
|
|
#include "registers.h"
|
|
#include "spi.h"
|
|
#include "stm32-dma.h"
|
|
#include "system.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_SPI, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
|
|
#define CPRINTF(format, args...) cprintf(CC_SPI, format, ## args)
|
|
|
|
/* SPI FIFO registers */
|
|
#ifdef CHIP_FAMILY_STM32H7
|
|
#define SPI_TXDR REG8(&STM32_SPI1_REGS->txdr)
|
|
#define SPI_RXDR REG8(&STM32_SPI1_REGS->rxdr)
|
|
#else
|
|
#define SPI_TXDR STM32_SPI1_REGS->dr
|
|
#define SPI_RXDR STM32_SPI1_REGS->dr
|
|
#endif
|
|
|
|
/* DMA channel option */
|
|
static const struct dma_option dma_tx_option = {
|
|
STM32_DMAC_SPI1_TX, (void *)&SPI_TXDR,
|
|
STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT
|
|
#ifdef CHIP_FAMILY_STM32F4
|
|
| STM32_DMA_CCR_CHANNEL(STM32_SPI1_TX_REQ_CH)
|
|
#endif
|
|
};
|
|
|
|
static const struct dma_option dma_rx_option = {
|
|
STM32_DMAC_SPI1_RX, (void *)&SPI_RXDR,
|
|
STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT
|
|
#ifdef CHIP_FAMILY_STM32F4
|
|
| STM32_DMA_CCR_CHANNEL(STM32_SPI1_RX_REQ_CH)
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* Timeout to wait for SPI request packet
|
|
*
|
|
* This affects the slowest SPI clock we can support. A delay of 8192 us
|
|
* permits a 512-byte request at 500 KHz, assuming the master starts sending
|
|
* bytes as soon as it asserts chip select. That's as slow as we would
|
|
* practically want to run the SPI interface, since running it slower
|
|
* significantly impacts firmware update times.
|
|
*/
|
|
#define SPI_CMD_RX_TIMEOUT_US 8192
|
|
|
|
#ifdef CONFIG_SPI_PROTOCOL_V2
|
|
/*
|
|
* Offset of output parameters needs to account for pad and framing bytes and
|
|
* one last past-end byte at the end so any additional bytes clocked out by
|
|
* the AP will have a known and identifiable value.
|
|
*/
|
|
#define SPI_PROTO2_OFFSET (EC_PROTO2_RESPONSE_HEADER_BYTES + 2)
|
|
#define SPI_PROTO2_OVERHEAD (SPI_PROTO2_OFFSET + \
|
|
EC_PROTO2_RESPONSE_TRAILER_BYTES + 1)
|
|
#endif /* defined(CONFIG_SPI_PROTOCOL_V2) */
|
|
/*
|
|
* Max data size for a version 3 request/response packet. This is big enough
|
|
* to handle a request/response header, flash write offset/size, and 512 bytes
|
|
* of flash data.
|
|
*/
|
|
#define SPI_MAX_REQUEST_SIZE 0x220
|
|
#define SPI_MAX_RESPONSE_SIZE 0x220
|
|
|
|
/*
|
|
* The AP blindly clocks back bytes over the SPI interface looking for a
|
|
* framing byte. So this preamble must always precede the actual response
|
|
* packet. Search for "spi-frame-header" in U-boot to see how that's
|
|
* implemented.
|
|
*
|
|
* The preamble must be 32-bit aligned so that the response buffer is also
|
|
* 32-bit aligned.
|
|
*/
|
|
static const uint8_t out_preamble[4] = {
|
|
EC_SPI_PROCESSING,
|
|
EC_SPI_PROCESSING,
|
|
EC_SPI_PROCESSING,
|
|
EC_SPI_FRAME_START, /* This is the byte which matters */
|
|
};
|
|
|
|
/*
|
|
* Space allocation of the past-end status byte (EC_SPI_PAST_END) in the out_msg
|
|
* buffer. This seems to be dynamic because the F0 family needs to send it 4
|
|
* times in order to make sure it actually stays at the repeating byte after DMA
|
|
* ends.
|
|
*
|
|
* See crosbug.com/p/31390
|
|
*/
|
|
#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32L4)
|
|
#define EC_SPI_PAST_END_LENGTH 4
|
|
#else
|
|
#define EC_SPI_PAST_END_LENGTH 1
|
|
#endif
|
|
|
|
/*
|
|
* Our input and output buffers. These must be large enough for our largest
|
|
* message, including protocol overhead, and must be 32-bit aligned.
|
|
*/
|
|
static uint8_t out_msg[SPI_MAX_RESPONSE_SIZE + sizeof(out_preamble) +
|
|
EC_SPI_PAST_END_LENGTH] __aligned(4) __uncached;
|
|
static uint8_t in_msg[SPI_MAX_REQUEST_SIZE] __aligned(4) __uncached;
|
|
static uint8_t enabled;
|
|
#ifdef CONFIG_SPI_PROTOCOL_V2
|
|
static struct host_cmd_handler_args args;
|
|
#endif
|
|
static struct host_packet spi_packet;
|
|
|
|
/*
|
|
* This is set if SPI NSS raises to high while EC is still processing a
|
|
* command.
|
|
*/
|
|
static int setup_transaction_later;
|
|
|
|
enum spi_state {
|
|
/* SPI not enabled (initial state, and when chipset is off) */
|
|
SPI_STATE_DISABLED = 0,
|
|
|
|
/* Setting up receive DMA */
|
|
SPI_STATE_PREPARE_RX,
|
|
|
|
/* Ready to receive next request */
|
|
SPI_STATE_READY_TO_RX,
|
|
|
|
/* Receiving request */
|
|
SPI_STATE_RECEIVING,
|
|
|
|
/* Processing request */
|
|
SPI_STATE_PROCESSING,
|
|
|
|
/* Sending response */
|
|
SPI_STATE_SENDING,
|
|
|
|
/*
|
|
* Received bad data - transaction started before we were ready, or
|
|
* packet header from host didn't parse properly. Ignoring received
|
|
* data.
|
|
*/
|
|
SPI_STATE_RX_BAD,
|
|
} state;
|
|
|
|
/**
|
|
* Wait until we have received a certain number of bytes
|
|
*
|
|
* Watch the DMA receive channel until it has the required number of bytes,
|
|
* or a timeout occurs
|
|
*
|
|
* We keep an eye on the NSS line - if this goes high then the transaction is
|
|
* over so there is no point in trying to receive the bytes.
|
|
*
|
|
* @param rxdma RX DMA channel to watch
|
|
* @param needed Number of bytes that are needed
|
|
* @param nss GPIO signal for NSS control line
|
|
* @return 0 if bytes received, -1 if we hit a timeout or NSS went high
|
|
*/
|
|
static int wait_for_bytes(dma_chan_t *rxdma, int needed,
|
|
enum gpio_signal nss)
|
|
{
|
|
timestamp_t deadline;
|
|
|
|
ASSERT(needed <= sizeof(in_msg));
|
|
deadline.val = 0;
|
|
while (1) {
|
|
if (dma_bytes_done(rxdma, sizeof(in_msg)) >= needed)
|
|
return 0;
|
|
if (gpio_get_level(nss))
|
|
return -1;
|
|
if (!deadline.val) {
|
|
deadline = get_time();
|
|
deadline.val += SPI_CMD_RX_TIMEOUT_US;
|
|
}
|
|
if (timestamp_expired(deadline, NULL))
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_PROTOCOL_V2
|
|
/**
|
|
* Send a reply on a given port.
|
|
*
|
|
* The format of a reply is as per the command interface, with a number of
|
|
* preamble bytes before it.
|
|
*
|
|
* The format of a reply is a sequence of bytes:
|
|
*
|
|
* <hdr> <status> <len> <msg bytes> <sum> [<preamble byte>...]
|
|
*
|
|
* The hdr byte is just a tag to indicate that the real message follows. It
|
|
* signals the end of any preamble required by the interface.
|
|
*
|
|
* The length is the entire packet size, including the header, length bytes,
|
|
* message payload, checksum, and postamble byte.
|
|
*
|
|
* The preamble is at least 2 bytes, but can be longer if the STM takes ages
|
|
* to react to the incoming message. Since we send our first byte as the AP
|
|
* sends us the command, we clearly can't send anything sensible for that
|
|
* byte. The second byte must be written to the output register just when the
|
|
* command byte is ready (I think), so we can't do anything there either.
|
|
* Any processing we do may increase this delay. That's the reason for the
|
|
* preamble.
|
|
*
|
|
* It is interesting to note that it seems to be possible to run the SPI
|
|
* interface faster than the CPU clock with this approach.
|
|
*
|
|
* We keep an eye on the NSS line - if this goes high then the transaction is
|
|
* over so there is no point in trying to send the reply.
|
|
*
|
|
* @param txdma TX DMA channel to send on
|
|
* @param status Status result to send
|
|
* @param msg_ptr Message payload to send, which normally starts
|
|
* SPI_PROTO2_OFFSET bytes into out_msg
|
|
* @param msg_len Number of message bytes to send
|
|
*/
|
|
static void reply(dma_chan_t *txdma,
|
|
enum ec_status status, char *msg_ptr, int msg_len)
|
|
{
|
|
char *msg = out_msg;
|
|
int need_copy = msg_ptr != msg + SPI_PROTO2_OFFSET;
|
|
int sum, i;
|
|
|
|
ASSERT(msg_len + SPI_PROTO2_OVERHEAD <= sizeof(out_msg));
|
|
|
|
/* Add our header bytes - the first one might not actually be sent */
|
|
msg[0] = EC_SPI_PROCESSING;
|
|
msg[1] = EC_SPI_FRAME_START;
|
|
msg[2] = status;
|
|
msg[3] = msg_len & 0xff;
|
|
|
|
/*
|
|
* Calculate the checksum; includes the status and message length bytes
|
|
* but not the pad and framing bytes since those are stripped by the AP
|
|
* driver.
|
|
*/
|
|
sum = status + msg_len;
|
|
for (i = 0; i < msg_len; i++) {
|
|
int ch = msg_ptr[i];
|
|
sum += ch;
|
|
if (need_copy)
|
|
msg[i + SPI_PROTO2_OFFSET] = ch;
|
|
}
|
|
|
|
/* Add the checksum and get ready to send */
|
|
msg[SPI_PROTO2_OFFSET + msg_len] = sum & 0xff;
|
|
msg[SPI_PROTO2_OFFSET + msg_len + 1] = EC_SPI_PAST_END;
|
|
dma_prepare_tx(&dma_tx_option, msg_len + SPI_PROTO2_OVERHEAD, msg);
|
|
|
|
/* Kick off the DMA to send the data */
|
|
dma_go(txdma);
|
|
}
|
|
#endif /* defined(CONFIG_SPI_PROTOCOL_V2) */
|
|
|
|
/**
|
|
* Sends a byte over SPI without DMA
|
|
*
|
|
* This is mostly used when we want to relay status bytes to the AP while we're
|
|
* receiving the message and we're thinking about it.
|
|
*
|
|
* @note It may be sent 0, 1, or >1 times, depending on whether the host clocks
|
|
* the bus or not. Basically, the EC is saying "if you ask me what my status is,
|
|
* you'll get this value. But you're not required to ask, or you can ask
|
|
* multiple times."
|
|
*
|
|
* @param byte status byte to send, one of the EC_SPI_* #defines from
|
|
* ec_commands.h
|
|
*/
|
|
static void tx_status(uint8_t byte)
|
|
{
|
|
stm32_spi_regs_t *spi __attribute__((unused)) = STM32_SPI1_REGS;
|
|
|
|
SPI_TXDR = byte;
|
|
#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32L4)
|
|
/* It sends the byte 4 times in order to be sure it bypassed the FIFO
|
|
* from the STM32F0 line.
|
|
*/
|
|
spi->dr = byte;
|
|
spi->dr = byte;
|
|
spi->dr = byte;
|
|
#elif defined(CHIP_FAMILY_STM32H7)
|
|
spi->udrdr = byte;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Get ready to receive a message from the master.
|
|
*
|
|
* Set up our RX DMA and disable our TX DMA. Set up the data output so that
|
|
* we will send preamble bytes.
|
|
*/
|
|
static void setup_for_transaction(void)
|
|
{
|
|
stm32_spi_regs_t *spi __attribute__((unused)) = STM32_SPI1_REGS;
|
|
volatile uint8_t dummy __attribute__((unused));
|
|
|
|
/* clear this as soon as possible */
|
|
setup_transaction_later = 0;
|
|
|
|
#ifndef CHIP_FAMILY_STM32H7 /* H7 is not ready to set status here */
|
|
/* Not ready to receive yet */
|
|
tx_status(EC_SPI_NOT_READY);
|
|
#endif
|
|
|
|
/* We are no longer actively processing a transaction */
|
|
state = SPI_STATE_PREPARE_RX;
|
|
|
|
/* Stop sending response, if any */
|
|
dma_disable(STM32_DMAC_SPI1_TX);
|
|
|
|
/*
|
|
* Read dummy bytes in case there are some pending; this prevents the
|
|
* receive DMA from getting that byte right when we start it.
|
|
*/
|
|
dummy = SPI_RXDR;
|
|
#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32L4)
|
|
/* 4 Bytes makes sure the RX FIFO on the F0 is empty as well. */
|
|
dummy = spi->dr;
|
|
dummy = spi->dr;
|
|
dummy = spi->dr;
|
|
#endif
|
|
|
|
/* Start DMA */
|
|
dma_start_rx(&dma_rx_option, sizeof(in_msg), in_msg);
|
|
|
|
/* Ready to receive */
|
|
state = SPI_STATE_READY_TO_RX;
|
|
tx_status(EC_SPI_OLD_READY);
|
|
|
|
#ifdef CHIP_FAMILY_STM32H7
|
|
spi->cr1 |= STM32_SPI_CR1_SPE;
|
|
#endif
|
|
}
|
|
|
|
/* Forward declaration */
|
|
static void spi_init(void);
|
|
|
|
/*
|
|
* If a setup_for_transaction() was postponed, call it now.
|
|
* Note that setup_for_transaction() cancels Tx DMA.
|
|
*/
|
|
static void check_setup_transaction_later(void)
|
|
{
|
|
if (setup_transaction_later) {
|
|
spi_init(); /* Fix for bug chrome-os-partner:31390 */
|
|
/*
|
|
* 'state' is set to SPI_STATE_READY_TO_RX. Somehow AP
|
|
* de-asserted the SPI NSS during the handler was running.
|
|
* Thus, the pending result will be dropped anyway.
|
|
*/
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_PROTOCOL_V2
|
|
/**
|
|
* Called for V2 protocol to indicate that a command has completed
|
|
*
|
|
* Some commands can continue for a while. This function is called by
|
|
* host_command when it completes.
|
|
*
|
|
*/
|
|
static void spi_send_response(struct host_cmd_handler_args *args)
|
|
{
|
|
enum ec_status result = args->result;
|
|
dma_chan_t *txdma;
|
|
|
|
/*
|
|
* If we're not processing, then the AP has already terminated the
|
|
* transaction, and won't be listening for a response.
|
|
*/
|
|
if (state != SPI_STATE_PROCESSING)
|
|
return;
|
|
|
|
/* state == SPI_STATE_PROCESSING */
|
|
|
|
if (args->response_size > args->response_max)
|
|
result = EC_RES_INVALID_RESPONSE;
|
|
|
|
/* Transmit the reply */
|
|
txdma = dma_get_channel(STM32_DMAC_SPI1_TX);
|
|
reply(txdma, result, args->response, args->response_size);
|
|
|
|
/*
|
|
* Before the state is set to SENDING, any CS de-assertion would
|
|
* set setup_transaction_later to 1.
|
|
*/
|
|
state = SPI_STATE_SENDING;
|
|
check_setup_transaction_later();
|
|
}
|
|
#endif /* defined(CONFIG_SPI_PROTOCOL_V2) */
|
|
|
|
/**
|
|
* Called to send a response back to the host.
|
|
*
|
|
* Some commands can continue for a while. This function is called by
|
|
* host_command when it completes.
|
|
*
|
|
*/
|
|
static void spi_send_response_packet(struct host_packet *pkt)
|
|
{
|
|
dma_chan_t *txdma;
|
|
|
|
/*
|
|
* If we're not processing, then the AP has already terminated the
|
|
* transaction, and won't be listening for a response.
|
|
*/
|
|
if (state != SPI_STATE_PROCESSING)
|
|
return;
|
|
|
|
/* state == SPI_STATE_PROCESSING */
|
|
|
|
/* Append our past-end byte, which we reserved space for. */
|
|
((uint8_t *)pkt->response)[pkt->response_size + 0] = EC_SPI_PAST_END;
|
|
#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32L4)
|
|
/* Make sure we are going to be outputting it properly when the DMA
|
|
* ends due to the TX FIFO bug on the F0. See crosbug.com/p/31390
|
|
*/
|
|
((uint8_t *)pkt->response)[pkt->response_size + 1] = EC_SPI_PAST_END;
|
|
((uint8_t *)pkt->response)[pkt->response_size + 2] = EC_SPI_PAST_END;
|
|
((uint8_t *)pkt->response)[pkt->response_size + 3] = EC_SPI_PAST_END;
|
|
#endif
|
|
|
|
/* Transmit the reply */
|
|
txdma = dma_get_channel(STM32_DMAC_SPI1_TX);
|
|
dma_prepare_tx(&dma_tx_option, sizeof(out_preamble) + pkt->response_size
|
|
+ EC_SPI_PAST_END_LENGTH, out_msg);
|
|
dma_go(txdma);
|
|
#ifdef CHIP_FAMILY_STM32H7
|
|
/* clear any previous underrun */
|
|
STM32_SPI1_REGS->ifcr = STM32_SPI_SR_UDR;
|
|
#endif /* CHIP_FAMILY_STM32H7 */
|
|
|
|
/*
|
|
* Before the state is set to SENDING, any CS de-assertion would
|
|
* set setup_transaction_later to 1.
|
|
*/
|
|
state = SPI_STATE_SENDING;
|
|
check_setup_transaction_later();
|
|
}
|
|
|
|
/**
|
|
* Handle an event on the NSS pin
|
|
*
|
|
* A falling edge of NSS indicates that the master is starting a new
|
|
* transaction. A rising edge indicates that we have finished.
|
|
*
|
|
* @param signal GPIO signal for the NSS pin
|
|
*/
|
|
void spi_event(enum gpio_signal signal)
|
|
{
|
|
dma_chan_t *rxdma;
|
|
uint16_t i;
|
|
|
|
/* If not enabled, ignore glitches on NSS */
|
|
if (!enabled)
|
|
return;
|
|
|
|
/* Check chip select. If it's high, the AP ended a transaction. */
|
|
if (gpio_get_level(GPIO_SPI1_NSS)) {
|
|
enable_sleep(SLEEP_MASK_SPI);
|
|
|
|
/*
|
|
* If the buffer is still used by the host command, postpone
|
|
* the DMA rx setup.
|
|
*/
|
|
if (state == SPI_STATE_PROCESSING) {
|
|
setup_transaction_later = 1;
|
|
return;
|
|
}
|
|
|
|
/* Set up for the next transaction */
|
|
spi_init(); /* Fix for bug chrome-os-partner:31390 */
|
|
return;
|
|
}
|
|
disable_sleep(SLEEP_MASK_SPI);
|
|
|
|
/* Chip select is low = asserted */
|
|
if (state != SPI_STATE_READY_TO_RX) {
|
|
/*
|
|
* AP started a transaction but we weren't ready for it.
|
|
* Tell AP we weren't ready, and ignore the received data.
|
|
*/
|
|
CPRINTS("SPI not ready");
|
|
tx_status(EC_SPI_NOT_READY);
|
|
state = SPI_STATE_RX_BAD;
|
|
return;
|
|
}
|
|
|
|
/* We're now inside a transaction */
|
|
state = SPI_STATE_RECEIVING;
|
|
tx_status(EC_SPI_RECEIVING);
|
|
rxdma = dma_get_channel(STM32_DMAC_SPI1_RX);
|
|
|
|
/* Wait for version, command, length bytes */
|
|
if (wait_for_bytes(rxdma, 3, GPIO_SPI1_NSS))
|
|
goto spi_event_error;
|
|
|
|
if (in_msg[0] == EC_HOST_REQUEST_VERSION) {
|
|
/* Protocol version 3 */
|
|
struct ec_host_request *r = (struct ec_host_request *)in_msg;
|
|
int pkt_size;
|
|
|
|
/* Wait for the rest of the command header */
|
|
if (wait_for_bytes(rxdma, sizeof(*r), GPIO_SPI1_NSS))
|
|
goto spi_event_error;
|
|
|
|
/*
|
|
* Check how big the packet should be. We can't just wait to
|
|
* see how much data the host sends, because it will keep
|
|
* sending dummy data until we respond.
|
|
*/
|
|
pkt_size = host_request_expected_size(r);
|
|
if (pkt_size == 0 || pkt_size > sizeof(in_msg))
|
|
goto spi_event_error;
|
|
|
|
/* Wait for the packet data */
|
|
if (wait_for_bytes(rxdma, pkt_size, GPIO_SPI1_NSS))
|
|
goto spi_event_error;
|
|
|
|
spi_packet.send_response = spi_send_response_packet;
|
|
|
|
spi_packet.request = in_msg;
|
|
spi_packet.request_temp = NULL;
|
|
spi_packet.request_max = sizeof(in_msg);
|
|
spi_packet.request_size = pkt_size;
|
|
|
|
/* Response must start with the preamble */
|
|
memcpy(out_msg, out_preamble, sizeof(out_preamble));
|
|
spi_packet.response = out_msg + sizeof(out_preamble);
|
|
/* Reserve space for the preamble and trailing past-end byte */
|
|
spi_packet.response_max = sizeof(out_msg)
|
|
- sizeof(out_preamble) - EC_SPI_PAST_END_LENGTH;
|
|
spi_packet.response_size = 0;
|
|
|
|
spi_packet.driver_result = EC_RES_SUCCESS;
|
|
|
|
/* Move to processing state */
|
|
state = SPI_STATE_PROCESSING;
|
|
tx_status(EC_SPI_PROCESSING);
|
|
|
|
host_packet_receive(&spi_packet);
|
|
return;
|
|
|
|
} else if (in_msg[0] >= EC_CMD_VERSION0) {
|
|
#ifdef CONFIG_SPI_PROTOCOL_V2
|
|
/*
|
|
* Protocol version 2
|
|
*
|
|
* TODO(crosbug.com/p/20257): Remove once kernel supports
|
|
* version 3.
|
|
*/
|
|
|
|
#ifdef CHIP_FAMILY_STM32F0
|
|
CPRINTS("WARNING: Protocol version 2 is not supported on the F0"
|
|
" line due to crosbug.com/p/31390");
|
|
#endif
|
|
|
|
args.version = in_msg[0] - EC_CMD_VERSION0;
|
|
args.command = in_msg[1];
|
|
args.params_size = in_msg[2];
|
|
|
|
/* Wait for parameters */
|
|
if (wait_for_bytes(rxdma, 3 + args.params_size, GPIO_SPI1_NSS))
|
|
goto spi_event_error;
|
|
|
|
/*
|
|
* Params are not 32-bit aligned in protocol version 2. As a
|
|
* workaround, move them to the beginning of the input buffer
|
|
* so they are aligned.
|
|
*/
|
|
if (args.params_size)
|
|
memmove(in_msg, in_msg + 3, args.params_size);
|
|
|
|
args.params = in_msg;
|
|
args.send_response = spi_send_response;
|
|
|
|
/* Allow room for the header bytes */
|
|
args.response = out_msg + SPI_PROTO2_OFFSET;
|
|
args.response_max = sizeof(out_msg) - SPI_PROTO2_OVERHEAD;
|
|
args.response_size = 0;
|
|
args.result = EC_RES_SUCCESS;
|
|
|
|
/* Move to processing state */
|
|
state = SPI_STATE_PROCESSING;
|
|
tx_status(EC_SPI_PROCESSING);
|
|
|
|
host_command_received(&args);
|
|
return;
|
|
#else /* !defined(CONFIG_SPI_PROTOCOL_V2) */
|
|
/* Protocol version 2 is deprecated. */
|
|
CPRINTS("ERROR: Protocol V2 is not supported!");
|
|
#endif /* defined(CONFIG_SPI_PROTOCOL_V2) */
|
|
}
|
|
|
|
spi_event_error:
|
|
/* Error, timeout, or protocol we can't handle. Ignore data. */
|
|
tx_status(EC_SPI_RX_BAD_DATA);
|
|
state = SPI_STATE_RX_BAD;
|
|
CPRINTS("SPI rx bad data");
|
|
|
|
CPRINTF("in_msg=[");
|
|
for (i = 0; i < dma_bytes_done(rxdma, sizeof(in_msg)); i++)
|
|
CPRINTF("%02x ", in_msg[i]);
|
|
CPRINTF("]\n");
|
|
}
|
|
|
|
static void spi_chipset_startup(void)
|
|
{
|
|
/* Enable pullup and interrupts on NSS */
|
|
gpio_set_flags(GPIO_SPI1_NSS, GPIO_INT_BOTH | GPIO_PULL_UP);
|
|
|
|
/* Set SPI pins to alternate function */
|
|
gpio_config_module(MODULE_SPI, 1);
|
|
|
|
/* Set up for next transaction */
|
|
setup_for_transaction();
|
|
|
|
enabled = 1;
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_RESUME, spi_chipset_startup, HOOK_PRIO_DEFAULT);
|
|
|
|
static void spi_chipset_shutdown(void)
|
|
{
|
|
enabled = 0;
|
|
state = SPI_STATE_DISABLED;
|
|
|
|
/* Disable pullup and interrupts on NSS */
|
|
gpio_set_flags(GPIO_SPI1_NSS, GPIO_INPUT);
|
|
|
|
/* Set SPI pins to inputs so we don't leak power when AP is off */
|
|
gpio_config_module(MODULE_SPI, 0);
|
|
|
|
/* Allow deep sleep when AP off */
|
|
enable_sleep(SLEEP_MASK_SPI);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, spi_chipset_shutdown, HOOK_PRIO_DEFAULT);
|
|
|
|
static void spi_init(void)
|
|
{
|
|
stm32_spi_regs_t *spi = STM32_SPI1_REGS;
|
|
uint8_t was_enabled = enabled;
|
|
|
|
/* Reset the SPI Peripheral to clear any existing weird states. */
|
|
/* Fix for bug chrome-os-partner:31390 */
|
|
enabled = 0;
|
|
state = SPI_STATE_DISABLED;
|
|
STM32_RCC_APB2RSTR |= STM32_RCC_PB2_SPI1;
|
|
STM32_RCC_APB2RSTR &= ~STM32_RCC_PB2_SPI1;
|
|
|
|
/* 40 MHz pin speed */
|
|
STM32_GPIO_OSPEEDR(GPIO_A) |= 0xff00;
|
|
|
|
/* Enable clocks to SPI1 module */
|
|
STM32_RCC_APB2ENR |= STM32_RCC_PB2_SPI1;
|
|
|
|
/* Delay 1 APB clock cycle after the clock is enabled */
|
|
clock_wait_bus_cycles(BUS_APB, 1);
|
|
|
|
/*
|
|
* Select the right DMA request for the variants using it.
|
|
* This is not required for STM32F4 since the channel (aka request) is
|
|
* set directly in the respective dma_option. In fact, it would be
|
|
* overridden in dma-stm32f4::prepare_stream().
|
|
*/
|
|
#ifdef CHIP_FAMILY_STM32L4
|
|
dma_select_channel(STM32_DMAC_SPI1_TX, 1);
|
|
dma_select_channel(STM32_DMAC_SPI1_RX, 1);
|
|
#elif defined(CHIP_FAMILY_STM32H7)
|
|
dma_select_channel(STM32_DMAC_SPI1_TX, DMAMUX1_REQ_SPI1_TX);
|
|
dma_select_channel(STM32_DMAC_SPI1_RX, DMAMUX1_REQ_SPI1_RX);
|
|
#endif
|
|
/*
|
|
* Enable rx/tx DMA and get ready to receive our first transaction and
|
|
* "disable" FIFO by setting event to happen after only 1 byte
|
|
*/
|
|
#ifdef CHIP_FAMILY_STM32H7
|
|
spi->cfg2 = 0;
|
|
spi->cfg1 = STM32_SPI_CFG1_DATASIZE(8) | STM32_SPI_CFG1_FTHLV(4) |
|
|
STM32_SPI_CFG1_CRCSIZE(8) |
|
|
STM32_SPI_CFG1_TXDMAEN | STM32_SPI_CFG1_RXDMAEN |
|
|
STM32_SPI_CFG1_UDRCFG_CONST |
|
|
STM32_SPI_CFG1_UDRDET_BEGIN_FRM;
|
|
spi->cr1 = 0;
|
|
#else /* !CHIP_FAMILY_STM32H7 */
|
|
spi->cr2 = STM32_SPI_CR2_RXDMAEN | STM32_SPI_CR2_TXDMAEN |
|
|
STM32_SPI_CR2_FRXTH | STM32_SPI_CR2_DATASIZE(8);
|
|
|
|
/* Enable the SPI peripheral */
|
|
spi->cr1 |= STM32_SPI_CR1_SPE;
|
|
#endif /* !CHIP_FAMILY_STM32H7 */
|
|
|
|
gpio_enable_interrupt(GPIO_SPI1_NSS);
|
|
|
|
/*
|
|
* If we were already enabled or chipset is already on,
|
|
* prepare for transaction
|
|
*/
|
|
if (was_enabled || chipset_in_state(CHIPSET_STATE_ON))
|
|
spi_chipset_startup();
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_INIT_SPI);
|
|
|
|
/**
|
|
* Get protocol information
|
|
*/
|
|
static enum ec_status spi_get_protocol_info(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_get_protocol_info *r = args->response;
|
|
|
|
memset(r, 0, sizeof(*r));
|
|
#ifdef CONFIG_SPI_PROTOCOL_V2
|
|
r->protocol_versions |= BIT(2);
|
|
#endif
|
|
r->protocol_versions |= BIT(3);
|
|
r->max_request_packet_size = SPI_MAX_REQUEST_SIZE;
|
|
r->max_response_packet_size = SPI_MAX_RESPONSE_SIZE;
|
|
r->flags = EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED;
|
|
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO,
|
|
spi_get_protocol_info,
|
|
EC_VER_MASK(0));
|