blob: 74ad2470398863cad964be188406c658eba85a03 [file] [log] [blame]
/* 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.
*/
/* Stantum board-specific SPI module */
#include "common.h"
#include "debug.h"
#include "dma.h"
#include "master_slave.h"
#include "registers.h"
#include "spi_comm.h"
#include "task.h"
#include "timer.h"
#include "touch_scan.h"
#include "util.h"
#define DUMMY_DATA 0xdd
/* DMA channel option */
static const struct dma_option dma_tx_option = {
STM32_DMAC_SPI1_TX, (void *)&STM32_SPI1_REGS->dr,
STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT
};
static const struct dma_option dma_rx_option = {
STM32_DMAC_SPI1_RX, (void *)&STM32_SPI1_REGS->dr,
STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT
};
static uint8_t out_msg[SPI_PACKET_MAX_SIZE + 2];
static uint8_t in_msg[SPI_PACKET_MAX_SIZE];
static stm32_spi_regs_t * const spi = STM32_SPI1_REGS;
static inline int wait_for_signal(uint32_t port, uint32_t mask,
int value, int timeout_us)
{
uint32_t start = get_time().le.lo;
while ((get_time().le.lo - start) < timeout_us) {
if ((!!(STM32_GPIO_IDR(port) & mask)) == value)
return EC_SUCCESS;
}
return EC_ERROR_TIMEOUT;
}
static volatile uint8_t slave_ready = 1;
static inline int wait_for_slave_ready(int timeout_us)
{
uint32_t start = get_time().le.lo;
while ((get_time().le.lo - start) < timeout_us) {
if (slave_ready) {
slave_ready = 0; /* Clear for the next event */
return EC_SUCCESS;
}
}
return EC_ERROR_TIMEOUT;
}
/*****************************************************************************/
/* Master */
void spi_master_init(void)
{
/*
* CLK: AFIO Push-pull
* MISO: Input
* MOSI: AFIO Push-pull
*/
STM32_GPIO_CRL(GPIO_A) = (STM32_GPIO_CRL(GPIO_A) & 0x0ff00fff) |
0xb004b000;
/* Set BR in CR1 */
spi->cr1 |= STM32_SPI_CR1_BR_DIV4R;
/* Set CPOL and CPHA */
/* Use default: 0, 0 */
/* Set DFF to 8-bit */
/* Use default: 8-bit */
/* Configure LSBFIRST */
/* Use default: MSB first */
/* Set SSOE */
/* Use default: software control */
/* Enable TX and RX DMA */
spi->cr2 |= STM32_SPI_CR2_TXDMAEN | STM32_SPI_CR2_RXDMAEN;
/* Set SSM and SSI */
spi->cr1 |= STM32_SPI_CR1_SSM | STM32_SPI_CR1_SSI;
/* Enable CRC */
spi->cr1 |= STM32_SPI_CR1_CRCEN;
/* Set MSTR and SPE */
spi->cr1 |= STM32_SPI_CR1_MSTR | STM32_SPI_CR1_SPE;
/* Enable interrupt on PA0 (GPIO_SPI_NSS) */
STM32_AFIO_EXTICR(0) &= ~0xF;
STM32_EXTI_IMR |= (1 << 0);
task_clear_pending_irq(STM32_IRQ_EXTI0);
task_enable_irq(STM32_IRQ_EXTI0);
}
static int spi_master_read_write_byte(uint8_t *in_buf, uint8_t *out_buf, int sz)
{
int ret;
dma_start_rx(&dma_rx_option, sz, in_buf);
dma_prepare_tx(&dma_tx_option, sz, out_buf);
dma_go(dma_get_channel(STM32_DMAC_SPI1_TX));
ret = dma_wait(STM32_DMAC_SPI1_TX);
ret |= dma_wait(STM32_DMAC_SPI1_RX);
dma_disable(STM32_DMAC_SPI1_TX);
dma_disable(STM32_DMAC_SPI1_RX);
dma_clear_isr(STM32_DMAC_SPI1_TX);
dma_clear_isr(STM32_DMAC_SPI1_RX);
return ret;
}
int spi_master_send_command(struct spi_comm_packet *cmd)
{
int ret;
if (cmd->size + 3 > SPI_PACKET_MAX_SIZE)
return EC_ERROR_OVERFLOW;
/* Wait for SPI_NSS to go low */
if (wait_for_signal(GPIO_A, 1 << 0, 0, 10 * MSEC))
return EC_ERROR_TIMEOUT;
/* Set CS1 (slave SPI_NSS) to low */
STM32_GPIO_BSRR(GPIO_A) = 1 << (6 + 16);
/* Wait for the slave to acknowledge */
master_slave_sync(5);
/* Clock out the packet size. */
spi->dr = cmd->size;
while (!(spi->sr & STM32_SPI_SR_RXNE))
;
ret = spi->dr;
/* Wait for the slave to acknowledge */
master_slave_sync(5);
/* Clock out command. Don't care about input. */
ret = spi_master_read_write_byte(in_msg, ((uint8_t *)cmd) + 1,
cmd->size + SPI_PACKET_HEADER_SIZE - 1);
return ret;
}
int spi_master_wait_response_async(void)
{
wait_for_slave_ready(100 * MSEC);
if (wait_for_signal(GPIO_A, 1 << 0, 1, 40 * MSEC))
goto err_wait_resp_async;
/* Discard potential garbage in SPI DR */
if (spi->sr & STM32_SPI_SR_RXNE)
in_msg[0] = spi->dr;
#ifdef CONFIG_KEYBORG_SPI_FULL_PACKET
dma_clear_isr(STM32_DMAC_SPI1_TX);
dma_clear_isr(STM32_DMAC_SPI1_RX);
dma_start_rx(&dma_rx_option, SPI_PACKET_MAX_SIZE, in_msg);
dma_prepare_tx(&dma_tx_option, SPI_PACKET_MAX_SIZE, out_msg);
dma_go(dma_get_channel(STM32_DMAC_SPI1_TX));
#else
/* Get the packet size */
spi->dr = DUMMY_DATA;
while (!(spi->sr & STM32_SPI_SR_RXNE))
;
in_msg[0] = spi->dr;
master_slave_sync(5);
dma_clear_isr(STM32_DMAC_SPI1_TX);
dma_clear_isr(STM32_DMAC_SPI1_RX);
/* Get the rest of the packet*/
dma_start_rx(&dma_rx_option,
in_msg[0] + SPI_PACKET_HEADER_SIZE - 1,
in_msg + 1);
dma_prepare_tx(&dma_tx_option,
in_msg[0] + SPI_PACKET_HEADER_SIZE - 1,
out_msg);
dma_go(dma_get_channel(STM32_DMAC_SPI1_TX));
#endif
return EC_SUCCESS;
err_wait_resp_async:
/* Set CS1 (slave SPI_NSS) to high */
STM32_GPIO_BSRR(GPIO_A) = 1 << 6;
return EC_ERROR_TIMEOUT;
}
const struct spi_comm_packet *spi_master_wait_response_done(void)
{
const struct spi_comm_packet *resp =
(const struct spi_comm_packet *)in_msg;
if (dma_wait(STM32_DMAC_SPI1_TX) || dma_wait(STM32_DMAC_SPI1_RX)) {
debug_printf("SPI: Incomplete response\n");
goto err_wait_response_done;
}
if (spi->sr & STM32_SPI_SR_CRCERR) {
debug_printf("SPI: CRC mismatch\n");
goto err_wait_response_done;
}
if (resp->cmd_sts != EC_SUCCESS) {
debug_printf("SPI: Slave error\n");
goto err_wait_response_done;
}
exit_wait_response_done:
dma_disable(STM32_DMAC_SPI1_TX);
dma_disable(STM32_DMAC_SPI1_RX);
dma_clear_isr(STM32_DMAC_SPI1_TX);
dma_clear_isr(STM32_DMAC_SPI1_RX);
/* Set CS1 (slave SPI_NSS) to high */
STM32_GPIO_BSRR(GPIO_A) = 1 << 6;
return resp;
err_wait_response_done:
resp = NULL;
goto exit_wait_response_done;
}
const struct spi_comm_packet *spi_master_wait_response(void)
{
if (spi_master_wait_response_async() != EC_SUCCESS)
return NULL;
return spi_master_wait_response_done();
}
static uint32_t myrnd(void)
{
static uint32_t last = 0x1357;
return last = (last * 8001 + 1);
}
int spi_hello_test(int iteration)
{
int i, j, xv, sz;
struct spi_comm_packet *cmd = (struct spi_comm_packet *)out_msg;
const struct spi_comm_packet *resp;
for (i = 0; i < iteration; ++i) {
xv = myrnd() & 0xff;
cmd->cmd_sts = TS_CMD_HELLO;
sz = myrnd() % (sizeof(out_msg) - 10) + 1;
cmd->size = sz + 2;
cmd->data[0] = sz;
cmd->data[1] = xv;
for (j = 0; j < sz; ++j)
cmd->data[j + 2] = myrnd() & 0xff;
if (spi_master_send_command(cmd))
return EC_ERROR_UNKNOWN;
resp = spi_master_wait_response();
if (resp == NULL || resp->size != sz)
return EC_ERROR_UNKNOWN;
for (j = 0; j < sz; ++j)
if (cmd->data[j + 2] != resp->data[j])
return EC_ERROR_UNKNOWN;
resp = spi_master_wait_response();
if (resp == NULL || resp->size != sz)
return EC_ERROR_UNKNOWN;
for (j = 0; j < sz; ++j)
if ((cmd->data[j + 2] ^ xv) != resp->data[j])
return EC_ERROR_UNKNOWN;
}
return EC_SUCCESS;
}
/*****************************************************************************/
/* Slave */
void spi_slave_init(void)
{
/*
* MISO: AFIO Push-pull
*/
STM32_GPIO_CRL(GPIO_A) = (STM32_GPIO_CRL(GPIO_A) & 0xfff0ffff) |
0x000b0000;
/* Set DFF to 8-bit (default) */
/* Set CPOL and CPHA (default) */
/* Configure LSBFIRST (default) */
/* Set SSM and clear SSI */
spi->cr1 |= STM32_SPI_CR1_SSM;
spi->cr1 &= ~STM32_SPI_CR1_SSI;
/* Enable RXNE interrupt */
spi->cr2 |= STM32_SPI_CR2_RXNEIE;
/*task_enable_irq(STM32_IRQ_SPI1);*/
/* Enable TX and RX DMA */
spi->cr2 |= STM32_SPI_CR2_TXDMAEN | STM32_SPI_CR2_RXDMAEN;
/* Clear MSTR */
spi->cr1 &= ~STM32_SPI_CR1_MSTR;
/* Enable CRC */
spi->cr1 |= STM32_SPI_CR1_CRCEN;
/* Set SPE */
spi->cr1 |= STM32_SPI_CR1_SPE;
/* Dummy byte to clock out when the next packet comes in */
spi->dr = DUMMY_DATA;
/* Enable interrupt on PA0 (GPIO_SPI_NSS) */
STM32_AFIO_EXTICR(0) &= ~0xF;
STM32_EXTI_IMR |= (1 << 0);
task_clear_pending_irq(STM32_IRQ_EXTI0);
task_enable_irq(STM32_IRQ_EXTI0);
}
int spi_slave_send_response(struct spi_comm_packet *resp)
{
int r;
r = spi_slave_send_response_async(resp);
r |= spi_slave_send_response_flush();
return r;
}
int spi_slave_send_response_async(struct spi_comm_packet *resp)
{
int size = resp->size + SPI_PACKET_HEADER_SIZE;
if (size > SPI_PACKET_MAX_SIZE)
return EC_ERROR_OVERFLOW;
if (out_msg != (uint8_t *)resp)
memcpy(out_msg, resp, size);
#ifdef CONFIG_KEYBORG_SPI_FULL_PACKET
dma_clear_isr(STM32_DMAC_SPI1_TX);
dma_prepare_tx(&dma_tx_option, SPI_PACKET_MAX_SIZE, out_msg);
dma_go(dma_get_channel(STM32_DMAC_SPI1_TX));
/* Set N_CHG (master SPI_NSS) to high */
STM32_GPIO_BSRR(GPIO_A) = 1 << 1;
#else
if (spi->sr & STM32_SPI_SR_RXNE)
in_msg[0] = spi->dr;
spi->dr = out_msg[0];
/* Set N_CHG (master SPI_NSS) to high */
STM32_GPIO_BSRR(GPIO_A) = 1 << 1;
while (!(spi->sr & STM32_SPI_SR_RXNE))
;
in_msg[0] = spi->dr;
dma_clear_isr(STM32_DMAC_SPI1_TX);
dma_prepare_tx(&dma_tx_option, size - 1, out_msg + 1);
dma_go(dma_get_channel(STM32_DMAC_SPI1_TX));
if (master_slave_sync(5) != EC_SUCCESS)
return EC_ERROR_UNKNOWN;
#endif
return EC_SUCCESS;
}
int spi_slave_send_response_flush(void)
{
int ret;
ret = dma_wait(STM32_DMAC_SPI1_TX);
dma_disable(STM32_DMAC_SPI1_TX);
dma_clear_isr(STM32_DMAC_SPI1_TX);
/* Set N_CHG (master SPI_NSS) to low */
STM32_GPIO_BSRR(GPIO_A) = 1 << (1 + 16);
return ret;
}
static void spi_slave_nack(void)
{
struct spi_comm_packet *resp = (struct spi_comm_packet *)out_msg;
resp->cmd_sts = EC_ERROR_UNKNOWN;
resp->size = 0;
spi_slave_send_response(resp);
}
static void spi_slave_hello_back(const struct spi_comm_packet *cmd)
{
struct spi_comm_packet *resp = (struct spi_comm_packet *)out_msg;
uint8_t buf[SPI_PACKET_MAX_SIZE];
int i, sz;
sz = cmd->data[0];
memcpy(buf, cmd->data, sz + 2);
resp->cmd_sts = EC_SUCCESS;
resp->size = sz;
for (i = 0; i < sz; ++i)
resp->data[i] = cmd->data[i + 2];
spi_slave_send_response(resp);
for (i = 0; i < sz; ++i)
resp->data[i] = buf[i + 2] ^ buf[1];
spi_slave_send_response(resp);
}
static void spi_nss_interrupt(void)
{
const struct spi_comm_packet *cmd =
(const struct spi_comm_packet *)in_msg;
if (master_slave_is_master()) {
slave_ready = 1;
return;
}
if (spi->sr & STM32_SPI_SR_RXNE)
in_msg[0] = spi->dr;
/*
* SPI_NSS is also used for the master to reboot the slave.
* If SPI_NSS goes low without a sync in time, reboots.
*/
if (master_slave_sync(5))
system_reboot();
/* Read in the packet size */
while (!(spi->sr & STM32_SPI_SR_RXNE))
;
in_msg[0] = spi->dr;
/* Read in the rest of the packet */
dma_clear_isr(STM32_DMAC_SPI1_RX);
dma_start_rx(&dma_rx_option, in_msg[0] + SPI_PACKET_HEADER_SIZE - 1,
in_msg + 1);
dma_prepare_tx(&dma_tx_option, in_msg[0] + SPI_PACKET_HEADER_SIZE - 1,
out_msg);
dma_go(dma_get_channel(STM32_DMAC_SPI1_TX));
master_slave_sync(5);
if (dma_wait(STM32_DMAC_SPI1_RX) != EC_SUCCESS) {
debug_printf("SPI: Incomplete packet\n");
spi_slave_nack();
return;
}
if (spi->sr & STM32_SPI_SR_CRCERR) {
debug_printf("SPI: CRC mismatch\n");
spi_slave_nack();
return;
}
if (cmd->cmd_sts == TS_CMD_HELLO)
spi_slave_hello_back(cmd);
else if (cmd->cmd_sts == TS_CMD_FULL_SCAN)
touch_scan_slave_start();
else
spi_slave_nack();
}
/* Interrupt handler for PA0 */
void spi_nss_interrupt_handler(void)
{
/* Clear the interrupt */
STM32_EXTI_PR = STM32_EXTI_PR;
/* SPI slave interrupt */
spi_nss_interrupt();
}
DECLARE_IRQ(STM32_IRQ_EXTI0, spi_nss_interrupt_handler, 1);