mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-03 13:39:53 +00:00
Typically this routine runs on low priority hook task. A pre-emption by a higher priority task might be mistaken for timeout. Double check the transfer done status after the timeout time has passed. Also clear the TXDONE status before starting a fresh transaction to make sure we wait for the current transaction to complete; an errand TXDONE status at start of the transaction will pre-empt waiting for the current transaction and return stale data. BRANCH=none TEST=mn50 stress test fails within minutes vs. now stable. Main test component is higher priority console task that does intermittent compute during usb-spi transfers. Change-Id: Ide4390e42d3957bc45eea8160617a52dd31ed866 Reviewed-on: https://chromium-review.googlesource.com/849662 Commit-Ready: Marius Schilder <mschilder@chromium.org> Tested-by: Marius Schilder <mschilder@chromium.org> Reviewed-by: Marius Schilder <mschilder@chromium.org> Reviewed-by: Nagendra Modadugu <ngm@google.com> Reviewed-by: Nick Sanders <nsanders@chromium.org> Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
254 lines
7.1 KiB
C
254 lines
7.1 KiB
C
/* Copyright 2015 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 "common.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "registers.h"
|
|
#include "spi.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Not defined in the hardware register spec, the RX and TX buffers are 128B. */
|
|
#define SPI_BUF_SIZE 0x80
|
|
|
|
/* This timeout should allow a full buffer transaction at the lowest SPI speed
|
|
* by using the largest uint8_t clock divider of 256 (~235kHz). */
|
|
#define SPI_TRANSACTION_TIMEOUT_USEC (5 * MSEC)
|
|
|
|
/* There are two SPI masters or ports on this chip. */
|
|
#define SPI_NUM_PORTS 2
|
|
|
|
static struct mutex spi_mutex[SPI_NUM_PORTS];
|
|
static enum spi_clock_mode clock_mode[SPI_NUM_PORTS];
|
|
|
|
/* The Cr50 SPI master is not DMA auto-fill/drain capable, so async and flush
|
|
* are not defined on purpose. */
|
|
int spi_transaction(const struct spi_device_t *spi_device,
|
|
const uint8_t *txdata, int txlen,
|
|
uint8_t *rxdata, int rxlen)
|
|
{
|
|
int port = spi_device->port;
|
|
int rv = EC_SUCCESS;
|
|
timestamp_t timeout;
|
|
int transaction_size = 0;
|
|
int rxoffset = 0;
|
|
|
|
/* If SPI0's passthrough is enabled, SPI0 is not available unless the
|
|
* SPS's BUSY bit is set. */
|
|
if (port == 0) {
|
|
if (GREAD_FIELD_I(SPI, port, CTRL, ENPASSTHRU) &&
|
|
!GREAD(SPS, EEPROM_BUSY_STATUS))
|
|
return EC_ERROR_BUSY;
|
|
}
|
|
|
|
if (rxlen == SPI_READBACK_ALL) {
|
|
/* Bidirectional SPI sends and receives a bit for each clock.
|
|
* We'll need to make sure the buffers for RX and TX are equal
|
|
* and return a bit received for every bit sent.
|
|
*/
|
|
if (txlen > SPI_BUF_SIZE)
|
|
return EC_ERROR_INVAL;
|
|
rxlen = txlen;
|
|
transaction_size = txlen;
|
|
rxoffset = 0;
|
|
} else {
|
|
/* Ensure it'll fit inside of the RX and TX buffers. Note that
|
|
* although the buffers are separate, the total transmission
|
|
* size must fit in the rx buffer.
|
|
*/
|
|
if (txlen + rxlen > SPI_BUF_SIZE)
|
|
return EC_ERROR_INVAL;
|
|
transaction_size = rxlen + txlen;
|
|
rxoffset = txlen;
|
|
}
|
|
|
|
/* Grab the port's mutex. */
|
|
mutex_lock(&spi_mutex[port]);
|
|
|
|
|
|
/* Copy the txdata into the 128B Transmit Buffer. */
|
|
memmove((uint8_t *)GREG32_ADDR_I(SPI, port, TX_DATA), txdata, txlen);
|
|
|
|
#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
|
|
/* Drive chip select low. */
|
|
gpio_set_level(spi_device->gpio_cs, 0);
|
|
#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
|
|
|
|
/* Initiate the transaction. */
|
|
GWRITE_FIELD_I(SPI, port, ISTATE_CLR, TXDONE, 1);
|
|
GWRITE_FIELD_I(SPI, port, XACT, SIZE, transaction_size - 1);
|
|
GWRITE_FIELD_I(SPI, port, XACT, START, 1);
|
|
|
|
/* Wait for the SPI master to finish the transaction. */
|
|
timeout.val = get_time().val + SPI_TRANSACTION_TIMEOUT_USEC;
|
|
while (!GREAD_FIELD_I(SPI, port, ISTATE, TXDONE)) {
|
|
/* Give up if the deadline has been exceeded. */
|
|
if (get_time().val > timeout.val) {
|
|
/* Might have been pre-empted by other task.
|
|
* Check ISTATE.TXDONE again for legit timeout.
|
|
*/
|
|
if (GREAD_FIELD_I(SPI, port, ISTATE, TXDONE))
|
|
break;
|
|
rv = EC_ERROR_TIMEOUT;
|
|
goto err_cs_high;
|
|
}
|
|
}
|
|
GWRITE_FIELD_I(SPI, port, ISTATE_CLR, TXDONE, 1);
|
|
|
|
/* Copy the result. */
|
|
memmove(rxdata,
|
|
&((uint8_t *)GREG32_ADDR_I(SPI, port, RX_DATA))[rxoffset],
|
|
rxlen);
|
|
|
|
err_cs_high:
|
|
#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
|
|
/* Drive chip select high. */
|
|
gpio_set_level(spi_device->gpio_cs, 1);
|
|
#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
|
|
|
|
/* Release the port's mutex. */
|
|
mutex_unlock(&spi_mutex[port]);
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Configure the SPI port's clock mode. The SPI port must be re-enabled after
|
|
* changing the clocking mode.
|
|
*/
|
|
void set_spi_clock_mode(int port, enum spi_clock_mode mode)
|
|
{
|
|
clock_mode[port] = mode;
|
|
}
|
|
|
|
/*
|
|
* Configure the SPI0 master's passthrough mode. Note:
|
|
* 1) This must be called after the SPI port is enabled.
|
|
* 2) Passthrough cannot be safely disabled while the SPI slave port is active
|
|
* and the SPI slave port's status register's BUSY bit is not set.
|
|
*/
|
|
void configure_spi0_passthrough(int enable)
|
|
{
|
|
int port = 0;
|
|
|
|
/* Grab the port's mutex. */
|
|
mutex_lock(&spi_mutex[port]);
|
|
|
|
GWRITE_FIELD_I(SPI, port, CTRL, ENPASSTHRU, enable);
|
|
|
|
/* Release the port's mutex. */
|
|
mutex_unlock(&spi_mutex[port]);
|
|
}
|
|
|
|
int spi_enable(int port, int enable)
|
|
{
|
|
int i;
|
|
|
|
if (enable) {
|
|
int spi_device_found = 0;
|
|
uint8_t max_div = 0;
|
|
|
|
#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
|
|
gpio_config_module(MODULE_SPI, 1);
|
|
#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
|
|
for (i = 0; i < spi_devices_used; i++) {
|
|
if (spi_devices[i].port != port)
|
|
continue;
|
|
|
|
spi_device_found = 1;
|
|
|
|
#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
|
|
/* Deassert CS# */
|
|
gpio_set_flags(spi_devices[i].gpio_cs, GPIO_OUTPUT);
|
|
gpio_set_level(spi_devices[i].gpio_cs, 1);
|
|
#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
|
|
|
|
/* Find the port's largest DIV (lowest frequency). */
|
|
if (spi_devices[i].div > max_div)
|
|
max_div = spi_devices[i].div;
|
|
}
|
|
|
|
/* Ensure there is at least one device behind the SPI port. */
|
|
if (!spi_device_found)
|
|
return EC_ERROR_INVAL;
|
|
|
|
/* configure the SPI clock mode */
|
|
GWRITE_FIELD_I(SPI, port, CTRL, CPOL,
|
|
(clock_mode[port] == SPI_CLOCK_MODE2) ||
|
|
(clock_mode[port] == SPI_CLOCK_MODE3));
|
|
GWRITE_FIELD_I(SPI, port, CTRL, CPHA,
|
|
(clock_mode[port] == SPI_CLOCK_MODE1) ||
|
|
(clock_mode[port] == SPI_CLOCK_MODE3));
|
|
|
|
/* Enforce the default setup and hold times. */
|
|
GWRITE_FIELD_I(SPI, port, CTRL, CSBSU, 0);
|
|
GWRITE_FIELD_I(SPI, port, CTRL, CSBHLD, 0);
|
|
|
|
/* Set the clock divider, where freq / (div + 1). */
|
|
GWRITE_FIELD_I(SPI, port, CTRL, IDIV, max_div);
|
|
|
|
/* Master's CS is active low. */
|
|
GWRITE_FIELD_I(SPI, port, CTRL, CSBPOL, 0);
|
|
|
|
/* Byte 0 bit 7 is first in each double word in the buffers. */
|
|
GWRITE_FIELD_I(SPI, port, CTRL, TXBITOR, 1);
|
|
GWRITE_FIELD_I(SPI, port, CTRL, TXBYTOR, 0);
|
|
GWRITE_FIELD_I(SPI, port, CTRL, RXBITOR, 1);
|
|
GWRITE_FIELD_I(SPI, port, CTRL, RXBYTOR, 0);
|
|
|
|
/* Disable passthrough by default. */
|
|
if (port == 0)
|
|
configure_spi0_passthrough(0);
|
|
|
|
/* Disable the TXDONE interrupt, we'll busy poll instead. */
|
|
GWRITE_FIELD_I(SPI, port, ICTRL, TXDONE, 0);
|
|
|
|
} else {
|
|
for (i = 0; i < spi_devices_used; i++) {
|
|
if (spi_devices[i].port != port)
|
|
continue;
|
|
|
|
#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
|
|
/* Make sure CS# is deasserted and disabled. */
|
|
gpio_set_level(spi_devices[i].gpio_cs, 1);
|
|
gpio_set_flags(spi_devices[i].gpio_cs, GPIO_ODR_HIGH);
|
|
#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
|
|
}
|
|
|
|
/* Disable passthrough. */
|
|
if (port == 0)
|
|
configure_spi0_passthrough(0);
|
|
|
|
gpio_config_module(MODULE_SPI, 1);
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* Hooks */
|
|
|
|
static void spi_init(void)
|
|
{
|
|
size_t i;
|
|
|
|
#ifdef CONFIG_SPI_MASTER_CONFIGURE_GPIOS
|
|
/* Set SPI_MISO as an input */
|
|
GWRITE_FIELD(PINMUX, DIOA11_CTL, IE, 1); /* SPI_MISO */
|
|
#endif
|
|
|
|
for (i = 0; i < SPI_NUM_PORTS; i++) {
|
|
/* Configure the SPI ports to default to mode0. */
|
|
set_spi_clock_mode(i, SPI_CLOCK_MODE0);
|
|
|
|
/* Ensure the SPI ports are disabled to prevent us from
|
|
* interfering with the main chipset when we're not explicitly
|
|
* using the SPI bus. */
|
|
spi_enable(i, 0);
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_DEFAULT);
|