mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-08 01:13:52 +00:00
Note that the failure case can only be reached when compiling for ARC side of Quark SE. Otherwise, the code is never compiled which reduces code size for other platforms. Change-Id: Ic76890cbaf22da5c3563e056cba9b39615d3da0c Signed-off-by: Daniel Leung <daniel.leung@intel.com>
529 lines
13 KiB
C
529 lines
13 KiB
C
/* spi_dw.c - Designware SPI driver implementation */
|
|
|
|
/*
|
|
* Copyright (c) 2015 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 <errno.h>
|
|
|
|
#include <nanokernel.h>
|
|
#include <arch/cpu.h>
|
|
|
|
#include <misc/__assert.h>
|
|
#include <board.h>
|
|
#include <device.h>
|
|
#include <init.h>
|
|
|
|
#include <sys_io.h>
|
|
#include <clock_control.h>
|
|
#include <misc/util.h>
|
|
|
|
#include <spi.h>
|
|
#include <spi_dw.h>
|
|
|
|
#ifdef CONFIG_IOAPIC
|
|
#include <drivers/ioapic.h>
|
|
#endif
|
|
|
|
#ifndef CONFIG_SPI_DEBUG
|
|
#define DBG(...) {; }
|
|
#define DBG_COUNTER_INIT() {; }
|
|
#define DBG_COUNTER_INC() {; }
|
|
#define DBG_COUNTER_RESULT() {; }
|
|
#else
|
|
#define DBG_COUNTER_INIT() \
|
|
uint32_t __cnt = 0
|
|
#define DBG_COUNTER_INC() \
|
|
(__cnt++)
|
|
#define DBG_COUNTER_RESULT() \
|
|
(__cnt)
|
|
#if defined(CONFIG_STDOUT_CONSOLE)
|
|
#include <stdio.h>
|
|
#define DBG printf
|
|
#else
|
|
#include <misc/printk.h>
|
|
#define DBG printk
|
|
#endif /* CONFIG_STDOUT_CONSOLE */
|
|
#endif /* CONFIG_SPI_DEBUG */
|
|
|
|
#define SPI_DW_CLK_DIVIDER(ssi_clk_hz) \
|
|
((CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / ssi_clk_hz) & 0xFFFF)
|
|
|
|
static void completed(struct device *dev, int error)
|
|
{
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
struct spi_dw_data *spi = dev->driver_data;
|
|
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
|
|
if (spi->fifo_diff ||
|
|
!((spi->tx_buf && !spi->tx_buf_len && !spi->rx_buf) ||
|
|
(spi->rx_buf && !spi->rx_buf_len && !spi->tx_buf) ||
|
|
(spi->tx_buf && !spi->tx_buf_len &&
|
|
spi->rx_buf && !spi->rx_buf_len))) {
|
|
return;
|
|
}
|
|
|
|
out:
|
|
spi->error = error;
|
|
|
|
/* Disabling interrupts */
|
|
write_imr(DW_SPI_IMR_MASK, info->regs);
|
|
/* Disabling the controller */
|
|
clear_bit_ssienr(info->regs);
|
|
|
|
_spi_control_cs(dev, 0);
|
|
|
|
DBG("SPI transaction completed %s error\n",
|
|
error ? "with" : "without");
|
|
|
|
device_sync_call_complete(&spi->sync);
|
|
}
|
|
|
|
static void push_data(struct device *dev)
|
|
{
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
struct spi_dw_data *spi = dev->driver_data;
|
|
uint32_t data = 0;
|
|
uint32_t f_tx;
|
|
DBG_COUNTER_INIT();
|
|
|
|
f_tx = DW_SPI_FIFO_DEPTH - read_txflr(info->regs) -
|
|
read_rxflr(info->regs) - 1;
|
|
while (f_tx) {
|
|
if (spi->tx_buf && spi->tx_buf_len > 0) {
|
|
switch (spi->dfs) {
|
|
case 1:
|
|
data = UNALIGNED_GET((uint8_t *)(spi->tx_buf));
|
|
break;
|
|
case 2:
|
|
data = UNALIGNED_GET((uint16_t *)(spi->tx_buf));
|
|
break;
|
|
#ifndef CONFIG_ARC
|
|
case 4:
|
|
data = UNALIGNED_GET((uint32_t *)(spi->tx_buf));
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
spi->tx_buf += spi->dfs;
|
|
spi->tx_buf_len--;
|
|
} else if (spi->rx_buf && spi->rx_buf_len > 0) {
|
|
/* No need to push more than necessary */
|
|
if (spi->rx_buf_len - spi->fifo_diff <= 0) {
|
|
break;
|
|
}
|
|
|
|
data = 0;
|
|
} else {
|
|
/* Nothing to push anymore */
|
|
break;
|
|
}
|
|
|
|
write_dr(data, info->regs);
|
|
f_tx--;
|
|
spi->fifo_diff++;
|
|
DBG_COUNTER_INC();
|
|
}
|
|
|
|
if (!spi->tx_buf_len && !spi->rx_buf_len) {
|
|
write_txftlr(0, info->regs);
|
|
}
|
|
|
|
DBG("Pushed: %d\n", DBG_COUNTER_RESULT());
|
|
}
|
|
|
|
static void pull_data(struct device *dev)
|
|
{
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
struct spi_dw_data *spi = dev->driver_data;
|
|
uint32_t data = 0;
|
|
DBG_COUNTER_INIT();
|
|
|
|
while (read_rxflr(info->regs)) {
|
|
data = read_dr(info->regs);
|
|
DBG_COUNTER_INC();
|
|
|
|
if (spi->rx_buf && spi->rx_buf_len > 0) {
|
|
switch (spi->dfs) {
|
|
case 1:
|
|
UNALIGNED_PUT(data, (uint8_t *)spi->rx_buf);
|
|
break;
|
|
case 2:
|
|
UNALIGNED_PUT(data, (uint16_t *)spi->rx_buf);
|
|
break;
|
|
#ifndef CONFIG_ARC
|
|
case 4:
|
|
UNALIGNED_PUT(data, (uint32_t *)spi->rx_buf);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
spi->rx_buf += spi->dfs;
|
|
spi->rx_buf_len--;
|
|
}
|
|
|
|
spi->fifo_diff--;
|
|
}
|
|
|
|
if (!spi->rx_buf_len && spi->tx_buf_len < DW_SPI_FIFO_DEPTH) {
|
|
write_rxftlr(spi->tx_buf_len - 1, info->regs);
|
|
} else if (read_rxftlr(info->regs) >= spi->rx_buf_len) {
|
|
write_rxftlr(spi->rx_buf_len - 1, info->regs);
|
|
}
|
|
|
|
DBG("Pulled: %d\n", DBG_COUNTER_RESULT());
|
|
}
|
|
|
|
static inline bool _spi_dw_is_controller_ready(struct device *dev)
|
|
{
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
|
|
if (test_bit_ssienr(info->regs) || test_bit_sr_busy(info->regs)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int spi_dw_configure(struct device *dev,
|
|
struct spi_config *config)
|
|
{
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
struct spi_dw_data *spi = dev->driver_data;
|
|
uint32_t flags = config->config;
|
|
uint32_t ctrlr0 = 0;
|
|
uint32_t mode;
|
|
|
|
DBG("%s: %p (0x%x), %p\n", __func__, dev, info->regs, config);
|
|
|
|
/* Check status */
|
|
if (!_spi_dw_is_controller_ready(dev)) {
|
|
DBG("%s: Controller is busy\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Word size */
|
|
ctrlr0 |= DW_SPI_CTRLR0_DFS(SPI_WORD_SIZE_GET(flags));
|
|
|
|
/* Determine how many bytes are required per-frame */
|
|
spi->dfs = SPI_DFS_TO_BYTES(SPI_WORD_SIZE_GET(flags));
|
|
|
|
/* SPI mode */
|
|
mode = SPI_MODE(flags);
|
|
if (mode & SPI_MODE_CPOL) {
|
|
ctrlr0 |= DW_SPI_CTRLR0_SCPOL;
|
|
}
|
|
|
|
if (mode & SPI_MODE_CPHA) {
|
|
ctrlr0 |= DW_SPI_CTRLR0_SCPH;
|
|
}
|
|
|
|
if (mode & SPI_MODE_LOOP) {
|
|
ctrlr0 |= DW_SPI_CTRLR0_SRL;
|
|
}
|
|
|
|
/* Installing the configuration */
|
|
write_ctrlr0(ctrlr0, info->regs);
|
|
|
|
/*
|
|
* Configure the rate. Use this small hack to allow the user to call
|
|
* spi_configure() with both a divider (as the driver was initially
|
|
* written) and a frequency (as the SPI API suggests to). The clock
|
|
* divider is a 16bit value, hence we can fairly, and safely, assume
|
|
* that everything above this value is a frequency. The trade-off is
|
|
* that if one wants to use a bus frequency of 64kHz (or less), it has
|
|
* the use a divider...
|
|
*/
|
|
if (config->max_sys_freq > 0xffff) {
|
|
write_baudr(SPI_DW_CLK_DIVIDER(config->max_sys_freq),
|
|
info->regs);
|
|
} else {
|
|
write_baudr(config->max_sys_freq, info->regs);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_dw_slave_select(struct device *dev, uint32_t slave)
|
|
{
|
|
struct spi_dw_data *spi = dev->driver_data;
|
|
|
|
DBG("%s: %p %d\n", __func__, dev, slave);
|
|
|
|
if (slave == 0 || slave > 4) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
spi->slave = 1 << (slave - 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_dw_transceive(struct device *dev,
|
|
const void *tx_buf, uint32_t tx_buf_len,
|
|
void *rx_buf, uint32_t rx_buf_len)
|
|
{
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
struct spi_dw_data *spi = dev->driver_data;
|
|
uint32_t rx_thsld = DW_SPI_RXFTLR_DFLT;
|
|
|
|
DBG("%s: %p, %p, %u, %p, %u\n",
|
|
__func__, dev, tx_buf, tx_buf_len, rx_buf, rx_buf_len);
|
|
|
|
/* Check status */
|
|
if (!_spi_dw_is_controller_ready(dev)) {
|
|
DBG("%s: Controller is busy\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Set buffers info */
|
|
spi->tx_buf = tx_buf;
|
|
spi->tx_buf_len = tx_buf_len/spi->dfs;
|
|
spi->rx_buf = rx_buf;
|
|
spi->rx_buf_len = rx_buf_len/spi->dfs;
|
|
spi->fifo_diff = 0;
|
|
|
|
/* Tx Threshold, always at default */
|
|
write_txftlr(DW_SPI_TXFTLR_DFLT, info->regs);
|
|
|
|
/* Does Rx thresholds needs to be lower? */
|
|
if (rx_buf_len && spi->rx_buf_len < DW_SPI_FIFO_DEPTH) {
|
|
rx_thsld = spi->rx_buf_len - 1;
|
|
} else if (!rx_buf_len && spi->tx_buf_len < DW_SPI_FIFO_DEPTH) {
|
|
rx_thsld = spi->tx_buf_len - 1;
|
|
}
|
|
|
|
write_rxftlr(rx_thsld, info->regs);
|
|
|
|
/* Slave select */
|
|
write_ser(spi->slave, info->regs);
|
|
|
|
_spi_control_cs(dev, 1);
|
|
|
|
/* Enable interrupts */
|
|
write_imr(DW_SPI_IMR_UNMASK, info->regs);
|
|
|
|
/* Enable the controller */
|
|
set_bit_ssienr(info->regs);
|
|
|
|
device_sync_call_wait(&spi->sync);
|
|
|
|
if (spi->error) {
|
|
spi->error = 0;
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_dw_suspend(struct device *dev)
|
|
{
|
|
DBG("%s: %p\n", __func__, dev);
|
|
|
|
_clock_off(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_dw_resume(struct device *dev)
|
|
{
|
|
DBG("%se: %p\n", __func__, dev);
|
|
|
|
_clock_on(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void spi_dw_isr(void *arg)
|
|
{
|
|
struct device *dev = arg;
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
uint32_t error = 0;
|
|
uint32_t int_status;
|
|
|
|
int_status = read_isr(info->regs);
|
|
|
|
DBG("SPI int_status 0x%x - (tx: %d, rx: %d)\n",
|
|
int_status, read_txflr(info->regs), read_rxflr(info->regs));
|
|
|
|
if (int_status & DW_SPI_ISR_ERRORS_MASK) {
|
|
error = 1;
|
|
goto out;
|
|
}
|
|
|
|
if (int_status & DW_SPI_ISR_RXFIS) {
|
|
pull_data(dev);
|
|
}
|
|
|
|
if (int_status & DW_SPI_ISR_TXEIS) {
|
|
push_data(dev);
|
|
}
|
|
|
|
out:
|
|
clear_interrupts(info->regs);
|
|
completed(dev, error);
|
|
}
|
|
|
|
static struct spi_driver_api dw_spi_api = {
|
|
.configure = spi_dw_configure,
|
|
.slave_select = spi_dw_slave_select,
|
|
.transceive = spi_dw_transceive,
|
|
.suspend = spi_dw_suspend,
|
|
.resume = spi_dw_resume,
|
|
};
|
|
|
|
int spi_dw_init(struct device *dev)
|
|
{
|
|
struct spi_dw_config *info = dev->config->config_info;
|
|
struct spi_dw_data *spi = dev->driver_data;
|
|
|
|
_clock_config(dev);
|
|
_clock_on(dev);
|
|
|
|
#ifndef CONFIG_SOC_QUARK_SE_SS
|
|
if (read_ssi_comp_version(info->regs) != DW_SSI_COMP_VERSION) {
|
|
dev->driver_api = NULL;
|
|
_clock_off(dev);
|
|
return -EPERM;
|
|
}
|
|
#endif
|
|
|
|
info->config_func();
|
|
|
|
device_sync_call_init(&spi->sync);
|
|
|
|
_spi_config_cs(dev);
|
|
|
|
/* Masking interrupt and making sure controller is disabled */
|
|
write_imr(DW_SPI_IMR_MASK, info->regs);
|
|
clear_bit_ssienr(info->regs);
|
|
|
|
DBG("Designware SPI driver initialized on device: %p\n", dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_IOAPIC) || defined(CONFIG_MVIC)
|
|
#if defined(CONFIG_SPI_DW_FALLING_EDGE)
|
|
#define SPI_DW_IRQ_FLAGS (IOAPIC_EDGE | IOAPIC_LOW)
|
|
#elif defined(CONFIG_SPI_DW_RISING_EDGE)
|
|
#define SPI_DW_IRQ_FLAGS (IOAPIC_EDGE | IOAPIC_HIGH)
|
|
#elif defined(CONFIG_SPI_DW_LEVEL_HIGH)
|
|
#define SPI_DW_IRQ_FLAGS (IOAPIC_LEVEL | IOAPIC_HIGH)
|
|
#elif defined(CONFIG_SPI_DW_LEVEL_LOW)
|
|
#define SPI_DW_IRQ_FLAGS (IOAPIC_LEVEL | IOAPIC_LOW)
|
|
#endif
|
|
#else
|
|
#define SPI_DW_IRQ_FLAGS 0
|
|
#endif /* CONFIG_IOAPIC */
|
|
|
|
#ifdef CONFIG_SPI_DW_PORT_0
|
|
void spi_config_0_irq(void);
|
|
|
|
struct spi_dw_data spi_dw_data_port_0;
|
|
|
|
struct spi_dw_config spi_dw_config_0 = {
|
|
.regs = CONFIG_SPI_DW_PORT_0_REGS,
|
|
#ifdef CONFIG_SPI_DW_CLOCK_GATE
|
|
.clock_data = UINT_TO_POINTER(CONFIG_SPI_DW_PORT_0_CLOCK_GATE_SUBSYS),
|
|
#endif /* CONFIG_SPI_DW_CLOCK_GATE */
|
|
#ifdef CONFIG_SPI_DW_CS_GPIO
|
|
.cs_gpio_name = CONFIG_SPI_DW_PORT_0_CS_GPIO_PORT,
|
|
.cs_gpio_pin = CONFIG_SPI_DW_PORT_0_CS_GPIO_PIN,
|
|
#endif
|
|
.config_func = spi_config_0_irq
|
|
};
|
|
|
|
DEVICE_AND_API_INIT(spi_dw_port_0, CONFIG_SPI_DW_PORT_0_DRV_NAME, spi_dw_init,
|
|
&spi_dw_data_port_0, &spi_dw_config_0,
|
|
SECONDARY, CONFIG_SPI_DW_INIT_PRIORITY,
|
|
&dw_spi_api);
|
|
|
|
void spi_config_0_irq(void)
|
|
{
|
|
#ifdef CONFIG_SPI_DW_INTERRUPT_SINGLE_LINE
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_0_IRQ, CONFIG_SPI_DW_PORT_0_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_0), SPI_DW_IRQ_FLAGS);
|
|
irq_enable(CONFIG_SPI_DW_PORT_0_IRQ);
|
|
_spi_int_unmask(SPI_DW_PORT_0_INT_MASK);
|
|
#else /* SPI_DW_INTERRUPT_SEPARATED_LINES */
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_0_RX_IRQ, CONFIG_SPI_DW_PORT_0_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_0), SPI_DW_IRQ_FLAGS);
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_0_TX_IRQ, CONFIG_SPI_DW_PORT_0_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_0), SPI_DW_IRQ_FLAGS);
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_0_ERROR_IRQ, CONFIG_SPI_DW_PORT_0_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_0), SPI_DW_IRQ_FLAGS);
|
|
|
|
irq_enable(CONFIG_SPI_DW_PORT_0_RX_IRQ);
|
|
irq_enable(CONFIG_SPI_DW_PORT_0_TX_IRQ);
|
|
irq_enable(CONFIG_SPI_DW_PORT_0_ERROR_IRQ);
|
|
|
|
_spi_int_unmask(SPI_DW_PORT_0_RX_INT_MASK);
|
|
_spi_int_unmask(SPI_DW_PORT_0_TX_INT_MASK);
|
|
_spi_int_unmask(SPI_DW_PORT_0_ERROR_INT_MASK);
|
|
#endif
|
|
}
|
|
#endif /* CONFIG_SPI_DW_PORT_0 */
|
|
#ifdef CONFIG_SPI_DW_PORT_1
|
|
void spi_config_1_irq(void);
|
|
|
|
struct spi_dw_data spi_dw_data_port_1;
|
|
|
|
struct spi_dw_config spi_dw_config_1 = {
|
|
.regs = CONFIG_SPI_DW_PORT_1_REGS,
|
|
#ifdef CONFIG_SPI_DW_CLOCK_GATE
|
|
.clock_data = UINT_TO_POINTER(CONFIG_SPI_DW_PORT_1_CLOCK_GATE_SUBSYS),
|
|
#endif /* CONFIG_SPI_DW_CLOCK_GATE */
|
|
#ifdef CONFIG_SPI_DW_CS_GPIO
|
|
.cs_gpio_name = CONFIG_SPI_DW_PORT_1_CS_GPIO_PORT,
|
|
.cs_gpio_pin = CONFIG_SPI_DW_PORT_1_CS_GPIO_PIN,
|
|
#endif
|
|
.config_func = spi_config_1_irq
|
|
};
|
|
|
|
DEVICE_AND_API_INIT(spi_dw_port_1, CONFIG_SPI_DW_PORT_1_DRV_NAME, spi_dw_init,
|
|
&spi_dw_data_port_1, &spi_dw_config_1,
|
|
SECONDARY, CONFIG_SPI_DW_INIT_PRIORITY,
|
|
&dw_spi_api);
|
|
|
|
void spi_config_1_irq(void)
|
|
{
|
|
#ifdef CONFIG_SPI_DW_INTERRUPT_SINGLE_LINE
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_1_IRQ, CONFIG_SPI_DW_PORT_1_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_1), SPI_DW_IRQ_FLAGS);
|
|
irq_enable(CONFIG_SPI_DW_PORT_1_IRQ);
|
|
_spi_int_unmask(SPI_DW_PORT_1_INT_MASK);
|
|
#else /* SPI_DW_INTERRUPT_SEPARATED_LINES */
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_1_RX_IRQ, CONFIG_SPI_DW_PORT_1_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_1), SPI_DW_IRQ_FLAGS);
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_1_TX_IRQ, CONFIG_SPI_DW_PORT_1_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_1), SPI_DW_IRQ_FLAGS);
|
|
IRQ_CONNECT(CONFIG_SPI_DW_PORT_1_ERROR_IRQ, CONFIG_SPI_DW_PORT_1_PRI,
|
|
spi_dw_isr, DEVICE_GET(spi_dw_port_1), SPI_DW_IRQ_FLAGS);
|
|
|
|
irq_enable(CONFIG_SPI_DW_PORT_1_RX_IRQ);
|
|
irq_enable(CONFIG_SPI_DW_PORT_1_TX_IRQ);
|
|
irq_enable(CONFIG_SPI_DW_PORT_1_ERROR_IRQ);
|
|
|
|
_spi_int_unmask(SPI_DW_PORT_1_RX_INT_MASK);
|
|
_spi_int_unmask(SPI_DW_PORT_1_TX_INT_MASK);
|
|
_spi_int_unmask(SPI_DW_PORT_1_ERROR_INT_MASK);
|
|
#endif
|
|
}
|
|
#endif /* CONFIG_SPI_DW_PORT_1 */
|