You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
12 KiB
395 lines
12 KiB
5 years ago
|
/*! \page License
|
||
|
* Copyright (C) 2009, H&D Wireless AB All rights reserved.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
* 3. The name of H&D Wireless AB may not be used to endorse or promote products derived
|
||
|
* from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY H&D WIRELESS AB ``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 EXPRESSLY AND
|
||
|
* SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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 <gpio.h>
|
||
|
#include <intc.h>
|
||
|
#include <string.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <wl_spi.h>
|
||
|
#include <printf-stdarg.h>
|
||
|
#include <board_init.h>
|
||
|
|
||
|
#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0])
|
||
|
|
||
|
__attribute__((__interrupt__)) void avr32_irq_handler(void);
|
||
|
void owl_spi_mdelay(uint32_t ms);
|
||
|
|
||
|
int owl_spi_init(U8 *flags)
|
||
|
{
|
||
|
#ifdef _ASSERT_ENABLE_ /* To silence warning if Assert() macro is empty */
|
||
|
volatile avr32_pm_t *pm = &AVR32_PM;
|
||
|
#endif
|
||
|
|
||
|
volatile avr32_spi_t *spi = &WL_SPI;
|
||
|
#if WL_SPI_CS == 1
|
||
|
volatile avr32_spi_csr1_t* CSR = &spi->CSR1;
|
||
|
#elif WL_SPI_CS == 2
|
||
|
volatile avr32_spi_csr2_t* CSR = &spi->CSR2;
|
||
|
#elif WL_SPI_CS == 3
|
||
|
volatile avr32_spi_csr3_t* CSR = &spi->CSR3;
|
||
|
#elif SPI_CS == 0
|
||
|
volatile avr32_spi_csr0_t* CSR = &spi->CSR0;
|
||
|
#endif
|
||
|
|
||
|
#ifndef WITH_NO_DMA
|
||
|
volatile avr32_pdca_channel_t *pdca_tx = &AVR32_PDCA.channel[0];
|
||
|
volatile avr32_pdca_channel_t *pdca_rx = &AVR32_PDCA.channel[1];
|
||
|
#endif
|
||
|
|
||
|
#ifndef WL_IRQ_PIN
|
||
|
*flags = SPI_FLAG_POLL;
|
||
|
#else
|
||
|
*flags = 0;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#ifdef WL_IRQ_PIN
|
||
|
/* input, irq */
|
||
|
gpio_enable_gpio_pin(WL_IRQ_PIN);
|
||
|
gpio_enable_pin_pull_up(WL_IRQ_PIN);
|
||
|
#endif
|
||
|
|
||
|
//#ifdef WL_RESET_PIN
|
||
|
// /* reset pin */
|
||
|
// gpio_enable_gpio_pin(WL_RESET_PIN);
|
||
|
// gpio_set_gpio_pin(WL_RESET_PIN);
|
||
|
//#endif
|
||
|
|
||
|
|
||
|
#ifdef WL_POWER_PIN
|
||
|
/* power off the device */
|
||
|
gpio_enable_gpio_pin(WL_POWER_PIN);
|
||
|
gpio_set_gpio_pin(WL_POWER_PIN);
|
||
|
#endif
|
||
|
|
||
|
#ifdef WL_SHUTDOWN_PIN
|
||
|
gpio_enable_gpio_pin(WL_SHUTDOWN_PIN);
|
||
|
|
||
|
#ifdef WL_NO_INTERNAL_RESET /* never defined for SPB104/SPB105 */
|
||
|
gpio_clr_gpio_pin(WL_SHUTDOWN_PIN);
|
||
|
#endif
|
||
|
|
||
|
#ifdef WL_EXTERNAL_RESET
|
||
|
gpio_enable_gpio_pin(WL_RESET_PIN);
|
||
|
#endif
|
||
|
|
||
|
#endif /* WL_SHUTDOWN_PIN */
|
||
|
|
||
|
#ifdef WL_POWER_PIN
|
||
|
/* power on the device */
|
||
|
gpio_clr_gpio_pin(WL_POWER_PIN);
|
||
|
#endif
|
||
|
|
||
|
#ifdef WL_SHUTDOWN_PIN
|
||
|
|
||
|
#ifdef WL_NO_INTERNAL_RESET /* never defined for SPB104/SPB105 */
|
||
|
owl_spi_mdelay(5);
|
||
|
gpio_set_gpio_pin(WL_SHUTDOWN_PIN);
|
||
|
|
||
|
#elif WL_EXTERNAL_RESET
|
||
|
owl_spi_mdelay(5);
|
||
|
gpio_set_gpio_pin(WL_SHUTDOWN_PIN);
|
||
|
|
||
|
owl_spi_mdelay(20);
|
||
|
//delay_ms(10); //2ms
|
||
|
|
||
|
/* reset pin */
|
||
|
gpio_set_gpio_pin(WL_RESET_PIN);
|
||
|
|
||
|
#else
|
||
|
|
||
|
/* The shutdown pin will go high once the device is powered */
|
||
|
{
|
||
|
#define SHUTDOWN_TIMEOUT 350
|
||
|
uint32_t shutdown_timer = 0;
|
||
|
while (gpio_get_pin_value(WL_SHUTDOWN_PIN) == 0) {
|
||
|
if (shutdown_timer > SHUTDOWN_TIMEOUT)
|
||
|
{
|
||
|
printk("Timeout WL Shutdown\n");
|
||
|
return -1;
|
||
|
}
|
||
|
owl_spi_mdelay(5);
|
||
|
shutdown_timer += 5;
|
||
|
}
|
||
|
}
|
||
|
#endif /* WL_NO_INTERNAL_RESET */
|
||
|
|
||
|
#else
|
||
|
/* We need to make a guess about the time needed to power the device,
|
||
|
* this will depend on the hardware design.
|
||
|
*/
|
||
|
owl_spi_mdelay(5);
|
||
|
#endif /* WL_SHUTDOWN_PIN */
|
||
|
|
||
|
/* Note: SPI0 clock enabled at reset in pm->pbamask (see 13.6.3) */
|
||
|
Assert(pm->pbamask & (1 << 5));
|
||
|
|
||
|
/* Note: GPIO clock enabled at reset in pm->pbamask (see 13.6.3) */
|
||
|
Assert(pm->pbamask & (1 << 1));
|
||
|
#ifdef WL_IRQ_PIN
|
||
|
/* 22.4.7: "In every port there are four interrupt lines
|
||
|
* connected to the interrupt controller. Every eigth
|
||
|
* interrupts in the port are ored together to form an
|
||
|
* interrupt line."
|
||
|
*
|
||
|
* WL_IRQ_# = (WL_IRQ_PIN / 32) * 4 + (WL_IRQ_PIN / 8) % 4
|
||
|
* 62 => 1 * 4 + 3 = 7
|
||
|
*/
|
||
|
INTC_register_interrupt(&avr32_irq_handler, WL_IRQ, AVR32_INTC_INT0);
|
||
|
#endif
|
||
|
|
||
|
#ifndef WITH_NO_DMA
|
||
|
INTC_register_interrupt(&avr32_irq_handler, AVR32_PDCA_IRQ_0,
|
||
|
AVR32_INTC_INT0);
|
||
|
INTC_register_interrupt(&avr32_irq_handler, AVR32_PDCA_IRQ_1,
|
||
|
AVR32_INTC_INT0);
|
||
|
pdca_tx->IER.terr = 1;
|
||
|
pdca_rx->IER.terr = 1;
|
||
|
#endif
|
||
|
|
||
|
#ifdef WL_SPI_CLOCK_DIVIDER
|
||
|
CSR->scbr = WL_SPI_CLOCK_DIVIDER;
|
||
|
#else
|
||
|
CSR->scbr = 2;
|
||
|
#endif
|
||
|
|
||
|
/* Use max width of TDR register, 16 bit transfers */
|
||
|
CSR->bits = 0x8;
|
||
|
|
||
|
/* Make sure that we can hold CS low until transfer is completed, e.g
|
||
|
* LASTXFER is set in TDR.
|
||
|
*/
|
||
|
CSR->csaat = 1;
|
||
|
|
||
|
/* NRG component requires clock polarity high */
|
||
|
CSR->cpol = 1;
|
||
|
|
||
|
|
||
|
#ifdef WL_IRQ_PIN
|
||
|
/* make sure to clear any pending bits in ifr here. */
|
||
|
gpio_clear_pin_interrupt_flag(WL_IRQ_PIN);
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifndef WITH_NO_DMA
|
||
|
static void dma_txrx(const U8* in, U8* out, U16 len)
|
||
|
{
|
||
|
volatile avr32_pdca_channel_t *pdca_tx = &AVR32_PDCA.channel[0];
|
||
|
volatile avr32_pdca_channel_t *pdca_rx = &AVR32_PDCA.channel[1];
|
||
|
|
||
|
/* setup tx */
|
||
|
pdca_tx->mar = (U32) in;
|
||
|
pdca_tx->PSR.pid = WL_PDCA_PID_TX;
|
||
|
pdca_tx->tcr = len / 2;
|
||
|
pdca_tx->MR.size = 1; /* 2-byte */
|
||
|
pdca_tx->IER.trc = 1;
|
||
|
|
||
|
/* setup rx */
|
||
|
pdca_rx->mar = (U32) out;
|
||
|
pdca_rx->PSR.pid = WL_PDCA_PID_RX;
|
||
|
pdca_rx->tcr = len / 2;
|
||
|
pdca_rx->MR.size = 1; /* 2-byte */
|
||
|
pdca_rx->IER.trc = 1;
|
||
|
|
||
|
/* start dma's. for some reason rx must be started prior to tx */
|
||
|
pdca_rx->CR.ten = 1;
|
||
|
pdca_tx->CR.ten = 1;
|
||
|
|
||
|
/* blocking wait until transfer is completed */
|
||
|
while (!(pdca_tx->ISR.trc && pdca_rx->ISR.trc));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* access data using byte pointers since we might get unaligned
|
||
|
* data from lwip. The cpu will issue a data abort if we try
|
||
|
* to access data which is not properly aligned. See data sheet.
|
||
|
*
|
||
|
* Note that fifo_txrx() doesn't handle the case where len is not a
|
||
|
* multiple of two bytes properly.
|
||
|
*
|
||
|
* However, there is no actual case where len is odd at the same time
|
||
|
* as the "out" pointer is non-NULL; therefore I think that in practice,
|
||
|
* we'll not write beyond the end of the "out" array.
|
||
|
*
|
||
|
* The extra unknown byte fetched from the in pointer will be discarded
|
||
|
* by the device since a length field included in the packet header will inform
|
||
|
* the device of the actual number of valid bytes (this implementation is
|
||
|
* kind of hidden inside the library).
|
||
|
*/
|
||
|
static void fifo_txrx(const U8 *in, U8* out, U16 len)
|
||
|
{
|
||
|
volatile avr32_spi_t *spi = &WL_SPI;
|
||
|
UnionCPtr in_ptr;
|
||
|
UnionPtr out_ptr;
|
||
|
U32 sr;
|
||
|
|
||
|
Assert(len);
|
||
|
|
||
|
in_ptr.u8ptr = in;
|
||
|
out_ptr.u8ptr = out;
|
||
|
|
||
|
while (len) {
|
||
|
U16 rdr;
|
||
|
union {
|
||
|
avr32_spi_tdr_t TDR;
|
||
|
U32 tdr;
|
||
|
} reg = { { 0 } };
|
||
|
|
||
|
while (!spi->SR.tdre);
|
||
|
while (!spi->SR.txempty);
|
||
|
|
||
|
/* prepare tx data register contents */
|
||
|
if (in_ptr.u8ptr) {
|
||
|
reg.TDR.td |= (in_ptr.u8ptr[0] << 8) | in_ptr.u8ptr[1];
|
||
|
in_ptr.u16ptr++;
|
||
|
}
|
||
|
else
|
||
|
reg.TDR.td |= 0xffff;
|
||
|
|
||
|
/* perform tx */
|
||
|
spi->tdr = reg.tdr;
|
||
|
|
||
|
/* wait until rx is ready */
|
||
|
while (!spi->SR.rdrf);
|
||
|
|
||
|
/* fetch rx data */
|
||
|
rdr = spi->RDR.rd;
|
||
|
if (out_ptr.u8ptr) {
|
||
|
out_ptr.u8ptr[0] = (rdr >> 8) & 0xff;
|
||
|
out_ptr.u8ptr[1] = rdr & 0xff;
|
||
|
out_ptr.u16ptr++;
|
||
|
}
|
||
|
|
||
|
if (len >= 2)
|
||
|
len -= 2;
|
||
|
else
|
||
|
len = 0;
|
||
|
}
|
||
|
|
||
|
sr = spi->sr;
|
||
|
Assert(!(sr & AVR32_SPI_SR_OVRES_MASK));
|
||
|
Assert(!(sr & AVR32_SPI_SR_MODF_MASK));
|
||
|
}
|
||
|
|
||
|
void owl_spi_txrx(const U8 *in, U8* out, U16 len)
|
||
|
{
|
||
|
#ifndef WITH_NO_DMA
|
||
|
static uint8_t buf[MAX_BLOCK_LEN];
|
||
|
|
||
|
/* unaligned data or odd number of bytes, then skip dma */
|
||
|
if ((U32) in % 4 || (U32) out % 4 || len % 2) {
|
||
|
fifo_txrx(in, out, len);
|
||
|
} else {
|
||
|
if (in == NULL) {
|
||
|
memset(buf, 0xff, len);
|
||
|
in = buf;
|
||
|
} else if (out == NULL) {
|
||
|
out = buf;
|
||
|
}
|
||
|
dma_txrx(in, out, len);
|
||
|
}
|
||
|
#else
|
||
|
fifo_txrx(in, out, len);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void owl_spi_irq(U8 enable)
|
||
|
{
|
||
|
#ifdef WL_IRQ_PIN
|
||
|
|
||
|
if (enable)
|
||
|
gpio_enable_pin_interrupt(WL_IRQ_PIN, GPIO_PIN_CHANGE);
|
||
|
else
|
||
|
gpio_disable_pin_interrupt(WL_IRQ_PIN);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void owl_spi_cs(U8 enable)
|
||
|
{
|
||
|
volatile avr32_spi_t *spi = &WL_SPI;
|
||
|
|
||
|
/*
|
||
|
* PCS = xxx0 => NPCS[3:0] = 1110
|
||
|
* PCS = xx01 => NPCS[3:0] = 1101
|
||
|
* PCS = x011 => NPCS[3:0] = 1011
|
||
|
* PCS = 0111 => NPCS[3:0] = 0111
|
||
|
* PCS = 1111 => forbidden (no peripheral is selected)
|
||
|
*/
|
||
|
|
||
|
if (enable)
|
||
|
#if WL_SPI_CS == 2
|
||
|
spi->MR.pcs = 0x3; /* cs2 */
|
||
|
#elif WL_SPI_CS == 1
|
||
|
spi->MR.pcs = 0x1; /* cs1 */
|
||
|
#elif WL_SPI_CS == 3
|
||
|
spi->MR.pcs = 0x7; /* cs3 */
|
||
|
#elif WL_SPI_CS == 0
|
||
|
spi->MR.pcs = 0x0; /* cs0 */
|
||
|
#endif
|
||
|
else
|
||
|
spi->MR.pcs = 0xf;
|
||
|
}
|
||
|
|
||
|
void owl_spi_mdelay(uint32_t ms)
|
||
|
{
|
||
|
volatile int a = 0;
|
||
|
int i;
|
||
|
for (i = 0; i < ms * 5000; i++)
|
||
|
a++;
|
||
|
}
|
||
|
|
||
|
__attribute__((__interrupt__)) void avr32_irq_handler(void)
|
||
|
{
|
||
|
#ifndef WITH_NO_DMA
|
||
|
volatile avr32_pdca_channel_t *pdca_tx = &AVR32_PDCA.channel[0];
|
||
|
volatile avr32_pdca_channel_t *pdca_rx = &AVR32_PDCA.channel[1];
|
||
|
|
||
|
/* tx xfer complete */
|
||
|
if (pdca_tx->IMR.trc && pdca_tx->ISR.trc) {
|
||
|
pdca_tx->IDR.trc = 1;
|
||
|
pdca_tx->CR.tdis = 1; /* disable tx xfer */
|
||
|
}
|
||
|
|
||
|
/* rx xfer complete */
|
||
|
if (pdca_rx->IMR.trc && pdca_rx->ISR.trc) {
|
||
|
pdca_rx->IDR.trc = 1;
|
||
|
pdca_rx->CR.tdis = 1; /* disable rx xfer */
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifdef WL_IRQ_PIN
|
||
|
if (gpio_get_pin_interrupt_flag(WL_IRQ_PIN)) {
|
||
|
gpio_clear_pin_interrupt_flag(WL_IRQ_PIN);
|
||
|
wl_spi_irq();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
}
|