coreboot-libre-fam15h-rdimm/3rdparty/chromeec/chip/lm4/spi.c

191 lines
4.9 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 module for Chrome EC */
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "registers.h"
#include "spi.h"
#include "task.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)
int spi_enable(int port, int enable)
{
int i;
if (enable) {
gpio_config_module(MODULE_SPI, 1);
for (i = 0; i < spi_devices_used; i++) {
if (spi_devices[i].port != port)
continue;
/*
* Don't use the SSI0 frame output.
* CS# is a GPIO so we can keep it low during an entire
* transaction.
*/
gpio_set_flags(spi_device[i]->gpio_cs, GPIO_OUTPUT);
gpio_set_level(spi_device[i]->gpio_cs, 1);
}
/* Enable SSI port */
LM4_SSI_CR1(0) |= 0x02;
} else {
/* Disable SSI port */
LM4_SSI_CR1(0) &= ~0x02;
for (i = 0; i < spi_devices_used; i++) {
if (spi_devices[i].port != port)
continue;
/* Make sure CS# is deselected */
gpio_set_level(spi_device[i]->gpio_cs, 1);
gpio_set_flags(spi_device->gpio_cs[i], GPIO_ODR_HIGH);
}
gpio_config_module(MODULE_SPI, 0);
}
return EC_SUCCESS;
}
int spi_transaction(const struct spi_device_t *spi_device,
const uint8_t *txdata, int txlen,
uint8_t *rxdata, int rxlen)
{
int totallen = txlen + rxlen;
int txcount = 0, rxcount = 0;
static struct mutex spi_mutex;
volatile uint32_t dummy __attribute__((unused));
mutex_lock(&spi_mutex);
/* Empty the receive FIFO */
while (LM4_SSI_SR(0) & LM4_SSI_SR_RNE)
dummy = LM4_SSI_DR(0);
/* Start transaction. Need to do this explicitly because the LM4
* SSI controller pulses its frame select every byte, and the EEPROM
* wants the chip select held low during the entire transaction. */
gpio_set_level(spi_device->gpio_cs, 0);
while (rxcount < totallen) {
/* Handle received bytes if any. We just checked rxcount <
* totallen, so we don't need to worry about overflowing the
* receive buffer. */
if (LM4_SSI_SR(0) & LM4_SSI_SR_RNE) {
if (rxcount < txlen) {
/* Throw away bytes received while we were
transmitting */
dummy = LM4_SSI_DR(0);
} else
*(rxdata++) = LM4_SSI_DR(0);
rxcount++;
}
/* Transmit another byte if needed */
if ((LM4_SSI_SR(0) & LM4_SSI_SR_TNF) && txcount < totallen) {
if (txcount < txlen)
LM4_SSI_DR(0) = *(txdata++);
else {
/* Clock out dummy byte so we can clock in the
* response byte */
LM4_SSI_DR(0) = 0;
}
txcount++;
}
}
/* End transaction */
gpio_set_level(spi_device->gpio_cs, 1);
mutex_unlock(&spi_mutex);
return EC_SUCCESS;
}
/*****************************************************************************/
/* Hooks */
static int spi_init(void)
{
/* Enable the SPI module in run and sleep modes */
clock_enable_peripheral(CGC_OFFSET_SSI, 0x1,
CGC_MODE_RUN | CGC_MODE_SLEEP);
LM4_SSI_CR1(0) = 0; /* Disable SSI */
LM4_SSI_CR0(0) = 0x0007; /* SCR=0, SPH=0, SPO=0, FRF=SPI, 8-bit */
/* Use PIOSC for clock. This limits us to 8MHz (PIOSC/2), but is
* simpler to configure and we don't need to worry about clock
* frequency changing when the PLL is disabled. If we really start
* using this, might be worth using the system clock and handling
* frequency change (like we do with PECI) so we can go faster. */
LM4_SSI_CC(0) = 1;
/* SSICLK = PIOSC / (CPSDVSR * (1 + SCR)
* = 16 MHz / (2 * (1 + 0))
* = 8 MHz */
LM4_SSI_CPSR(0) = 2;
/* Ensure the SPI port is disabled. This keeps us from interfering
* with the main chipset when we're not explicitly using the SPI
* bus. */
spi_enable(CONFIG_SPI_FLASH_PORT, 0);
return EC_SUCCESS;
}
DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_INIT_SPI);
/*****************************************************************************/
/* Console commands */
static int printrx(const char *desc, const uint8_t *txdata, int txlen,
int rxlen)
{
uint8_t rxdata[32];
int rv;
int i;
rv = spi_transaction(SPI_FLASH_DEVICE, txdata, txlen, rxdata, rxlen);
if (rv)
return rv;
ccprintf("%-12s:", desc);
for (i = 0; i < rxlen; i++)
ccprintf(" 0x%02x", rxdata[i]);
ccputs("\n");
return EC_SUCCESS;
}
static int command_spirom(int argc, char **argv)
{
uint8_t txmandev[] = {0x90, 0x00, 0x00, 0x00};
uint8_t txjedec[] = {0x9f};
uint8_t txunique[] = {0x4b, 0x00, 0x00, 0x00, 0x00};
uint8_t txsr1[] = {0x05};
uint8_t txsr2[] = {0x35};
spi_enable(CONFIG_SPI_FLASH_PORT, 1);
printrx("Man/Dev ID", txmandev, sizeof(txmandev), 2);
printrx("JEDEC ID", txjedec, sizeof(txjedec), 3);
printrx("Unique ID", txunique, sizeof(txunique), 8);
printrx("Status reg 1", txsr1, sizeof(txsr1), 1);
printrx("Status reg 2", txsr2, sizeof(txsr2), 1);
spi_enable(CONFIG_SPI_FLASH_PORT, 0);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(spirom, command_spirom,
NULL,
"Test reading SPI EEPROM");