Ubertooth Zero solderpad

ubertooth-zero / firmware / common / ubertooth.c

8.0 KB · raw

/*
 * Copyright 2010, 2011 Michael Ossmann
 *
 * This file is part of Project Ubertooth.
 *
 * 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, 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "ubertooth.h"

/* delay a number of seconds while on internal oscillator (4 MHz) */
void wait(u8 seconds)
{
	u32 i = 400000 * seconds;
	while (--i);
}

/*
 * This should be called very early by every firmware in order to ensure safe
 * operating conditions for the CC2400.
 */
void gpio_init()
{
	/* 
	 * Set all pins for GPIO.  This shouldn't be necessary after a reset, but
	 * we might get called at other times.
	 */
	PINSEL0 = 0;
	PINSEL1 = 0;
	PINSEL2 = 0;
	PINSEL3 = 0;
	PINSEL4 = 0;
	PINSEL7 = 0;
	PINSEL8 = 0;
	PINSEL9 = 0;
	PINSEL10 = 0;

	/* set certain pins as outputs, all others inputs */
#ifdef UBERTOOTH_ZERO
	FIO0DIR = PIN_USRLED;
	FIO1DIR = (PIN_CC3V3 | PIN_RX | PIN_TX | PIN_CSN |
			PIN_SCLK | PIN_MOSI | PIN_CC1V8 | PIN_BTGR);
	FIO2DIR = PIN_SSEL0;
	FIO3DIR = 0;
	FIO4DIR = (PIN_RXLED | PIN_TXLED);
#endif
#ifdef UBERTOOTH_ONE
	FIO0DIR = 0;
	FIO1DIR = (PIN_USRLED | PIN_RXLED | PIN_TXLED | PIN_CC3V3 |
			PIN_RX | PIN_CC1V8 | PIN_BTGR);
	FIO2DIR = (PIN_CSN | PIN_SCLK | PIN_MOSI | PIN_PAEN | PIN_HGM);
	FIO3DIR = 0;
	FIO4DIR = (PIN_TX | PIN_SSEL1);

	/* set P2.9 as USB_CONNECT */
	PINSEL4 = (PINSEL4 & ~(3 << 18)) | (1 << 18);
#endif

	/* set all outputs low */
	FIO0PIN = 0;
	FIO1PIN = 0;
	FIO2PIN = 0;
	FIO3PIN = 0;
	FIO4PIN = 0;
}

/*
 * Every application that uses the main oscillator (including any that use both
 * USB and the CC2400) should start with this.
 */
void ubertooth_init()
{
	gpio_init();
	cc2400_init();
	clock_start();
}

/* configure SSP for CC2400's secondary serial data interface */
void dio_ssp_init()
{
#ifdef UBERTOOTH_ZERO
	/* set P0.15 as SCK0 */
	PINSEL0 = (PINSEL0 & ~(3 << 30)) | (2 << 30);

	/* set P1.16 as SSEL0 */
	PINSEL1 = (PINSEL1 & ~(3 << 0)) | (2 << 0);

	/* set P1.17 as MISO0 */
	PINSEL1 = (PINSEL1 & ~(3 << 2)) | (2 << 2);

	/* set P1.18 as MOSI0 */
	PINSEL1 = (PINSEL1 & ~(3 << 4)) | (2 << 4);
#endif
#ifdef UBERTOOTH_ONE
	/* set P0.7 as SCK1 */
	PINSEL0 = (PINSEL0 & ~(3 << 14)) | (2 << 14);

	/* set P0.6 as SSEL1 */
	PINSEL0 = (PINSEL0 & ~(3 << 12)) | (2 << 12);

	/* set P0.8 as MISO1 */
	PINSEL0 = (PINSEL0 & ~(3 << 16)) | (2 << 16);

	/* set P0.9 as MOSI1 */
	PINSEL0 = (PINSEL0 & ~(3 << 18)) | (2 << 18);
#endif

	/*
	 * DIO_SSEL is a GPIO output connected directly to the SSEL input on the
	 * microcontroller for the CC2400's un-buffered (DIO/DCLK) serial
	 * interface.  Since the CC2400 doesn't have a slave select output, we
	 * control it with this.  DIO_SSEL should already be configured by
	 * gpio_init().  We set it high by default because it is an active low
	 * signal.
	 */
	DIO_SSEL_SET;

	/* configure DIO_SSP */
	DIO_SSP_CR0 = (0x7 /* 8 bit transfer */ | SSPCR0_CPOL | SSPCR0_CPHA);
	DIO_SSP_CR1 = (SSPCR1_MS | SSPCR1_SOD);
}

void atest_init()
{
	/*
	 * ADC can optionally be configured for ATEST1 and ATEST2, but for now we
	 * set them as floating inputs.
	 */

	/* P0.25 is ATEST1, P0.26 is ATEST2 */
	PINSEL1 &= ~((0x3 << 20) | (0x3 << 18)); // set as GPIO
	FIO0DIR &= ~((0x3 << 25)); // set as input
	PINMODE1 |= ((0x5 << 19)); // no pull-up/pull-down
	PINMODE1 &= ~((0x5 << 18)); // no pull-up/pull-down
}

void cc2400_init()
{
	atest_init();

	/* activate 1V8 supply for CC2400 */
	CC1V8_SET;
	wait(1); //FIXME only need to wait 50us

	/* CSN (slave select) is active low */
	CSN_SET;

	/* activate 3V3 supply for CC2400 IO */
	CC3V3_SET;
}

static void spi_delay()
{
	u32 i = 10;
	while (--i);
}

/*
 * This is a single SPI transaction of variable length, usually 8 or 24 bits.
 * The CC2400 also supports longer transactions (e.g. for the FIFO), but we
 * haven't implemented anything longer than 32 bits.
 *
 * We're bit-banging because:
 *
 * 1. We're using one SPI peripheral for the CC2400's unbuffered data
 *    interace.
 * 2. We're saving the second SPI peripheral for an expansion port.
 * 3. The CC2400 needs CSN held low for the entire transaction which the
 *    LPC17xx SPI peripheral won't do without some workaround anyway.
 */
u32 cc2400_spi(u8 len, u32 data)
{
	u32 msb = 1 << (len - 1);

	/* start transaction by dropping CSN */
	CSN_CLR;

	while (len--) {
		if (data & msb)
			MOSI_SET;
		else
			MOSI_CLR;
		data <<= 1;

		spi_delay();

		SCLK_SET;
		if (MISO)
			data |= 1;

		spi_delay();

		SCLK_CLR;
	}

	spi_delay();

	/* end transaction by raising CSN */
	CSN_SET;

	return data;
}

/* read 16 bit value from a register */
u16 cc2400_get(u8 reg)
{
	u32 in;

	u32 out = (reg | 0x80) << 16;
	in = cc2400_spi(24, out);
	return in & 0xFFFF;
}

/* write 16 bit value to a register */
void cc2400_set(u8 reg, u16 val)
{
	u32 out = (reg << 16) | val;
	cc2400_spi(24, out);
}

/* read 8 bit value from a register */
u8 cc2400_get8(u8 reg)
{
	u16 in;

	u16 out = (reg | 0x80) << 8;
	in = cc2400_spi(16, out);
	return in & 0xFF;
}

/* write 8 bit value to a register */
void cc2400_set8(u8 reg, u8 val)
{
	u32 out = (reg << 8) | val;
	cc2400_spi(16, out);
}

/* get the status */
u8 cc2400_status()
{
	return cc2400_spi(8, 0);
}

/* strobe register, return status */
u8 cc2400_strobe(u8 reg)
{
	return cc2400_spi(8, reg);
}

/*
 * Warning: This should only be called when running on the internal oscillator.
 * Otherwise use clock_start().
 */
void cc2400_reset()
{
	cc2400_set(MAIN, 0x0000);
	while (cc2400_get(MAIN) != 0x0000);
	cc2400_set(MAIN, 0x8000);
	while (cc2400_get(MAIN) != 0x8000);
}

/* activate the CC2400's 16 MHz oscillator and sync LPC175x to it */
void clock_start()
{
	/* configure flash accelerator for higher clock rate */
	FLASHCFG = (0x03A | (FLASHTIM << 12));

	/* switch to the internal oscillator if necessary */
	CLKSRCSEL = 0;

	/* disconnect PLL0 */
	PLL0CON &= ~PLL0CON_PLLC0;
	PLL0FEED_SEQUENCE;
	while (PLL0STAT & PLL0STAT_PLLC0_STAT);

	/* turn off PLL0 */
	PLL0CON &= ~PLL0CON_PLLE0;
	PLL0FEED_SEQUENCE;
	while (PLL0STAT & PLL0STAT_PLLE0_STAT);

	/* temporarily set CPU clock divider to 1 */
	CCLKCFG = 0;

	/* configure CC2400 oscillator */
	cc2400_reset();
	cc2400_set(IOCFG, (GIO_ZERO << 9) | (GIO_CLK_16M << 3));
	cc2400_strobe(SXOSCON);
	while (!(cc2400_status() & XOSC16M_STABLE));

	/* activate main oscillator */
	SCS = SCS_OSCEN;
	while (!(SCS & SCS_OSCSTAT));

	/*
	 * errata sheet says we must select peripheral clock before enabling and
	 * connecting PLL0
 	 */
	PCLKSEL0  = (2 << 2); /* TIMER0 at cclk/2 (50 MHz) */
	PCLKSEL1  = 0;

	/* switch to main oscillator */
	CLKSRCSEL = 1;

	/* configure PLL0 */
	PLL0CFG = (MSEL0 << 0) | (NSEL0 << 16);
	PLL0FEED_SEQUENCE;

	/* turn on PLL0 */
	PLL0CON |= PLL0CON_PLLE0;
	PLL0FEED_SEQUENCE;
	while (!(PLL0STAT & PLL0STAT_PLLE0_STAT));

	/* set CPU clock divider */
	CCLKCFG = CCLKSEL;

	/* connect PLL0 */
	PLL0CON |= PLL0CON_PLLC0;
	PLL0FEED_SEQUENCE;
	while (!(PLL0STAT & PLL0STAT_PLLC0_STAT));

	/* configure PLL1 */
	PLL1CFG = (MSEL1 << 0) | (PSEL1 << 5);
	PLL1FEED_SEQUENCE;

	/* turn on PLL1 */
	PLL1CON |= PLL1CON_PLLE1;
	PLL1FEED_SEQUENCE;
	while (!(PLL1STAT & PLL1STAT_PLLE1_STAT));
	while (!(PLL1STAT & PLL1STAT_PLOCK1));

	/* connect PLL1 */
	PLL1CON |= PLL1CON_PLLC1;
	PLL1FEED_SEQUENCE;
	while (!(PLL1STAT & PLL1STAT_PLLC1_STAT));
}


/* reset the LPC17xx, the cc2400 will be handled by the boot code */
void reset()
{
	/* Enable the watchdog with reset enabled */
	USRLED_CLR;
	WDMOD |= WDMOD_WDEN | WDMOD_WDRESET;
	WDFEED_SEQUENCE;
	
	/* Set watchdog timeout to 256us (minimum) */
	
	/* sleep for 1s (minimum) */
	wait(1);
}

//FIXME ssp
//FIXME tx/rx