Files
OpenCellular/chip/g/spi_master.c
Mary Ruthven 41f872ed53 g: tristate spi master pins
Having DIO A4, A8, and A14 connected to the spi master output pads
prevents reef from booting. We need to tristate those pins when spi ccd
is not in use.

This change disconnects those pins from the spi peripheral when spi is
disabled and reconnects them when spi is enabled.

BUG=chrome-os-partner:53582
BRANCH=none
TEST=manual
	use 'pinmux' to verify DIOA4, DIOA8, and DIOA14 are set have
	GPIO 7, 8, and 9 as their output sources when not using the usb
	spi interface.

	Check the usb spi interface still works.

	Enable usb spi then disable ccd and check that the pins are
	connected back to the non-peripheral gpios.

	Verify the AP on kevin and reef can be flashed using servo.

	Verify the AP boots successfully on both.

Change-Id: I85d70422a30da445076432d2bfc81960aeba8578
Signed-off-by: Mary Ruthven <mruthven@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/357883
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
2016-07-14 23:34:38 -07:00

230 lines
6.4 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;
/* 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;
}
/* 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;
/* 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, XACT, SIZE, rxlen + txlen - 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) {
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))[txlen],
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 deaserted 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);