Files
OpenCellular/chip/mchp/spi.c
Scott Worley 0a6a7be572 ec_chip_mchp: Add SPI files
Add Microchip MEC17xx family SPI master
controllers and flash files. SPI implements
public interface wrapper for QMSPI and GPSPI.
MEC17xx family uses QMSPI for loading
EC firmware. GPSPI is for general use (sensor).

BRANCH=none
BUG=
TEST=Review only.

Change-Id: I23001e254dd437caa0917949f4dd2eb903f1adb1
Signed-off-by: Scott Worley <scott.worley@microchip.corp-partner.google.com>
2017-12-28 14:50:30 -08:00

298 lines
5.9 KiB
C

/* Copyright 2017 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.
*/
/* QMSPI master module for MCHP MEC family */
#include "common.h"
#include "console.h"
#include "dma.h"
#include "gpio.h"
#include "registers.h"
#include "spi.h"
#include "timer.h"
#include "util.h"
#include "hooks.h"
#include "task.h"
#include "spi_chip.h"
#include "qmspi_chip.h"
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
#include "gpspi_chip.h"
#endif
#include "tfdp_chip.h"
#define CPUTS(outstr) cputs(CC_SPI, outstr)
#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
#define SPI_BYTE_TRANSFER_TIMEOUT_US (3 * MSEC)
#define SPI_BYTE_TRANSFER_POLL_INTERVAL_US 100
static const struct dma_option spi_rx_option[] = {
{
MCHP_DMAC_QMSPI0_RX,
(void *)(MCHP_QMSPI0_RX_FIFO_ADDR),
MCHP_DMA_XFER_SIZE(1) + MCHP_DMA_INC_MEM
},
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
#if CONFIG_MCHP_GPSPI & 0x01
{
MCHP_DMAC_SPI0_RX,
(void *)&MCHP_SPI_RD(0),
MCHP_DMA_XFER_SIZE(1) + MCHP_DMA_INC_MEM
},
#endif
#if CONFIG_MCHP_GPSPI & 0x02
{
MCHP_DMAC_SPI1_RX,
(void *)&MCHP_SPI_RD(1),
MCHP_DMA_XFER_SIZE(1) + MCHP_DMA_INC_MEM
},
#endif
#endif
};
static const struct dma_option spi_tx_option[] = {
{
MCHP_DMAC_QMSPI0_TX,
(void *)(MCHP_QMSPI0_TX_FIFO_ADDR),
MCHP_DMA_XFER_SIZE(1) + MCHP_DMA_INC_MEM
},
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
#if CONFIG_MCHP_GPSPI & 0x01
{
MCHP_DMAC_SPI0_TX,
(void *)&MCHP_SPI_TD(0),
MCHP_DMA_XFER_SIZE(1) + MCHP_DMA_INC_MEM
},
#endif
#if CONFIG_MCHP_GPSPI & 0x02
{
MCHP_DMAC_SPI1_TX,
(void *)&MCHP_SPI_TD(1),
MCHP_DMA_XFER_SIZE(1) + MCHP_DMA_INC_MEM
},
#endif
#endif
};
/* only regular image needs mutex, LFW does not have scheduling */
#ifndef LFW
static struct mutex spi_mutex[ARRAY_SIZE(spi_rx_option)];
/*
* Acquire mutex for specified SPI controller/port.
* Note if mutex is owned by another task this routine
* will block until mutex is released.
*/
static void spi_mutex_lock(uint8_t hw_port)
{
uint32_t n;
n = 0;
#ifdef CONFIG_MCHP_GPSPI
if (hw_port & 0xF0) {
#if (CONFIG_MCHP_GPSPI & 0x03) == 0x03
n = (hw_port & 0x0F) + 1;
#else
n = 1;
#endif
}
#endif
mutex_lock(&spi_mutex[n]);
}
/*
* Release mutex for specified SPI controller/port.
*/
static void spi_mutex_unlock(uint8_t hw_port)
{
uint32_t n;
n = 0;
#ifdef CONFIG_MCHP_GPSPI
if (hw_port & 0xF0) {
#if (CONFIG_MCHP_GPSPI & 0x03) == 0x03
n = (hw_port & 0x0F) + 1;
#else
n = 1;
#endif
}
#endif
mutex_unlock(&spi_mutex[n]);
}
#endif /* #ifndef LFW */
/*
* Public SPI interface
*/
const void *spi_dma_option(const struct spi_device_t *spi_device,
int is_tx)
{
uint32_t n;
if (spi_device == NULL)
return NULL;
n = 0;
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
if (spi_device->port & 0xF0) {
#if (CONFIG_MCHP_GPSPI & 0x03) == 0x03
n = (spi_device->port & 0x0F) + 1;
#else
n = 1;
#endif
}
#endif
if (is_tx)
return &spi_tx_option[n];
else
return &spi_rx_option[n];
}
int spi_transaction_async(const struct spi_device_t *spi_device,
const uint8_t *txdata, int txlen,
uint8_t *rxdata, int rxlen)
{
int rc;
if (spi_device == NULL)
return EC_ERROR_INVAL;
switch (spi_device->port) {
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
case GPSPI0_PORT:
case GPSPI1_PORT:
rc = gpspi_transaction_async(spi_device, txdata,
txlen, rxdata, rxlen);
break;
#endif
case QMSPI0_PORT:
rc = qmspi_transaction_async(spi_device, txdata,
txlen, rxdata, rxlen);
break;
default:
rc = EC_ERROR_INVAL;
}
return rc;
}
int spi_transaction_flush(const struct spi_device_t *spi_device)
{
int rc;
if (spi_device == NULL)
return EC_ERROR_INVAL;
switch (spi_device->port) {
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
case GPSPI0_PORT:
case GPSPI1_PORT:
rc = gpspi_transaction_flush(spi_device);
break;
#endif
case QMSPI0_PORT:
rc = qmspi_transaction_flush(spi_device);
break;
default:
rc = EC_ERROR_INVAL;
}
return rc;
}
/* Wait for async response received but do not de-assert chip select */
int spi_transaction_wait(const struct spi_device_t *spi_device)
{
int rc;
if (spi_device == NULL)
return EC_ERROR_INVAL;
switch (spi_device->port) {
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
#ifndef LFW
case GPSPI0_PORT:
case GPSPI1_PORT:
rc = gpspi_transaction_wait(spi_device);
break;
#endif
#endif
case QMSPI0_PORT:
rc = qmspi_transaction_wait(spi_device);
break;
default:
rc = EC_ERROR_INVAL;
}
return rc;
}
/*
* called from common/spi_flash.c
* For tranfers reading less than the size of QMSPI RX FIFO call
* a routine where reads use FIFO only no DMA.
* GP-SPI only has a one byte RX FIFO but small data transfers will be OK
* without the overhead of DMA setup.
*/
int spi_transaction(const struct spi_device_t *spi_device,
const uint8_t *txdata, int txlen,
uint8_t *rxdata, int rxlen)
{
int rc;
if (spi_device == NULL)
return EC_ERROR_PARAM1;
#ifndef LFW
spi_mutex_lock(spi_device->port);
#endif
rc = spi_transaction_async(spi_device, txdata, txlen, rxdata, rxlen);
if (rc == EC_SUCCESS)
rc = spi_transaction_flush(spi_device);
#ifndef LFW
spi_mutex_unlock(spi_device->port);
#endif
return rc;
}
/**
* Enable SPI port and associated controller
*
* @param port Zero based index into spi_device an array of
* struct spi_device_t
* @param enable
* @return EC_SUCCESS or EC_ERROR_INVAL if port is unrecognized
* @note called from common/spi_flash.c
*
* spi_devices[].port is defined as
* bits[3:0] = controller instance
* bits[7:4] = controller family 0 = QMSPI, 1 = GPSPI
*/
int spi_enable(int port, int enable)
{
int rc;
uint8_t hw_port;
rc = EC_ERROR_INVAL;
if (port < spi_devices_used) {
hw_port = spi_devices[port].port;
if ((hw_port & 0xF0) == QMSPI_CLASS)
rc = qmspi_enable(hw_port, enable);
#if defined(CONFIG_MCHP_GPSPI) && !defined(LFW)
if ((hw_port & 0xF0) == GPSPI_CLASS)
rc = gpspi_enable(hw_port, enable);
#endif
}
return rc;
}