842 lines
22 KiB
C
842 lines
22 KiB
C
/* AT91SAM7 SSC controller routines for OpenPICC
|
|
* (C) 2006 by Harald Welte <hwelte@hmw-consulting.de>
|
|
* (C) 2007-2008 Henryk Plötz <henryk@ploetzli.ch>
|
|
*
|
|
* This program is free 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 2 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* We use SSC for both TX and RX side.
|
|
*
|
|
* RX side is interconnected with demodulated carrier
|
|
*
|
|
* TX side is interconnected with load modulation circuitry
|
|
*/
|
|
|
|
#include <FreeRTOS.h>
|
|
#include <queue.h>
|
|
#include <task.h>
|
|
#include <openpicc.h>
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "ssc.h"
|
|
#include "iso14443.h"
|
|
|
|
#include "clock_switch.h"
|
|
#include "tc_cdiv_sync.h"
|
|
#include "tc_fdt.h"
|
|
#include "led.h"
|
|
|
|
#include "usb_print.h"
|
|
#include "cmd.h"
|
|
|
|
#define PRINT_DEBUG 0
|
|
#define DEBUG_DATA_GATING 0
|
|
|
|
struct _ssc_handle {
|
|
enum ssc_mode mode;
|
|
const struct openpicc_hardware *openpicc;
|
|
ssc_dma_rx_buffer_t* rx_buffer[2];
|
|
ssc_dma_tx_buffer_t* tx_buffer;
|
|
ssc_callback_t callback;
|
|
xQueueHandle rx_queue;
|
|
AT91PS_PDC pdc;
|
|
AT91PS_SSC ssc;
|
|
u_int8_t rx_enabled, tx_enabled;
|
|
u_int8_t rx_running, tx_running, tx_need_switching;
|
|
};
|
|
|
|
static ssc_handle_t _ssc;
|
|
static const ssc_mode_def ssc_modes[] = {
|
|
/* Undefined, no size set */
|
|
[SSC_MODE_NONE] = {SSC_MODE_NONE, 0, 0, 0},
|
|
/* 14443A Frame */
|
|
[SSC_MODE_14443A] = {SSC_MODE_14443A,
|
|
ISO14443_BITS_PER_SSC_TRANSFER * ISO14443A_SAMPLE_LEN, // transfersize_ssc
|
|
ISO14443_BITS_PER_SSC_TRANSFER * ISO14443A_SAMPLE_LEN <= 8 ? 8 : 16, // transfersize_pdc
|
|
DIV_ROUND_UP(ISO14443A_MAX_RX_FRAME_SIZE_IN_BITS, ISO14443_BITS_PER_SSC_TRANSFER) },
|
|
};
|
|
static struct {
|
|
ssc_metric metric;
|
|
char *name;
|
|
int value;
|
|
} ssc_metrics[] = {
|
|
[METRIC_RX_OVERFLOWS] = {METRIC_RX_OVERFLOWS, "Rx overflows", 0},
|
|
[METRIC_FREE_RX_BUFFERS] = {METRIC_FREE_RX_BUFFERS, "Free Rx buffers", 0},
|
|
[METRIC_MANAGEMENT_ERRORS] = {METRIC_MANAGEMENT_ERRORS, "Internal buffer management error", 0},
|
|
[METRIC_MANAGEMENT_ERRORS_1] = {METRIC_MANAGEMENT_ERRORS_1, "Internal buffer management error type 1", 0},
|
|
[METRIC_MANAGEMENT_ERRORS_2] = {METRIC_MANAGEMENT_ERRORS_2, "Internal buffer management error type 2", 0},
|
|
[METRIC_MANAGEMENT_ERRORS_3] = {METRIC_MANAGEMENT_ERRORS_3, "Internal buffer management error type 3", 0},
|
|
[METRIC_LATE_TX_FRAMES] = {METRIC_LATE_TX_FRAMES, "Late Tx frames", 0},
|
|
[METRIC_RX_FRAMES] = {METRIC_RX_FRAMES, "Rx frames", 0},
|
|
[METRIC_TX_FRAMES] = {METRIC_TX_FRAMES, "Tx frames", 0},
|
|
[METRIC_TX_ABORTED_FRAMES] = {METRIC_TX_ABORTED_FRAMES, "Aborted Tx frames", 0},
|
|
};
|
|
|
|
static ssc_dma_rx_buffer_t _rx_buffers[SSC_DMA_BUFFER_COUNT];
|
|
ssc_dma_tx_buffer_t _tx_buffer;
|
|
|
|
/******* PRIVATE "meat" code *************************************************/
|
|
|
|
#define SSC_RX_IRQ_MASK (AT91C_SSC_RXRDY | \
|
|
AT91C_SSC_OVRUN | \
|
|
AT91C_SSC_ENDRX | \
|
|
AT91C_SSC_RXBUFF | \
|
|
AT91C_SSC_RXSYN | \
|
|
AT91C_SSC_CP0 | \
|
|
AT91C_SSC_CP1)
|
|
|
|
#define SSC_TX_IRQ_MASK (AT91C_SSC_TXRDY | \
|
|
AT91C_SSC_TXEMPTY | \
|
|
AT91C_SSC_ENDTX | \
|
|
AT91C_SSC_TXBUFE | \
|
|
AT91C_SSC_TXSYN)
|
|
|
|
static __ramfunc ssc_dma_rx_buffer_t* _unload_rx(ssc_handle_t *sh);
|
|
static __ramfunc int _reload_rx(ssc_handle_t *sh);
|
|
|
|
|
|
static int __ramfunc _ssc_rx_irq(u_int32_t orig_sr, int start_asserted, portBASE_TYPE task_woken)
|
|
{
|
|
int end_asserted = 0;
|
|
ssc_handle_t *sh = &_ssc;
|
|
u_int32_t orig_rcmr = sh->ssc->SSC_RCMR;
|
|
|
|
#if PRINT_DEBUG
|
|
int old = usb_print_set_default_flush(0); // DEBUG OUTPUT
|
|
DumpStringToUSB("orig:"); // DEBUG OUTPUT
|
|
DumpUIntToUSB(orig_sr); // DEBUG OUTPUT
|
|
DumpStringToUSB("\n\r"); // DEBUG OUTPUT
|
|
#endif
|
|
u_int32_t sr = orig_sr | _ssc.ssc->SSC_SR;
|
|
|
|
if( (sr & AT91C_SSC_RXSYN) || start_asserted) {
|
|
sh->ssc->SSC_RCMR = (orig_rcmr & (~AT91C_SSC_START)) | (AT91C_SSC_START_CONTINOUS);
|
|
/* Receiving has started */
|
|
if(sh->callback != NULL) {
|
|
sh->callback(SSC_CALLBACK_RX_FRAME_BEGIN, &end_asserted);
|
|
if(end_asserted)
|
|
sr = orig_sr | _ssc.ssc->SSC_SR;
|
|
}
|
|
}
|
|
|
|
#if PRINT_DEBUG
|
|
DumpStringToUSB("sr:"); // DEBUG OUTPUT
|
|
DumpUIntToUSB(sr); // DEBUG OUTPUT
|
|
DumpStringToUSB("\n\r"); // DEBUG OUTPUT
|
|
usb_print_set_default_flush(old); // DEBUG OUTPUT
|
|
#endif
|
|
|
|
if(((sr & (AT91C_SSC_CP1 | AT91C_SSC_ENDRX)) || end_asserted) && (sh->rx_buffer[0] != NULL)) {
|
|
/* Receiving has ended */
|
|
AT91F_PDC_DisableRx(sh->pdc);
|
|
AT91F_SSC_DisableRx(sh->ssc);
|
|
sh->ssc->SSC_RCMR = ((sh->ssc->SSC_RCMR) & (~AT91C_SSC_START)) | (AT91C_SSC_START_0);
|
|
|
|
ssc_dma_rx_buffer_t *buffer = _unload_rx(sh);
|
|
if(buffer != NULL) {
|
|
if(sh->callback != NULL)
|
|
sh->callback(SSC_CALLBACK_RX_FRAME_ENDED, buffer);
|
|
|
|
if(buffer->state != SSC_FREE) {
|
|
task_woken = xQueueSendFromISR(sh->rx_queue, &buffer, task_woken);
|
|
}
|
|
}
|
|
|
|
if(_reload_rx(sh)) {
|
|
/* Clear the receive holding register. Is this necessary? */
|
|
int dummy = sh->ssc->SSC_RHR; (void)dummy;
|
|
AT91F_PDC_EnableRx(sh->pdc);
|
|
if(!sh->tx_running) {
|
|
/* Only start the receiver if no Tx has been started. Warning: Need
|
|
* to make sure that the receiver is restarted again when the Tx is over.
|
|
* Note that this is only necessary when not using Compare 0 to start
|
|
* reception. When using Compare 0 the data gating mechanism will keep
|
|
* CP0 from happening and thus keep the receiver stopped.
|
|
*
|
|
* Note also that we're not resetting rx_running to 0, because conceptionally
|
|
* the Rx is still running,. So rx_running=1 and tx_running=1 means SSC_RX
|
|
* stopped and SSC_RX should be started as soon as SSC_TX stops.
|
|
*/
|
|
AT91F_SSC_EnableRx(sh->ssc);
|
|
}
|
|
} else {
|
|
sh->ssc->SSC_IDR = SSC_RX_IRQ_MASK;
|
|
sh->rx_running = 0;
|
|
sh->callback(SSC_CALLBACK_RX_STOPPED, sh);
|
|
}
|
|
|
|
}
|
|
|
|
sh->ssc->SSC_RCMR = orig_rcmr;
|
|
return task_woken;
|
|
}
|
|
|
|
/* Exported callback for the case when the frame start has been detected externally */
|
|
void ssc_frame_started(void)
|
|
{
|
|
_ssc_rx_irq(_ssc.ssc->SSC_SR, 1, pdFALSE);
|
|
}
|
|
|
|
static void __ramfunc _ssc_tx_end(ssc_handle_t *sh, int is_an_abort)
|
|
{
|
|
AT91F_PDC_DisableTx(sh->pdc);
|
|
AT91F_SSC_DisableTx(sh->ssc);
|
|
sh->ssc->SSC_IDR = SSC_TX_IRQ_MASK;
|
|
|
|
AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA, AT91C_PA15_TF, 0);
|
|
usb_print_string_f(">",0);
|
|
|
|
AT91F_PDC_SetTx(sh->pdc, 0, 0);
|
|
AT91F_PDC_SetNextTx(sh->pdc, 0, 0);
|
|
|
|
if(sh->tx_buffer) {
|
|
sh->tx_buffer->state = SSC_FREE;
|
|
sh->tx_running = 0;
|
|
}
|
|
|
|
if(sh->rx_running) {
|
|
/* Receiver has been suspended by the pending transmission. Restart it. */
|
|
AT91F_SSC_EnableRx(sh->ssc);
|
|
}
|
|
|
|
if(sh->callback) {
|
|
if(is_an_abort)
|
|
sh->callback(SSC_CALLBACK_TX_FRAME_ABORTED, sh->tx_buffer);
|
|
else
|
|
sh->callback(SSC_CALLBACK_TX_FRAME_ENDED, sh->tx_buffer);
|
|
}
|
|
|
|
sh->tx_buffer = NULL;
|
|
}
|
|
|
|
static int __ramfunc _ssc_tx_irq(u_int32_t sr, portBASE_TYPE task_woken)
|
|
{
|
|
ssc_handle_t *sh = &_ssc;
|
|
|
|
if( sr & AT91C_SSC_TXSYN ) {
|
|
/* Tx starting, hardwire TF pin to high */
|
|
AT91F_PIO_SetOutput(AT91C_BASE_PIOA, AT91C_PA15_TF);
|
|
AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, AT91C_PA15_TF);
|
|
usb_print_string_f("<",0);
|
|
|
|
/* Also set SSC mode to continous
|
|
* FIXME BUG: This will somehow add two samples on the SSC
|
|
* The abomination below is designed to find a pause in the outgoing modulation data
|
|
* (WARNING: Will only work with manchester, not with PSK) so that the SSC start type
|
|
* switch will happen during a time of no subcarrier modulation (in order to not
|
|
* upset the subcarrier). Still need to find a way to 'absorb' the extra two samples. */
|
|
if(sh->tx_need_switching) {
|
|
vLedSetGreen(0);
|
|
int j = 0;
|
|
off_pre:
|
|
if(j++ > 100) goto out;
|
|
if(!AT91F_PIO_IsInputSet(AT91C_BASE_PIOA, OPENPICC_MOD_PWM)) {
|
|
goto off_pre;
|
|
}
|
|
on:
|
|
if(j++ > 100) goto out;
|
|
if(AT91F_PIO_IsInputSet(AT91C_BASE_PIOA, OPENPICC_MOD_PWM)) goto on;
|
|
int i=0;
|
|
off:
|
|
if(j++ > 100) goto out;
|
|
if(!AT91F_PIO_IsInputSet(AT91C_BASE_PIOA, OPENPICC_MOD_PWM)) {
|
|
if(i++ > 2) goto out; else goto off;
|
|
} else goto on;
|
|
out:
|
|
sh->ssc->SSC_TCMR = (sh->ssc->SSC_TCMR & ~AT91C_SSC_START) | AT91C_SSC_START_CONTINOUS;
|
|
vLedSetGreen(1);
|
|
}
|
|
|
|
if(sh->callback)
|
|
sh->callback(SSC_CALLBACK_TX_FRAME_BEGIN, NULL);
|
|
}
|
|
|
|
if( sr & AT91C_SSC_TXEMPTY ) {
|
|
/* Tx has ended */
|
|
ssc_metrics[METRIC_TX_FRAMES].value++;
|
|
_ssc_tx_end(sh, 0);
|
|
}
|
|
|
|
return task_woken;
|
|
}
|
|
|
|
static void __ramfunc ssc_irq(void) __attribute__ ((naked));
|
|
static void __ramfunc ssc_irq(void)
|
|
{
|
|
portENTER_SWITCHING_ISR();
|
|
vLedSetRed(1);
|
|
portBASE_TYPE task_woken = pdFALSE;
|
|
|
|
u_int32_t sr = _ssc.ssc->SSC_SR;
|
|
if(sr & AT91C_SSC_RXSYN) {
|
|
task_woken = _ssc_rx_irq(sr, 1, task_woken);
|
|
} else if(sr & SSC_RX_IRQ_MASK) {
|
|
task_woken = _ssc_rx_irq(sr, 0, task_woken);
|
|
}
|
|
|
|
if(sr & SSC_TX_IRQ_MASK) {
|
|
task_woken = _ssc_tx_irq(sr, task_woken);
|
|
}
|
|
|
|
AT91F_AIC_ClearIt(AT91C_ID_SSC);
|
|
AT91F_AIC_AcknowledgeIt();
|
|
|
|
vLedSetRed(0);
|
|
portEXIT_SWITCHING_ISR(task_woken);
|
|
}
|
|
|
|
static __ramfunc ssc_dma_rx_buffer_t *_get_buffer(ssc_dma_buffer_state_t old, ssc_dma_buffer_state_t new)
|
|
{
|
|
ssc_dma_rx_buffer_t *buffer = NULL;
|
|
int i;
|
|
for(i=0; i < SSC_DMA_BUFFER_COUNT; i++) {
|
|
if(_rx_buffers[i].state == old) {
|
|
buffer = &_rx_buffers[i];
|
|
buffer->state = new;
|
|
break;
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/* Doesn't check sh, must be called with interrupts disabled */
|
|
static __ramfunc int _reload_rx(ssc_handle_t *sh)
|
|
{
|
|
int result = 0;
|
|
if(sh->rx_buffer[0] != NULL) {
|
|
ssc_metrics[METRIC_MANAGEMENT_ERRORS_1].value++;
|
|
result = 1;
|
|
goto out;
|
|
}
|
|
|
|
ssc_dma_rx_buffer_t *buffer = _get_buffer(SSC_FREE, SSC_PENDING);
|
|
|
|
if(buffer == NULL) {
|
|
ssc_metrics[METRIC_RX_OVERFLOWS].value++;
|
|
goto out;
|
|
}
|
|
|
|
buffer->reception_mode = &ssc_modes[sh->mode];
|
|
buffer->len_transfers = ssc_modes[sh->mode].transfers;
|
|
|
|
AT91F_PDC_SetRx(sh->pdc, buffer->data, buffer->len_transfers);
|
|
sh->rx_buffer[0] = buffer;
|
|
|
|
result = 1;
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
// Doesn't check sh, call with interrupts disabled, SSC/PDC stopped
|
|
static __ramfunc ssc_dma_rx_buffer_t* _unload_rx(ssc_handle_t *sh)
|
|
{
|
|
if(sh->rx_buffer[0] == NULL) {
|
|
ssc_metrics[METRIC_MANAGEMENT_ERRORS_2].value++;
|
|
return NULL;
|
|
}
|
|
|
|
ssc_dma_rx_buffer_t *buffer = sh->rx_buffer[0];
|
|
u_int32_t rpr = sh->pdc->PDC_RPR,
|
|
rcr = sh->pdc->PDC_RCR;
|
|
AT91F_PDC_SetRx(sh->pdc, 0, 0);
|
|
sh->rx_buffer[0] = NULL;
|
|
buffer->state = SSC_FULL;
|
|
|
|
if(rcr == 0) {
|
|
buffer->flags.overflow = 1;
|
|
} else {
|
|
buffer->flags.overflow = 0;
|
|
}
|
|
|
|
if(rcr > 0) {
|
|
/* Append a zero to make sure the buffer decoder finds the stop bit */
|
|
switch(buffer->reception_mode->transfersize_pdc) {
|
|
case 8:
|
|
//*((u_int8_t*)rpr++) = sh->ssc->SSC_RSHR;
|
|
*((u_int8_t*)rpr++) = 0;
|
|
--rcr;
|
|
break;
|
|
case 16:
|
|
*((u_int16_t*)rpr++) = 0;
|
|
--rcr;
|
|
break;
|
|
case 32:
|
|
*((u_int32_t*)rpr++) = 0;
|
|
--rcr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if((buffer->len_transfers - rcr) != (rpr - (unsigned int)buffer->data)*(buffer->reception_mode->transfersize_pdc/8)) {
|
|
ssc_metrics[METRIC_MANAGEMENT_ERRORS_3].value++;
|
|
buffer->state = SSC_FREE;
|
|
return NULL;
|
|
}
|
|
|
|
buffer->len_transfers = buffer->len_transfers - rcr;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void ssc_set_gate(int data_enabled) {
|
|
if(OPENPICC->features.data_gating) {
|
|
if(data_enabled) {
|
|
AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPICC->DATA_GATE);
|
|
if(DEBUG_DATA_GATING) usb_print_string_f("(", 0);
|
|
} else {
|
|
AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPICC->DATA_GATE);
|
|
if(DEBUG_DATA_GATING) usb_print_string_f(")", 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _ssc_start_rx(ssc_handle_t *sh)
|
|
{
|
|
taskENTER_CRITICAL();
|
|
if(sh != &_ssc) goto out;
|
|
if(sh->rx_running) goto out;
|
|
if(!sh->rx_enabled) goto out;
|
|
|
|
// Load buffer
|
|
if(!_reload_rx(sh))
|
|
goto out;
|
|
|
|
sh->ssc->SSC_IER = AT91C_SSC_RXSYN | \
|
|
AT91C_SSC_CP1 | AT91C_SSC_ENDRX;
|
|
sh->rx_running = 1;
|
|
if(sh->callback != NULL)
|
|
sh->callback(SSC_CALLBACK_RX_STARTING, sh);
|
|
|
|
// Actually enable reception
|
|
int dummy = sh->ssc->SSC_RHR; (void)dummy;
|
|
AT91F_PDC_EnableRx(sh->pdc);
|
|
AT91F_SSC_EnableRx(sh->ssc);
|
|
|
|
out:
|
|
taskEXIT_CRITICAL();
|
|
usb_print_string_f(sh->rx_running ? "SSC now running\n\r":"SSC not running\n\r", 0);
|
|
}
|
|
|
|
static void _ssc_stop_rx(ssc_handle_t *sh)
|
|
{
|
|
taskENTER_CRITICAL();
|
|
sh->ssc->SSC_IDR = SSC_RX_IRQ_MASK;
|
|
sh->rx_running = 0;
|
|
if(sh->callback != NULL)
|
|
sh->callback(SSC_CALLBACK_RX_STOPPED, sh);
|
|
taskEXIT_CRITICAL();
|
|
}
|
|
|
|
/******* PRIVATE Initialization Code *****************************************/
|
|
static void _ssc_rx_mode_set(ssc_handle_t *sh, enum ssc_mode ssc_mode)
|
|
{
|
|
taskENTER_CRITICAL();
|
|
int was_running = sh->rx_running;
|
|
if(was_running) _ssc_stop_rx(sh);
|
|
|
|
u_int8_t data_len=0, num_data=0, sync_len=0;
|
|
u_int32_t start_cond=0;
|
|
u_int32_t clock_gating=0;
|
|
u_int8_t stop = 0, invert=0;
|
|
|
|
switch(ssc_mode) {
|
|
case SSC_MODE_14443A:
|
|
/* Start on Compare 0. The funky calculations down there are designed to allow a different
|
|
* (longer) compare length for Compare 1 than for Compare 0. Both lengths are set by the
|
|
* same register. */
|
|
start_cond = AT91C_SSC_START_0;
|
|
sync_len = ISO14443A_EOF_LEN;
|
|
sh->ssc->SSC_RC0R = ISO14443A_SOF_SAMPLE << (ISO14443A_EOF_LEN-ISO14443A_SOF_LEN);
|
|
sh->ssc->SSC_RC1R = ISO14443A_EOF_SAMPLE;
|
|
|
|
data_len = ssc_modes[SSC_MODE_14443A].transfersize_ssc;
|
|
|
|
/* We are using stop on Compare 1. The docs are ambiguous but my interpretation is that
|
|
* this means that num_data is basically ignored and reception is continuous until stop
|
|
* event. Set num_data to the maximum anyways. */
|
|
num_data = 16;
|
|
stop = 1;
|
|
|
|
stop = 0;
|
|
start_cond = AT91C_SSC_START_CONTINOUS;
|
|
sync_len = 0;
|
|
|
|
/* We can't use receive clock gating with RF because RF will only go up with the first
|
|
* edge, this doesn't cooperate with the longer sync len above.
|
|
* FIXME: What's the interaction between clock BURST on v0.4p1, RF and Compare 0?
|
|
* In theory this shouldn't work even without SSC clock_gating because BURST gates the
|
|
* clock before the SSC and so it shouldn't get sync_len samples before Compare 0.
|
|
* I believe there's a bug waiting to happen somewhere here. */
|
|
clock_gating = (0x0 << 6);
|
|
//invert = AT91C_SSC_CKI;
|
|
break;
|
|
case SSC_MODE_NONE:
|
|
goto out;
|
|
break;
|
|
}
|
|
|
|
/* Receive frame mode register */
|
|
sh->ssc->SSC_RFMR = ((data_len-1) & 0x1f) |
|
|
(((num_data-1) & 0x0f) << 8) |
|
|
(((sync_len-1) & 0x0f) << 16);
|
|
|
|
/* Receive clock mode register, Clock selection: RK, Clock output: None */
|
|
sh->ssc->SSC_RCMR = AT91C_SSC_CKS_RK | AT91C_SSC_CKO_NONE |
|
|
clock_gating | invert | start_cond | (stop << 12);
|
|
|
|
out:
|
|
sh->mode = ssc_mode;
|
|
if(was_running) _ssc_start_rx(sh);
|
|
taskEXIT_CRITICAL();
|
|
}
|
|
|
|
#if SSC_DMA_BUFFER_COUNT > 0
|
|
static inline int _init_ssc_rx(ssc_handle_t *sh)
|
|
{
|
|
tc_cdiv_sync_init();
|
|
tc_cdiv_sync_enable();
|
|
|
|
if(sh->rx_queue == NULL) {
|
|
sh->rx_queue = xQueueCreate(10, sizeof(sh->rx_buffer[0]));
|
|
if(sh->rx_queue == NULL)
|
|
goto out_fail_queue;
|
|
}
|
|
|
|
AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA,
|
|
OPENPICC_SSC_DATA | OPENPICC_SSC_CLOCK |
|
|
OPENPICC_PIO_FRAME,
|
|
0);
|
|
|
|
/* FIXME: This is handled by tc_cdiv_sync and shouldn't be necessary */
|
|
AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC_PIO_SSC_DATA_CONTROL);
|
|
AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPICC_PIO_SSC_DATA_CONTROL);
|
|
|
|
if(OPENPICC->features.clock_switching) {
|
|
AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC->CLOCK_SWITCH);
|
|
clock_switch(CLOCK_SELECT_PLL);
|
|
}
|
|
|
|
/* Disable all interrupts */
|
|
sh->ssc->SSC_IDR = SSC_RX_IRQ_MASK;
|
|
|
|
/* don't divide clock inside SSC, we do that in tc_cdiv */
|
|
sh->ssc->SSC_CMR = 0;
|
|
|
|
unsigned int i;
|
|
for(i=0; i<sizeof(_rx_buffers)/sizeof(_rx_buffers[0]); i++)
|
|
memset(&_rx_buffers[i], 0, sizeof(_rx_buffers[i]));
|
|
|
|
sh->rx_buffer[0] = sh->rx_buffer[1] = NULL;
|
|
|
|
/* Will be set to a real value some time later
|
|
* FIXME Layering? */
|
|
tc_fdt_set(0xff00);
|
|
|
|
return 1;
|
|
|
|
out_fail_queue:
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static inline int _init_ssc_tx(ssc_handle_t *sh)
|
|
{
|
|
/* IMPORTANT: Disable PA23 (PWM0) output, since it is connected to
|
|
* PA17 !! */
|
|
AT91F_PIO_CfgInput(AT91C_BASE_PIOA, OPENPICC_MOD_PWM);
|
|
AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA, OPENPICC_MOD_SSC |
|
|
OPENPICC_SSC_CLOCK | OPENPICC_SSC_TF, 0);
|
|
|
|
if(OPENPICC->features.clock_switching) {
|
|
clock_switch_init();
|
|
/* Users: remember to clock_switch(CLOCK_SELECT_CARRIER) as
|
|
* early as possible, e.g. right after receive end */
|
|
}
|
|
|
|
/* Disable all interrupts */
|
|
sh->ssc->SSC_IDR = SSC_TX_IRQ_MASK;
|
|
|
|
/* don't divide clock inside SSC, we do that in tc_cdiv */
|
|
sh->ssc->SSC_CMR = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _ssc_register_callback(ssc_handle_t *sh, ssc_callback_t _callback)
|
|
{
|
|
if(!sh) return -EINVAL;
|
|
if(sh->callback != NULL) return -EBUSY;
|
|
sh->callback = _callback;
|
|
if(sh->callback != NULL)
|
|
sh->callback(SSC_CALLBACK_SETUP, sh);
|
|
return 0;
|
|
}
|
|
|
|
static int _ssc_unregister_callback(ssc_handle_t *sh, ssc_callback_t _callback)
|
|
{
|
|
if(!sh) return -EINVAL;
|
|
if(_callback == NULL || sh->callback == _callback) {
|
|
if(sh->callback != NULL)
|
|
sh->callback(SSC_CALLBACK_TEARDOWN, sh);
|
|
sh->callback = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/******* PUBLIC API **********************************************************/
|
|
ssc_handle_t* ssc_open(u_int8_t init_rx, u_int8_t init_tx, enum ssc_mode mode, ssc_callback_t callback)
|
|
{
|
|
ssc_handle_t *sh = &_ssc;
|
|
|
|
if(sh->rx_enabled || sh->tx_enabled || sh->rx_running) {
|
|
if( ssc_close(sh) != 0) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if(init_rx || init_tx) {
|
|
sh->ssc = AT91C_BASE_SSC;
|
|
sh->pdc = (AT91PS_PDC) &(sh->ssc->SSC_RPR);
|
|
|
|
AT91F_SSC_CfgPMC();
|
|
|
|
if(OPENPICC->features.data_gating) {
|
|
AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC->DATA_GATE);
|
|
ssc_set_gate(1);
|
|
}
|
|
}
|
|
|
|
|
|
if(init_tx) {
|
|
sh->tx_enabled = _init_ssc_tx(sh);
|
|
if(!sh->tx_enabled) {
|
|
ssc_close(sh);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if(init_rx) {
|
|
#if SSC_DMA_BUFFER_COUNT > 0
|
|
sh->rx_enabled = _init_ssc_rx(sh);
|
|
if(!sh->rx_enabled) {
|
|
ssc_close(sh);
|
|
return NULL;
|
|
}
|
|
#else
|
|
ssc_close(sh);
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
if(sh->rx_enabled || sh->tx_enabled) {
|
|
_ssc_rx_mode_set(sh, mode);
|
|
AT91F_AIC_ConfigureIt(AT91C_ID_SSC,
|
|
OPENPICC_IRQ_PRIO_SSC,
|
|
AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, (THandler)&ssc_irq);
|
|
//AT91C_AIC_SRCTYPE_INT_POSITIVE_EDGE, (THandler)&ssc_irq);
|
|
AT91F_AIC_EnableIt(AT91C_ID_SSC);
|
|
}
|
|
|
|
if(callback != NULL)
|
|
_ssc_register_callback(sh, callback);
|
|
|
|
if(init_rx)
|
|
_ssc_start_rx(sh);
|
|
|
|
return sh;
|
|
}
|
|
|
|
int ssc_recv(ssc_handle_t* sh, ssc_dma_rx_buffer_t* *buffer,unsigned int timeout)
|
|
{
|
|
if(sh == NULL) return -EINVAL;
|
|
if(!sh->rx_enabled) return -EINVAL;
|
|
|
|
taskENTER_CRITICAL();
|
|
if(sh->rx_running) {
|
|
if(PRINT_DEBUG) usb_print_string_f("Continuing SSC Rx\n\r",0); // DEBUG OUTPUT
|
|
} else {
|
|
if(PRINT_DEBUG) usb_print_string_f("ERR: SSC halted\n\r",0); // DEBUG OUTPUT
|
|
/* Try starting the Reception */
|
|
_ssc_start_rx(sh);
|
|
}
|
|
taskEXIT_CRITICAL();
|
|
|
|
if(xQueueReceive(sh->rx_queue, buffer, timeout)){
|
|
if(*buffer != NULL) return 0;
|
|
else return -EINTR;
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
extern u_int32_t fdt_offset;
|
|
/* Must be called with IRQs disabled. E.g. from IRQ context or within a critical section. */
|
|
int ssc_send(ssc_handle_t* sh, ssc_dma_tx_buffer_t *buffer)
|
|
{
|
|
if(sh == NULL) return -EINVAL;
|
|
if(!sh->tx_enabled) return -EINVAL;
|
|
if(sh->tx_running) return -EBUSY;
|
|
|
|
sh->tx_buffer = buffer;
|
|
sh->tx_running = 1;
|
|
|
|
/* disable Tx */
|
|
sh->ssc->SSC_IDR = SSC_TX_IRQ_MASK;
|
|
AT91F_PDC_DisableTx(sh->pdc);
|
|
AT91F_SSC_DisableTx(sh->ssc);
|
|
|
|
int start_cond = AT91C_SSC_START_HIGH_RF;
|
|
|
|
int sync_len = 1;
|
|
int data_len = 32;
|
|
int num_data = buffer->len/(data_len/8); /* FIXME This is broken for more than 64 bytes, or is it? */
|
|
int num_data_ssc = num_data > 16 ? 16 : num_data;
|
|
sh->tx_need_switching = (num_data > 16);
|
|
|
|
sh->ssc->SSC_TFMR = ((data_len-1) & 0x1f) |
|
|
(((num_data_ssc-1) & 0x0f) << 8) |
|
|
(((sync_len-1) & 0x0f) << 16);
|
|
sh->ssc->SSC_TCMR = 0x01 | AT91C_SSC_CKO_NONE | (AT91C_SSC_CKI&0) | start_cond;
|
|
|
|
AT91F_PDC_SetTx(sh->pdc, buffer->data, num_data);
|
|
AT91F_PDC_SetNextTx(sh->pdc, 0, 0);
|
|
buffer->state = SSC_PENDING;
|
|
|
|
sh->ssc->SSC_IER = AT91C_SSC_TXEMPTY | AT91C_SSC_TXSYN;
|
|
/* Enable DMA */
|
|
sh->ssc->SSC_THR = 0;
|
|
AT91F_PDC_EnableTx(sh->pdc);
|
|
|
|
/* Disable Receiver, see comments in _ssc_rx_irq */
|
|
AT91F_SSC_DisableRx(sh->ssc);
|
|
/* Start Transmission */
|
|
AT91F_SSC_EnableTx(sh->ssc);
|
|
vLedSetGreen(1);
|
|
|
|
if(AT91F_PIO_IsInputSet(AT91C_BASE_PIOA, OPENPICC_SSC_TF)) {
|
|
ssc_metrics[METRIC_LATE_TX_FRAMES].value++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ssc_send_abort(ssc_handle_t* sh)
|
|
{
|
|
if(!sh) return -EINVAL;
|
|
if(!sh->tx_enabled) return -EINVAL;
|
|
if(!sh->tx_running) return -EINVAL;
|
|
|
|
ssc_metrics[METRIC_TX_ABORTED_FRAMES].value++;
|
|
_ssc_tx_end(sh, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ssc_close(ssc_handle_t* sh)
|
|
{
|
|
if(sh->rx_running)
|
|
_ssc_stop_rx(sh);
|
|
|
|
if(sh->rx_enabled) {
|
|
// FIXME Implement
|
|
sh->rx_enabled = 0;
|
|
}
|
|
if(sh->tx_enabled) {
|
|
// FIXME Implement
|
|
sh->tx_enabled = 0;
|
|
}
|
|
|
|
_ssc_unregister_callback(sh, NULL);
|
|
return 0;
|
|
}
|
|
|
|
/* Hard reset the SSC to flush all buffers and whatnot. Call with IRQs disabled */
|
|
void ssc_hard_reset(ssc_handle_t *sh)
|
|
{
|
|
if(sh == NULL) return;
|
|
if(!sh->rx_enabled && !sh->tx_enabled) return;
|
|
|
|
u_int32_t
|
|
cmr = sh->ssc->SSC_CMR,
|
|
rcmr = sh->ssc->SSC_RCMR,
|
|
rfmr = sh->ssc->SSC_RFMR,
|
|
tcmr = sh->ssc->SSC_TCMR,
|
|
tfmr = sh->ssc->SSC_TFMR,
|
|
rc0r = sh->ssc->SSC_RC0R,
|
|
rc1r = sh->ssc->SSC_RC1R,
|
|
sr = sh->ssc->SSC_SR,
|
|
imr = sh->ssc->SSC_IMR;
|
|
|
|
sh->ssc->SSC_CR = AT91C_SSC_SWRST;
|
|
|
|
sh->ssc->SSC_CMR = cmr;
|
|
sh->ssc->SSC_RCMR = rcmr;
|
|
sh->ssc->SSC_RFMR = rfmr;
|
|
sh->ssc->SSC_TCMR = tcmr;
|
|
sh->ssc->SSC_TFMR = tfmr;
|
|
sh->ssc->SSC_RC0R = rc0r;
|
|
sh->ssc->SSC_RC1R = rc1r;
|
|
|
|
sh->ssc->SSC_IDR = ~imr;
|
|
sh->ssc->SSC_IER = imr;
|
|
|
|
if(sr & AT91C_SSC_RXEN)
|
|
AT91F_SSC_EnableRx(sh->ssc);
|
|
else
|
|
AT91F_SSC_DisableRx(sh->ssc);
|
|
|
|
if(sr & AT91C_SSC_TXEN)
|
|
AT91F_SSC_EnableTx(sh->ssc);
|
|
else
|
|
AT91F_SSC_DisableTx(sh->ssc);
|
|
}
|
|
|
|
|
|
int ssc_get_metric(ssc_metric metric, char **description, int *value)
|
|
{
|
|
char *_name="Undefined";
|
|
int _value=0;
|
|
int valid=0;
|
|
|
|
if(metric < sizeof(ssc_metrics)/sizeof(ssc_metrics[0])) {
|
|
_name = ssc_metrics[metric].name;
|
|
_value = ssc_metrics[metric].value;
|
|
valid = 1;
|
|
}
|
|
|
|
switch(metric) {
|
|
case METRIC_FREE_RX_BUFFERS:
|
|
_value = 0;
|
|
int i;
|
|
for(i=0; i < SSC_DMA_BUFFER_COUNT; i++)
|
|
if(_rx_buffers[i].state == SSC_FREE) _value++;
|
|
break;
|
|
case METRIC_MANAGEMENT_ERRORS:
|
|
_value = ssc_metrics[METRIC_MANAGEMENT_ERRORS_1].value +
|
|
ssc_metrics[METRIC_MANAGEMENT_ERRORS_2].value +
|
|
ssc_metrics[METRIC_MANAGEMENT_ERRORS_3].value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if(!valid) return 0;
|
|
|
|
if(description != NULL) *description = _name;
|
|
if(value != NULL) *value = _value;
|
|
return 1;
|
|
}
|
|
|