/* 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(const struct spi_device_t *spi_device, int enable) { if (enable) { gpio_config_module(MODULE_SPI, 1); /* * 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->gpio_cs, GPIO_OUTPUT); gpio_set_level(spi_device->gpio_cs, 1); /* Enable SSI port */ LM4_SSI_CR1(0) |= 0x02; } else { /* Disable SSI port */ LM4_SSI_CR1(0) &= ~0x02; /* Make sure CS# is deselected */ gpio_set_level(spi_device->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 unused __attribute__((unused)); mutex_lock(&spi_mutex); /* Empty the receive FIFO */ while (LM4_SSI_SR(0) & LM4_SSI_SR_RNE) unused = 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 */ unused = 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 unused 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(SPI_FLASH_DEVICE, 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(SPI_FLASH_DEVICE, 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(SPI_FLASH_DEVICE, 0); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(spirom, command_spirom, NULL, "Test reading SPI EEPROM");