diff options
Diffstat (limited to 'openpicc/application/ssc.c')
-rw-r--r-- | openpicc/application/ssc.c | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/openpicc/application/ssc.c b/openpicc/application/ssc.c new file mode 100644 index 0000000..f15b25d --- /dev/null +++ b/openpicc/application/ssc.c @@ -0,0 +1,599 @@ +/* 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 "tc_cdiv_sync.h" +#include "tc_fdt.h" +#include "led.h" + +#include "usb_print.h" +#include "cmd.h" + +#define PRINT_DEBUG 0 + +// BROKEN Old FIQ code +int ssc_tx_pending=0, ssc_tx_fiq_fdt_cdiv=0, ssc_tx_fiq_fdt_ssc=0; + +struct _ssc_handle { + enum ssc_mode mode; + const struct openpicc_hardware *openpicc; + ssc_dma_rx_buffer_t* rx_buffer[2]; + 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; +}; + +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}, +}; + +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(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(CALLBACK_RX_FRAME_ENDED, buffer); + + if(buffer->state != FREE) { + task_woken = xQueueSendFromISR(sh->rx_queue, &buffer, task_woken); + } + } + + if(_reload_rx(sh)) { + int dummy = sh->ssc->SSC_RHR; (void)dummy; + AT91F_PDC_EnableRx(sh->pdc); + AT91F_SSC_EnableRx(sh->ssc); + } else { + sh->ssc->SSC_IDR = SSC_RX_IRQ_MASK; + sh->rx_running = 0; + sh->callback(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_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); + } + + 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(FREE, 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 = 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 = FREE; + return NULL; + } + + buffer->len_transfers = buffer->len_transfers - rcr; + + return buffer; +} + +void ssc_select_clock(enum ssc_clock_source clock) +{ + if(!OPENPICC->features.clock_switching) return; + switch(clock) { + case CLOCK_SELECT_PLL: + AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPICC->CLOCK_SWITCH); + break; + case CLOCK_SELECT_CARRIER: + AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPICC->CLOCK_SWITCH); + break; + default: break; + } +} + +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(CALLBACK_RX_STARTED, 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(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(); +} + +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; + } + + sh->ssc = AT91C_BASE_SSC; + sh->pdc = (AT91PS_PDC) &(sh->ssc->SSC_RPR); + + AT91F_SSC_CfgPMC(); + + AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA, + OPENPICC_SSC_DATA | OPENPICC_SSC_CLOCK | + OPENPICC_PIO_FRAME, + 0); + + 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.data_gating) { + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC->DATA_GATE); + AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPICC->DATA_GATE); + } + + if(OPENPICC->features.clock_switching) { + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC->CLOCK_SWITCH); + ssc_select_clock(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; + + 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; +} + +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(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(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; + unsigned int i; + + if(sh->rx_enabled || sh->tx_enabled || sh->rx_running) { + if( ssc_close(sh) != 0) { + return NULL; + } + } + + for(i=0; i<sizeof(_rx_buffers)/sizeof(_rx_buffers[0]); i++) + memset(&_rx_buffers[i], 0, sizeof(_rx_buffers[i])); + + if(init_tx) { + // FIXME Implement + sh->tx_enabled = 0; + return NULL; + } + + if(init_rx) { + sh->rx_enabled = _init_ssc_rx(sh); + if(!sh->rx_enabled) { + ssc_close(sh); + return NULL; + } else { + _ssc_rx_mode_set(sh, mode); + } + } + + if(sh->rx_enabled || sh->tx_enabled) { + 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; + + 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; +} + +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; +} + +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 == 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; +} + |