268 lines
6.9 KiB
C
268 lines
6.9 KiB
C
/* Copyright 2014 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 "clock.h"
|
|
#include "clock_chip.h"
|
|
#include "registers.h"
|
|
#include "spi.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#if !DEBUG_SPI
|
|
#define CPUTS(...)
|
|
#define CPRINTS(...)
|
|
#else
|
|
#define CPUTS(outstr) cputs(CC_SPI, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
|
|
#endif
|
|
|
|
/* SPI IP as SPI master */
|
|
#define SPI_CLK 8000000
|
|
/**
|
|
* Clear SPI data buffer.
|
|
*
|
|
* @param none
|
|
* @return none.
|
|
*/
|
|
static void clear_databuf(void)
|
|
{
|
|
volatile uint8_t dummy __attribute__((unused));
|
|
while (IS_BIT_SET(NPCX_SPI_STAT, NPCX_SPI_STAT_RBF))
|
|
dummy = NPCX_SPI_DATA;
|
|
}
|
|
|
|
/**
|
|
* Preset SPI operation clock.
|
|
*
|
|
* @param none
|
|
* @return none
|
|
* @notes changed when initial or HOOK_FREQ_CHANGE command
|
|
*/
|
|
void spi_freq_changed(void)
|
|
{
|
|
uint8_t prescaler_divider = 0;
|
|
|
|
/* Set clock prescaler divider to SPI module*/
|
|
prescaler_divider = (uint8_t)((uint32_t)clock_get_apb2_freq()
|
|
/ 2 / SPI_CLK);
|
|
if (prescaler_divider >= 1)
|
|
prescaler_divider = prescaler_divider - 1;
|
|
if (prescaler_divider > 0x7F)
|
|
prescaler_divider = 0x7F;
|
|
|
|
/* Set core clock division factor in order to obtain the SPI clock */
|
|
NPCX_SPI_CTL1 = (NPCX_SPI_CTL1&(~(((1<<7)-1)<<NPCX_SPI_CTL1_SCDV)))
|
|
|(prescaler_divider<<NPCX_SPI_CTL1_SCDV);
|
|
}
|
|
DECLARE_HOOK(HOOK_FREQ_CHANGE, spi_freq_changed, HOOK_PRIO_FIRST);
|
|
|
|
/**
|
|
* Set SPI enabled.
|
|
*
|
|
* @spi_port port to act on. Only one port supported, one gpio.
|
|
* @param enable enabled flag
|
|
* @return success
|
|
*/
|
|
int spi_enable(int port, int enable)
|
|
{
|
|
int i;
|
|
enum gpio_signal gpio;
|
|
|
|
if (enable) {
|
|
/* Enabling spi module for gpio configuration */
|
|
gpio_config_module(MODULE_SPI, 1);
|
|
/* GPIO No SPI Select */
|
|
CLEAR_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_GPIO_NO_SPIP);
|
|
|
|
for (i = 0; i < spi_devices_used; i++) {
|
|
if (spi_devices[i].port != port)
|
|
continue;
|
|
gpio = spi_devices[i].gpio_cs;
|
|
/* Make sure CS# is a GPIO output mode. */
|
|
gpio_set_flags(gpio, GPIO_OUTPUT);
|
|
/* Make sure CS# is deselected */
|
|
gpio_set_level(gpio, 1);
|
|
}
|
|
/* Enabling spi module */
|
|
SET_BIT(NPCX_SPI_CTL1, NPCX_SPI_CTL1_SPIEN);
|
|
} else {
|
|
/* Disabling spi module */
|
|
CLEAR_BIT(NPCX_SPI_CTL1, NPCX_SPI_CTL1_SPIEN);
|
|
for (i = 0; i < spi_devices_used; i++) {
|
|
if (spi_devices[i].port != port)
|
|
continue;
|
|
gpio = spi_devices[i].gpio_cs;
|
|
/* Make sure CS# is deselected */
|
|
gpio_set_level(gpio, 1);
|
|
gpio_set_flags(gpio, GPIO_ODR_HIGH);
|
|
}
|
|
/* Disabling spi module for gpio configuration */
|
|
gpio_config_module(MODULE_SPI, 0);
|
|
/* GPIO No SPI Select */
|
|
SET_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_GPIO_NO_SPIP);
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
* Flush an SPI transaction and receive data from slave.
|
|
*
|
|
* @param spi_device device to talk to
|
|
* @param txdata transfer data
|
|
* @param txlen transfer length
|
|
* @param rxdata receive data
|
|
* @param rxlen receive length
|
|
* @return success
|
|
* @notes set master transaction mode in npcx chip
|
|
*/
|
|
int spi_transaction(const struct spi_device_t *spi_device,
|
|
const uint8_t *txdata, int txlen,
|
|
uint8_t *rxdata, int rxlen)
|
|
{
|
|
int i = 0;
|
|
enum gpio_signal gpio = spi_device->gpio_cs;
|
|
static struct mutex spi_lock;
|
|
|
|
mutex_lock(&spi_lock);
|
|
/* Make sure CS# is a GPIO output mode. */
|
|
gpio_set_flags(gpio, GPIO_OUTPUT);
|
|
/* Make sure CS# is deselected */
|
|
gpio_set_level(gpio, 1);
|
|
/* Cleaning junk data in the buffer */
|
|
clear_databuf();
|
|
/* Assert CS# to start transaction */
|
|
gpio_set_level(gpio, 0);
|
|
CPRINTS("NPCX_SPI_DATA=%x", NPCX_SPI_DATA);
|
|
CPRINTS("NPCX_SPI_CTL1=%x", NPCX_SPI_CTL1);
|
|
CPRINTS("NPCX_SPI_STAT=%x", NPCX_SPI_STAT);
|
|
/* Writing the data */
|
|
for (i = 0; i < txlen; ++i) {
|
|
/* Making sure we can write */
|
|
while (IS_BIT_SET(NPCX_SPI_STAT, NPCX_SPI_STAT_BSY))
|
|
;
|
|
/* Write the data */
|
|
NPCX_SPI_DATA = txdata[i];
|
|
CPRINTS("txdata[i]=%x", txdata[i]);
|
|
/* Waiting till reading is finished */
|
|
while (!IS_BIT_SET(NPCX_SPI_STAT, NPCX_SPI_STAT_RBF))
|
|
;
|
|
/* Reading the (dummy) data */
|
|
clear_databuf();
|
|
}
|
|
CPRINTS("write end");
|
|
/* Reading the data */
|
|
for (i = 0; i < rxlen; ++i) {
|
|
/* Making sure we can write */
|
|
while (IS_BIT_SET(NPCX_SPI_STAT, NPCX_SPI_STAT_BSY))
|
|
;
|
|
/* Write the (dummy) data */
|
|
NPCX_SPI_DATA = 0;
|
|
/* Wait till reading is finished */
|
|
while (!IS_BIT_SET(NPCX_SPI_STAT, NPCX_SPI_STAT_RBF))
|
|
;
|
|
/* Reading the data */
|
|
rxdata[i] = (uint8_t)NPCX_SPI_DATA;
|
|
CPRINTS("rxdata[i]=%x", rxdata[i]);
|
|
}
|
|
/* Deassert CS# (high) to end transaction */
|
|
gpio_set_level(gpio, 1);
|
|
mutex_unlock(&spi_lock);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* SPI initial.
|
|
*
|
|
* @param none
|
|
* @return none
|
|
*/
|
|
static void spi_init(void)
|
|
{
|
|
int i;
|
|
/* Enable clock for SPI peripheral */
|
|
clock_enable_peripheral(CGC_OFFSET_SPI, CGC_SPI_MASK,
|
|
CGC_MODE_RUN | CGC_MODE_SLEEP);
|
|
|
|
/* Disabling spi module */
|
|
for (i = 0; i < spi_devices_used; i++)
|
|
spi_enable(spi_devices[i].port, 0);
|
|
|
|
/* Disabling spi irq */
|
|
CLEAR_BIT(NPCX_SPI_CTL1, NPCX_SPI_CTL1_EIR);
|
|
CLEAR_BIT(NPCX_SPI_CTL1, NPCX_SPI_CTL1_EIW);
|
|
|
|
/* Setting clocking mode to normal mode */
|
|
CLEAR_BIT(NPCX_SPI_CTL1, NPCX_SPI_CTL1_SCM);
|
|
/* Setting 8bit mode transfer */
|
|
CLEAR_BIT(NPCX_SPI_CTL1, NPCX_SPI_CTL1_MOD);
|
|
/* Set core clock division factor in order to obtain the spi clock */
|
|
spi_freq_changed();
|
|
|
|
/* We emit zeros in idle (default behaivor) */
|
|
CLEAR_BIT(NPCX_SPI_CTL1, NPCX_SPI_CTL1_SCIDL);
|
|
|
|
CPRINTS("nSPI_COMP=%x", IS_BIT_SET(NPCX_STRPST, NPCX_STRPST_SPI_COMP));
|
|
CPRINTS("SPI_SP_SEL=%x", IS_BIT_SET(NPCX_DEV_CTL4,
|
|
NPCX_DEV_CTL4_SPI_SP_SEL));
|
|
/* Cleaning junk data in the buffer */
|
|
clear_databuf();
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_INIT_SPI);
|
|
|
|
/*****************************************************************************/
|
|
/* Console commands */
|
|
#ifdef CONFIG_CMD_SPI_FLASH
|
|
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;
|
|
|
|
CPRINTS("%-12s:", desc);
|
|
for (i = 0; i < rxlen; i++)
|
|
CPRINTS(" 0x%02x", rxdata[i]);
|
|
CPUTS("\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");
|
|
#endif
|