spi: Add QMSI-based implementation

This driver uses the QMSI library and mostly translates calls from the
Zephyr API to QMSI ones.

This driver conflicts with the native driver implemenation. In order to
enable it, you must set:
CONFIG_QMSI_DRIVERS=y
CONFIG_QMSI_INSTALL_PATH="PATH_TO_QMSI"
CONFIG_SPI_QMSI=y
CONFIG_SPI_QMSI_PORT_0=y
CONFIG_SPI_QMSI_PORT_1=y

Missing:
 - Support for using a GPIO pin as Chip Select;

Change-Id: I0d8eca88a2a803b6b3604f396f874313fe90753c
Signed-off-by: Vinicius Costa Gomes <vinicius.gomes@intel.com>
This commit is contained in:
Vinicius Costa Gomes 2016-01-28 10:42:12 -02:00 committed by Anas Nashif
commit 40f8914376
4 changed files with 351 additions and 0 deletions

View file

@ -191,6 +191,7 @@ endif
if SPI if SPI
config SPI_DW config SPI_DW
def_bool y def_bool y
if SPI_DW
config SPI_DW_CLOCK_GATE config SPI_DW_CLOCK_GATE
def_bool n def_bool n
config SPI_DW_CLOCK_GATE_DRV_NAME config SPI_DW_CLOCK_GATE_DRV_NAME
@ -215,6 +216,21 @@ config SPI_DW_PORT_1_REGS
default 0xb0001400 default 0xb0001400
config SPI_DW_PORT_1_IRQ config SPI_DW_PORT_1_IRQ
default 3 default 3
endif # SPI_DW
config SPI_QMSI
def_bool n
if SPI_QMSI
config SPI_QMSI_PORT_0
def_bool y
config SPI_QMSI_PORT_0_IRQ
default 2
config SPI_QMSI_PORT_1
def_bool y
config SPI_QMSI_PORT_1_IRQ
default 3
endif # SPI_QMSI
endif endif
if WATCHDOG if WATCHDOG

View file

@ -239,6 +239,7 @@ config SPI_DW_CS_GPIO
config SPI_DW_INIT_PRIORITY config SPI_DW_INIT_PRIORITY
int "Init priority" int "Init priority"
depends on SPI_DW
default 60 default 60
help help
Device driver initialization priority. Device driver initialization priority.
@ -376,4 +377,65 @@ config SPI_DW_PORT_1_PRI
depends on SPI_DW_PORT_1 depends on SPI_DW_PORT_1
default 2 default 2
config SPI_QMSI
bool "QMSI driver for SPI controller"
depends on SPI && QMSI_DRIVERS
default n
help
SPI driver implementation using QMSI library. QMSI is the
Quark Microcontroller Software Interface, providing a common
interface to the Quark family of microcontrollers.
config SPI_QMSI_PORT_0
bool
prompt "QMSI SPI port 0"
depends on SPI_QMSI
default n
help
Enable QMSI's SPI controller port 0.
config SPI_QMSI_PORT_0_DRV_NAME
string
prompt "QMSI SPI port 0 device name"
depends on SPI_QMSI_PORT_0
default "spi_0"
config SPI_QMSI_PORT_0_IRQ
int
prompt "Port 0 interrupt"
depends on SPI_QMSI_PORT_0
default 0
config SPI_QMSI_PORT_0_PRI
int
prompt "Port 0 interrupt priority"
depends on SPI_QMSI_PORT_0
default 2
config SPI_QMSI_PORT_1
bool
prompt "QMSI SPI port 1"
depends on SPI_QMSI
default n
help
Enable QMSI's SPI controller port 1.
config SPI_QMSI_PORT_1_DRV_NAME
string
prompt "QMSI SPI port 1 device name"
depends on SPI_QMSI_PORT_1
default "spi_1"
config SPI_QMSI_PORT_1_IRQ
int
prompt "Port 1 interrupt"
depends on SPI_QMSI_PORT_1
default 0
config SPI_QMSI_PORT_1_PRI
int
prompt "Port 0 interrupt priority"
depends on SPI_QMSI_PORT_1
default 2
endif endif

View file

@ -1,2 +1,4 @@
ccflags-$(CONFIG_SPI_QMSI) +=-I$(CONFIG_QMSI_INSTALL_PATH)/include
obj-$(CONFIG_SPI_INTEL) += spi_intel.o obj-$(CONFIG_SPI_INTEL) += spi_intel.o
obj-$(CONFIG_SPI_DW) += spi_dw.o obj-$(CONFIG_SPI_DW) += spi_dw.o
obj-$(CONFIG_SPI_QMSI) += spi_qmsi.o

271
drivers/spi/spi_qmsi.c Normal file
View file

@ -0,0 +1,271 @@
/*
* Copyright (c) 2016 Intel Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <device.h>
#include <drivers/ioapic.h>
#include <init.h>
#include <nanokernel.h>
#include <spi.h>
#include "qm_scss.h"
#include "qm_spi.h"
struct pending_transfer {
struct device *dev;
qm_spi_async_transfer_t xfer;
int counter;
};
static struct pending_transfer pending_transfers[QM_SPI_NUM];
struct spi_qmsi_config {
qm_spi_t spi;
};
struct spi_qmsi_runtime {
device_sync_call_t sync;
qm_spi_config_t cfg;
qm_rc_t rc;
bool loopback;
};
static inline qm_spi_bmode_t config_to_bmode(uint8_t mode)
{
switch (mode) {
case SPI_MODE_CPHA:
return QM_SPI_BMODE_1;
case SPI_MODE_CPOL:
return QM_SPI_BMODE_2;
case SPI_MODE_CPOL | SPI_MODE_CPHA:
return QM_SPI_BMODE_3;
default:
return QM_SPI_BMODE_0;
}
}
static int spi_qmsi_configure(struct device *dev,
struct spi_config *config)
{
struct spi_qmsi_runtime *context = dev->driver_data;
qm_spi_config_t *cfg = &context->cfg;
cfg->frame_size = SPI_WORD_SIZE_GET(config->config) - 1;
cfg->bus_mode = config_to_bmode(SPI_MODE(config->config));
/* As loopback is implemented inside the controller,
* the bus mode doesn't matter.
*/
context->loopback = SPI_MODE(config->config) & SPI_MODE_LOOP;
cfg->clk_divider = config->max_sys_freq;
/* Will set the configuration before the transfer starts */
return DEV_OK;
}
static void pending_transfer_complete(uint32_t id, qm_rc_t rc)
{
struct pending_transfer *pending = &pending_transfers[id];
struct device *dev = pending->dev;
struct spi_qmsi_runtime *context;
qm_spi_config_t *cfg;
if (!dev)
return;
context = dev->driver_data;
cfg = &context->cfg;
pending->counter++;
/*
* When it is TX/RX transfer this function will be called twice.
*/
if (cfg->transfer_mode == QM_SPI_TMOD_TX_RX && pending->counter == 1)
return;
pending->dev = NULL;
pending->counter = 0;
context->rc = rc;
device_sync_call_complete(&context->sync);
}
static void spi_qmsi_tx_callback(uint32_t id, uint32_t len)
{
pending_transfer_complete(id, QM_RC_OK);
}
static void spi_qmsi_rx_callback(uint32_t id, uint32_t len)
{
pending_transfer_complete(id, QM_RC_OK);
}
static void spi_qmsi_err_callback(uint32_t id, qm_rc_t err)
{
pending_transfer_complete(id, err);
}
static int spi_qmsi_slave_select(struct device *dev, uint32_t slave)
{
struct spi_qmsi_config *spi_config = dev->config->config_info;
qm_spi_t spi = spi_config->spi;
return qm_spi_slave_select(spi, 1 << (slave - 1)) ? DEV_FAIL : DEV_OK;
}
static inline uint8_t frame_size_to_dfs(qm_spi_frame_size_t frame_size)
{
if (frame_size <= QM_SPI_FRAME_SIZE_8_BIT)
return 1;
if (frame_size <= QM_SPI_FRAME_SIZE_16_BIT)
return 2;
if (frame_size <= QM_SPI_FRAME_SIZE_32_BIT)
return 4;
/* This should never happen, it will crash later on. */
return 0;
}
static int spi_qmsi_transceive(struct device *dev,
uint8_t *tx_buf, uint32_t tx_buf_len,
uint8_t *rx_buf, uint32_t rx_buf_len)
{
struct spi_qmsi_config *spi_config = dev->config->config_info;
qm_spi_t spi = spi_config->spi;
struct spi_qmsi_runtime *context = dev->driver_data;
qm_spi_config_t *cfg = &context->cfg;
uint8_t dfs = frame_size_to_dfs(cfg->frame_size);
qm_spi_async_transfer_t *xfer;
qm_rc_t rc;
if (pending_transfers[spi].dev)
return DEV_USED;
pending_transfers[spi].dev = dev;
xfer = &pending_transfers[spi].xfer;
xfer->rx = rx_buf;
xfer->rx_len = rx_buf_len / dfs;
xfer->tx = tx_buf;
xfer->tx_len = tx_buf_len / dfs;
xfer->id = spi;
xfer->tx_callback = spi_qmsi_tx_callback;
xfer->rx_callback = spi_qmsi_rx_callback;
xfer->err_callback = spi_qmsi_err_callback;
if (tx_buf_len == 0)
cfg->transfer_mode = QM_SPI_TMOD_RX;
else if (rx_buf_len == 0)
cfg->transfer_mode = QM_SPI_TMOD_TX;
else {
/* FIXME: QMSI expects rx_buf_len and tx_buf_len to
* have the same size.
*/
cfg->transfer_mode = QM_SPI_TMOD_TX_RX;
}
if (context->loopback)
QM_SPI[spi]->ctrlr0 |= BIT(11);
rc = qm_spi_set_config(spi, cfg);
if (rc != QM_RC_OK)
return DEV_INVALID_CONF;
rc = qm_spi_irq_transfer(spi, xfer);
if (rc != QM_RC_OK)
return DEV_FAIL;
device_sync_call_wait(&context->sync);
return context->rc ? DEV_FAIL : DEV_OK;
}
static int spi_qmsi_suspend(struct device *dev)
{
/* FIXME */
return 0;
}
static int spi_qmsi_resume(struct device *dev)
{
/* FIXME */
return 0;
}
static struct spi_driver_api spi_qmsi_api = {
.configure = spi_qmsi_configure,
.slave_select = spi_qmsi_slave_select,
.transceive = spi_qmsi_transceive,
.suspend = spi_qmsi_suspend,
.resume = spi_qmsi_resume,
};
static int spi_qmsi_init(struct device *dev)
{
struct spi_qmsi_config *spi_config = dev->config->config_info;
struct spi_qmsi_runtime *context = dev->driver_data;
dev->driver_api = &spi_qmsi_api;
switch (spi_config->spi) {
case QM_SPI_MST_0:
IRQ_CONNECT(CONFIG_SPI_QMSI_PORT_0_IRQ,
CONFIG_SPI_QMSI_PORT_0_PRI, qm_spi_master_0_isr,
0, IOAPIC_LEVEL | IOAPIC_HIGH);
irq_enable(CONFIG_SPI_QMSI_PORT_0_IRQ);
clk_periph_enable(CLK_PERIPH_CLK | CLK_PERIPH_SPI_M0_REGISTER);
QM_SCSS_INT->int_spi_mst_0_mask &= ~BIT(0);
break;
case QM_SPI_MST_1:
IRQ_CONNECT(CONFIG_SPI_QMSI_PORT_1_IRQ,
CONFIG_SPI_QMSI_PORT_1_PRI, qm_spi_master_1_isr,
0, IOAPIC_LEVEL | IOAPIC_HIGH);
irq_enable(CONFIG_SPI_QMSI_PORT_1_IRQ);
clk_periph_enable(CLK_PERIPH_CLK | CLK_PERIPH_SPI_M1_REGISTER);
QM_SCSS_INT->int_spi_mst_1_mask &= ~BIT(0);
break;
default:
return DEV_FAIL;
}
device_sync_call_init(&context->sync);
return DEV_OK;
}
#ifdef CONFIG_SPI_QMSI_PORT_0
static struct spi_qmsi_config spi_qmsi_mst_0_config = {
.spi = QM_SPI_MST_0,
};
static struct spi_qmsi_runtime spi_qmsi_mst_0_runtime;
DEVICE_INIT(spi_master_0, CONFIG_SPI_QMSI_PORT_0_DRV_NAME,
spi_qmsi_init, &spi_qmsi_mst_0_runtime, &spi_qmsi_mst_0_config,
SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif /* CONFIG_SPI_QMSI_PORT_0 */
#ifdef CONFIG_SPI_QMSI_PORT_1
static struct spi_qmsi_config spi_qmsi_mst_1_config = {
.spi = QM_SPI_MST_1,
};
static struct spi_qmsi_runtime spi_qmsi_mst_1_runtime;
DEVICE_INIT(spi_master_1, CONFIG_SPI_QMSI_PORT_1_DRV_NAME,
spi_qmsi_init, &spi_qmsi_mst_1_runtime, &spi_qmsi_mst_1_config,
SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif /* CONFIG_SPI_QMSI_PORT_1 */