diff options
Diffstat (limited to 'src/atsam')
-rw-r--r-- | src/atsam/Kconfig | 53 | ||||
-rw-r--r-- | src/atsam/Makefile | 58 | ||||
-rw-r--r-- | src/atsam/adc.c | 96 | ||||
-rw-r--r-- | src/atsam/gpio.c | 142 | ||||
-rw-r--r-- | src/atsam/gpio.h | 51 | ||||
-rw-r--r-- | src/atsam/i2c.c | 175 | ||||
-rw-r--r-- | src/atsam/internal.h | 24 | ||||
-rw-r--r-- | src/atsam/main.c | 77 | ||||
-rw-r--r-- | src/atsam/sam4_cache.c | 16 | ||||
-rw-r--r-- | src/atsam/sam4_usb.c | 238 | ||||
-rw-r--r-- | src/atsam/sam4e_afec.c | 203 | ||||
-rw-r--r-- | src/atsam/sam4s_sysinit.c | 65 | ||||
-rw-r--r-- | src/atsam/sam4s_timer.c | 118 | ||||
-rw-r--r-- | src/atsam/serial.c | 77 | ||||
-rw-r--r-- | src/atsam/spi.c | 285 | ||||
-rw-r--r-- | src/atsam/timer.c | 65 | ||||
-rw-r--r-- | src/atsam/usb_cdc_ep.h | 10 |
17 files changed, 1753 insertions, 0 deletions
diff --git a/src/atsam/Kconfig b/src/atsam/Kconfig new file mode 100644 index 00000000..0ea82241 --- /dev/null +++ b/src/atsam/Kconfig @@ -0,0 +1,53 @@ +# Kconfig settings for Atmel SAM processors + +if MACH_ATSAM + +config ATSAM_SELECT + bool + default y + select HAVE_GPIO + select HAVE_GPIO_ADC + select HAVE_GPIO_I2C + select HAVE_GPIO_SPI + select HAVE_GPIO_BITBANGING + +config BOARD_DIRECTORY + string + default "atsam" + +choice + prompt "Processor model" + config MACH_SAM3X8E + bool "SAM3x8e (Arduino Due)" + config MACH_SAM4S8C + bool "SAM4s8c (Duet Maestro)" + config MACH_SAM4E8E + bool "SAM4e8e (Duet Wifi/Eth)" +endchoice + +config MCU + string + default "sam3x8e" if MACH_SAM3X8E + default "sam4s8c" if MACH_SAM4S8C + default "sam4e8e" if MACH_SAM4E8E + +config CLOCK_FREQ + int + default 42000000 if MACH_SAM3X8E # 84000000/2 + default 15000000 if MACH_SAM4S8C # 120000000/8 + default 60000000 if MACH_SAM4E8E # 120000000/2 + +config USBSERIAL + depends on MACH_SAM4S8C || MACH_SAM4E8E + bool "Use USB for communication (instead of serial)" + default y +config SERIAL + depends on !USBSERIAL + bool + default y +config SERIAL_BAUD + depends on SERIAL + int "Baud rate for serial port" + default 250000 + +endif diff --git a/src/atsam/Makefile b/src/atsam/Makefile new file mode 100644 index 00000000..4aece952 --- /dev/null +++ b/src/atsam/Makefile @@ -0,0 +1,58 @@ +# Additional ATSAM build rules + +# Setup the toolchain +CROSS_PREFIX=arm-none-eabi- + +dirs-y += src/atsam src/generic +dirs-$(CONFIG_MACH_SAM3X8E) += lib/sam3x/gcc/gcc +dirs-$(CONFIG_MACH_SAM4S8C) += lib/sam4s/gcc/gcc +dirs-$(CONFIG_MACH_SAM4E8E) += lib/sam4e/gcc/gcc + +CFLAGS-$(CONFIG_MACH_SAM3X8E) += -mcpu=cortex-m3 -falign-loops=16 +CFLAGS-$(CONFIG_MACH_SAM3X8E) += -Ilib/sam3x/include -D__SAM3X8E__ +CFLAGS-$(CONFIG_MACH_SAM4S8C) += -mcpu=cortex-m4 +CFLAGS-$(CONFIG_MACH_SAM4S8C) += -Ilib/sam4s/include -D__SAM4S8C__ +CFLAGS-$(CONFIG_MACH_SAM4E8E) += -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard +CFLAGS-$(CONFIG_MACH_SAM4E8E) += -Ilib/sam4e/include -D__SAM4E8E__ +CFLAGS += -mthumb $(CFLAGS-y) -Ilib/cmsis-core + +eflags-$(CONFIG_MACH_SAM3X8E) += -Llib/sam3x/gcc/gcc +eflags-$(CONFIG_MACH_SAM3X8E) += -T lib/sam3x/gcc/gcc/sam3x8e_flash.ld +eflags-$(CONFIG_MACH_SAM4S8C) += -Llib/sam4s/gcc/gcc +eflags-$(CONFIG_MACH_SAM4S8C) += -T lib/sam4s/gcc/gcc/sam4s8c_flash.ld +eflags-$(CONFIG_MACH_SAM4E8E) += -Llib/sam4e/gcc/gcc +eflags-$(CONFIG_MACH_SAM4E8E) += -T lib/sam4e/gcc/gcc/sam4e8e_flash.ld +CFLAGS_klipper.elf += $(eflags-y) --specs=nano.specs --specs=nosys.specs + +# Add source files +src-y += atsam/main.c atsam/gpio.c atsam/i2c.c atsam/spi.c +src-y += generic/crc16_ccitt.c generic/alloc.c +src-y += generic/armcm_irq.c generic/timer_irq.c +src-$(CONFIG_USBSERIAL) += atsam/sam4_usb.c generic/usb_cdc.c +src-$(CONFIG_SERIAL) += atsam/serial.c generic/serial_irq.c +src-$(CONFIG_MACH_SAM3X8E) += atsam/adc.c atsam/timer.c +src-$(CONFIG_MACH_SAM3X8E) += ../lib/sam3x/gcc/system_sam3xa.c +src-$(CONFIG_MACH_SAM3X8E) += ../lib/sam3x/gcc/gcc/startup_sam3xa.c +src-$(CONFIG_MACH_SAM4S8C) += atsam/adc.c atsam/sam4s_timer.c atsam/sam4s_sysinit.c +src-$(CONFIG_MACH_SAM4S8C) += ../lib/sam4s/gcc/gcc/startup_sam4s.c +src-$(CONFIG_MACH_SAM4E8E) += atsam/sam4e_afec.c atsam/timer.c atsam/sam4_cache.c +src-$(CONFIG_MACH_SAM4E8E) += ../lib/sam4e/gcc/system_sam4e.c +src-$(CONFIG_MACH_SAM4E8E) += ../lib/sam4e/gcc/gcc/startup_sam4e.c + +# Build the additional hex output file +target-y += $(OUT)klipper.bin + +$(OUT)klipper.bin: $(OUT)klipper.elf + @echo " Creating bin file $@" + $(Q)$(OBJCOPY) -O binary $< $@ + +# Flash rules +lib/bossac/bin/bossac: + @echo " Building bossac" + $(Q)make -C lib/bossac bin/bossac + +flash: $(OUT)klipper.bin lib/bossac/bin/bossac + @echo " Flashing $^ to $(FLASH_DEVICE) via bossac" + $(Q)if [ -z $(FLASH_DEVICE) ]; then echo "Please specify FLASH_DEVICE"; exit 1; fi + $(Q)lib/bossac/bin/bossac -U -p "$(FLASH_DEVICE)" -a -e -w $(OUT)klipper.bin -v -b + $(Q)lib/bossac/bin/bossac -p "$(FLASH_DEVICE)" -b -R > /dev/null 2>&1 || true diff --git a/src/atsam/adc.c b/src/atsam/adc.c new file mode 100644 index 00000000..425d60a9 --- /dev/null +++ b/src/atsam/adc.c @@ -0,0 +1,96 @@ +// Analog to digital support +// +// Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "autoconf.h" // CONFIG_CLOCK_FREQ +#include "board/irq.h" // irq_save +#include "command.h" // shutdown +#include "compiler.h" // ARRAY_SIZE +#include "gpio.h" // gpio_adc_setup +#include "internal.h" // GPIO +#include "sched.h" // sched_shutdown + +static const uint8_t adc_pins[] = { +#if CONFIG_MACH_SAM3X8E + GPIO('A', 2), GPIO('A', 3), GPIO('A', 4), GPIO('A', 6), + GPIO('A', 22), GPIO('A', 23), GPIO('A', 24), GPIO('A', 16), + GPIO('B', 12), GPIO('B', 13), GPIO('B', 17), GPIO('B', 18), + GPIO('B', 19), GPIO('B', 20) +#elif CONFIG_MACH_SAM4S8C + GPIO('A', 17), GPIO('A', 18), GPIO('A', 19), GPIO('A', 20), + GPIO('B', 0), GPIO('B', 1), GPIO('B', 2), GPIO('B', 3), + GPIO('A', 21), GPIO('A', 22), GPIO('C', 13), GPIO('C', 15), + GPIO('C', 12), GPIO('C', 29), GPIO('C', 30) +#endif +}; + +#define ADC_FREQ_MAX 20000000 +DECL_CONSTANT(ADC_MAX, 4095); + +struct gpio_adc +gpio_adc_setup(uint8_t pin) +{ + // Find pin in adc_pins table + int chan; + for (chan=0; ; chan++) { + if (chan >= ARRAY_SIZE(adc_pins)) + shutdown("Not a valid ADC pin"); + if (adc_pins[chan] == pin) + break; + } + + if (!is_enabled_pclock(ID_ADC)) { + // Setup ADC + enable_pclock(ID_ADC); + uint32_t prescal = SystemCoreClock / (2 * ADC_FREQ_MAX) - 1; + ADC->ADC_MR = (ADC_MR_PRESCAL(prescal) + | ADC_MR_STARTUP_SUT768 + | ADC_MR_TRANSFER(1)); + } + return (struct gpio_adc){ .chan = 1 << chan }; +} + +// Try to sample a value. Returns zero if sample ready, otherwise +// returns the number of clock ticks the caller should wait before +// retrying this function. +uint32_t +gpio_adc_sample(struct gpio_adc g) +{ + uint32_t chsr = ADC->ADC_CHSR & 0xffff; + if (!chsr) { + // Start sample + ADC->ADC_CHER = g.chan; + ADC->ADC_CR = ADC_CR_START; + goto need_delay; + } + if (chsr != g.chan) + // Sampling in progress on another channel + goto need_delay; + if (!(ADC->ADC_ISR & ADC_ISR_DRDY)) + // Conversion still in progress + goto need_delay; + // Conversion ready + return 0; +need_delay: + return ADC_FREQ_MAX * 1000ULL / CONFIG_CLOCK_FREQ; +} + +// Read a value; use only after gpio_adc_sample() returns zero +uint16_t +gpio_adc_read(struct gpio_adc g) +{ + ADC->ADC_CHDR = g.chan; + return ADC->ADC_LCDR; +} + +// Cancel a sample that may have been started with gpio_adc_sample() +void +gpio_adc_cancel_sample(struct gpio_adc g) +{ + irqstatus_t flag = irq_save(); + if ((ADC->ADC_CHSR & 0xffff) == g.chan) + gpio_adc_read(g); + irq_restore(flag); +} diff --git a/src/atsam/gpio.c b/src/atsam/gpio.c new file mode 100644 index 00000000..7e6a1dab --- /dev/null +++ b/src/atsam/gpio.c @@ -0,0 +1,142 @@ +// GPIO functions on sam3/sam4 +// +// Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/irq.h" // irq_save +#include "command.h" // shutdown +#include "compiler.h" // ARRAY_SIZE +#include "gpio.h" // gpio_out_setup +#include "internal.h" // gpio_peripheral +#include "sched.h" // sched_shutdown + +static Pio * const digital_regs[] = { +#if CONFIG_MACH_SAM3X8E + PIOA, PIOB, PIOC, PIOD +#elif CONFIG_MACH_SAM4S8C + PIOA, PIOB, PIOC +#elif CONFIG_MACH_SAM4E8E + PIOA, PIOB, PIOC, PIOD, PIOE +#endif +}; + + +/**************************************************************** + * Pin multiplexing + ****************************************************************/ + +void +gpio_peripheral(uint32_t gpio, char ptype, int32_t pull_up) +{ + uint32_t bank = GPIO2PORT(gpio), bit = GPIO2BIT(gpio), pt = ptype - 'A'; + Pio *regs = digital_regs[bank]; + +#if CONFIG_MACH_SAM3X8E + regs->PIO_ABSR = (regs->PIO_ABSR & ~bit) | (pt & 0x01 ? bit : 0); +#else + regs->PIO_ABCDSR[0] = (regs->PIO_ABCDSR[0] & ~bit) | (pt & 0x01 ? bit : 0); + regs->PIO_ABCDSR[1] = (regs->PIO_ABCDSR[1] & ~bit) | (pt & 0x02 ? bit : 0); +#endif + + if (pull_up > 0) + regs->PIO_PUER = bit; + else + regs->PIO_PUDR = bit; + regs->PIO_PDR = bit; +} + + +/**************************************************************** + * General Purpose Input Output (GPIO) pins + ****************************************************************/ + +struct gpio_out +gpio_out_setup(uint8_t pin, uint8_t val) +{ + if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs)) + goto fail; + Pio *regs = digital_regs[GPIO2PORT(pin)]; + struct gpio_out g = { .regs=regs, .bit=GPIO2BIT(pin) }; + gpio_out_reset(g, val); + return g; +fail: + shutdown("Not an output pin"); +} + +void +gpio_out_reset(struct gpio_out g, uint8_t val) +{ + Pio *regs = g.regs; + irqstatus_t flag = irq_save(); + if (val) + regs->PIO_SODR = g.bit; + else + regs->PIO_CODR = g.bit; + regs->PIO_OER = g.bit; + regs->PIO_OWER = g.bit; + regs->PIO_PER = g.bit; + regs->PIO_PUDR = g.bit; + irq_restore(flag); +} + +void +gpio_out_toggle_noirq(struct gpio_out g) +{ + Pio *regs = g.regs; + regs->PIO_ODSR ^= g.bit; +} + +void +gpio_out_toggle(struct gpio_out g) +{ + irqstatus_t flag = irq_save(); + gpio_out_toggle_noirq(g); + irq_restore(flag); +} + +void +gpio_out_write(struct gpio_out g, uint8_t val) +{ + Pio *regs = g.regs; + if (val) + regs->PIO_SODR = g.bit; + else + regs->PIO_CODR = g.bit; +} + + +struct gpio_in +gpio_in_setup(uint8_t pin, int8_t pull_up) +{ + if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs)) + goto fail; + uint32_t port = GPIO2PORT(pin); + enable_pclock(ID_PIOA + port); + struct gpio_in g = { .regs=digital_regs[port], .bit=GPIO2BIT(pin) }; + gpio_in_reset(g, pull_up); + return g; +fail: + shutdown("Not an input pin"); +} + +void +gpio_in_reset(struct gpio_in g, int8_t pull_up) +{ + Pio *regs = g.regs; + irqstatus_t flag = irq_save(); + if (pull_up) + regs->PIO_PUER = g.bit; + else + regs->PIO_PUDR = g.bit; + regs->PIO_ODR = g.bit; + regs->PIO_PER = g.bit; + irq_restore(flag); +} + +uint8_t +gpio_in_read(struct gpio_in g) +{ + Pio *regs = g.regs; + return !!(regs->PIO_PDSR & g.bit); +} diff --git a/src/atsam/gpio.h b/src/atsam/gpio.h new file mode 100644 index 00000000..e106e094 --- /dev/null +++ b/src/atsam/gpio.h @@ -0,0 +1,51 @@ +#ifndef __SAM3_GPIO_H +#define __SAM3_GPIO_H + +#include <stdint.h> // uint32_t + +struct gpio_out { + void *regs; + uint32_t bit; +}; +struct gpio_out gpio_out_setup(uint8_t pin, uint8_t val); +void gpio_out_reset(struct gpio_out g, uint8_t val); +void gpio_out_toggle_noirq(struct gpio_out g); +void gpio_out_toggle(struct gpio_out g); +void gpio_out_write(struct gpio_out g, uint8_t val); + +struct gpio_in { + void *regs; + uint32_t bit; +}; +struct gpio_in gpio_in_setup(uint8_t pin, int8_t pull_up); +void gpio_in_reset(struct gpio_in g, int8_t pull_up); +uint8_t gpio_in_read(struct gpio_in g); + +struct gpio_adc { + uint32_t chan; +}; +struct gpio_adc gpio_adc_setup(uint8_t pin); +uint32_t gpio_adc_sample(struct gpio_adc g); +uint16_t gpio_adc_read(struct gpio_adc g); +void gpio_adc_cancel_sample(struct gpio_adc g); + +struct spi_config { + void *spidev; + uint32_t cfg; +}; +struct spi_config spi_setup(uint32_t bus, uint8_t mode, uint32_t rate); +void spi_prepare(struct spi_config config); +void spi_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data); + +struct i2c_config { + void *twi; + uint8_t addr; +}; + +struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); +void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); +void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg + , uint8_t read_len, uint8_t *read); + +#endif // gpio.h diff --git a/src/atsam/i2c.c b/src/atsam/i2c.c new file mode 100644 index 00000000..910471fd --- /dev/null +++ b/src/atsam/i2c.c @@ -0,0 +1,175 @@ +// SAM4 I2C Port +// +// Copyright (C) 2018 Florian Heilmann <Florian.Heilmann@gmx.net> +// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/misc.h" // timer_from_us +#include "command.h" // shutdown +#include "gpio.h" // i2c_setup +#include "internal.h" // gpio_peripheral +#include "sched.h" // sched_shutdown + +// I2C pin definitions +#if CONFIG_MACH_SAM3X8E +#define TWI0_SCL_GPIO GPIO('A', 18) +#define TWI0_SDA_GPIO GPIO('A', 17) +#define TWI1_SCL_GPIO GPIO('B', 13) +#define TWI1_SDA_GPIO GPIO('B', 12) +#elif CONFIG_MACH_SAM4S8C || CONFIG_MACH_SAM4E8E +#define TWI0_SCL_GPIO GPIO('A', 4) +#define TWI0_SDA_GPIO GPIO('A', 3) +#define TWI1_SCL_GPIO GPIO('B', 5) +#define TWI1_SDA_GPIO GPIO('B', 4) +#endif + +static void +i2c_init(Twi *p_twi, uint32_t rate) +{ + enable_pclock(p_twi == TWI0 ? ID_TWI0 : ID_TWI1); + if (p_twi == TWI0) { + gpio_peripheral(TWI0_SCL_GPIO, 'A', 0); + gpio_peripheral(TWI0_SDA_GPIO, 'A', 0); + } else { + gpio_peripheral(TWI1_SCL_GPIO, 'A', 0); + gpio_peripheral(TWI1_SDA_GPIO, 'A', 0); + } + p_twi->TWI_IDR = 0xFFFFFFFF; + (void)p_twi->TWI_SR; + p_twi->TWI_CR = TWI_CR_SWRST; + (void)p_twi->TWI_RHR; + p_twi->TWI_CR = TWI_CR_MSDIS; + p_twi->TWI_CR = TWI_CR_SVDIS; + p_twi->TWI_CR = TWI_CR_MSEN; + + uint32_t cldiv = 0; + uint32_t chdiv = 0; + uint32_t ckdiv = 0; + + cldiv = SystemCoreClock / ((rate > 384000 ? 384000 : rate) * 4) - 4; + + while((cldiv > 255) && (ckdiv < 7)) { + ckdiv++; + cldiv /= 2; + } + + if (rate > 348000) { + chdiv = SystemCoreClock / ((2 * rate - 384000) * 4) - 4; + while((chdiv > 255) && (ckdiv < 7)) { + ckdiv++; + chdiv /= 2; + } + } else { + chdiv = cldiv; + } + p_twi->TWI_CWGR = TWI_CWGR_CLDIV(cldiv) | \ + TWI_CWGR_CHDIV(chdiv) | \ + TWI_CWGR_CKDIV(ckdiv); +} + +static uint32_t +addr_to_u32(uint8_t addr_len, uint8_t *addr) +{ + uint32_t address = addr[0]; + if (addr_len > 1) { + address <<= 8; + address |= addr[1]; + } + if (addr_len > 2) { + address <<= 8; + address |= addr[2]; + } + if (addr_len > 3) { + shutdown("Addresses larger than 3 bytes are not supported"); + } + return address; +} + +struct i2c_config +i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr) +{ + if ((bus > 1) | (rate > 400000)) + shutdown("Invalid i2c_setup parameters!"); + Twi *p_twi = (bus == 0) ? TWI0 : TWI1; + i2c_init(p_twi, rate); + return (struct i2c_config){ .twi=p_twi, .addr=addr}; +} + +void +i2c_write(struct i2c_config config, + uint8_t write_len, uint8_t *write) +{ + Twi *p_twi = config.twi; + uint32_t status; + uint32_t bytes_to_send = write_len; + p_twi->TWI_MMR = TWI_MMR_DADR(config.addr); + for(;;) { + status = p_twi->TWI_SR; + if (status & TWI_SR_NACK) + shutdown("I2C NACK error encountered!"); + if (!(status & TWI_SR_TXRDY)) + continue; + if (!bytes_to_send) + break; + p_twi->TWI_THR = *write++; + bytes_to_send--; + } + p_twi->TWI_CR = TWI_CR_STOP; + while(!(p_twi->TWI_SR& TWI_SR_TXCOMP)) { + } +} + +static void +i2c_wait(Twi* p_twi, uint32_t bit, uint32_t timeout) +{ + for (;;) { + uint32_t flags = p_twi->TWI_SR; + if (flags & bit) + break; + if (!timer_is_before(timer_read_time(), timeout)) + shutdown("I2C timeout occured"); + } +} + +void +i2c_read(struct i2c_config config, + uint8_t reg_len, uint8_t *reg, + uint8_t read_len, uint8_t *read) +{ + Twi *p_twi = config.twi; + uint32_t status; + uint32_t bytes_to_send=read_len; + uint8_t stop = 0; + p_twi->TWI_MMR = 0; + p_twi->TWI_MMR = TWI_MMR_MREAD | TWI_MMR_DADR(config.addr) | + ((reg_len << TWI_MMR_IADRSZ_Pos) & + TWI_MMR_IADRSZ_Msk); + p_twi->TWI_IADR = 0; + p_twi->TWI_IADR = addr_to_u32(reg_len, reg); + if (bytes_to_send == 1) { + p_twi->TWI_CR = TWI_CR_START | TWI_CR_STOP; + stop = 1; + } else { + p_twi->TWI_CR = TWI_CR_START; + stop = 0; + } + while (bytes_to_send > 0) { + status = p_twi->TWI_SR; + if (status & TWI_SR_NACK) { + shutdown("I2C NACK error encountered!"); + } + if (bytes_to_send == 1 && !stop) { + p_twi->TWI_CR = TWI_CR_STOP; + stop = 1; + } + i2c_wait(p_twi, TWI_SR_RXRDY, timer_read_time() + timer_from_us(5000)); + if (!(status & TWI_SR_RXRDY)) { + continue; + } + *read++ = p_twi->TWI_RHR; + bytes_to_send--; + } + while (!(p_twi->TWI_SR & TWI_SR_TXCOMP)) {} + (void)p_twi->TWI_SR; +} diff --git a/src/atsam/internal.h b/src/atsam/internal.h new file mode 100644 index 00000000..dd94a609 --- /dev/null +++ b/src/atsam/internal.h @@ -0,0 +1,24 @@ +#ifndef __SAM3_INTERNAL_H +#define __SAM3_INTERNAL_H +// Local definitions for sam3/sam4 code + +#include <stdint.h> // uint32_t +#include "autoconf.h" // CONFIG_MACH_SAM3X8E + +#if CONFIG_MACH_SAM3X8E +#include "sam3x8e.h" +#elif CONFIG_MACH_SAM4S8C +#include "sam4s.h" +#elif CONFIG_MACH_SAM4E8E +#include "sam4e.h" +#endif + +#define GPIO(PORT, NUM) (((PORT)-'A') * 32 + (NUM)) +#define GPIO2PORT(PIN) ((PIN) / 32) +#define GPIO2BIT(PIN) (1<<((PIN) % 32)) + +void gpio_peripheral(uint32_t gpio, char ptype, int32_t pull_up); +int is_enabled_pclock(uint32_t id); +void enable_pclock(uint32_t id); + +#endif // internal.h diff --git a/src/atsam/main.c b/src/atsam/main.c new file mode 100644 index 00000000..1b9375db --- /dev/null +++ b/src/atsam/main.c @@ -0,0 +1,77 @@ +// Main starting point for SAM3/SAM4 boards +// +// Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/irq.h" // irq_disable +#include "command.h" // DECL_CONSTANT +#include "internal.h" // WDT +#include "sched.h" // sched_main + +DECL_CONSTANT(MCU, CONFIG_MCU); + + +/**************************************************************** + * watchdog handler + ****************************************************************/ + +void +watchdog_reset(void) +{ + WDT->WDT_CR = 0xA5000001; +} +DECL_TASK(watchdog_reset); + +void +watchdog_init(void) +{ + uint32_t timeout = 500 * 32768 / 128 / 1000; // 500ms timeout + WDT->WDT_MR = WDT_MR_WDRSTEN | WDT_MR_WDV(timeout) | WDT_MR_WDD(timeout); +} +DECL_INIT(watchdog_init); + + +/**************************************************************** + * misc functions + ****************************************************************/ + +// Check if a peripheral clock has been enabled +int +is_enabled_pclock(uint32_t id) +{ + if (id < 32) + return !!(PMC->PMC_PCSR0 & (1 << id)); + else + return !!(PMC->PMC_PCSR1 & (1 << (id - 32))); +} + +// Enable a peripheral clock +void +enable_pclock(uint32_t id) +{ + if (id < 32) + PMC->PMC_PCER0 = 1 << id; + else + PMC->PMC_PCER1 = 1 << (id - 32); +} + +void +command_reset(uint32_t *args) +{ + irq_disable(); + RSTC->RSTC_CR = ((0xA5 << RSTC_CR_KEY_Pos) | RSTC_CR_PROCRST + | RSTC_CR_PERRST); + for (;;) + ; +} +DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "reset"); + +// Main entry point +int +main(void) +{ + SystemInit(); + sched_main(); + return 0; +} diff --git a/src/atsam/sam4_cache.c b/src/atsam/sam4_cache.c new file mode 100644 index 00000000..75459660 --- /dev/null +++ b/src/atsam/sam4_cache.c @@ -0,0 +1,16 @@ +// SAM4 cache enable +// +// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "sam4e.h" // CMCC +#include "sched.h" // DECL_INIT + +void +sam4_cache_init(void) +{ + if (!(CMCC->CMCC_SR & CMCC_SR_CSTS)) + CMCC->CMCC_CTRL = CMCC_CTRL_CEN; +} +DECL_INIT(sam4_cache_init); diff --git a/src/atsam/sam4_usb.c b/src/atsam/sam4_usb.c new file mode 100644 index 00000000..ecf8e544 --- /dev/null +++ b/src/atsam/sam4_usb.c @@ -0,0 +1,238 @@ +// Hardware interface to SAM4 USB Device Port +// +// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include <string.h> // NULL +#include "board/irq.h" // irq_disable +#include "board/usb_cdc.h" // usb_notify_ep0 +#include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN +#include "internal.h" // UDP +#include "sched.h" // DECL_INIT + +#define CSR_EP0 (UDP_CSR_EPTYPE_CTRL | UDP_CSR_EPEDS) +#define CSR_ACM (UDP_CSR_EPTYPE_INT_IN | UDP_CSR_EPEDS) +#define CSR_BULK_OUT (UDP_CSR_EPTYPE_BULK_OUT | UDP_CSR_EPEDS) +#define CSR_BULK_IN (UDP_CSR_EPTYPE_BULK_IN | UDP_CSR_EPEDS) + +static void +usb_write_packet(uint32_t ep, const uint8_t *data, uint32_t len) +{ + while (len--) + UDP->UDP_FDR[ep] = *data++; +} + +static uint32_t +usb_read_packet(uint32_t ep, uint32_t csr, uint8_t *data, uint32_t max_len) +{ + uint32_t pk_len = (csr & UDP_CSR_RXBYTECNT_Msk) >> UDP_CSR_RXBYTECNT_Pos; + uint32_t len = pk_len < max_len ? pk_len : max_len, orig_len = len; + while (len--) + *data++ = UDP->UDP_FDR[ep]; + return orig_len; +} + +int_fast8_t +usb_read_bulk_out(void *data, uint_fast8_t max_len) +{ + static uint32_t next_bk = UDP_CSR_RX_DATA_BK0; + const uint32_t other_irqs = (UDP_CSR_RXSETUP | UDP_CSR_STALLSENT + | UDP_CSR_TXCOMP); + uint32_t csr = UDP->UDP_CSR[USB_CDC_EP_BULK_OUT]; + uint32_t bk = csr & (UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1); + if (!bk) { + // Not ready to receive data + if (csr & other_irqs) + UDP->UDP_CSR[USB_CDC_EP_BULK_OUT] = ( + CSR_BULK_OUT | UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1); + UDP->UDP_IER = 1<<USB_CDC_EP_BULK_OUT; + return -1; + } + uint32_t len = usb_read_packet(USB_CDC_EP_BULK_OUT, csr, data, max_len); + if (bk == (UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1)) + bk = next_bk; + next_bk = bk ^ (UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1); + UDP->UDP_CSR[USB_CDC_EP_BULK_OUT] = CSR_BULK_OUT | next_bk; + return len; +} + +int_fast8_t +usb_send_bulk_in(void *data, uint_fast8_t len) +{ + const uint32_t other_irqs = (UDP_CSR_RXSETUP | UDP_CSR_STALLSENT + | UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1); + uint32_t csr = UDP->UDP_CSR[USB_CDC_EP_BULK_IN]; + if (csr & UDP_CSR_TXPKTRDY) { + // Not ready to send + if (csr & other_irqs) + UDP->UDP_CSR[USB_CDC_EP_BULK_IN] = CSR_BULK_IN | UDP_CSR_TXCOMP; + UDP->UDP_IER = 1<<USB_CDC_EP_BULK_IN; + return -1; + } + usb_write_packet(USB_CDC_EP_BULK_IN, data, len); + UDP->UDP_CSR[USB_CDC_EP_BULK_IN] = CSR_BULK_IN | UDP_CSR_TXPKTRDY; + return len; +} + +int_fast8_t +usb_read_ep0(void *data, uint_fast8_t max_len) +{ + const uint32_t other_irqs = (UDP_CSR_RXSETUP | UDP_CSR_STALLSENT + | UDP_CSR_TXCOMP | UDP_CSR_RX_DATA_BK1); + uint32_t csr = UDP->UDP_CSR[0]; + if (csr & other_irqs) + return -2; + if (!(csr & UDP_CSR_RX_DATA_BK0)) { + // Not ready to receive data + UDP->UDP_IER = 1<<0; + return -1; + } + uint32_t len = usb_read_packet(0, csr, data, max_len); + if (UDP->UDP_CSR[0] & other_irqs) + return -2; + UDP->UDP_CSR[0] = CSR_EP0 | other_irqs; + return len; +} + +int_fast8_t +usb_read_ep0_setup(void *data, uint_fast8_t max_len) +{ + const uint32_t other_irqs = (UDP_CSR_STALLSENT | UDP_CSR_TXCOMP + | UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1); + uint32_t csr = UDP->UDP_CSR[0]; + if (!(csr & UDP_CSR_RXSETUP)) { + // No data ready to read + if (csr & other_irqs) + UDP->UDP_CSR[0] = CSR_EP0 | UDP_CSR_RXSETUP; + UDP->UDP_IER = 1<<0; + return -1; + } + uint32_t len = usb_read_packet(0, csr, data, max_len); + uint32_t dir = *(uint8_t*)data & 0x80 ? UDP_CSR_DIR : 0; + UDP->UDP_CSR[0] = CSR_EP0 | dir; + return len; +} + +int_fast8_t +usb_send_ep0(const void *data, uint_fast8_t len) +{ + const uint32_t other_irqs = (UDP_CSR_RXSETUP | UDP_CSR_STALLSENT + | UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1); + uint32_t csr = UDP->UDP_CSR[0]; + if (csr & other_irqs) + return -2; + if (csr & UDP_CSR_TXPKTRDY) { + // Not ready to send + UDP->UDP_IER = 1<<0; + return -1; + } + usb_write_packet(0, data, len); + uint32_t dir = csr & UDP_CSR_DIR; + UDP->UDP_CSR[0] = CSR_EP0 | dir | UDP_CSR_TXPKTRDY | other_irqs; + return len; +} + +void +usb_stall_ep0(void) +{ + UDP->UDP_CSR[0] = CSR_EP0 | UDP_CSR_FORCESTALL; + UDP->UDP_IER = 1<<0; +} + +static uint32_t set_address; + +void +usb_set_address(uint_fast8_t addr) +{ + set_address = addr | UDP_FADDR_FEN; + usb_send_ep0(NULL, 0); + UDP->UDP_IER = 1<<0; +} + +void +usb_set_configure(void) +{ + UDP->UDP_CSR[USB_CDC_EP_ACM] = CSR_ACM; + UDP->UDP_CSR[USB_CDC_EP_BULK_OUT] = CSR_BULK_OUT; + UDP->UDP_CSR[USB_CDC_EP_BULK_IN] = CSR_BULK_IN; + UDP->UDP_GLB_STAT |= UDP_GLB_STAT_CONFG; +} + +#if CONFIG_MACH_SAM4S8C +#define EFC_HW EFC0 +#elif CONFIG_MACH_SAM4E8E +#define EFC_HW EFC +#endif + +void noinline __aligned(16) // align for predictable flash code access +usb_request_bootloader(void) +{ + irq_disable(); + // Request boot from ROM (instead of boot from flash) + while ((EFC_HW->EEFC_FSR & EEFC_FSR_FRDY) == 0) + ; + EFC_HW->EEFC_FCR = (EEFC_FCR_FCMD_CGPB | EEFC_FCR_FARG(1) + | EEFC_FCR_FKEY_PASSWD); + while ((EFC_HW->EEFC_FSR & EEFC_FSR_FRDY) == 0) + ; + // Reboot + RSTC->RSTC_CR = RSTC_CR_KEY(0xA5) | RSTC_CR_PROCRST | RSTC_CR_PERRST; + for (;;) + ; +} + +void +usbserial_init(void) +{ + // Enable clocks + enable_pclock(ID_UDP); + PMC->PMC_USB = PMC_USB_USBDIV(5 - 1); // PLLA=240Mhz; divide by 5 for 48Mhz + PMC->PMC_SCER = PMC_SCER_UDP; + + // Enable USB pullup + UDP->UDP_TXVC = UDP_TXVC_PUON | UDP_TXVC_TXVDIS; + + // Enable interrupts + UDP->UDP_ICR = 0xffffffff; + NVIC_SetPriority(UDP_IRQn, 1); + NVIC_EnableIRQ(UDP_IRQn); +} +DECL_INIT(usbserial_init); + +// Configure endpoint 0 after usb reset completes +static void +handle_end_reset(void) +{ + UDP->UDP_ICR = UDP_ISR_ENDBUSRES; + + UDP->UDP_CSR[0] = CSR_EP0; + UDP->UDP_IER = 1<<0; + + UDP->UDP_TXVC = UDP_TXVC_PUON; +} + +void __visible +UDP_Handler(void) +{ + uint32_t s = UDP->UDP_ISR; + UDP->UDP_IDR = s; + if (s & UDP_ISR_ENDBUSRES) + handle_end_reset(); + if (s & UDP_ISR_RXRSM) + UDP->UDP_ICR = UDP_ISR_RXRSM; + if (s & (1<<0)) { + usb_notify_ep0(); + + if (set_address && UDP->UDP_CSR[0] & UDP_CSR_TXCOMP) { + // Ack from set_address command sent - now update address + UDP->UDP_FADDR = set_address; + UDP->UDP_GLB_STAT |= UDP_GLB_STAT_FADDEN; + set_address = 0; + } + } + if (s & (1<<USB_CDC_EP_BULK_OUT)) + usb_notify_bulk_out(); + if (s & (1<<USB_CDC_EP_BULK_IN)) + usb_notify_bulk_in(); +} diff --git a/src/atsam/sam4e_afec.c b/src/atsam/sam4e_afec.c new file mode 100644 index 00000000..0e1c60ac --- /dev/null +++ b/src/atsam/sam4e_afec.c @@ -0,0 +1,203 @@ +// SAM4e8e Analog Front-End Converter (AFEC) support +// +// Copyright (C) 2018 Florian Heilmann <Florian.Heilmann@gmx.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "autoconf.h" // CONFIG_CLOCK_FREQ +#include "command.h" // shutdown +#include "gpio.h" // gpio_adc_setup +#include "internal.h" // GPIO +#include "sam4e.h" // AFEC0 +#include "sched.h" // sched_shutdown + +static const uint8_t afec_pins[] = { + //remove first channel, since it offsets the channel number: GPIO('A', 8), + GPIO('A', 17), GPIO('A', 18), GPIO('A', 19), + GPIO('A', 20), GPIO('B', 0), GPIO('B', 1), GPIO('C', 13), + GPIO('C', 15), GPIO('C', 12), GPIO('C', 29), GPIO('C', 30), + GPIO('C', 31), GPIO('C', 26), GPIO('C', 27), GPIO('C',0), + // AFEC1 + GPIO('B', 2), GPIO('B', 3), GPIO('A', 21), GPIO('A', 22), + GPIO('C', 1), GPIO('C', 2), GPIO('C', 3), GPIO('C', 4), +}; + +#define AFEC1_START 15 // The first 15 pins are on afec0 + +static inline struct gpio_adc +pin_to_gpio_adc(uint8_t pin) +{ + int chan; + for (chan=0; ; chan++) { + if (chan >= ARRAY_SIZE(afec_pins)) + shutdown("Not a valid ADC pin"); + if (afec_pins[chan] == pin) { + break; + } + } + return (struct gpio_adc){ .chan=chan }; +} + +static inline Afec * +gpio_adc_to_afec(struct gpio_adc g) +{ + return (g.chan >= AFEC1_START ? AFEC1 : AFEC0); +} + +static inline uint32_t +gpio_adc_to_afec_chan(struct gpio_adc g) +{ + return (g.chan >= AFEC1_START ? g.chan - AFEC1_START : g.chan); +} + +#define ADC_FREQ_MAX 6000000UL +DECL_CONSTANT(ADC_MAX, 4095); + +static int +init_afec(Afec* afec) { + + // Enable PMC + enable_pclock(afec == AFEC0 ? ID_AFEC0 : ID_AFEC1); + + // If busy, return busy + if ((afec->AFE_ISR & AFE_ISR_DRDY) == AFE_ISR_DRDY) { + return -1; + } + + // Reset + afec->AFE_CR = AFE_CR_SWRST; + + // Configure afec + afec->AFE_MR = AFE_MR_ANACH_ALLOWED | \ + AFE_MR_PRESCAL (SystemCoreClock / (2 * ADC_FREQ_MAX) -1) | \ + AFE_MR_SETTLING_AST3 | \ + AFE_MR_TRACKTIM(2) | \ + AFE_MR_TRANSFER(1) | \ + AFE_MR_STARTUP_SUT64; + afec->AFE_EMR = AFE_EMR_TAG | \ + AFE_EMR_RES_NO_AVERAGE | \ + AFE_EMR_STM; + afec->AFE_ACR = AFE_ACR_IBCTL(1); + + // Disable interrupts + afec->AFE_IDR = 0xDF00803F; + + // Disable SW triggering + uint32_t mr = afec->AFE_MR; + + mr &= ~(AFE_MR_TRGSEL_Msk | AFE_MR_TRGEN | AFE_MR_FREERUN_ON); + mr |= AFE_MR_TRGEN_DIS; + afec->AFE_MR = mr; + + return 0; +} + +void +gpio_afec_init(void) { + + while(init_afec(AFEC0) != 0) { + (void)(AFEC0->AFE_LCDR & AFE_LCDR_LDATA_Msk); + } + while(init_afec(AFEC1) != 0) { + (void)(AFEC1->AFE_LCDR & AFE_LCDR_LDATA_Msk); + } + +} +DECL_INIT(gpio_afec_init); + +struct gpio_adc +gpio_adc_setup(uint8_t pin) +{ + struct gpio_adc adc_pin = pin_to_gpio_adc(pin); + Afec *afec = gpio_adc_to_afec(adc_pin); + uint32_t afec_chan = gpio_adc_to_afec_chan(adc_pin); + + //config channel + uint32_t reg = afec->AFE_DIFFR; + reg &= ~(1u << afec_chan); + afec->AFE_DIFFR = reg; + reg = afec->AFE_CGR; + reg &= ~(0x03u << (2 * afec_chan)); + reg |= 1 << (2 * afec_chan); + afec->AFE_CGR = reg; + + // Configure channel + // afec_ch_get_config_defaults(&ch_cfg); + // afec_ch_set_config(afec, afec_chan, &ch_cfg); + // Remove default internal offset from channel + // See Atmel Appnote AT03078 Section 1.5 + afec->AFE_CSELR = afec_chan; + afec->AFE_COCR = (0x800 & AFE_COCR_AOFF_Msk); + + // Enable and calibrate Channel + afec->AFE_CHER = 1 << afec_chan; + + reg = afec->AFE_CHSR; + afec->AFE_CDOR = reg; + afec->AFE_CR = AFE_CR_AUTOCAL; + + return adc_pin; +} + +enum { AFE_DUMMY=0xff }; +uint8_t active_channel = AFE_DUMMY; + +// Try to sample a value. Returns zero if sample ready, otherwise +// returns the number of clock ticks the caller should wait before +// retrying this function. +uint32_t +gpio_adc_sample(struct gpio_adc g) +{ + Afec *afec = gpio_adc_to_afec(g); + uint32_t afec_chan = gpio_adc_to_afec_chan(g); + if (active_channel == g.chan) { + if ((afec->AFE_ISR & AFE_ISR_DRDY) + && (afec->AFE_ISR & (1 << afec_chan))) { + // Sample now ready + return 0; + } else { + // Busy + goto need_delay; + } + } else if (active_channel != AFE_DUMMY) { + goto need_delay; + } + + afec->AFE_CHDR = 0x803F; // Disable all channels + afec->AFE_CHER = 1 << afec_chan; + + active_channel = g.chan; + + for (uint32_t chan = 0; chan < 16; ++chan) + { + if ((afec->AFE_ISR & (1 << chan)) != 0) + { + afec->AFE_CSELR = chan; + (void)(afec->AFE_CDR); + } + } + afec->AFE_CR = AFE_CR_START; + +need_delay: + return ADC_FREQ_MAX * 10000ULL / CONFIG_CLOCK_FREQ; // about 400 mcu clock cycles or 40 afec cycles +} + +// Read a value; use only after gpio_adc_sample() returns zero +uint16_t +gpio_adc_read(struct gpio_adc g) +{ + Afec *afec = gpio_adc_to_afec(g); + uint32_t afec_chan = gpio_adc_to_afec_chan(g); + active_channel = AFE_DUMMY; + afec->AFE_CSELR = afec_chan; + return afec->AFE_CDR; +} + +// Cancel a sample that may have been started with gpio_adc_sample() +void +gpio_adc_cancel_sample(struct gpio_adc g) +{ + if (active_channel == g.chan) { + active_channel = AFE_DUMMY; + } +} diff --git a/src/atsam/sam4s_sysinit.c b/src/atsam/sam4s_sysinit.c new file mode 100644 index 00000000..41a2dcfe --- /dev/null +++ b/src/atsam/sam4s_sysinit.c @@ -0,0 +1,65 @@ +// This code is from lib/sam4e/gcc/system_sam4e.c - it is unclear why +// it is not defined in the Atmel sam4s cmsis code. + +#include "internal.h" + +/* Clock Settings (120MHz) */ +#define SYS_BOARD_OSCOUNT (CKGR_MOR_MOSCXTST(0x8U)) +#define SYS_BOARD_PLLAR (CKGR_PLLAR_ONE \ + | CKGR_PLLAR_MULA(0x13U) \ + | CKGR_PLLAR_PLLACOUNT(0x3fU) \ + | CKGR_PLLAR_DIVA(0x1U)) +#define SYS_BOARD_MCKR (PMC_MCKR_PRES_CLK_2 | PMC_MCKR_CSS_PLLA_CLK) + +#define SYS_CKGR_MOR_KEY_VALUE CKGR_MOR_KEY(0x37) /* Key to unlock MOR register */ + +uint32_t SystemCoreClock = CHIP_FREQ_MAINCK_RC_4MHZ; + +void SystemInit( void ) +{ + /* Set FWS according to SYS_BOARD_MCKR configuration */ + EFC0->EEFC_FMR = EEFC_FMR_FWS(5); + + /* Initialize main oscillator */ + if ( !(PMC->CKGR_MOR & CKGR_MOR_MOSCSEL) ) + { + PMC->CKGR_MOR = SYS_CKGR_MOR_KEY_VALUE | SYS_BOARD_OSCOUNT | CKGR_MOR_MOSCRCEN | CKGR_MOR_MOSCXTEN; + + while ( !(PMC->PMC_SR & PMC_SR_MOSCXTS) ) + { + } + } + + /* Switch to 3-20MHz Xtal oscillator */ + PMC->CKGR_MOR = SYS_CKGR_MOR_KEY_VALUE | SYS_BOARD_OSCOUNT | CKGR_MOR_MOSCRCEN | CKGR_MOR_MOSCXTEN | CKGR_MOR_MOSCSEL; + + while ( !(PMC->PMC_SR & PMC_SR_MOSCSELS) ) + { + } + + PMC->PMC_MCKR = (PMC->PMC_MCKR & ~(uint32_t)PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK; + + while ( !(PMC->PMC_SR & PMC_SR_MCKRDY) ) + { + } + + /* Initialize PLLA */ + PMC->CKGR_PLLAR = SYS_BOARD_PLLAR; + while ( !(PMC->PMC_SR & PMC_SR_LOCKA) ) + { + } + + /* Switch to main clock */ + PMC->PMC_MCKR = (SYS_BOARD_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK; + while ( !(PMC->PMC_SR & PMC_SR_MCKRDY) ) + { + } + + /* Switch to PLLA */ + PMC->PMC_MCKR = SYS_BOARD_MCKR; + while ( !(PMC->PMC_SR & PMC_SR_MCKRDY) ) + { + } + + SystemCoreClock = CHIP_FREQ_CPU_MAX; +} diff --git a/src/atsam/sam4s_timer.c b/src/atsam/sam4s_timer.c new file mode 100644 index 00000000..a051f08c --- /dev/null +++ b/src/atsam/sam4s_timer.c @@ -0,0 +1,118 @@ +// SAM4s 16bit timer interrupt scheduling +// +// Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/io.h" // readl +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time +#include "board/timer_irq.h" // timer_dispatch_many +#include "command.h" // DECL_SHUTDOWN +#include "internal.h" // TC0 +#include "sched.h" // DECL_INIT + + +/**************************************************************** + * Low level timer code + ****************************************************************/ + +// Get the 16bit timer value +static uint32_t +timer_get(void) +{ + return TC0->TC_CHANNEL[0].TC_CV; +} + +// Set the next irq time +static void +timer_set(uint32_t value) +{ + TC0->TC_CHANNEL[0].TC_RA = value; +} + +// Activate timer dispatch as soon as possible +void +timer_kick(void) +{ + timer_set(timer_read_time() + 50); +} + + +/**************************************************************** + * 16bit hardware timer to 32bit conversion + ****************************************************************/ + +// High bits of timer (top 17 bits) +static uint32_t timer_high; + +// Return the current time (in absolute clock ticks). +uint32_t __always_inline +timer_read_time(void) +{ + uint32_t th = readl(&timer_high); + uint32_t cur = timer_get(); + // Combine timer_high (high 17 bits) and current time (low 16 + // bits) using method that handles rollovers correctly. + return (th ^ cur) + (th & 0x8000); +} + +// Update timer_high every 0x8000 clock ticks +static uint_fast8_t +timer_event(struct timer *t) +{ + timer_high += 0x8000; + t->waketime = timer_high + 0x8000; + return SF_RESCHEDULE; +} +static struct timer wrap_timer = { + .func = timer_event, + .waketime = 0x8000, +}; + +void +timer_reset(void) +{ + sched_add_timer(&wrap_timer); +} +DECL_SHUTDOWN(timer_reset); + + +/**************************************************************** + * Timer init + ****************************************************************/ + +void +timer_init(void) +{ + TcChannel *tc = &TC0->TC_CHANNEL[0]; + // Reset the timer + tc->TC_CCR = TC_CCR_CLKDIS; + tc->TC_IDR = 0xFFFFFFFF; + // Enable it + enable_pclock(ID_TC0); + tc->TC_CMR = TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK2; + tc->TC_IER = TC_IER_CPAS; + NVIC_SetPriority(TC0_IRQn, 2); + NVIC_EnableIRQ(TC0_IRQn); + timer_kick(); + timer_reset(); + tc->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG; +} +DECL_INIT(timer_init); + + +/**************************************************************** + * Main timer dispatch irq handler + ****************************************************************/ + +// IRQ handler +void __visible __aligned(16) // aligning helps stabilize perf benchmarks +TC0_Handler(void) +{ + irq_disable(); + uint32_t next = timer_dispatch_many(); + timer_set(next); + TC0->TC_CHANNEL[0].TC_SR; // read to clear irq pending + irq_enable(); +} diff --git a/src/atsam/serial.c b/src/atsam/serial.c new file mode 100644 index 00000000..c87539ad --- /dev/null +++ b/src/atsam/serial.c @@ -0,0 +1,77 @@ +// sam3/sam4 serial port +// +// Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "autoconf.h" // CONFIG_SERIAL_BAUD +#include "board/serial_irq.h" // serial_rx_data +#include "internal.h" // gpio_peripheral +#include "sched.h" // DECL_INIT + +// Serial port pins +#if CONFIG_MACH_SAM3X8E +#define Serial_IRQ_Handler UART_Handler +static Uart * const Port = UART; +static const uint32_t Pmc_id = ID_UART, Irq_id = UART_IRQn; +static const uint32_t rx_pin = GPIO('A', 8); +static const uint32_t tx_pin = GPIO('A', 9); +#elif CONFIG_MACH_SAM4S8C +#define Serial_IRQ_Handler UART1_Handler +static Uart * const Port = UART1; +static const uint32_t Pmc_id = ID_UART1, Irq_id = UART1_IRQn; +static const uint32_t rx_pin = GPIO('B', 2); +static const uint32_t tx_pin = GPIO('B', 3); +#elif CONFIG_MACH_SAM4E8E +#define Serial_IRQ_Handler UART0_Handler +static Uart * const Port = UART0; +static const uint32_t Pmc_id = ID_UART0, Irq_id = UART0_IRQn; +static const uint32_t rx_pin = GPIO('A', 9); +static const uint32_t tx_pin = GPIO('A', 10); +#endif + +void +serial_init(void) +{ + gpio_peripheral(rx_pin, 'A', 1); + gpio_peripheral(tx_pin, 'A', 0); + + // Reset uart + enable_pclock(Pmc_id); + Port->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS; + Port->UART_CR = (UART_CR_RSTRX | UART_CR_RSTTX + | UART_CR_RXDIS | UART_CR_TXDIS); + Port->UART_IDR = 0xFFFFFFFF; + + // Enable uart + Port->UART_MR = (US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO + | UART_MR_CHMODE_NORMAL); + Port->UART_BRGR = SystemCoreClock / (16 * CONFIG_SERIAL_BAUD); + Port->UART_IER = UART_IER_RXRDY; + NVIC_EnableIRQ(Irq_id); + NVIC_SetPriority(Irq_id, 0); + Port->UART_CR = UART_CR_RXEN | UART_CR_TXEN; +} +DECL_INIT(serial_init); + +void __visible +Serial_IRQ_Handler(void) +{ + uint32_t status = Port->UART_SR; + if (status & UART_SR_RXRDY) + serial_rx_byte(Port->UART_RHR); + if (status & UART_SR_TXRDY) { + uint8_t data; + int ret = serial_get_tx_byte(&data); + if (ret) + Port->UART_IDR = UART_IDR_TXRDY; + else + Port->UART_THR = data; + } +} + +void +serial_enable_tx_irq(void) +{ + Port->UART_IER = UART_IDR_TXRDY; +} diff --git a/src/atsam/spi.c b/src/atsam/spi.c new file mode 100644 index 00000000..5f79a02c --- /dev/null +++ b/src/atsam/spi.c @@ -0,0 +1,285 @@ +// SPI transmissions on sam3 +// +// Copyright (C) 2018 Petri Honkala <cruwaller@gmail.com> +// Copyright (C) 2018 Florian Heilmann <Florian.Heilmann@gmx.net> +// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // shutdown +#include "gpio.h" // spi_setup +#include "internal.h" // gpio_peripheral +#include "sched.h" // sched_shutdown + + +/**************************************************************** + * SPI/USART buses and pins + ****************************************************************/ + +struct spi_info { + void *dev; + uint32_t dev_id; + uint8_t miso_pin, mosi_pin, sck_pin, rxtx_periph, sck_periph; +}; + +static const struct spi_info spi_bus[] = { +#if CONFIG_MACH_SAM3X8E + { SPI0, ID_SPI0, GPIO('A', 25), GPIO('A', 26), GPIO('A', 27), 'A', 'A' }, + { USART0, ID_USART0, GPIO('A', 10), GPIO('A', 11), GPIO('A', 17), 'A', 'B'}, + { USART1, ID_USART1, GPIO('A', 12), GPIO('A', 13), GPIO('A', 16), 'A', 'A'}, + { USART2, ID_USART2, GPIO('B', 21), GPIO('B', 20), GPIO('B', 24), 'A', 'A'}, +#elif CONFIG_MACH_SAM4S8C + { SPI, ID_SPI, GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A' }, + { USART0, ID_USART0, GPIO('A', 5), GPIO('A', 6), GPIO('A', 2), 'A', 'B' }, + { USART1, ID_USART1, GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A'}, +#elif CONFIG_MACH_SAM4E8E + { USART0, ID_USART0, GPIO('B', 0), GPIO('B', 1), GPIO('B', 13), 'C', 'C' }, + { USART1, ID_USART1, GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A'}, + { SPI, ID_SPI, GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A' }, +#endif +}; + +static int +is_spihw(void *dev) +{ +#if CONFIG_MACH_SAM3X8E + return dev == SPI0; +#else + return dev == SPI; +#endif +} + +static void +init_pins(uint32_t bus) +{ + const struct spi_info *si = &spi_bus[bus]; + gpio_peripheral(si->sck_pin, si->sck_periph, 0); + gpio_peripheral(si->miso_pin, si->rxtx_periph, 1); + gpio_peripheral(si->mosi_pin, si->rxtx_periph, 0); + enable_pclock(si->dev_id); +} + + +/**************************************************************** + * SPI hardware + ****************************************************************/ + +#define CHANNEL 0 // Use same channel for all + +static void +spihw_init(uint32_t bus) +{ + init_pins(bus); + Spi *pSpi = spi_bus[bus].dev; + + /* Disable SPI */ + pSpi->SPI_CR = SPI_CR_SPIDIS; + /* Execute a software reset of the SPI twice */ + pSpi->SPI_CR = SPI_CR_SWRST; + pSpi->SPI_CR = SPI_CR_SWRST; + + pSpi->SPI_MR = ( SPI_MR_MSTR | // Set master mode + SPI_MR_MODFDIS | // Disable fault detection + SPI_MR_PCS(CHANNEL) // Fixes peripheral select + ); + pSpi->SPI_IDR = 0xffffffff; // Disable ISRs + + /* Clear Chip Select Registers */ + pSpi->SPI_CSR[0] = 0; + pSpi->SPI_CSR[1] = 0; + pSpi->SPI_CSR[2] = 0; + pSpi->SPI_CSR[3] = 0; + + /* Set basic channel config */ + pSpi->SPI_CSR[CHANNEL] = 0; + /* Enable SPI */ + pSpi->SPI_CR = SPI_CR_SPIEN; +} + +static struct spi_config +spihw_setup(uint32_t bus, uint8_t mode, uint32_t rate) +{ + // Make sure bus is enabled + spihw_init(bus); + + uint32_t config = 0; + uint32_t clockDiv; + if (rate < (CHIP_FREQ_CPU_MAX / 255)) { + clockDiv = 255; + } else if (rate >= (CHIP_FREQ_CPU_MAX / 2)) { + clockDiv = 2; + } else { + clockDiv = (CHIP_FREQ_CPU_MAX / (rate + 1)) + 1; + } + + /****** Will be written to SPI_CSRx register ******/ + // CSAAT : Chip Select Active After Transfer + config = SPI_CSR_CSAAT; + config |= SPI_CSR_BITS_8_BIT; // TODO: support for SPI_CSR_BITS_16_BIT + // NOTE: NCPHA is inverted, CPHA normal!! + switch(mode) { + case 0: + config |= SPI_CSR_NCPHA; + break; + case 1: + config |= 0; + break; + case 2: + config |= SPI_CSR_NCPHA; + config |= SPI_CSR_CPOL; + break; + case 3: + config |= SPI_CSR_CPOL; + break; + }; + + config |= ((clockDiv & 0xffu) << SPI_CSR_SCBR_Pos); + return (struct spi_config){ .spidev = spi_bus[bus].dev, .cfg = config }; +} + +static void +spihw_prepare(struct spi_config config) +{ + Spi *pSpi = config.spidev; + pSpi->SPI_CSR[CHANNEL] = config.cfg; +} + +static void +spihw_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data) +{ + Spi *pSpi = config.spidev; + if (receive_data) { + while (len--) { + pSpi->SPI_TDR = *data; + // wait for receive register + while (!(pSpi->SPI_SR & SPI_SR_RDRF)) + ; + // get data + *data++ = pSpi->SPI_RDR; + } + } else { + while (len--) { + pSpi->SPI_TDR = *data++; + // wait for receive register + while (!(pSpi->SPI_SR & SPI_SR_RDRF)) + ; + // read data (to clear RDRF) + pSpi->SPI_RDR; + } + } +} + + +/**************************************************************** + * USART hardware + ****************************************************************/ + +static struct spi_config +usart_setup(uint32_t bus, uint8_t mode, uint32_t rate) +{ + init_pins(bus); + Usart *p_usart = spi_bus[bus].dev; + + p_usart->US_MR = 0; + p_usart->US_RTOR = 0; + p_usart->US_TTGR = 0; + + p_usart->US_CR = US_CR_RSTTX | US_CR_RSTRX | US_CR_TXDIS | US_CR_RXDIS; + + uint32_t br = DIV_ROUND_UP(CHIP_FREQ_CPU_MAX, rate); + p_usart->US_BRGR = br << US_BRGR_CD_Pos; + + uint32_t reg = US_MR_CHRL_8_BIT | + US_MR_USART_MODE_SPI_MASTER | + US_MR_CLKO | + US_MR_CHMODE_NORMAL; + switch (mode) { + case 0: + reg |= US_MR_CPHA; + reg &= ~US_MR_CPOL; + break; + case 1: + reg &= ~US_MR_CPHA; + reg &= ~US_MR_CPOL; + break; + case 2: + reg |= US_MR_CPHA; + reg |= US_MR_CPOL; + break; + case 3: + reg &= ~US_MR_CPHA; + reg |= US_MR_CPOL; + break; + } + + p_usart->US_MR |= reg; + p_usart->US_CR = US_CR_RXEN | US_CR_TXEN; + return (struct spi_config){ .spidev=p_usart, .cfg=p_usart->US_MR }; +} + +static void +usart_prepare(struct spi_config config) +{ +} + +static void +usart_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data) +{ + Usart *p_usart = config.spidev; + if (receive_data) { + for (uint32_t i = 0; i < len; ++i) { + uint32_t co = (uint32_t)*data & 0x000000FF; + while(!(p_usart->US_CSR & US_CSR_TXRDY)) {} + p_usart->US_THR = US_THR_TXCHR(co); + uint32_t ci = 0; + while(!(p_usart->US_CSR & US_CSR_RXRDY)) {} + ci = p_usart->US_RHR & US_RHR_RXCHR_Msk; + *data++ = (uint8_t)ci; + } + } else { + for (uint32_t i = 0; i < len; ++i) { + uint32_t co = (uint32_t)*data & 0x000000FF; + while(!(p_usart->US_CSR & US_CSR_TXRDY)) {} + p_usart->US_THR = US_THR_TXCHR(co); + while(!(p_usart->US_CSR & US_CSR_RXRDY)) {} + (void)(p_usart->US_RHR & US_RHR_RXCHR_Msk); + (void)*data++; + } + } +} + + +/**************************************************************** + * Interface + ****************************************************************/ + +struct spi_config +spi_setup(uint32_t bus, uint8_t mode, uint32_t rate) +{ + if (bus >= ARRAY_SIZE(spi_bus)) + shutdown("Invalid spi bus"); + if (is_spihw(spi_bus[bus].dev)) + return spihw_setup(bus, mode, rate); + return usart_setup(bus, mode, rate); +} + +void +spi_prepare(struct spi_config config) +{ + if (is_spihw(config.spidev)) + spihw_prepare(config); + else + usart_prepare(config); +} + +void +spi_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data) +{ + if (is_spihw(config.spidev)) + spihw_transfer(config, receive_data, len, data); + else + usart_transfer(config, receive_data, len, data); +} diff --git a/src/atsam/timer.c b/src/atsam/timer.c new file mode 100644 index 00000000..b2d600a5 --- /dev/null +++ b/src/atsam/timer.c @@ -0,0 +1,65 @@ +// SAM3/SAM4 timer interrupt scheduling +// +// Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time +#include "board/timer_irq.h" // timer_dispatch_many +#include "command.h" // DECL_SHUTDOWN +#include "internal.h" // TC0 +#include "sched.h" // DECL_INIT + +// Set the next irq time +static void +timer_set(uint32_t value) +{ + TC0->TC_CHANNEL[0].TC_RA = value; +} + +// Return the current time (in absolute clock ticks). +uint32_t +timer_read_time(void) +{ + return TC0->TC_CHANNEL[0].TC_CV; +} + +// Activate timer dispatch as soon as possible +void +timer_kick(void) +{ + timer_set(timer_read_time() + 50); + TC0->TC_CHANNEL[0].TC_SR; // read to clear irq pending +} + +void +timer_init(void) +{ + TcChannel *tc = &TC0->TC_CHANNEL[0]; + // Reset the timer + tc->TC_CCR = TC_CCR_CLKDIS; + tc->TC_IDR = 0xFFFFFFFF; + // Enable it + enable_pclock(ID_TC0); + tc->TC_CMR = TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1; + tc->TC_IER = TC_IER_CPAS; + NVIC_SetPriority(TC0_IRQn, 2); + NVIC_EnableIRQ(TC0_IRQn); + timer_kick(); + tc->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG; +} +DECL_INIT(timer_init); + +// IRQ handler +void __visible __aligned(16) // aligning helps stabilize perf benchmarks +TC0_Handler(void) +{ + irq_disable(); + uint32_t status = TC0->TC_CHANNEL[0].TC_SR; // read to clear irq pending + if (likely(status & TC_SR_CPAS)) { + uint32_t next = timer_dispatch_many(); + timer_set(next); + } + irq_enable(); +} diff --git a/src/atsam/usb_cdc_ep.h b/src/atsam/usb_cdc_ep.h new file mode 100644 index 00000000..bcf1d3e3 --- /dev/null +++ b/src/atsam/usb_cdc_ep.h @@ -0,0 +1,10 @@ +#ifndef __SAM3_USB_CDC_EP_H +#define __SAM3_USB_CDC_EP_H + +enum { + USB_CDC_EP_ACM = 3, + USB_CDC_EP_BULK_OUT = 1, + USB_CDC_EP_BULK_IN = 2, +}; + +#endif // usb_cdc_ep.h |