zephyr/drivers/spi/spi_k64.c
Daniel Leung a2ba5d1ba3 spi/k64: remove SoC specific SPI constants from kconfig
The base address, IRQ line, chip select numbers, and clock
gating constants are static per SoC, so there is no need to
make them configurable in Kconfig.

Change-Id: I9f87ca29c28c38c42d4e4f1a3a41fa231f63ef03
Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2016-03-26 20:36:32 -04:00

1117 lines
28 KiB
C

/* spi_k64.c - Driver implementation for K64 SPI controller */
/*
* Copyright (c) 2015-2016 Wind River Systems, Inc.
*
* 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.
*
* Portions of this file are derived from material that is
* Copyright (c) 2013 - 2014, Freescale Semiconductor, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Freescale Semiconductor, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <errno.h>
#include <nanokernel.h>
#include <arch/cpu.h>
#include <misc/__assert.h>
#include <board.h>
#include <init.h>
#include <sys_io.h>
#include <limits.h>
#include <spi.h>
#include <spi/spi_k64.h>
#include "spi_k64_priv.h"
#ifndef CONFIG_SPI_DEBUG
#define DBG(...) do { } while ((0))
#else
#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 */
/* SPI protocol frequency = K64 bus clock frequency, in hz */
#define SPI_K64_PROTOCOL_FREQ (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / \
CONFIG_K64_BUS_CLOCK_DIVIDER)
/* SPI protocol period, in ns */
#define SPI_K64_PROTOCOL_PERIOD_NS (NSEC_PER_SEC/SPI_K64_PROTOCOL_FREQ)
/* # of possible SPI baud rate and delay prescaler and scaler values */
#define SPI_K64_NUM_PRESCALERS 4
#define SPI_K64_NUM_SCALERS 16
/*
* SPI baud rate prescaler and scaler values, indexed by the clocking and timing
* attribute register (CTAR) parameters CTAR[PBR] and CTAR[BR], respectively.
*/
static const uint32_t baud_rate_prescaler[] = { 2, 3, 5, 7 };
static const uint32_t baud_rate_scaler[] = {
2, 4, 6, 8, 16, 32, 64, 128, 256, 512,
1024, 2048, 4096, 8192, 16384, 32768
};
/*
* SPI delay prescaler and scaler values, indexed by clocking and timing
* attribute register (CTAR) parameter pairs:
* CTAR[PCSSCK]/CTAR[CSSCK] for the PCS to SCK delay,
* CTAR[PASC]/CTAR[ASC] for the after SCK delay, and
* CTAR[PDT]/CTAR[DT] for the after transfer delay.
*/
static const uint32_t delay_prescaler[] = { 1, 3, 5, 7 };
static const uint32_t delay_scaler[] = {
2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
2048, 4096, 8192, 16384, 32768, 65536
};
/**
* @brief Halt SPI module operation.
* @param dev Pointer to the device structure for the driver instance
* @return None.
*/
static inline void spi_k64_halt(struct device *dev)
{
struct spi_k64_config *info = dev->config->config_info;
/* Ensure module operation is stopped */
sys_set_bit((info->regs + SPI_K64_REG_MCR), SPI_K64_MCR_HALT_BIT);
while (sys_read32(info->regs + SPI_K64_REG_SR) & SPI_K64_SR_TXRXS) {
DBG("SPI Controller dev %p is running. Waiting for Halt.\n", dev);
}
}
/**
* @brief Enable SPI module operation.
* @param dev Pointer to the device structure for the driver instance
* @return None.
*/
static inline void spi_k64_start(struct device *dev)
{
struct spi_k64_config *info = dev->config->config_info;
/* Allow module operation */
sys_clear_bit((info->regs + SPI_K64_REG_MCR), SPI_K64_MCR_HALT_BIT);
}
/**
* @brief Set a SPI baud rate nearest to the desired rate, without exceeding it.
* @param baud_rate The desired baud rate.
* @param ctar_ptr Pointer to clocking and timing attribute storage.
* @return The calculated baud rate or 0 if an error occurred.
*/
static uint32_t spi_k64_set_baud_rate(uint32_t baud_rate, uint32_t *ctar_ptr)
{
/*
* The 'volatile' attribute is added to some of the variables in this
* function to prevent bad code generation by gcc toolchains for ARM
* when an optimization setting above -O0 is used.
*
* Specifically, a register is loaded with the constant 0 and is used as
* the divisor in a following divide instruction, resulting in a
* divide-by-zero exception.
* This issue has been seen with gcc versions 4.8.1 and 5.2.0.
*/
/* prescaler values,CTAR[PBR] */
uint32_t prescaler;
volatile uint32_t best_prescaler;
/* scaler values, CTAR[BR] */
uint32_t scaler;
volatile uint32_t best_scaler;
/* doubler values,CTAR[DBR]-1 */
uint32_t dbr, best_dbr;
/* baud rate */
uint32_t calc_baud_rate;
volatile uint32_t best_baud_rate;
/* calculated differences */
uint32_t diff, min_diff;
min_diff = 0xFFFFFFFFU;
best_dbr = 1;
/*
* Master mode is assumed.
*
* Find the combination of prescaler, scaler and doubler factors that
* results in the baud rate closest to the requested value, without
* exceeding it.
*/
DBG("spi_k64_set_baud_rate - ");
/*
* Initialize the prescaler and scaler to their maximum values to calculate
* the minimum baud rate and check if it is greater than the desired rate.
*/
best_prescaler = SPI_K64_NUM_PRESCALERS - 1;
best_scaler = SPI_K64_NUM_SCALERS - 1;
best_baud_rate = SPI_K64_PROTOCOL_PERIOD_NS /
(baud_rate_prescaler[best_prescaler] *
baud_rate_prescaler[best_scaler]);
if (best_baud_rate > baud_rate) {
DBG("ERROR : Minimum baud rate %d is greater than desired rate %d\n",
best_baud_rate, baud_rate);
return 0;
}
/*
* Note that no further combinations are checked if the calculated baud rate
* equals the requested baud rate.
*/
for (prescaler = 0;
(prescaler < SPI_K64_NUM_PRESCALERS) && min_diff;
prescaler++) {
for (scaler = 0; (scaler < SPI_K64_NUM_SCALERS) && min_diff; scaler++) {
for (dbr = 1; (dbr < 3) && min_diff; dbr++) {
calc_baud_rate = ((SPI_K64_PROTOCOL_FREQ * dbr) /
(baud_rate_prescaler[prescaler] *
baud_rate_scaler[scaler]));
/* ensure the rate will not exceed the one requested */
if (baud_rate >= calc_baud_rate) {
diff = baud_rate - calc_baud_rate;
if (min_diff > diff) {
/* a better match was found */
min_diff = diff;
best_prescaler = prescaler;
best_scaler = scaler;
best_baud_rate = calc_baud_rate;
best_dbr = dbr;
}
}
}
}
}
/* save the best baud rate dbr, prescaler and scaler */
*ctar_ptr = *ctar_ptr | SPI_K64_CTAR_DBR_SET(best_dbr - 1) |
SPI_K64_CTAR_PBR_SET(best_prescaler) | best_scaler;
/* return the actual baud rate */
DBG("%d bps desired, %d bps set\n", baud_rate, best_baud_rate);
return best_baud_rate;
}
/**
* @brief Set the specified delay nearest to the desired value, but not lower.
* @param delay_id The delay identifier.
* @param delay_ns The desired delay value, in ns.
* @param ctar_ptr Pointer to clocking and timing attribute storage.
* @return The calculated delay or 0 if an error occurred.
*/
static uint32_t spi_k64_set_delay(enum spi_k64_delay_id delay_id,
uint32_t delay_ns,
uint32_t *ctar_ptr)
{
/*
* The 'volatile' attribute is added to some of the variables in this
* function to prevent bad code generation by gcc toolchains for ARM
* when an optimization setting above -O0 is used.
*
* Specifically, a register is loaded with the constant 0 and is used as
* the divisor in a following divide instruction, resulting in a
* divide-by-zero exception.
* This issue has been seen with gcc versions 4.8.1 and 5.2.0.
*/
uint32_t prescaler; /* prescaler values */
volatile uint32_t best_prescaler;
uint32_t scaler; /* scaler values */
volatile uint32_t best_scaler;
uint32_t calc_delay; /* delay values */
volatile uint32_t best_delay;
uint32_t diff, min_diff; /* difference values */
DBG("spi_k64_set_delay - ");
/*
* This function can calculate the clocking and timing attribute register
* (CTAR) values for:
* - PCS to SCK delay prescaler (PCSSCK) and scaler (CSSCK),
* - After SCK delay prescaler (PASC) and scaler (ASC), or
* - Delay after transfer prescaler (PDT) and scaler (DT).
*/
if ((delay_id != DELAY_PCS_TO_SCK) && (delay_id != DELAY_AFTER_SCK) &&
(delay_id != DELAY_AFTER_XFER)) {
DBG("ERROR : Unknown delay type %d\n", delay_id);
return 0;
}
/*
* Initialize the prescaler and scaler to their maximum values to calculate
* the maximum delay and check if it is less than the desired delay.
*/
best_prescaler = SPI_K64_NUM_PRESCALERS - 1;
best_scaler = SPI_K64_NUM_SCALERS - 1;
best_delay = SPI_K64_PROTOCOL_PERIOD_NS * delay_prescaler[best_prescaler] *
delay_scaler[best_scaler];
if (best_delay < delay_ns) {
DBG("ERROR : Maximum delay %d does meet desired minimum of %d\n",
best_delay, delay_ns);
return 0;
}
min_diff = 0xFFFFFFFFU;
/*
* Check if the minimum delay (prescaler value = 1, scaler value = 2) is
* greater than the desired delay. If so, set the prescaler and scaler to
* their associated minimum in the CTAR (0).
*/
calc_delay = SPI_K64_PROTOCOL_PERIOD_NS * 2;
if (calc_delay >= delay_ns) {
best_prescaler = 0;
best_scaler = 0;
min_diff = 0; /* skip remaining calculations */
}
/*
* Note that no further combinations are checked if the calculated delay
* equals the requested delay.
*/
for (prescaler = 0;
(prescaler < SPI_K64_NUM_PRESCALERS) && min_diff;
prescaler++) {
for (scaler = 0; (scaler < SPI_K64_NUM_SCALERS) && min_diff; scaler++) {
calc_delay = SPI_K64_PROTOCOL_PERIOD_NS *
delay_prescaler[prescaler] * delay_scaler[scaler];
/* ensure the delay is at least as long as the one requested */
if (calc_delay >= delay_ns) {
diff = calc_delay - delay_ns;
if (min_diff > diff) {
/* a better match was found */
min_diff = diff;
best_prescaler = prescaler;
best_scaler = scaler;
best_delay = calc_delay;
}
}
}
}
/* save the best delay prescaler and scaler */
switch (delay_id) {
case DELAY_PCS_TO_SCK:
*ctar_ptr = *ctar_ptr | SPI_K64_CTAR_PCSSCK_SET(best_prescaler) |
SPI_K64_CTAR_CSSCK_SET(best_scaler);
DBG("DELAY_PCS_TO_SCK: ");
break;
case DELAY_AFTER_SCK:
*ctar_ptr = *ctar_ptr | SPI_K64_CTAR_PASC_SET(best_prescaler) |
SPI_K64_CTAR_ASC_SET(best_scaler);
DBG("DELAY_AFTER_SCK: ");
break;
case DELAY_AFTER_XFER:
*ctar_ptr = *ctar_ptr | SPI_K64_CTAR_PDT_SET(best_prescaler) |
SPI_K64_CTAR_DT_SET(best_scaler);
DBG("DELAY_AFTER_XFER: ");
break;
default:
break;
}
/* return the actual delay */
DBG("%d delay desired, %d delay set\n", delay_ns, best_delay);
return best_delay;
}
/**
* @brief Configure the SPI host controller for operating against slaves
* @param dev Pointer to the device structure for the driver instance
* @param config Pointer to the application provided configuration
*
* @return 0 if successful, another DEV_* code otherwise.
*/
static int spi_k64_configure(struct device *dev, struct spi_config *config)
{
struct spi_k64_config *info = dev->config->config_info;
struct spi_k64_data *spi_data = dev->driver_data;
uint32_t flags = config->config;
uint32_t mcr; /* mode configuration attributes, for MCR */
uint32_t ctar = 0; /* clocking and timing attributes, for CTAR */
uint32_t frame_sz; /* frame size, in bits */
DBG("spi_k64_configure: dev %p (regs @ 0x%x), ", dev, info->regs);
DBG("config 0x%x, freq 0x%x",
config->config, config->max_sys_freq);
/* Disable transfer operations during configuration */
spi_k64_halt(dev);
/*
* Set the common configuration:
* Master mode, normal SPI transfers, PCS strobe disabled,
* Rx overflow data ignored, PCSx inactive low signal, Doze disabled,
* Rx/Tx FIFOs enabled.
*
* Also, keep transfers disabled.
*/
mcr = SPI_K64_MCR_MSTR | SPI_K64_MCR_HALT;
/* Set PCSx signal polarities and continuous SCK, as requested */
mcr |= (SPI_K64_MCR_PCSIS_SET(SPI_PCS_POL_GET(flags)) |
SPI_K64_MCR_CONT_SCKE_SET(SPI_CONT_SCK_GET(flags)));
sys_write32(mcr, (info->regs + SPI_K64_REG_MCR));
/* Set clocking and timing parameters */
/* SCK polarity and phase, and bit order of data */
if (flags & SPI_MODE_CPOL) {
ctar |= SPI_K64_CTAR_CPOL;
}
if (flags & SPI_MODE_CPHA) {
ctar |= SPI_K64_CTAR_CPHA;
}
if (flags & SPI_TRANSFER_MASK) {
ctar |= SPI_K64_CTAR_LSBFE;
}
/*
* Frame size is limited to 16 bits (vs. 8 bit value in struct spi_config),
* programmed as: (frame_size - 1)
*/
frame_sz = SPI_WORD_SIZE_GET(flags);
if (frame_sz > SPI_K64_WORD_SIZE_MAX) {
return -ENOTSUP;
}
spi_data->frame_sz = frame_sz;
ctar |= (SPI_K64_CTAR_FRMSZ_SET(frame_sz - 1));
/* Set baud rate and signal timing parameters (delays) */
if (spi_k64_set_baud_rate(config->max_sys_freq, &ctar) == 0) {
return -ENOTSUP;
}
/*
* Set signal timing parameters (delays):
* - PCS to SCK delay is set to the minimum, CTAR[PCSSCK] = CTAR[CSSCK] = 0;
* - After SCK delay is set to at least half of the baud rate period,
* (using the combination of CTAR[PASC] and CTAR[ASC]); and
* - Delay after transfer is set to the minimum, CTAR[PDT] = CTAR[DT] = 0.
*/
if (spi_k64_set_delay(DELAY_AFTER_SCK,
(NSEC_PER_SEC / 2) / config->max_sys_freq,
&ctar) == 0) {
return -ENOTSUP;
}
DBG("spi_k64_configure: MCR: 0x%x CTAR0: 0x%x\n", mcr, ctar);
sys_write32(ctar, (info->regs + SPI_K64_REG_CTAR0));
/* Initialize Tx/Rx parameters */
spi_data->tx_buf = spi_data->rx_buf = NULL;
spi_data->tx_buf_len = spi_data->rx_buf_len = 0;
/* Store continuous slave/PCS signal selection mode */
spi_data->cont_pcs_sel = SPI_CONT_PCS_GET(flags);
return 0;
}
/**
* @brief Select a slave to transmit data to.
*
* @param dev Pointer to the device structure for the driver instance
* @param slave An integer identifying the slave, where the bit values denote:
* 0 - Negate the associated PCS signal
* 1 - Assert the associated PCS signal
*
* Note: The polarity of each PCS signal is defined by the Peripheral Chip
* Select inactive state setting, MCR[PCSIS], determined by the configuration
* data parameter to spi_configure()/spi_k64_configure().
*
* @return 0 if successful, another DEV_* code otherwise.
*/
static int spi_k64_slave_select(struct device *dev, uint32_t slave)
{
struct spi_k64_data *spi_data = dev->driver_data;
/*
* Note that the number of valid PCS signals differs for each
* K64 SPI module:
* - SPI0 uses PCS0-5;
* - SPI1 uses PCS0-3;
* - SPI2 uses PCS0-1;
*/
DBG("spi_k64_slave_select: slave 0x%x selected for dev %p\n",
(uint8_t)slave, dev);
spi_data->pcs = (uint8_t)slave;
return 0;
}
/**
* @brief Read and/or write a defined amount of data through an SPI driver
*
* @param dev Pointer to the device structure for the driver instance
* @param tx_buf Memory buffer that data should be transferred from
* @param tx_buf_len Size of the memory buffer available for reading from
* @param rx_buf Memory buffer that data should be transferred to
* @param rx_buf_len Size of the memory buffer available for writing to
*
* @return 0 if successful, another DEV_* code otherwise.
*/
static int spi_k64_transceive(struct device *dev,
const void *tx_buf, uint32_t tx_buf_len,
void *rx_buf, uint32_t rx_buf_len)
{
struct spi_k64_config *info = dev->config->config_info;
struct spi_k64_data *spi_data = dev->driver_data;
uint32_t int_config; /* interrupt configuration */
DBG("spi_k64_transceive: dev %p, Tx buf %p, ", dev, tx_buf);
DBG("Tx len %u, Rx buf %p, Rx len %u\n", tx_buf_len, rx_buf, rx_buf_len);
#ifdef CONFIG_SPI_DEBUG
__ASSERT(!((tx_buf_len && (tx_buf == NULL)) ||
(rx_buf_len && (rx_buf == NULL))),
"spi_k64_transceive: ERROR - NULL buffer");
#endif
/* Check Tx FIFO status */
if (tx_buf_len &&
((sys_read32(info->regs + SPI_K64_REG_SR) & SPI_K64_SR_TFFF) == 0)) {
DBG("spi_k64_transceive: Tx FIFO is already full\n");
return -EBUSY;
}
/* Set buffers info */
spi_data->tx_buf = tx_buf;
spi_data->tx_buf_len = tx_buf_len;
spi_data->rx_buf = rx_buf;
spi_data->rx_buf_len = rx_buf_len;
/* enable transfer operations - must be done before enabling interrupts */
spi_k64_start(dev);
/*
* Enable interrupts:
* - Transmit FIFO Fill (Tx FIFO not full); and/or
* - Receive FIFO Drain (Rx FIFO not empty);
*
* Note: DMA requests are not supported.
*/
int_config = sys_read32(info->regs + SPI_K64_REG_RSER);
if (tx_buf_len) {
int_config |= SPI_K64_RSER_TFFF_RE;
}
if (rx_buf_len) {
int_config |= SPI_K64_RSER_RFDF_RE;
}
sys_write32(int_config, (info->regs + SPI_K64_REG_RSER));
/* wait for transfer to complete */
device_sync_call_wait(&spi_data->sync_info);
/* check completion status */
if (spi_data->error) {
spi_data->error = 0;
return -EIO;
}
return 0;
}
/**
* @brief Suspend SPI host controller operations.
* @param dev Pointer to the device structure for the driver instance
* @return 0 if successful, another DEV_* code otherwise.
*/
static int spi_k64_suspend(struct device *dev)
{
struct spi_k64_config *info = dev->config->config_info;
DBG("spi_k64_suspend: %p\n", dev);
/* disable module */
sys_set_bit((info->regs + SPI_K64_REG_MCR), SPI_K64_MCR_MDIS_BIT);
irq_disable(info->irq);
while (sys_read32(info->regs + SPI_K64_REG_SR) & SPI_K64_SR_TXRXS) {
DBG("SPI Controller dev %p is running. Waiting to stop.\n", dev);
}
return 0;
}
/**
* @brief Resume SPI host controller operations.
* @param dev Pointer to the device structure for the driver instance
* @return 0 if successful, another DEV_* code otherwise.
*/
static int spi_k64_resume(struct device *dev)
{
struct spi_k64_config *info = dev->config->config_info;
DBG("spi_k64_resume: %p\n", dev);
/* enable module */
sys_clear_bit((info->regs + SPI_K64_REG_MCR), SPI_K64_MCR_MDIS_BIT);
irq_enable(info->irq);
return 0;
}
/**
* @brief SPI module data push (write) operation.
* @param dev Pointer to the device structure for the driver instance
* @return None.
*/
static void spi_k64_push_data(struct device *dev)
{
struct spi_k64_config *info = dev->config->config_info;
struct spi_k64_data *spi_data = dev->driver_data;
uint32_t data;
#ifdef CONFIG_SPI_DEBUG
uint32_t cnt = 0; /* # of bytes pushed */
#endif
DBG("spi_k64_push_data - ");
do { /* initial status already checked by spi_k64_isr() */
if (spi_data->tx_buf && (spi_data->tx_buf_len > 0)) {
if (spi_data->frame_sz > CHAR_BIT) {
/* get 2nd byte with frame sizes larger than 8 bits */
data = (uint32_t)(*(uint16_t *)(spi_data->tx_buf));
spi_data->tx_buf += 2;
spi_data->tx_buf_len -= 2;
#ifdef CONFIG_SPI_DEBUG
cnt += 2;
#endif
} else {
data = (uint32_t)(*(spi_data->tx_buf));
spi_data->tx_buf++;
spi_data->tx_buf_len--;
#ifdef CONFIG_SPI_DEBUG
cnt++;
#endif
}
/* Write data to the selected slave */
if (spi_data->cont_pcs_sel && (spi_data->tx_buf_len == 0)) {
/* clear continuous PCS enabling in the last frame */
sys_write32((data | SPI_K64_PUSHR_PCS_SET(spi_data->pcs)),
(info->regs + SPI_K64_REG_PUSHR));
} else {
sys_write32((data | SPI_K64_PUSHR_PCS_SET(spi_data->pcs) |
SPI_K64_PUSHR_CONT_SET(spi_data->cont_pcs_sel)),
(info->regs + SPI_K64_REG_PUSHR));
}
/* Clear interrupt */
sys_write32(SPI_K64_SR_TFFF, (info->regs + SPI_K64_REG_SR));
} else {
/* Nothing more to push */
break;
}
} while (sys_read32(info->regs + SPI_K64_REG_SR) & SPI_K64_SR_TFFF);
DBG("pushed: %d\n", cnt);
}
/**
* @brief SPI module data pull (read) operation.
* @param dev Pointer to the device structure for the driver instance
* @return None.
*/
static void spi_k64_pull_data(struct device *dev)
{
struct spi_k64_config *info = dev->config->config_info;
struct spi_k64_data *spi_data = dev->driver_data;
uint16_t data;
#ifdef CONFIG_SPI_DEBUG
uint32_t cnt = 0; /* # of bytes pulled */
#endif
DBG("spi_k64_pull_data - ");
do { /* initial status already checked by spi_k64_isr() */
if (spi_data->rx_buf && spi_data->rx_buf_len > 0) {
data = (uint16_t)sys_read32(info->regs + SPI_K64_REG_POPR);
if (spi_data->frame_sz > CHAR_BIT) {
/* store 2nd byte with frame sizes larger than 8 bits */
*((uint16_t *)(spi_data->rx_buf)) = data;
spi_data->rx_buf += 2;
spi_data->rx_buf_len -= 2;
#ifdef CONFIG_SPI_DEBUG
cnt += 2;
#endif
} else {
*(spi_data->rx_buf) = (uint8_t)data;
spi_data->rx_buf++;
spi_data->rx_buf_len--;
#ifdef CONFIG_SPI_DEBUG
cnt++;
#endif
}
/* Clear interrupt */
sys_write32(SPI_K64_SR_RFDF, (info->regs + SPI_K64_REG_SR));
} else {
/* No buffer to store data to */
break;
}
} while (sys_read32(info->regs + SPI_K64_REG_SR) & SPI_K64_SR_RFDF);
DBG("pulled: %d\n", cnt);
}
/**
* @brief Complete SPI module data transfer operations.
* @param dev Pointer to the device structure for the driver instance
* @param error Error condition (0 = no error, otherwise an error occurred)
* @return None.
*/
static void spi_k64_complete(struct device *dev, uint32_t error)
{
struct spi_k64_data *spi_data = dev->driver_data;
struct spi_k64_config *info = dev->config->config_info;
uint32_t int_config; /* interrupt configuration */
if (error) {
DBG("spi_k64_complete - ERROR condition\n");
goto complete;
}
/* Check for a completed transfer */
if (spi_data->tx_buf && (spi_data->tx_buf_len == 0) && !spi_data->rx_buf) {
/* disable Tx interrupts */
int_config = sys_read32(info->regs + SPI_K64_REG_RSER);
int_config &= ~SPI_K64_RSER_TFFF_RE;
sys_write32(int_config, (info->regs + SPI_K64_REG_RSER));
} else if (spi_data->rx_buf && (spi_data->rx_buf_len == 0) &&
!spi_data->tx_buf) {
/* disable Rx interrupts */
int_config = sys_read32(info->regs + SPI_K64_REG_RSER);
int_config &= ~SPI_K64_RSER_RFDF_RE;
sys_write32(int_config, (info->regs + SPI_K64_REG_RSER));
} else if (spi_data->tx_buf && spi_data->tx_buf_len == 0 &&
spi_data->rx_buf && spi_data->rx_buf_len == 0) {
/* disable Tx, Rx interrupts */
int_config = sys_read32(info->regs + SPI_K64_REG_RSER);
int_config &= ~(SPI_K64_RSER_TFFF_RE | SPI_K64_RSER_RFDF_RE);
sys_write32(int_config, (info->regs + SPI_K64_REG_RSER));
} else {
return;
}
complete:
spi_data->tx_buf = spi_data->rx_buf = NULL;
spi_data->tx_buf_len = spi_data->rx_buf_len = 0;
/* Disable transfer operations */
spi_k64_halt(dev);
/* Save status */
spi_data->error = error;
/* Signal completion */
device_sync_call_complete(&spi_data->sync_info);
}
/**
* @brief SPI module interrupt handler.
* @param arg Pointer to the device structure for the driver instance
* @return None.
*/
void spi_k64_isr(void *arg)
{
struct device *dev = arg;
struct spi_k64_config *info = dev->config->config_info;
uint32_t error = 0;
uint32_t status;
status = sys_read32(info->regs + SPI_K64_REG_SR);
DBG("spi_k64_isr: dev %p, status 0x%x\n", dev, status);
if (status & (SPI_K64_SR_RFOF | SPI_K64_SR_TFUF)) {
/* Unrecoverable error: Rx overflow, Tx underflow */
error = 1;
} else {
if (status & SPI_K64_SR_TFFF) {
spi_k64_push_data(dev);
}
if (status & SPI_K64_SR_RFDF) {
spi_k64_pull_data(dev);
}
}
/* finish processing, if data transfer is complete */
spi_k64_complete(dev, error);
}
static struct spi_driver_api k64_spi_api = {
.configure = spi_k64_configure,
.slave_select = spi_k64_slave_select,
.transceive = spi_k64_transceive,
.suspend = spi_k64_suspend,
.resume = spi_k64_resume,
};
int spi_k64_init(struct device *dev)
{
struct spi_k64_config *info = dev->config->config_info;
struct spi_k64_data *data = dev->driver_data;
uint32_t mcr;
dev->driver_api = &k64_spi_api;
/* Enable module clocking */
sys_set_bit(info->clk_gate_reg, info->clk_gate_bit);
/*
* Ensure module operation is stopped and enabled before writing anything
* more to the registers.
* (Clear MCR[MDIS] and set MCR[HALT].)
*/
DBG("halt\n");
mcr = SPI_K64_MCR_HALT;
sys_write32(mcr, (info->regs + SPI_K64_REG_MCR));
while (sys_read32(info->regs + SPI_K64_REG_SR) & SPI_K64_SR_TXRXS) {
DBG("SPI Controller dev %p is running. Waiting for Halt.\n", dev);
}
/* Clear Tx and Rx FIFOs */
mcr |= (SPI_K64_MCR_CLR_RXF | SPI_K64_MCR_CLR_TXF);
DBG("fifo clr\n");
sys_write32(mcr, (info->regs + SPI_K64_REG_MCR));
/* Set master mode */
mcr = SPI_K64_MCR_MSTR | SPI_K64_MCR_HALT;
DBG("master mode\n");
sys_write32(mcr, (info->regs + SPI_K64_REG_MCR));
/* Disable SPI module interrupt generation */
DBG("irq disable\n");
sys_write32(0, (info->regs + SPI_K64_REG_RSER));
/* Clear status */
DBG("status clr\n");
sys_write32((SPI_K64_SR_RFDF | SPI_K64_SR_RFOF | SPI_K64_SR_TFUF |
SPI_K64_SR_EOQF | SPI_K64_SR_TCF),
(info->regs + SPI_K64_REG_SR));
/* Set up the synchronous call mechanism */
device_sync_call_init(&data->sync_info);
/* Configure and enable SPI module IRQs */
info->config_func();
irq_enable(info->irq);
/*
* Enable Rx overflow interrupt generation.
* Note that Tx underflow is only generated when in slave mode.
*/
DBG("rxfifo overflow enable\n");
sys_write32(SPI_K64_RSER_RFOF_RE, (info->regs + SPI_K64_REG_RSER));
DBG("K64 SPI Driver initialized on device: %p\n", dev);
/* operation remains disabled (MCR[HALT] = 1)*/
return 0;
}
/* system bindings */
#ifdef CONFIG_SPI_K64_0
void spi_config_0_irq(void);
struct spi_k64_data spi_k64_data_port_0;
struct spi_k64_config spi_k64_config_0 = {
.regs = SPI_K64_0_BASE_ADDR,
.clk_gate_reg = SPI_K64_0_CLK_GATE_REG_ADDR,
.clk_gate_bit = SPI_K64_0_CLK_GATE_REG_BIT,
.irq = SPI_K64_0_IRQ,
.config_func = spi_config_0_irq
};
DEVICE_INIT(spi_k64_port_0, CONFIG_SPI_K64_0_DEV_NAME, spi_k64_init,
&spi_k64_data_port_0, &spi_k64_config_0,
PRIMARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
void spi_config_0_irq(void)
{
IRQ_CONNECT(SPI_K64_0_IRQ, CONFIG_SPI_K64_0_PRI,
spi_k64_isr, DEVICE_GET(spi_k64_port_0), 0);
}
#endif /* CONFIG_SPI_K64_0 */
#ifdef CONFIG_SPI_K64_1
void spi_config_1_irq(void);
struct spi_k64_data spi_k64_data_port_1;
struct spi_k64_config spi_k64_config_1 = {
.regs = SPI_K64_1_BASE_ADDR,
.clk_gate_reg = SPI_K64_1_CLK_GATE_REG_ADDR,
.clk_gate_bit = SPI_K64_1_CLK_GATE_REG_BIT,
.irq = SPI_K64_1_IRQ,
.config_func = spi_config_1_irq
};
DEVICE_INIT(spi_k64_port_1, CONFIG_SPI_K64_1_DEV_NAME, spi_k64_init,
&spi_k64_data_port_1, &spi_k64_config_1,
PRIMARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
void spi_config_1_irq(void)
{
IRQ_CONNECT(SPI_K64_1_IRQ, CONFIG_SPI_K64_1_PRI,
spi_k64_isr, DEVICE_GET(spi_k64_port_1), 0);
}
#endif /* CONFIG_SPI_K64_1 */
#ifdef CONFIG_SPI_K64_2
void spi_config_2_irq(void);
struct spi_k64_data spi_k64_data_port_2;
struct spi_k64_config spi_k64_config_2 = {
.regs = SPI_K64_2_BASE_ADDR,
.clk_gate_reg = SPI_K64_2_CLK_GATE_REG_ADDR,
.clk_gate_bit = SPI_K64_2_CLK_GATE_REG_BIT,
.irq = SPI_K64_2_IRQ,
.config_func = spi_config_2_irq
};
DEVICE_INIT(spi_k64_port_2, CONFIG_SPI_K64_2_DEV_NAME, spi_k64_init,
&spi_k64_data_port_2, &spi_k64_config_2,
PRIMARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
void spi_config_2_irq(void)
{
IRQ_CONNECT(SPI_K64_2_IRQ, CONFIG_SPI_K64_2_PRI,
spi_k64_isr, DEVICE_GET(spi_k64_port_2), 0);
}
#endif /* CONFIG_SPI_K64_2 */