509 lines
12 KiB
C++
509 lines
12 KiB
C++
/*******************************************************************************
|
|
* Copyright (C) Dean Miller
|
|
* All rights reserved.
|
|
*
|
|
* This program is open source software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 "qpcpp.h"
|
|
#include "qp_extras.h"
|
|
|
|
#include "hsm_id.h"
|
|
#include "AOSERCOM.h"
|
|
#include "event.h"
|
|
|
|
#include "SeesawConfig.h"
|
|
#include "RegisterMap.h"
|
|
#include "bsp_sercom.h"
|
|
#include "bsp_gpio.h"
|
|
#include "bsp_dma.h"
|
|
|
|
//for direct UART<->USB
|
|
#include "USB/USBCore.h"
|
|
#include "USB/USBAPI.h"
|
|
#include "USB/USBDesc.h"
|
|
|
|
Q_DEFINE_THIS_FILE
|
|
|
|
using namespace FW;
|
|
|
|
#ifdef USB_UART_DMA
|
|
static uint8_t DMA_IN_BUF[2][64];
|
|
static bool dma_ix = 0;
|
|
#endif
|
|
|
|
//TODO: we probably will need these for each sercom
|
|
static Fifo *m_rxFifo;
|
|
|
|
AOSERCOM::AOSERCOM( Sercom *sercom, uint8_t id, uint8_t offset ) :
|
|
QActive((QStateHandler)&AOSERCOM::InitialPseudoState),
|
|
#ifdef USB_UART_DMA
|
|
m_syncTimer(this, SERCOM_UART_SYNC),
|
|
#endif
|
|
m_id(id), m_name("SERCOM"), m_sercom(sercom), m_offset(offset) {}
|
|
|
|
QState AOSERCOM::InitialPseudoState(AOSERCOM * const me, QEvt const * const e) {
|
|
(void)e;
|
|
#ifdef USB_UART_DMA
|
|
me->m_deferQueue.init(me->m_deferQueueStor, ARRAY_COUNT(me->m_deferQueueStor));
|
|
me->subscribe(SERCOM_UART_SYNC);
|
|
#endif
|
|
|
|
me->subscribe(SERCOM_START_REQ);
|
|
me->subscribe(SERCOM_STOP_REQ);
|
|
|
|
me->subscribe(SERCOM_WRITE_DATA_REQ);
|
|
me->subscribe(SERCOM_READ_DATA_REQ);
|
|
me->subscribe(SERCOM_READ_REG_REQ);
|
|
me->subscribe(SERCOM_WRITE_REG_REQ);
|
|
|
|
me->subscribe(SERCOM_RX_INTERRUPT);
|
|
|
|
return Q_TRAN(&AOSERCOM::Root);
|
|
}
|
|
|
|
QState AOSERCOM::Root(AOSERCOM * const me, QEvt const * const e) {
|
|
QState status;
|
|
switch (e->sig) {
|
|
case Q_ENTRY_SIG: {
|
|
LOG_EVENT(e);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case Q_EXIT_SIG: {
|
|
LOG_EVENT(e);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case Q_INIT_SIG: {
|
|
status = Q_TRAN(&AOSERCOM::Stopped);
|
|
break;
|
|
}
|
|
case SERCOM_STOP_REQ: {
|
|
LOG_EVENT(e);
|
|
status = Q_TRAN(&AOSERCOM::Stopped);
|
|
break;
|
|
}
|
|
default: {
|
|
status = Q_SUPER(&QHsm::top);
|
|
break;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
QState AOSERCOM::Stopped(AOSERCOM * const me, QEvt const * const e) {
|
|
QState status;
|
|
switch (e->sig) {
|
|
case Q_ENTRY_SIG: {
|
|
LOG_EVENT(e);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case Q_EXIT_SIG: {
|
|
LOG_EVENT(e);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case SERCOM_STOP_REQ: {
|
|
LOG_EVENT(e);
|
|
Evt const &req = EVT_CAST(*e);
|
|
Evt *evt = new SERCOMStopCfm(req.GetSeq(), ERROR_SUCCESS);
|
|
QF::PUBLISH(evt, me);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case SERCOM_START_REQ: {
|
|
LOG_EVENT(e);
|
|
|
|
SercomStartReq const &req = static_cast<SercomStartReq const &>(*e);
|
|
m_rxFifo = req.getRxFifo();
|
|
|
|
me->m_status.set(0x00);
|
|
me->m_inten.set(0x00);
|
|
me->m_intenclr.set(0x00);
|
|
|
|
me->m_baud = CONFIG_SERCOM_UART_BAUD_RATE;
|
|
|
|
Evt const &r = EVT_CAST(*e);
|
|
Evt *evt = new SERCOMStartCfm(r.GetSeq(), ERROR_SUCCESS);
|
|
QF::PUBLISH(evt, me);
|
|
|
|
status = Q_TRAN(&AOSERCOM::Started);
|
|
break;
|
|
}
|
|
default: {
|
|
status = Q_SUPER(&AOSERCOM::Root);
|
|
break;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
QState AOSERCOM::Started(AOSERCOM * const me, QEvt const * const e) {
|
|
QState status;
|
|
switch (e->sig) {
|
|
case Q_ENTRY_SIG: {
|
|
LOG_EVENT(e);
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case Q_INIT_SIG: {
|
|
status = Q_TRAN(&AOSERCOM::UART);
|
|
break;
|
|
}
|
|
case Q_EXIT_SIG: {
|
|
LOG_EVENT(e);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case SERCOM_STOP_REQ: {
|
|
LOG_EVENT(e);
|
|
Evt const &req = EVT_CAST(*e);
|
|
Evt *evt = new SERCOMStopCfm(req.GetSeq(), ERROR_SUCCESS);
|
|
QF::PUBLISH(evt, me);
|
|
#ifdef USB_UART_DMA
|
|
//hacky but don't restart if we're doing USB to UART DMA
|
|
status = Q_HANDLED();
|
|
#else
|
|
status = Q_TRAN(AOSERCOM::Stopped);
|
|
#endif
|
|
break;
|
|
}
|
|
default: {
|
|
status = Q_SUPER(&AOSERCOM::Root);
|
|
break;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
QState AOSERCOM::UART(AOSERCOM * const me, QEvt const * const e) {
|
|
QState status;
|
|
switch (e->sig) {
|
|
case Q_ENTRY_SIG: {
|
|
LOG_EVENT(e);
|
|
|
|
//set up UART
|
|
pinPeripheral(CONFIG_SERCOM_UART_PIN_RX, CONFIG_SERCOM_UART_MUX_RX);
|
|
pinPeripheral(CONFIG_SERCOM_UART_PIN_TX, CONFIG_SERCOM_UART_MUX_TX);
|
|
|
|
initUART(me->m_sercom, SAMPLE_RATE_x16, me->m_baud);
|
|
initFrame(me->m_sercom, CONFIG_SERCOM_UART_CHAR_SIZE, LSB_FIRST, CONFIG_SERCOM_UART_PARITY, CONFIG_SERCOM_UART_STOP_BIT);
|
|
initPads(me->m_sercom, CONFIG_SERCOM_UART_PAD_TX, CONFIG_SERCOM_UART_PAD_RX);
|
|
|
|
#ifdef USB_UART_DMA
|
|
me->m_syncTimer.armX(5, 5);
|
|
disableInterruptsUART(me->m_sercom);
|
|
dmac_init();
|
|
|
|
dmac_alloc(CONFIG_USB_UART_DMA_CHANNEL_RX);
|
|
dmac_set_action(CONFIG_USB_UART_DMA_CHANNEL_RX, DMA_TRIGGER_ACTON_BEAT);
|
|
dmac_set_trigger(CONFIG_USB_UART_DMA_CHANNEL_RX, CONFIG_USB_UART_DMA_TRIGGER_RX);
|
|
|
|
dmac_set_descriptor(
|
|
CONFIG_USB_UART_DMA_CHANNEL_RX,
|
|
(void *)&me->m_sercom->USART.DATA.reg,
|
|
(void *)DMA_IN_BUF[0],
|
|
sizeof(DMA_IN_BUF[0]),
|
|
DMA_BEAT_SIZE_BYTE,
|
|
false,
|
|
true);
|
|
dmac_start(CONFIG_USB_UART_DMA_CHANNEL_RX);
|
|
#endif
|
|
|
|
enableUART(me->m_sercom);
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
|
|
case SERCOM_WRITE_DATA_REQ: {
|
|
|
|
SercomWriteDataReq const &req = static_cast<SercomWriteDataReq const &>(*e);
|
|
Fifo *source = req.getSource();
|
|
|
|
int len = req.getLen();
|
|
uint8_t c = 0;
|
|
for(int i=len; i>0; i--){
|
|
source->Read(&c, 1);
|
|
writeDataUART(me->m_sercom, c);
|
|
}
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case SERCOM_READ_DATA_REQ: {
|
|
|
|
//clear DATA_RDY flag
|
|
me->m_status.DATA_RDY = 0;
|
|
|
|
if(me->m_inten.DATA_RDY){
|
|
//post an interrupt event
|
|
Evt *evt = new InterruptClearReq( (SEESAW_INTERRUPT_SERCOM0_DATA_RDY << me->m_offset) );
|
|
QF::PUBLISH(evt, me);
|
|
}
|
|
|
|
SercomReadDataReq const &req = static_cast<SercomReadDataReq const &>(*e);
|
|
Evt *evt = new DelegateDataReady(req.getRequesterId(), m_rxFifo);
|
|
QF::PUBLISH(evt, me);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case SERCOM_READ_REG_REQ: {
|
|
SercomReadRegReq const &req = static_cast<SercomReadRegReq const &>(*e);
|
|
Fifo *dest = req.getDest();
|
|
uint8_t reg = req.getReg();
|
|
|
|
//there should be nothing in the destination pipe
|
|
Q_ASSERT(!dest->GetUsedCount());
|
|
|
|
Evt *evt;
|
|
uint8_t c;
|
|
switch (reg){
|
|
case SEESAW_SERCOM_STATUS:{
|
|
c = me->m_status.get();
|
|
break;
|
|
}
|
|
case SEESAW_SERCOM_INTEN:{
|
|
c = me->m_inten.get();
|
|
break;
|
|
}
|
|
default:
|
|
//TODO: lets handle this error better
|
|
Q_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
dest->Write(&c, 1);
|
|
evt = new DelegateDataReady(req.getRequesterId());
|
|
QF::PUBLISH(evt, me);
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case SERCOM_WRITE_REG_REQ: {
|
|
SercomWriteRegReq const &req = static_cast<SercomWriteRegReq const &>(*e);
|
|
uint8_t reg = req.getReg();
|
|
|
|
switch (reg){
|
|
case SEESAW_SERCOM_INTEN:{
|
|
me->m_inten.set(req.getValue());
|
|
break;
|
|
}
|
|
case SEESAW_SERCOM_BAUD:{
|
|
me->m_baud = req.getValue();
|
|
setUARTBaud(me->m_sercom, me->m_baud);
|
|
break;
|
|
}
|
|
default:
|
|
//TODO: lets handle this error better
|
|
Q_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
#ifdef USB_UART_DMA
|
|
case SERCOM_UART_SYNC: {
|
|
//TODO: disable interrupts
|
|
|
|
dmac_abort(CONFIG_USB_UART_DMA_CHANNEL_RX);
|
|
uint8_t count = dmac_get_transfer_count(CONFIG_USB_UART_DMA_CHANNEL_RX);
|
|
if(count > 0){
|
|
//swap the buffers and send the data to USB if anything has been received
|
|
USBDevice.send(CDC_ENDPOINT_IN, DMA_IN_BUF[dma_ix], count);
|
|
dma_ix = !dma_ix;
|
|
|
|
dmac_set_descriptor(
|
|
CONFIG_USB_UART_DMA_CHANNEL_RX,
|
|
(void *)&me->m_sercom->USART.DATA.reg,
|
|
(void *)DMA_IN_BUF[dma_ix],
|
|
sizeof(DMA_IN_BUF[dma_ix]),
|
|
DMA_BEAT_SIZE_BYTE,
|
|
false,
|
|
true);
|
|
}
|
|
dmac_start(CONFIG_USB_UART_DMA_CHANNEL_RX);
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
#endif
|
|
case SERCOM_RX_INTERRUPT: {
|
|
#ifdef USB_UART_DIRECT
|
|
//messy but just pipe this right out to USB for dsp feather
|
|
uint8_t toUSB[64];
|
|
uint8_t bytesRead = m_rxFifo->Read(toUSB, m_rxFifo->GetUsedCount());
|
|
|
|
USBDevice.send(CDC_ENDPOINT_IN, toUSB, bytesRead);
|
|
#else
|
|
me->m_status.DATA_RDY = 1;
|
|
if(me->m_inten.DATA_RDY){
|
|
//post an interrupt event
|
|
Evt *evt = new InterruptSetReq( (SEESAW_INTERRUPT_SERCOM0_DATA_RDY << me->m_offset) );
|
|
QF::PUBLISH(evt, me);
|
|
}
|
|
#endif
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case Q_EXIT_SIG: {
|
|
LOG_EVENT(e);
|
|
#ifdef USB_UART_DMA
|
|
me->m_syncTimer.disarm();
|
|
#endif
|
|
resetUART( me->m_sercom );
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
|
|
#ifdef USB_UART_DMA
|
|
case SERCOM_START_REQ:{
|
|
Evt const &r = EVT_CAST(*e);
|
|
Evt *evt = new SERCOMStartCfm(r.GetSeq(), ERROR_SUCCESS);
|
|
QF::PUBLISH(evt, me);
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
default: {
|
|
status = Q_SUPER(&AOSERCOM::Started);
|
|
break;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
QState AOSERCOM::SPI(AOSERCOM * const me, QEvt const * const e) {
|
|
QState status;
|
|
switch (e->sig) {
|
|
case Q_ENTRY_SIG: {
|
|
LOG_EVENT(e);
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case Q_EXIT_SIG: {
|
|
LOG_EVENT(e);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
status = Q_SUPER(&AOSERCOM::Started);
|
|
break;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void AOSERCOM::RxCallback(){
|
|
Evt *evt = new Evt(SERCOM_RX_INTERRUPT);
|
|
QF::PUBLISH(evt, 0);
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
void sercom_handler( Sercom * sercom )
|
|
{
|
|
if( isEnabledUART( sercom ) ){
|
|
if (availableDataUART( sercom )) {
|
|
uint8_t c = readDataUART( sercom );
|
|
#ifndef USB_UART_DIRECT
|
|
m_rxFifo->Write(&c, 1);
|
|
AOSERCOM::RxCallback();
|
|
#else
|
|
USBDevice.send(CDC_ENDPOINT_IN, &c, 1);
|
|
#endif
|
|
}
|
|
|
|
if (isUARTError( sercom )) {
|
|
acknowledgeUARTError( sercom );
|
|
// TODO: if (sercom->isBufferOverflowErrorUART()) ....
|
|
// TODO: if (sercom->isFrameErrorUART()) ....
|
|
// TODO: if (sercom->isParityErrorUART()) ....
|
|
clearStatusUART( sercom );
|
|
}
|
|
}
|
|
//TODO: else if ( isEnabledSPI( sercom ) ) { }
|
|
}
|
|
#if defined(SERCOM0) && CONFIG_SERCOM0
|
|
void SERCOM0_Handler(void){
|
|
QXK_ISR_ENTRY();
|
|
sercom_handler(SERCOM0);
|
|
QXK_ISR_EXIT();
|
|
}
|
|
#endif
|
|
#if defined(SERCOM1) && CONFIG_SERCOM1
|
|
void SERCOM1_Handler(void){
|
|
QXK_ISR_ENTRY();
|
|
sercom_handler(SERCOM1);
|
|
QXK_ISR_EXIT();
|
|
}
|
|
#endif
|
|
#if defined(SERCOM2) && CONFIG_SERCOM2
|
|
void SERCOM2_Handler(void){
|
|
QXK_ISR_ENTRY();
|
|
sercom_handler(SERCOM2);
|
|
QXK_ISR_EXIT();
|
|
}
|
|
#endif
|
|
#if defined(SERCOM5) && CONFIG_SERCOM5
|
|
void SERCOM5_Handler(void){
|
|
QXK_ISR_ENTRY();
|
|
sercom_handler(SERCOM5);
|
|
QXK_ISR_EXIT();
|
|
}
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
QState AOSERCOM::stateName(AOSERCOM * const me, QEvt const * const e) {
|
|
QState status;
|
|
switch (e->sig) {
|
|
case Q_ENTRY_SIG: {
|
|
LOG_EVENT(e);
|
|
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
case Q_EXIT_SIG: {
|
|
LOG_EVENT(e);
|
|
status = Q_HANDLED();
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
status = Q_SUPER(&AOSERCOM::Root);
|
|
break;
|
|
}
|
|
}
|
|
return status;
|
|
}*/
|