blob: 705a95c44ea5ce14a300b04500e86eac75476574 [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.
*/
/* SPI master module for MEC1322 */
#include "common.h"
#include "console.h"
#include "dma.h"
#include "gpio.h"
#include "registers.h"
#include "spi.h"
#include "timer.h"
#include "util.h"
#include "hooks.h"
#include "task.h"
#define CPUTS(outstr) cputs(CC_SPI, outstr)
#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
#define SPI_BYTE_TRANSFER_TIMEOUT_US (3 * MSEC)
#define SPI_BYTE_TRANSFER_POLL_INTERVAL_US 100
#define SPI_DMA_CHANNEL(port) (MEC1322_DMAC_SPI0_RX + (port) * 2)
/* only regular image needs mutex, LFW does not have scheduling */
/* TODO: Move SPI locking to common code */
#ifndef LFW
static struct mutex spi_mutex;
#endif
static const struct dma_option spi_rx_option[] = {
{
SPI_DMA_CHANNEL(0),
(void *)&MEC1322_SPI_RD(0),
MEC1322_DMA_XFER_SIZE(1)
},
{
SPI_DMA_CHANNEL(1),
(void *)&MEC1322_SPI_RD(1),
MEC1322_DMA_XFER_SIZE(1)
},
};
static int wait_byte(const int port)
{
timestamp_t deadline;
deadline.val = get_time().val + SPI_BYTE_TRANSFER_TIMEOUT_US;
while ((MEC1322_SPI_SR(port) & 0x3) != 0x3) {
if (timestamp_expired(deadline, NULL))
return EC_ERROR_TIMEOUT;
usleep(SPI_BYTE_TRANSFER_POLL_INTERVAL_US);
}
return EC_SUCCESS;
}
static int spi_tx(const int port, const uint8_t *txdata, int txlen)
{
int i;
int ret = EC_SUCCESS;
uint8_t dummy __attribute__((unused)) = 0;
for (i = 0; i < txlen; ++i) {
MEC1322_SPI_TD(port) = txdata[i];
ret = wait_byte(port);
if (ret != EC_SUCCESS)
return ret;
dummy = MEC1322_SPI_RD(port);
}
return ret;
}
int spi_transaction_async(const struct spi_device_t *spi_device,
const uint8_t *txdata, int txlen,
uint8_t *rxdata, int rxlen)
{
int port = spi_device->port;
int ret = EC_SUCCESS;
gpio_set_level(spi_device->gpio_cs, 0);
/* Disable auto read */
MEC1322_SPI_CR(port) &= ~(1 << 5);
ret = spi_tx(port, txdata, txlen);
if (ret != EC_SUCCESS)
return ret;
/* Enable auto read */
MEC1322_SPI_CR(port) |= 1 << 5;
if (rxlen != 0) {
dma_start_rx(&spi_rx_option[port], rxlen, rxdata);
MEC1322_SPI_TD(port) = 0;
}
return ret;
}
int spi_transaction_flush(const struct spi_device_t *spi_device)
{
int port = spi_device->port;
int ret = dma_wait(SPI_DMA_CHANNEL(port));
uint8_t dummy __attribute__((unused)) = 0;
timestamp_t deadline;
/* Disable auto read */
MEC1322_SPI_CR(port) &= ~(1 << 5);
deadline.val = get_time().val + SPI_BYTE_TRANSFER_TIMEOUT_US;
/* Wait for FIFO empty SPISR_TXBE */
while ((MEC1322_SPI_SR(port) & 0x01) != 0x1) {
if (timestamp_expired(deadline, NULL))
return EC_ERROR_TIMEOUT;
usleep(SPI_BYTE_TRANSFER_POLL_INTERVAL_US);
}
dma_disable(SPI_DMA_CHANNEL(port));
dma_clear_isr(SPI_DMA_CHANNEL(port));
if (MEC1322_SPI_SR(port) & 0x2)
dummy = MEC1322_SPI_RD(port);
gpio_set_level(spi_device->gpio_cs, 1);
return ret;
}
int spi_transaction(const struct spi_device_t *spi_device,
const uint8_t *txdata, int txlen,
uint8_t *rxdata, int rxlen)
{
int ret;
#ifndef LFW
mutex_lock(&spi_mutex);
#endif
ret = spi_transaction_async(spi_device, txdata, txlen, rxdata, rxlen);
if (ret)
return ret;
ret = spi_transaction_flush(spi_device);
#ifndef LFW
mutex_unlock(&spi_mutex);
#endif
return ret;
}
int spi_enable(int port, int enable)
{
if (enable) {
gpio_config_module(MODULE_SPI, 1);
/* Set enable bit in SPI_AR */
MEC1322_SPI_AR(port) |= 0x1;
/* Set SPDIN to 0 -> Full duplex */
MEC1322_SPI_CR(port) &= ~(0x3 << 2);
/* Set CLKPOL, TCLKPH, RCLKPH to 0 */
MEC1322_SPI_CC(port) &= ~0x7;
/* Set LSBF to 0 -> MSB first */
MEC1322_SPI_CR(port) &= ~0x1;
} else {
/* Clear enable bit in SPI_AR */
MEC1322_SPI_AR(port) &= ~0x1;
gpio_config_module(MODULE_SPI, 0);
}
return EC_SUCCESS;
}