forked from Imagelibrary/rtems
Use interrupts instead of polled or DMA driven mode. Change license to BSD-2-Clause. Close #3724.
473 lines
9.7 KiB
C
473 lines
9.7 KiB
C
/**
|
|
* @file
|
|
*
|
|
* @ingroup RTEMSBSPsARMLPC24XXSSP
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (C) 2008, 2019 embedded brains GmbH
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. 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.
|
|
*
|
|
* 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 OWNER 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 <bsp/ssp.h>
|
|
#include <bsp.h>
|
|
#include <bsp/io.h>
|
|
#include <bsp/irq.h>
|
|
#include <bsp/lpc24xx.h>
|
|
|
|
#include <rtems/score/assert.h>
|
|
|
|
#include <dev/spi/spi.h>
|
|
|
|
typedef struct {
|
|
spi_bus base;
|
|
volatile lpc24xx_ssp *regs;
|
|
size_t tx_todo;
|
|
const uint8_t *tx_buf;
|
|
size_t tx_inc;
|
|
size_t rx_todo;
|
|
uint8_t *rx_buf;
|
|
size_t rx_inc;
|
|
const spi_ioc_transfer *msg;
|
|
uint32_t msg_todo;
|
|
int msg_error;
|
|
rtems_binary_semaphore sem;
|
|
lpc24xx_module module;
|
|
rtems_vector_number irq;
|
|
} lpc24xx_ssp_bus;
|
|
|
|
typedef struct {
|
|
volatile lpc24xx_ssp *regs;
|
|
lpc24xx_module module;
|
|
rtems_vector_number irq;
|
|
} lpc24xx_ssp_config;
|
|
|
|
static uint8_t lpc24xx_ssp_trash;
|
|
|
|
static const uint8_t lpc24xx_ssp_idle = 0xff;
|
|
|
|
static void lpc24xx_ssp_done(lpc24xx_ssp_bus *bus, int error)
|
|
{
|
|
bus->msg_error = error;
|
|
rtems_binary_semaphore_post(&bus->sem);
|
|
}
|
|
|
|
static int lpc24xx_ssp_do_setup(
|
|
lpc24xx_ssp_bus *bus,
|
|
uint32_t speed_hz,
|
|
uint32_t mode
|
|
)
|
|
{
|
|
volatile lpc24xx_ssp *regs;
|
|
uint32_t clk;
|
|
uint32_t scr_plus_one;
|
|
uint32_t cr0;
|
|
|
|
if (speed_hz > bus->base.max_speed_hz || speed_hz == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((mode & ~(SPI_CPOL | SPI_CPHA)) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
regs = bus->regs;
|
|
clk = bus->base.max_speed_hz;
|
|
scr_plus_one = (clk + speed_hz - 1) / speed_hz;
|
|
|
|
if (scr_plus_one > 256) {
|
|
uint32_t pre;
|
|
|
|
pre = (scr_plus_one + 255) / 256;
|
|
|
|
if (pre <= 127) {
|
|
scr_plus_one = (clk / pre + speed_hz - 1) / speed_hz;
|
|
} else {
|
|
pre = 127;
|
|
scr_plus_one = 256;
|
|
}
|
|
|
|
regs->cpsr = 2 * pre;
|
|
}
|
|
|
|
cr0 = SET_SSP_CR0_DSS(0, 0x7) | SET_SSP_CR0_SCR(0, scr_plus_one - 1);
|
|
|
|
if ((mode & SPI_CPOL) != 0) {
|
|
cr0 |= SSP_CR0_CPOL;
|
|
}
|
|
|
|
if ((mode & SPI_CPHA) != 0) {
|
|
cr0 |= SSP_CR0_CPHA;
|
|
}
|
|
|
|
regs->cr0 = cr0;
|
|
|
|
bus->base.speed_hz = speed_hz;
|
|
bus->base.mode = mode;
|
|
return 0;
|
|
}
|
|
|
|
static bool lpc24xx_ssp_msg_setup(
|
|
lpc24xx_ssp_bus *bus,
|
|
const spi_ioc_transfer *msg
|
|
)
|
|
{
|
|
if (msg->cs_change == 0 || msg->bits_per_word != 8) {
|
|
lpc24xx_ssp_done(bus, -EINVAL);
|
|
return false;
|
|
}
|
|
|
|
if (msg->speed_hz != bus->base.speed_hz || msg->mode != bus->base.mode) {
|
|
int error;
|
|
|
|
error = lpc24xx_ssp_do_setup(bus, msg->speed_hz, msg->mode);
|
|
if (error != 0) {
|
|
lpc24xx_ssp_done(bus, error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bus->tx_todo = msg->len;
|
|
bus->rx_todo = msg->len;
|
|
|
|
if (msg->tx_buf != NULL) {
|
|
bus->tx_buf = msg->tx_buf;
|
|
bus->tx_inc = 1;
|
|
} else {
|
|
bus->tx_buf = &lpc24xx_ssp_idle;
|
|
bus->tx_inc = 0;
|
|
}
|
|
|
|
if (msg->rx_buf != NULL) {
|
|
bus->rx_buf = msg->rx_buf;
|
|
bus->rx_inc = 1;
|
|
} else {
|
|
bus->rx_buf = &lpc24xx_ssp_trash;
|
|
bus->rx_inc = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool lpc24xx_ssp_do_tx_and_rx(
|
|
lpc24xx_ssp_bus *bus,
|
|
volatile lpc24xx_ssp *regs,
|
|
uint32_t sr
|
|
)
|
|
{
|
|
size_t tx_todo;
|
|
const uint8_t *tx_buf;
|
|
size_t tx_inc;
|
|
size_t rx_todo;
|
|
uint8_t *rx_buf;
|
|
size_t rx_inc;
|
|
uint32_t imsc;
|
|
|
|
tx_todo = bus->tx_todo;
|
|
tx_buf = bus->tx_buf;
|
|
tx_inc = bus->tx_inc;
|
|
rx_todo = bus->rx_todo;
|
|
rx_buf = bus->rx_buf;
|
|
rx_inc = bus->rx_inc;
|
|
|
|
while (tx_todo > 0 && (sr & SSP_SR_TNF) != 0) {
|
|
regs->dr = *tx_buf;
|
|
--tx_todo;
|
|
tx_buf += tx_inc;
|
|
|
|
if (rx_todo > 0 && (sr & SSP_SR_RNE) != 0) {
|
|
*rx_buf = regs->dr;
|
|
--rx_todo;
|
|
rx_buf += rx_inc;
|
|
}
|
|
|
|
sr = regs->sr;
|
|
}
|
|
|
|
while (rx_todo > 0 && (sr & SSP_SR_RNE) != 0) {
|
|
*rx_buf = regs->dr;
|
|
--rx_todo;
|
|
rx_buf += rx_inc;
|
|
|
|
sr = regs->sr;
|
|
}
|
|
|
|
bus->tx_todo = tx_todo;
|
|
bus->tx_buf = tx_buf;
|
|
bus->rx_todo = rx_todo;
|
|
bus->rx_buf = rx_buf;
|
|
|
|
imsc = 0;
|
|
|
|
if (tx_todo > 0) {
|
|
imsc |= SSP_IMSC_TXIM;
|
|
} else if (rx_todo > 0) {
|
|
imsc |= SSP_IMSC_RXIM | SSP_IMSC_RTIM;
|
|
regs->icr = SSP_ICR_RTRIS;
|
|
}
|
|
|
|
regs->imsc = imsc;
|
|
|
|
return tx_todo == 0 && rx_todo == 0;
|
|
}
|
|
|
|
static void lpc24xx_ssp_start(
|
|
lpc24xx_ssp_bus *bus,
|
|
const spi_ioc_transfer *msg
|
|
)
|
|
{
|
|
while (true) {
|
|
if (lpc24xx_ssp_msg_setup(bus, msg)) {
|
|
volatile lpc24xx_ssp *regs;
|
|
uint32_t sr;
|
|
bool next_msg;
|
|
|
|
regs = bus->regs;
|
|
sr = regs->sr;
|
|
|
|
if ((sr & (SSP_SR_RNE | SSP_SR_TFE)) != SSP_SR_TFE) {
|
|
lpc24xx_ssp_done(bus, -EIO);
|
|
break;
|
|
}
|
|
|
|
next_msg = lpc24xx_ssp_do_tx_and_rx(bus, regs, sr);
|
|
if (!next_msg) {
|
|
break;
|
|
}
|
|
|
|
--bus->msg_todo;
|
|
|
|
if (bus->msg_todo == 0) {
|
|
lpc24xx_ssp_done(bus, 0);
|
|
break;
|
|
}
|
|
|
|
++msg;
|
|
bus->msg = msg;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void lpc24xx_ssp_interrupt(void *arg)
|
|
{
|
|
lpc24xx_ssp_bus *bus;
|
|
volatile lpc24xx_ssp *regs;
|
|
|
|
bus = arg;
|
|
regs = bus->regs;
|
|
|
|
while (true) {
|
|
if (lpc24xx_ssp_do_tx_and_rx(bus, regs, regs->sr)) {
|
|
--bus->msg_todo;
|
|
|
|
if (bus->msg_todo > 0) {
|
|
++bus->msg;
|
|
|
|
if (!lpc24xx_ssp_msg_setup(bus, bus->msg)) {
|
|
break;
|
|
}
|
|
} else {
|
|
lpc24xx_ssp_done(bus, 0);
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int lpc24xx_ssp_transfer(
|
|
spi_bus *base,
|
|
const spi_ioc_transfer *msgs,
|
|
uint32_t msg_count
|
|
)
|
|
{
|
|
lpc24xx_ssp_bus *bus;
|
|
|
|
if (msg_count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
bus = (lpc24xx_ssp_bus *) base;
|
|
bus->msg = msgs;
|
|
bus->msg_todo = msg_count;
|
|
lpc24xx_ssp_start(bus, msgs);
|
|
rtems_binary_semaphore_wait(&bus->sem);
|
|
|
|
return bus->msg_error;
|
|
}
|
|
|
|
static void lpc24xx_ssp_destroy(spi_bus *base)
|
|
{
|
|
lpc24xx_ssp_bus *bus;
|
|
rtems_status_code sc;
|
|
|
|
bus = (lpc24xx_ssp_bus *) base;
|
|
|
|
sc = rtems_interrupt_handler_remove(
|
|
bus->irq,
|
|
lpc24xx_ssp_interrupt,
|
|
bus
|
|
);
|
|
_Assert(sc == RTEMS_SUCCESSFUL);
|
|
(void) sc;
|
|
|
|
/* Disable SSP module */
|
|
bus->regs->cr1 = 0;
|
|
|
|
sc = lpc24xx_module_disable(bus->module);
|
|
_Assert(sc == RTEMS_SUCCESSFUL);
|
|
(void) sc;
|
|
|
|
rtems_binary_semaphore_destroy(&bus->sem);
|
|
spi_bus_destroy_and_free(&bus->base);
|
|
}
|
|
|
|
static int lpc24xx_ssp_setup(spi_bus *base)
|
|
{
|
|
lpc24xx_ssp_bus *bus;
|
|
|
|
bus = (lpc24xx_ssp_bus *) base;
|
|
|
|
if (bus->base.bits_per_word != 8) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return lpc24xx_ssp_do_setup(bus, bus->base.speed_hz, bus->base.mode);
|
|
}
|
|
|
|
static int lpc24xx_ssp_init(lpc24xx_ssp_bus *bus)
|
|
{
|
|
rtems_status_code sc;
|
|
|
|
sc = lpc24xx_module_enable(bus->module, LPC24XX_MODULE_PCLK_DEFAULT);
|
|
_Assert(sc == RTEMS_SUCCESSFUL);
|
|
(void) sc;
|
|
|
|
/* Disable SSP module */
|
|
bus->regs->cr1 = 0;
|
|
|
|
sc = rtems_interrupt_handler_install(
|
|
bus->irq,
|
|
"SSP",
|
|
RTEMS_INTERRUPT_UNIQUE,
|
|
lpc24xx_ssp_interrupt,
|
|
bus
|
|
);
|
|
if (sc != RTEMS_SUCCESSFUL) {
|
|
return EAGAIN;
|
|
}
|
|
|
|
rtems_binary_semaphore_init(&bus->sem, "SSP");
|
|
|
|
/* Initialize SSP module */
|
|
bus->regs->dmacr = 0;
|
|
bus->regs->imsc = 0;
|
|
bus->regs->cpsr = 2;
|
|
bus->regs->cr0 = SET_SSP_CR0_DSS(0, 0x7);
|
|
bus->regs->cr1 = SSP_CR1_SSE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_bus_register_lpc24xx_ssp(
|
|
const char *bus_path,
|
|
const lpc24xx_ssp_config *config
|
|
)
|
|
{
|
|
lpc24xx_ssp_bus *bus;
|
|
int eno;
|
|
|
|
bus = (lpc24xx_ssp_bus *) spi_bus_alloc_and_init(sizeof(*bus));
|
|
if (bus == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
bus->base.max_speed_hz = LPC24XX_PCLK / 2;
|
|
bus->base.bits_per_word = 8;
|
|
bus->base.speed_hz = bus->base.max_speed_hz;
|
|
bus->regs = config->regs;
|
|
bus->module = config->module;
|
|
bus->irq = config->irq;
|
|
|
|
eno = lpc24xx_ssp_init(bus);
|
|
if (eno != 0) {
|
|
(*bus->base.destroy)(&bus->base);
|
|
rtems_set_errno_and_return_minus_one(eno);
|
|
}
|
|
|
|
bus->base.transfer = lpc24xx_ssp_transfer;
|
|
bus->base.destroy = lpc24xx_ssp_destroy;
|
|
bus->base.setup = lpc24xx_ssp_setup;
|
|
|
|
return spi_bus_register(&bus->base, bus_path);
|
|
}
|
|
|
|
int lpc24xx_register_ssp_0(void)
|
|
{
|
|
static const lpc24xx_ssp_config config = {
|
|
.regs = (volatile lpc24xx_ssp *) SSP0_BASE_ADDR,
|
|
.module = LPC24XX_MODULE_SSP_0,
|
|
.irq = LPC24XX_IRQ_SPI_SSP_0
|
|
};
|
|
|
|
return spi_bus_register_lpc24xx_ssp(
|
|
LPC24XX_SSP_0_BUS_PATH,
|
|
&config
|
|
);
|
|
}
|
|
|
|
int lpc24xx_register_ssp_1(void)
|
|
{
|
|
static const lpc24xx_ssp_config config = {
|
|
.regs = (volatile lpc24xx_ssp *) SSP1_BASE_ADDR,
|
|
.module = LPC24XX_MODULE_SSP_1,
|
|
.irq = LPC24XX_IRQ_SSP_1
|
|
};
|
|
|
|
return spi_bus_register_lpc24xx_ssp(
|
|
LPC24XX_SSP_2_BUS_PATH,
|
|
&config
|
|
);
|
|
}
|
|
|
|
#ifdef ARM_MULTILIB_ARCH_V7M
|
|
int lpc24xx_register_ssp_2(void)
|
|
{
|
|
static const lpc24xx_ssp_config config = {
|
|
.regs = (volatile lpc24xx_ssp *) SSP2_BASE_ADDR,
|
|
.module = LPC24XX_MODULE_SSP_2,
|
|
.irq = LPC24XX_IRQ_SSP_2
|
|
};
|
|
|
|
return spi_bus_register_lpc24xx_ssp(
|
|
LPC24XX_SSP_2_BUS_PATH,
|
|
&config
|
|
);
|
|
}
|
|
#endif
|