src: Rename source folders for atsam and atsamd architectures
Signed-off-by: Florian Heilmann <Florian.Heilmann@gmx.net>
This commit is contained in:
committed by
KevinOConnor
parent
432e6c490a
commit
6256599a6d
57
src/atsamd/Kconfig
Normal file
57
src/atsamd/Kconfig
Normal file
@@ -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
|
||||
54
src/atsamd/Makefile
Normal file
54
src/atsamd/Makefile
Normal file
@@ -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
|
||||
121
src/atsamd/adc.c
Normal file
121
src/atsamd/adc.c
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
77
src/atsamd/clock.c
Normal file
77
src/atsamd/clock.c
Normal file
@@ -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);
|
||||
}
|
||||
141
src/atsamd/gpio.c
Normal file
141
src/atsamd/gpio.c
Normal file
@@ -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);
|
||||
}
|
||||
55
src/atsamd/gpio.h
Normal file
55
src/atsamd/gpio.h
Normal file
@@ -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
|
||||
102
src/atsamd/hard_pwm.c
Normal file
102
src/atsamd/hard_pwm.c
Normal file
@@ -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;
|
||||
}
|
||||
114
src/atsamd/i2c.c
Normal file
114
src/atsamd/i2c.c
Normal file
@@ -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");
|
||||
}
|
||||
14
src/atsamd/internal.h
Normal file
14
src/atsamd/internal.h
Normal file
@@ -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
|
||||
52
src/atsamd/main.c
Normal file
52
src/atsamd/main.c
Normal file
@@ -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;
|
||||
}
|
||||
61
src/atsamd/serial.c
Normal file
61
src/atsamd/serial.c
Normal file
@@ -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;
|
||||
}
|
||||
92
src/atsamd/spi.c
Normal file
92
src/atsamd/spi.c
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/atsamd/timer.c
Normal file
66
src/atsamd/timer.c
Normal file
@@ -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();
|
||||
}
|
||||
246
src/atsamd/usbserial.c
Normal file
246
src/atsamd/usbserial.c
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user