src: Rename source folders for atsam and atsamd architectures

Signed-off-by: Florian Heilmann <Florian.Heilmann@gmx.net>
This commit is contained in:
Florian Heilmann
2019-01-13 02:14:50 +01:00
committed by KevinOConnor
parent 432e6c490a
commit 6256599a6d
37 changed files with 84 additions and 77 deletions

57
src/atsamd/Kconfig Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();
}
}