diff options
Diffstat (limited to 'src/atsamd')
-rw-r--r-- | src/atsamd/Kconfig | 57 | ||||
-rw-r--r-- | src/atsamd/Makefile | 54 | ||||
-rw-r--r-- | src/atsamd/adc.c | 121 | ||||
-rw-r--r-- | src/atsamd/clock.c | 77 | ||||
-rw-r--r-- | src/atsamd/gpio.c | 141 | ||||
-rw-r--r-- | src/atsamd/gpio.h | 55 | ||||
-rw-r--r-- | src/atsamd/hard_pwm.c | 102 | ||||
-rw-r--r-- | src/atsamd/i2c.c | 114 | ||||
-rw-r--r-- | src/atsamd/internal.h | 14 | ||||
-rw-r--r-- | src/atsamd/main.c | 52 | ||||
-rw-r--r-- | src/atsamd/serial.c | 61 | ||||
-rw-r--r-- | src/atsamd/spi.c | 92 | ||||
-rw-r--r-- | src/atsamd/timer.c | 66 | ||||
-rw-r--r-- | src/atsamd/usbserial.c | 246 |
14 files changed, 1252 insertions, 0 deletions
diff --git a/src/atsamd/Kconfig b/src/atsamd/Kconfig new file mode 100644 index 00000000..02e21b7b --- /dev/null +++ b/src/atsamd/Kconfig @@ -0,0 +1,57 @@ +# Kconfig settings for Atmel SAMD processors + +if MACH_ATSAMD + +config ATSAMD_SELECT + bool + default y + select HAVE_GPIO + select HAVE_GPIO_ADC + select HAVE_GPIO_I2C + select HAVE_GPIO_SPI + select HAVE_GPIO_HARD_PWM + select HAVE_GPIO_BITBANGING + +config BOARD_DIRECTORY + string + default "atsamd" + +choice + prompt "Processor model" + config MACH_SAMD21A + bool "SAMD21G18A (Arduino Zero)" +endchoice + +config CLOCK_FREQ + int + default 48000000 + +choice + prompt "Bootloader offset" + config FLASH_START_0000 + bool "No bootloader" + config FLASH_START_2000 + bool "8KiB bootloader (Arduino Zero)" + config FLASH_START_4000 + bool "16KiB bootloader (Arduino M0)" +endchoice + +config FLASH_START + hex + default 0x4000 if FLASH_START_4000 + default 0x2000 if FLASH_START_2000 + default 0x0000 + +config USBSERIAL + 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/atsamd/Makefile b/src/atsamd/Makefile new file mode 100644 index 00000000..ca8b2e99 --- /dev/null +++ b/src/atsamd/Makefile @@ -0,0 +1,54 @@ +# Additional atsamd build rules + +# Setup the toolchain +CROSS_PREFIX=arm-none-eabi- + +dirs-y += src/atsamd src/generic +dirs-$(CONFIG_MACH_SAMD21A) += lib/samd21/samd21a/gcc/gcc/ + +CFLAGS-$(CONFIG_MACH_SAMD21A) += -mcpu=cortex-m0plus +CFLAGS-$(CONFIG_MACH_SAMD21A) += -Ilib/samd21/samd21a/include -D__SAMD21G18A__ +CFLAGS += $(CFLAGS-y) -mthumb -Ilib/cmsis-core + +eflags-$(CONFIG_MACH_SAMD21A) += -T $(OUT)samd21a.ld +CFLAGS_klipper.elf += $(eflags-y) --specs=nano.specs --specs=nosys.specs + +# Add source files +src-y += atsamd/main.c atsamd/timer.c atsamd/clock.c atsamd/gpio.c +src-y += generic/crc16_ccitt.c generic/alloc.c +src-y += generic/armcm_irq.c generic/timer_irq.c +src-$(CONFIG_USBSERIAL) += atsamd/usbserial.c generic/usb_cdc.c +src-$(CONFIG_SERIAL) += atsamd/serial.c generic/serial_irq.c +src-$(CONFIG_HAVE_GPIO_ADC) += atsamd/adc.c +src-$(CONFIG_HAVE_GPIO_I2C) += atsamd/i2c.c +src-$(CONFIG_HAVE_GPIO_SPI) += atsamd/spi.c +src-$(CONFIG_HAVE_GPIO_HARD_PWM) += atsamd/hard_pwm.c +src-$(CONFIG_MACH_SAMD21A) += ../lib/samd21/samd21a/gcc/gcc/startup_samd21.c + +# Support bootloader offset address +target-$(CONFIG_MACH_SAMD21A) := $(OUT)samd21a.ld $(target-y) + +$(OUT)samd21a.ld: lib/samd21/samd21a/gcc/gcc/samd21g18a_flash.ld $(OUT)board-link + @echo " Preprocessing $@" + $(Q)$(CPP) -P -MD -MT $@ -DFLASH_START=$(CONFIG_FLASH_START) $< -o $@ + +# Build the additional hex and bin output files +target-y += $(OUT)klipper.bin $(OUT)klipper.elf.hex + +$(OUT)klipper.bin: $(OUT)klipper.elf + @echo " Creating hex file $@" + $(Q)$(OBJCOPY) -O binary $< $@ + +$(OUT)klipper.elf.hex: $(OUT)klipper.elf + @echo " Creating hex file $@" + $(Q)$(OBJCOPY) -j .text -j .relocate -O ihex $< $@ + +# 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 --offset=0x2000 -w $(OUT)klipper.bin -v -b -R diff --git a/src/atsamd/adc.c b/src/atsamd/adc.c new file mode 100644 index 00000000..38836592 --- /dev/null +++ b/src/atsamd/adc.c @@ -0,0 +1,121 @@ +// Analog to Digital Converter support +// +// 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" // gpio_adc_read +#include "internal.h" // GPIO +#include "samd21.h" // ADC +#include "sched.h" // sched_shutdown + +static const uint8_t adc_pins[] = { + GPIO('A', 2), GPIO('A', 3), GPIO('B', 8), GPIO('B', 9), GPIO('A', 4), + GPIO('A', 5), GPIO('A', 6), GPIO('A', 7), GPIO('B', 0), GPIO('B', 1), + GPIO('B', 2), GPIO('B', 3), GPIO('B', 4), GPIO('B', 5), GPIO('B', 6), + GPIO('B', 7), GPIO('A', 8), GPIO('A', 9), GPIO('A', 10), GPIO('A', 11) +}; + +DECL_CONSTANT(ADC_MAX, 4095); + +static void +adc_init(void) +{ + static uint8_t have_run_init; + if (have_run_init) + return; + have_run_init = 1; + + // Enable adc clock + enable_pclock(ADC_GCLK_ID, PM_APBCMASK_ADC); + + // Load calibraiton info + uint32_t v = *((uint32_t*)ADC_FUSES_BIASCAL_ADDR); + uint32_t bias = (v & ADC_FUSES_BIASCAL_Msk) >> ADC_FUSES_BIASCAL_Pos; + v = *((uint32_t*)ADC_FUSES_LINEARITY_0_ADDR); + uint32_t li0 = (v & ADC_FUSES_LINEARITY_0_Msk) >> ADC_FUSES_LINEARITY_0_Pos; + v = *((uint32_t*)ADC_FUSES_LINEARITY_1_ADDR); + uint32_t li5 = (v & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos; + uint32_t lin = li0 | (li5 << 5); + ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(lin); + + // Setup and enable adc + ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1; + ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV128; + ADC->SAMPCTRL.reg = 63; + ADC->CTRLA.reg = ADC_CTRLA_ENABLE; +} + +struct gpio_adc +gpio_adc_setup(uint8_t pin) +{ + // Find pin in adc_pins table + uint8_t chan; + for (chan=0; ; chan++) { + if (chan >= ARRAY_SIZE(adc_pins)) + shutdown("Not a valid ADC pin"); + if (adc_pins[chan] == pin) + break; + } + + // Enable ADC + adc_init(); + + // Set pin in ADC mode + gpio_peripheral(pin, 'B', 0); + + return (struct gpio_adc){ chan }; +} + +enum { ADC_DUMMY=0xff }; +static uint8_t last_analog_read = ADC_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) +{ + if (last_analog_read == g.chan) { + if (ADC->INTFLAG.reg & ADC_INTFLAG_RESRDY) + // Sample now ready + return 0; + // ADC is still busy + goto need_delay; + } + if (last_analog_read != ADC_DUMMY) + // Sample on another channel in progress + goto need_delay; + last_analog_read = g.chan; + + // Set the channel to sample + ADC->INPUTCTRL.reg = (ADC_INPUTCTRL_MUXPOS(g.chan) + | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_GAIN_DIV2); + + // Start the sample + ADC->SWTRIG.reg = ADC_SWTRIG_START; + + // Schedule next attempt after sample is likely to be complete +need_delay: + return 42 * 128 + 200; // 42 == 1 + (63+1)/2 + 1 + 12/2 + 1.5 +} + +// Read a value; use only after gpio_adc_sample() returns zero +uint16_t +gpio_adc_read(struct gpio_adc g) +{ + last_analog_read = ADC_DUMMY; + return ADC->RESULT.reg; +} + +// Cancel a sample that may have been started with gpio_adc_sample() +void +gpio_adc_cancel_sample(struct gpio_adc g) +{ + if (last_analog_read == g.chan) { + ADC->SWTRIG.reg = ADC_SWTRIG_FLUSH; + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + last_analog_read = ADC_DUMMY; + } +} diff --git a/src/atsamd/clock.c b/src/atsamd/clock.c new file mode 100644 index 00000000..ef0cd9bc --- /dev/null +++ b/src/atsamd/clock.c @@ -0,0 +1,77 @@ +// Code to setup peripheral clocks on the SAMD21 +// +// Copyright (C) 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 "compiler.h" // DIV_ROUND_CLOSEST +#include "internal.h" // enable_pclock +#include "samd21.h" // GCLK + +// The "generic clock generators" that are configured +#define CLKGEN_MAIN 0 +#define CLKGEN_32K 1 +#define CLKGEN_ULP32K 2 + +// Enable a peripheral clock and power to that peripheral +void +enable_pclock(uint32_t clock_id, uint32_t pmask) +{ + GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_ID(clock_id) + | GCLK_CLKCTRL_GEN(CLKGEN_MAIN) | GCLK_CLKCTRL_CLKEN); + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY) + ; + PM->APBCMASK.reg |= pmask; +} + +#define FREQ_XOSC32K 32768 + +void +SystemInit(void) +{ + // Setup flash to work with 48Mhz clock + NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_RWS_HALF; + + // Enable external 32Khz crystal + uint32_t val = (SYSCTRL_XOSC32K_STARTUP(6) + | SYSCTRL_XOSC32K_XTALEN | SYSCTRL_XOSC32K_EN32K); + SYSCTRL->XOSC32K.reg = val; + SYSCTRL->XOSC32K.reg = val | SYSCTRL_XOSC32K_ENABLE; + while (!(SYSCTRL->PCLKSR.reg & SYSCTRL_PCLKSR_XOSC32KRDY)) + ; + + // Reset GCLK + GCLK->CTRL.reg = GCLK_CTRL_SWRST; + while (GCLK->CTRL.reg & GCLK_CTRL_SWRST) + ; + + // Route external 32Khz clock to DFLL48M (via CLKGEN_32K) + GCLK->GENDIV.reg = GCLK_GENDIV_ID(CLKGEN_32K); + GCLK->GENCTRL.reg = (GCLK_GENCTRL_ID(CLKGEN_32K) + | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_GENEN); + GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_ID(SYSCTRL_GCLK_ID_DFLL48) + | GCLK_CLKCTRL_GEN(CLKGEN_32K) | GCLK_CLKCTRL_CLKEN); + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY) + ; + + // Configure DFLL48M clock + SYSCTRL->DFLLCTRL.reg = 0; + uint32_t mul = DIV_ROUND_CLOSEST(CONFIG_CLOCK_FREQ, FREQ_XOSC32K); + SYSCTRL->DFLLMUL.reg = (SYSCTRL_DFLLMUL_CSTEP(31) + | SYSCTRL_DFLLMUL_FSTEP(511) + | SYSCTRL_DFLLMUL_MUL(mul)); + SYSCTRL->DFLLCTRL.reg = (SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_WAITLOCK + | SYSCTRL_DFLLCTRL_QLDIS + | SYSCTRL_DFLLCTRL_ENABLE); + uint32_t ready = (SYSCTRL_PCLKSR_DFLLRDY | SYSCTRL_PCLKSR_DFLLLCKC + | SYSCTRL_PCLKSR_DFLLLCKF); + while ((SYSCTRL->PCLKSR.reg & ready) != ready) + ; + + // Switch main clock to DFLL48M clock + GCLK->GENDIV.reg = GCLK_GENDIV_ID(CLKGEN_MAIN); + GCLK->GENCTRL.reg = (GCLK_GENCTRL_ID(CLKGEN_MAIN) + | GCLK_GENCTRL_SRC_DFLL48M + | GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN); +} diff --git a/src/atsamd/gpio.c b/src/atsamd/gpio.c new file mode 100644 index 00000000..045dc003 --- /dev/null +++ b/src/atsamd/gpio.c @@ -0,0 +1,141 @@ +// samd21 gpio functions +// +// 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> // ffs +#include "board/irq.h" // irq_save +#include "command.h" // shutdown +#include "gpio.h" // gpio_out_setup +#include "internal.h" // gpio_peripheral +#include "samd21.h" // PORT +#include "sched.h" // sched_shutdown + + +/**************************************************************** + * Pin multiplexing + ****************************************************************/ + +void +gpio_peripheral(uint32_t gpio, char ptype, int32_t pull_up) +{ + uint32_t bank = GPIO2PORT(gpio), bit = gpio % 32; + PortGroup *pg = &PORT->Group[bank]; + if (ptype) { + volatile uint8_t *pmux = &pg->PMUX[bit/2].reg; + uint8_t shift = (bit & 1) ? 4 : 0, mask = ~(0xf << shift); + *pmux = (*pmux & mask) | ((ptype - 'A') << shift); + } + if (pull_up) { + if (pull_up > 0) + pg->OUTSET.reg = (1<<bit); + else + pg->OUTCLR.reg = (1<<bit); + } + + pg->PINCFG[bit].reg = ((ptype ? PORT_PINCFG_PMUXEN : 0) + | (pull_up ? PORT_PINCFG_PULLEN : 0)); +} + + +/**************************************************************** + * General Purpose Input Output (GPIO) pins + ****************************************************************/ + +#define NUM_PORT 2 + +struct gpio_out +gpio_out_setup(uint8_t pin, uint8_t val) +{ + if (GPIO2PORT(pin) >= NUM_PORT) + goto fail; + PortGroup *pg = &PORT->Group[GPIO2PORT(pin)]; + struct gpio_out g = { .regs=pg, .bit=GPIO2BIT(pin) }; + gpio_out_reset(g, val); + return g; +fail: + shutdown("Not an output pin"); +} + +static void +set_pincfg(PortGroup *pg, uint32_t bit, uint8_t cfg) +{ + pg->PINCFG[ffs(bit)-1].reg = cfg; +} + +void +gpio_out_reset(struct gpio_out g, uint8_t val) +{ + PortGroup *pg = g.regs; + irqstatus_t flag = irq_save(); + if (val) + pg->OUTSET.reg = g.bit; + else + pg->OUTCLR.reg = g.bit; + pg->DIRSET.reg = g.bit; + set_pincfg(pg, g.bit, 0); + irq_restore(flag); +} + +void +gpio_out_toggle_noirq(struct gpio_out g) +{ + PortGroup *pg = g.regs; + pg->OUTTGL.reg = g.bit; +} + +void +gpio_out_toggle(struct gpio_out g) +{ + gpio_out_toggle_noirq(g); +} + +void +gpio_out_write(struct gpio_out g, uint8_t val) +{ + PortGroup *pg = g.regs; + if (val) + pg->OUTSET.reg = g.bit; + else + pg->OUTCLR.reg = g.bit; +} + + +struct gpio_in +gpio_in_setup(uint8_t pin, int8_t pull_up) +{ + if (GPIO2PORT(pin) >= NUM_PORT) + goto fail; + PortGroup *pg = &PORT->Group[GPIO2PORT(pin)]; + struct gpio_in g = { .regs=pg, .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) +{ + PortGroup *pg = g.regs; + irqstatus_t flag = irq_save(); + uint32_t cfg = PORT_PINCFG_INEN; + if (pull_up) { + cfg |= PORT_PINCFG_PULLEN; + if (pull_up > 0) + pg->OUTSET.reg = g.bit; + else + pg->OUTCLR.reg = g.bit; + } + set_pincfg(pg, g.bit, cfg); + pg->DIRCLR.reg = g.bit; + irq_restore(flag); +} + +uint8_t +gpio_in_read(struct gpio_in g) +{ + PortGroup *pg = g.regs; + return !!(pg->IN.reg & g.bit); +} diff --git a/src/atsamd/gpio.h b/src/atsamd/gpio.h new file mode 100644 index 00000000..5bf44454 --- /dev/null +++ b/src/atsamd/gpio.h @@ -0,0 +1,55 @@ +#ifndef __SAM3X8E_GPIO_H +#define __SAM3X8E_GPIO_H + +#include <stdint.h> + +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_pwm { + void *reg; +}; +struct gpio_pwm gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val); +void gpio_pwm_write(struct gpio_pwm g, uint8_t val); + +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 { + uint32_t ctrla, baud; +}; +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 { + 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/atsamd/hard_pwm.c b/src/atsamd/hard_pwm.c new file mode 100644 index 00000000..7deecfb9 --- /dev/null +++ b/src/atsamd/hard_pwm.c @@ -0,0 +1,102 @@ +// Hardware PWM support on samd21 +// +// 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" // gpio_pwm_write +#include "internal.h" // GPIO +#include "samd21.h" // TCC0 +#include "sched.h" // sched_shutdown + +struct gpio_pwm_info { + uint32_t gpio; + Tcc *tcc; + uint32_t clock_id, power_id, channel; + char ptype; +}; + +static const struct gpio_pwm_info pwm_regs[] = { + { GPIO('A', 4), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 0, 'E' }, + { GPIO('A', 5), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 1, 'E' }, + { GPIO('A', 6), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 0, 'E' }, + { GPIO('A', 7), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 1, 'E' }, + { GPIO('A', 8), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 0, 'E' }, + { GPIO('A', 9), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 1, 'E' }, + { GPIO('A', 10), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 0, 'E' }, + { GPIO('A', 11), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 1, 'E' }, + { GPIO('A', 12), TCC2, TCC2_GCLK_ID, PM_APBCMASK_TCC2, 0, 'E' }, + { GPIO('A', 13), TCC2, TCC2_GCLK_ID, PM_APBCMASK_TCC2, 1, 'E' }, + { GPIO('A', 16), TCC2, TCC2_GCLK_ID, PM_APBCMASK_TCC2, 0, 'E' }, + { GPIO('A', 17), TCC2, TCC2_GCLK_ID, PM_APBCMASK_TCC2, 1, 'E' }, + { GPIO('A', 18), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 2, 'F' }, + { GPIO('A', 19), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 3, 'F' }, + { GPIO('A', 24), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 2, 'F' }, + { GPIO('A', 25), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 3, 'F' }, + { GPIO('A', 30), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 0, 'E' }, + { GPIO('A', 31), TCC1, TCC1_GCLK_ID, PM_APBCMASK_TCC1, 1, 'E' }, + { GPIO('B', 30), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 0, 'E' }, + { GPIO('B', 31), TCC0, TCC0_GCLK_ID, PM_APBCMASK_TCC0, 1, 'E' }, +}; + +#define MAX_PWM 255 + +DECL_CONSTANT(PWM_MAX, MAX_PWM); + +struct gpio_pwm +gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val) +{ + // Find pin in pwm_regs table + const struct gpio_pwm_info *p = pwm_regs; + for (; ; p++) { + if (p >= &pwm_regs[ARRAY_SIZE(pwm_regs)]) + shutdown("Not a valid PWM pin"); + if (p->gpio == pin) + break; + } + + // Enable timer clock + enable_pclock(p->clock_id, p->power_id); + + // Map cycle_time to pwm clock divisor + uint32_t cs; + switch (cycle_time) { + case 0 ... (1+2) * MAX_PWM / 2 - 1: cs = 0; break; + case (1+2) * MAX_PWM / 2 ... (2+4) * MAX_PWM / 2 - 1: cs = 1; break; + case (2+4) * MAX_PWM / 2 ... (4+8) * MAX_PWM / 2 - 1: cs = 2; break; + case (4+8) * MAX_PWM / 2 ... (8+16) * MAX_PWM / 2 - 1: cs = 3; break; + case (8+16) * MAX_PWM / 2 ... (16+64) * MAX_PWM / 2 - 1: cs = 4; break; + case (16+64) * MAX_PWM / 2 ... (64+256) * MAX_PWM / 2 - 1: cs = 5; break; + case (64+256) * MAX_PWM / 2 ... (256+1024) * MAX_PWM / 2 - 1: cs = 6; break; + default: cs = 7; break; + } + uint32_t ctrla = TCC_CTRLA_ENABLE | TCC_CTRLA_PRESCALER(cs); + + // Enable timer + Tcc *tcc = p->tcc; + uint32_t old_ctrla = tcc->CTRLA.reg; + if (old_ctrla != ctrla) { + if (old_ctrla & TCC_CTRLA_ENABLE) + shutdown("PWM already programmed at different speed"); + tcc->CTRLA.reg = ctrla & ~TCC_CTRLA_ENABLE; + tcc->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; + tcc->PER.reg = MAX_PWM; + tcc->CTRLA.reg = ctrla; + } + + // Set initial value + struct gpio_pwm g = (struct gpio_pwm) { (void*)&tcc->CCB[p->channel].reg }; + gpio_pwm_write(g, val); + + // Route output to pin + gpio_peripheral(pin, p->ptype, 0); + + return g; +} + +void +gpio_pwm_write(struct gpio_pwm g, uint8_t val) +{ + *(volatile uint32_t*)g.reg = val; +} diff --git a/src/atsamd/i2c.c b/src/atsamd/i2c.c new file mode 100644 index 00000000..801c0e32 --- /dev/null +++ b/src/atsamd/i2c.c @@ -0,0 +1,114 @@ +// i2c support on samd21 +// +// Copyright (C) 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 "internal.h" // enable_pclock +#include "command.h" // shutdown +#include "gpio.h" // i2c_setup +#include "samd21.h" // SERCOM3 +#include "sched.h" // sched_shutdown + +#define TIME_RISE 125ULL // 125 nanoseconds +#define I2C_FREQ 100000 + +static void +i2c_init(void) +{ + static int have_run_init; + if (have_run_init) + return; + have_run_init = 1; + + // Setup clock + enable_pclock(SERCOM3_GCLK_ID_CORE, PM_APBCMASK_SERCOM3); + + // Configure SDA, SCL pins + gpio_peripheral(GPIO('A', 22), 'C', 0); + gpio_peripheral(GPIO('A', 23), 'C', 0); + + // Configure i2c + SercomI2cm *si = &SERCOM3->I2CM; + si->CTRLA.reg = 0; + uint32_t areg = (SERCOM_I2CM_CTRLA_LOWTOUTEN + | SERCOM_I2CM_CTRLA_INACTOUT(3) + | SERCOM_I2CM_STATUS_SEXTTOUT + | SERCOM_I2CM_STATUS_MEXTTOUT + | SERCOM_I2CM_CTRLA_MODE_I2C_MASTER); + si->CTRLA.reg = areg; + uint32_t baud = (CONFIG_CLOCK_FREQ / I2C_FREQ + - 10 - CONFIG_CLOCK_FREQ*TIME_RISE/1000000000) / 2; + si->BAUD.reg = baud; + si->CTRLA.reg = areg | SERCOM_I2CM_CTRLA_ENABLE; + while (si->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_ENABLE) + ; + + // Go into idle mode + si->STATUS.reg = SERCOM_I2CM_STATUS_BUSSTATE(1); + while (si->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_SYSOP) + ; +} + +struct i2c_config +i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr) +{ + if (bus) + shutdown("Unsupported i2c bus"); + i2c_init(); + return (struct i2c_config){ .addr=addr<<1 }; +} + +static void +i2c_wait(SercomI2cm *si) +{ + for (;;) { + uint32_t intflag = si->INTFLAG.reg; + if (!(intflag & SERCOM_I2CM_INTFLAG_MB)) { + if (si->STATUS.reg & SERCOM_I2CM_STATUS_BUSERR) + shutdown("i2c buserror"); + continue; + } + if (intflag & SERCOM_I2CM_INTFLAG_ERROR) + shutdown("i2c error"); + break; + } +} + +static void +i2c_start(SercomI2cm *si, uint8_t addr) +{ + si->ADDR.reg = addr; + i2c_wait(si); +} + +static void +i2c_send_byte(SercomI2cm *si, uint8_t b) +{ + si->DATA.reg = b; + i2c_wait(si); +} + +static void +i2c_stop(SercomI2cm *si) +{ + si->CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(3); +} + +void +i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write) +{ + SercomI2cm *si = &SERCOM3->I2CM; + i2c_start(si, config.addr); + while (write_len--) + i2c_send_byte(si, *write++); + i2c_stop(si); +} + +void +i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg + , uint8_t read_len, uint8_t *read) +{ + shutdown("i2c_read not supported on samd21"); +} diff --git a/src/atsamd/internal.h b/src/atsamd/internal.h new file mode 100644 index 00000000..7043007f --- /dev/null +++ b/src/atsamd/internal.h @@ -0,0 +1,14 @@ +#ifndef __SAMD21_INTERNAL_H +#define __SAMD21_INTERNAL_H +// Local definitions for samd21 code + +#include <stdint.h> // uint32_t + +#define GPIO(PORT, NUM) (((PORT)-'A') * 32 + (NUM)) +#define GPIO2PORT(PIN) ((PIN) / 32) +#define GPIO2BIT(PIN) (1<<((PIN) % 32)) + +void enable_pclock(uint32_t clock_id, uint32_t pmask); +void gpio_peripheral(uint32_t gpio, char ptype, int32_t pull_up); + +#endif // internal.h diff --git a/src/atsamd/main.c b/src/atsamd/main.c new file mode 100644 index 00000000..4687fc34 --- /dev/null +++ b/src/atsamd/main.c @@ -0,0 +1,52 @@ +// Main starting point for SAMD21 boards +// +// 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" // DECL_CONSTANT +#include "samd21.h" // SystemInit +#include "sched.h" // sched_main + +DECL_CONSTANT(MCU, "samd21g"); + + +/**************************************************************** + * watchdog handler + ****************************************************************/ + +void +watchdog_reset(void) +{ + WDT->CLEAR.reg = 0xa5; +} +DECL_TASK(watchdog_reset); + +void +watchdog_init(void) +{ + WDT->CONFIG.reg = WDT_CONFIG_PER_16K; // 500ms timeout + WDT->CTRL.reg = WDT_CTRL_ENABLE; +} +DECL_INIT(watchdog_init); + + +/**************************************************************** + * misc functions + ****************************************************************/ + +void +command_reset(uint32_t *args) +{ + NVIC_SystemReset(); +} +DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "reset"); + +// Main entry point +int +main(void) +{ + SystemInit(); + sched_main(); + return 0; +} diff --git a/src/atsamd/serial.c b/src/atsamd/serial.c new file mode 100644 index 00000000..895ffb27 --- /dev/null +++ b/src/atsamd/serial.c @@ -0,0 +1,61 @@ +// samd21 serial port +// +// Copyright (C) 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" // enable_pclock +#include "samd21.h" // SERCOM0 +#include "sched.h" // DECL_INIT + +void +serial_init(void) +{ + // Enable serial clock + enable_pclock(SERCOM0_GCLK_ID_CORE, PM_APBCMASK_SERCOM0); + // Enable pins + gpio_peripheral(GPIO('A', 10), 'C', 0); + gpio_peripheral(GPIO('A', 11), 'C', 0); + // Configure serial + SercomUsart *su = &SERCOM0->USART; + su->CTRLA.reg = 0; + uint32_t areg = (SERCOM_USART_CTRLA_MODE_USART_INT_CLK + | SERCOM_USART_CTRLA_DORD + | SERCOM_USART_CTRLA_SAMPR(1) + | SERCOM_USART_CTRLA_TXPO(1) + | SERCOM_USART_CTRLA_RXPO(3)); + su->CTRLA.reg = areg; + su->CTRLB.reg = SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN; + uint32_t baud8 = CONFIG_CLOCK_FREQ / (2 * CONFIG_SERIAL_BAUD); + su->BAUD.reg = (SERCOM_USART_BAUD_FRAC_BAUD(baud8 / 8) + | SERCOM_USART_BAUD_FRAC_FP(baud8 % 8)); + NVIC_SetPriority(SERCOM0_IRQn, 0); + NVIC_EnableIRQ(SERCOM0_IRQn); + su->INTENSET.reg = SERCOM_USART_INTENSET_RXC; + su->CTRLA.reg = areg | SERCOM_USART_CTRLA_ENABLE; +} +DECL_INIT(serial_init); + +void __visible +SERCOM0_Handler(void) +{ + uint32_t status = SERCOM0->USART.INTFLAG.reg; + if (status & SERCOM_USART_INTFLAG_RXC) + serial_rx_byte(SERCOM0->USART.DATA.reg); + if (status & SERCOM_USART_INTFLAG_DRE) { + uint8_t data; + int ret = serial_get_tx_byte(&data); + if (ret) + SERCOM0->USART.INTENCLR.reg = SERCOM_USART_INTENSET_DRE; + else + SERCOM0->USART.DATA.reg = data; + } +} + +void +serial_enable_tx_irq(void) +{ + SERCOM0->USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE; +} diff --git a/src/atsamd/spi.c b/src/atsamd/spi.c new file mode 100644 index 00000000..12c7d680 --- /dev/null +++ b/src/atsamd/spi.c @@ -0,0 +1,92 @@ +// spi support on samd21 +// +// Copyright (C) 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 "internal.h" // enable_pclock +#include "command.h" // shutdown +#include "gpio.h" // spi_setup +#include "samd21.h" // SERCOM4 +#include "sched.h" // sched_shutdown + +static void +spi_init(uint32_t ctrla, uint32_t baud) +{ + static int have_run_init; + if (have_run_init) + return; + have_run_init = 1; + + // Setup clock + enable_pclock(SERCOM4_GCLK_ID_CORE, PM_APBCMASK_SERCOM4); + + // Configure MISO, MOSI, SCK pins + gpio_peripheral(GPIO('A', 12), 'D', 0); + gpio_peripheral(GPIO('B', 10), 'D', 0); + gpio_peripheral(GPIO('B', 11), 'D', 0); + + // Configure spi + SercomSpi *ss = &SERCOM4->SPI; + ss->CTRLA.reg = 0; + ss->CTRLA.reg = ctrla & ~SERCOM_SPI_CTRLA_ENABLE; + ss->CTRLB.reg = SERCOM_SPI_CTRLB_RXEN; + ss->BAUD.reg = baud; + ss->CTRLA.reg = ctrla; +} + +struct spi_config +spi_setup(uint32_t bus, uint8_t mode, uint32_t rate) +{ + if (bus) + shutdown("Invalid spi bus"); + + uint32_t ctrla = (SERCOM_SPI_CTRLA_MODE_SPI_MASTER + | (mode << SERCOM_SPI_CTRLA_CPHA_Pos) + | SERCOM_SPI_CTRLA_DIPO(0) + | SERCOM_SPI_CTRLA_DOPO(1) + | SERCOM_SPI_CTRLA_ENABLE); + uint32_t baud = CONFIG_CLOCK_FREQ / (2 * rate) - 1; + spi_init(ctrla, baud); + return (struct spi_config){ .ctrla = ctrla, .baud = baud }; +} + +void +spi_prepare(struct spi_config config) +{ + uint32_t ctrla = config.ctrla, baud = config.baud; + SercomSpi *ss = &SERCOM4->SPI; + if (ctrla == ss->CTRLA.reg && baud == ss->BAUD.reg) + return; + ss->CTRLA.reg = ctrla & ~SERCOM_SPI_CTRLA_ENABLE; + ss->CTRLA.reg = ctrla & ~SERCOM_SPI_CTRLA_ENABLE; + ss->BAUD.reg = baud; + ss->CTRLA.reg = ctrla; +} + +void +spi_transfer(struct spi_config config, uint8_t receive_data + , uint8_t len, uint8_t *data) +{ + SercomSpi *ss = &SERCOM4->SPI; + if (receive_data) { + while (len--) { + ss->DATA.reg = *data; + // wait for receive register + while (!(ss->INTFLAG.reg & SERCOM_SPI_INTFLAG_RXC)) + ; + // get data + *data++ = ss->DATA.reg; + } + } else { + while (len--) { + ss->DATA.reg = *data++; + // wait for receive register + while (!(ss->INTFLAG.reg & SERCOM_SPI_INTFLAG_RXC)) + ; + // read data (to clear RXC) + ss->DATA.reg; + } + } +} diff --git a/src/atsamd/timer.c b/src/atsamd/timer.c new file mode 100644 index 00000000..882b4c10 --- /dev/null +++ b/src/atsamd/timer.c @@ -0,0 +1,66 @@ +// SAMD21 timer interrupt scheduling +// +// Copyright (C) 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 "internal.h" // enable_pclock +#include "samd21.h" // TC4 +#include "sched.h" // DECL_INIT + +// Set the next irq time +static void +timer_set(uint32_t value) +{ + TC4->COUNT32.CC[0].reg = value; + TC4->COUNT32.INTFLAG.reg = TC_INTFLAG_MC0; +} + +// Return the current time (in absolute clock ticks). +uint32_t +timer_read_time(void) +{ + return TC4->COUNT32.COUNT.reg; +} + +// Activate timer dispatch as soon as possible +void +timer_kick(void) +{ + timer_set(timer_read_time() + 50); +} + +void +timer_init(void) +{ + // Supply power and clock to the timer + enable_pclock(TC3_GCLK_ID, PM_APBCMASK_TC3); + enable_pclock(TC4_GCLK_ID, PM_APBCMASK_TC4); + + // Configure the timer + TcCount32 *tc = &TC4->COUNT32; + irqstatus_t flag = irq_save(); + tc->CTRLA.reg = 0; + tc->CTRLA.reg = TC_CTRLA_MODE_COUNT32; + NVIC_SetPriority(TC4_IRQn, 2); + NVIC_EnableIRQ(TC4_IRQn); + tc->INTENSET.reg = TC_INTENSET_MC0; + tc->COUNT.reg = 0; + timer_kick(); + tc->CTRLA.reg = TC_CTRLA_MODE_COUNT32 | TC_CTRLA_ENABLE; + irq_restore(flag); +} +DECL_INIT(timer_init); + +// IRQ handler +void __visible __aligned(16) // aligning helps stabilize perf benchmarks +TC4_Handler(void) +{ + irq_disable(); + uint32_t next = timer_dispatch_many(); + timer_set(next); + irq_enable(); +} diff --git a/src/atsamd/usbserial.c b/src/atsamd/usbserial.c new file mode 100644 index 00000000..0864a4c5 --- /dev/null +++ b/src/atsamd/usbserial.c @@ -0,0 +1,246 @@ +// Hardware interface to USB on samd21 +// +// 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> // memcpy +#include "autoconf.h" // CONFIG_FLASH_START +#include "board/io.h" // readl +#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" // enable_pclock +#include "samd21.h" // USB +#include "sched.h" // DECL_INIT + + +/**************************************************************** + * USB transfer memory + ****************************************************************/ + +static uint8_t __aligned(4) ep0out[USB_CDC_EP0_SIZE]; +static uint8_t __aligned(4) ep0in[USB_CDC_EP0_SIZE]; +static uint8_t __aligned(4) acmin[USB_CDC_EP_ACM_SIZE]; +static uint8_t __aligned(4) bulkout[USB_CDC_EP_BULK_OUT_SIZE]; +static uint8_t __aligned(4) bulkin[USB_CDC_EP_BULK_IN_SIZE]; + +static UsbDeviceDescriptor usb_desc[USB_CDC_EP_BULK_IN + 1] = { + [0] = { { + { + .ADDR.reg = (uint32_t)ep0out, + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(ep0out) >> 4), + }, { + .ADDR.reg = (uint32_t)ep0in, + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(ep0in) >> 4), + }, + } }, + [USB_CDC_EP_ACM] = { { + { + }, { + .ADDR.reg = (uint32_t)acmin, + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(acmin) >> 4), + }, + } }, + [USB_CDC_EP_BULK_OUT] = { { + { + .ADDR.reg = (uint32_t)bulkout, + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(bulkout) >> 4), + }, { + }, + } }, + [USB_CDC_EP_BULK_IN] = { { + { + }, { + .ADDR.reg = (uint32_t)bulkin, + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(bulkin) >> 4), + }, + } }, +}; + + +/**************************************************************** + * Interface + ****************************************************************/ + +#define EP0 USB->DEVICE.DeviceEndpoint[0] +#define EP_ACM USB->DEVICE.DeviceEndpoint[USB_CDC_EP_ACM] +#define EP_BULKOUT USB->DEVICE.DeviceEndpoint[USB_CDC_EP_BULK_OUT] +#define EP_BULKIN USB->DEVICE.DeviceEndpoint[USB_CDC_EP_BULK_IN] + +static int_fast8_t +usb_write_packet(uint32_t ep, uint32_t bank, const void *data, uint_fast8_t len) +{ + // Check if there is room for this packet + UsbDeviceEndpoint *ude = &USB->DEVICE.DeviceEndpoint[ep]; + uint8_t sts = ude->EPSTATUS.reg; + uint8_t bkrdy = (bank ? USB_DEVICE_EPSTATUS_BK1RDY + : USB_DEVICE_EPSTATUS_BK0RDY); + if (sts & bkrdy) + return -1; + // Copy the packet to the given buffer + UsbDeviceDescBank *uddb = &usb_desc[ep].DeviceDescBank[bank]; + memcpy((void*)uddb->ADDR.reg, data, len); + // Inform the USB hardware of the available packet + uint32_t pcksize = uddb->PCKSIZE.reg; + uint32_t c = pcksize & ~USB_DEVICE_PCKSIZE_BYTE_COUNT_Msk; + uddb->PCKSIZE.reg = c | USB_DEVICE_PCKSIZE_BYTE_COUNT(len); + ude->EPSTATUSSET.reg = bkrdy; + return len; +} + +static int_fast8_t +usb_read_packet(uint32_t ep, uint32_t bank, void *data, uint_fast8_t max_len) +{ + // Check if there is a packet ready + UsbDeviceEndpoint *ude = &USB->DEVICE.DeviceEndpoint[ep]; + uint8_t sts = ude->EPSTATUS.reg; + uint8_t bkrdy = (bank ? USB_DEVICE_EPSTATUS_BK1RDY + : USB_DEVICE_EPSTATUS_BK0RDY); + if (!(sts & bkrdy)) + return -1; + // Copy the packet to the given buffer + UsbDeviceDescBank *uddb = &usb_desc[ep].DeviceDescBank[bank]; + uint32_t pcksize = uddb->PCKSIZE.reg; + uint32_t c = pcksize & USB_DEVICE_PCKSIZE_BYTE_COUNT_Msk; + if (c > max_len) + c = max_len; + memcpy(data, (void*)uddb->ADDR.reg, c); + // Notify the USB hardware that the space is now available + ude->EPSTATUSCLR.reg = bkrdy; + return c; +} + +int_fast8_t +usb_read_bulk_out(void *data, uint_fast8_t max_len) +{ + return usb_read_packet(USB_CDC_EP_BULK_OUT, 0, data, max_len); +} + +int_fast8_t +usb_send_bulk_in(void *data, uint_fast8_t len) +{ + return usb_write_packet(USB_CDC_EP_BULK_IN, 1, data, len); +} + +int_fast8_t +usb_read_ep0(void *data, uint_fast8_t max_len) +{ + return usb_read_packet(0, 0, data, max_len); +} + +int_fast8_t +usb_read_ep0_setup(void *data, uint_fast8_t max_len) +{ + return usb_read_ep0(data, max_len); +} + +int_fast8_t +usb_send_ep0(const void *data, uint_fast8_t len) +{ + return usb_write_packet(0, 1, data, len); +} + +void +usb_stall_ep0(void) +{ + EP0.EPSTATUSSET.reg = USB_DEVICE_EPSTATUS_STALLRQ(3); +} + +static uint8_t set_address; + +void +usb_set_address(uint_fast8_t addr) +{ + writeb(&set_address, addr | USB_DEVICE_DADD_ADDEN); + usb_send_ep0(NULL, 0); +} + +void +usb_set_configure(void) +{ + EP_ACM.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE1(4); + + EP_BULKOUT.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(3); + EP_BULKOUT.EPINTENSET.reg = ( + USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1); + + EP_BULKIN.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE1(3); + EP_BULKIN.EPINTENSET.reg = ( + USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1); +} + +void +usb_request_bootloader(void) +{ + if (CONFIG_FLASH_START) { + // Arduino Zero bootloader hack + irq_disable(); + writel((void*)0x20007FFC, 0x07738135); + NVIC_SystemReset(); + } +} + +void +usbserial_init(void) +{ + // configure usb clock + enable_pclock(USB_GCLK_ID, 0); + // configure USBD+ and USBD- pins + gpio_peripheral(GPIO('A', 24), 'G', 0); + gpio_peripheral(GPIO('A', 25), 'G', 0); + uint16_t trim = (readl((void*)USB_FUSES_TRIM_ADDR) + & USB_FUSES_TRIM_Msk) >> USB_FUSES_TRIM_Pos; + uint16_t transp = (readl((void*)USB_FUSES_TRANSP_ADDR) + & USB_FUSES_TRANSP_Msk) >> USB_FUSES_TRANSP_Pos; + uint16_t transn = (readl((void*)USB_FUSES_TRANSN_ADDR) + & USB_FUSES_TRANSN_Msk) >> USB_FUSES_TRANSN_Pos; + USB->DEVICE.PADCAL.reg = (USB_PADCAL_TRIM(trim) | USB_PADCAL_TRANSP(transp) + | USB_PADCAL_TRANSN(transn)); + // Enable USB in device mode + USB->DEVICE.CTRLA.reg = USB_CTRLA_ENABLE; + USB->DEVICE.DESCADD.reg = (uint32_t)usb_desc; + EP0.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(1) | USB_DEVICE_EPCFG_EPTYPE1(1); + EP_ACM.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE1(4); + USB->DEVICE.CTRLB.reg = 0; + // enable irqs + USB->DEVICE.INTENSET.reg = USB_DEVICE_INTENSET_EORST; + NVIC_SetPriority(USB_IRQn, 1); + NVIC_EnableIRQ(USB_IRQn); +} +DECL_INIT(usbserial_init); + +void __visible +USB_Handler(void) +{ + uint8_t s = USB->DEVICE.INTFLAG.reg; + if (s & USB_DEVICE_INTFLAG_EORST) { + USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_EORST; + // Enable endpoint 0 irqs + EP0.EPINTENSET.reg = ( + USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1 + | USB_DEVICE_EPINTENSET_RXSTP); + } + + uint16_t ep = USB->DEVICE.EPINTSMRY.reg; + if (ep & (1<<0)) { + uint8_t sts = EP0.EPINTFLAG.reg; + EP0.EPINTFLAG.reg = sts; + if (set_address && sts & USB_DEVICE_EPINTFLAG_TRCPT1) { + // Apply address after last "in" message transmitted + USB->DEVICE.DADD.reg = set_address; + set_address = 0; + } + usb_notify_ep0(); + } + if (ep & (1<<USB_CDC_EP_BULK_OUT)) { + uint8_t sts = EP_BULKOUT.EPINTFLAG.reg; + EP_BULKOUT.EPINTFLAG.reg = sts; + usb_notify_bulk_out(); + } + if (ep & (1<<USB_CDC_EP_BULK_IN)) { + uint8_t sts = EP_BULKIN.EPINTFLAG.reg; + EP_BULKIN.EPINTFLAG.reg = sts; + usb_notify_bulk_in(); + } +} |