191 lines
4.9 KiB
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");
|