/* AT91SAM7 SSC controller routines for OpenPICC * (C) 2006 by Harald Welte * (C) 2007-2008 Henryk Plötz * * 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 #include #include #include #include #include #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; irx_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; }