aboutsummaryrefslogtreecommitdiffstats
path: root/src/atsam
diff options
context:
space:
mode:
Diffstat (limited to 'src/atsam')
-rw-r--r--src/atsam/Kconfig53
-rw-r--r--src/atsam/Makefile58
-rw-r--r--src/atsam/adc.c96
-rw-r--r--src/atsam/gpio.c142
-rw-r--r--src/atsam/gpio.h51
-rw-r--r--src/atsam/i2c.c175
-rw-r--r--src/atsam/internal.h24
-rw-r--r--src/atsam/main.c77
-rw-r--r--src/atsam/sam4_cache.c16
-rw-r--r--src/atsam/sam4_usb.c238
-rw-r--r--src/atsam/sam4e_afec.c203
-rw-r--r--src/atsam/sam4s_sysinit.c65
-rw-r--r--src/atsam/sam4s_timer.c118
-rw-r--r--src/atsam/serial.c77
-rw-r--r--src/atsam/spi.c285
-rw-r--r--src/atsam/timer.c65
-rw-r--r--src/atsam/usb_cdc_ep.h10
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