diff --git a/src/samd21/Kconfig b/src/samd21/Kconfig index a3d4d56d..3b9b796a 100644 --- a/src/samd21/Kconfig +++ b/src/samd21/Kconfig @@ -6,6 +6,7 @@ config SAMD_SELECT bool default y select HAVE_GPIO + select HAVE_GPIO_I2C select HAVE_GPIO_BITBANGING config BOARD_DIRECTORY diff --git a/src/samd21/Makefile b/src/samd21/Makefile index c71a22df..d1f72fb2 100644 --- a/src/samd21/Makefile +++ b/src/samd21/Makefile @@ -20,6 +20,7 @@ src-y += generic/armcm_irq.c generic/timer_irq.c src-y += ../lib/samd21/samd21a/gcc/gcc/startup_samd21.c src-$(CONFIG_USBSERIAL) += samd21/usbserial.c generic/usb_cdc.c src-$(CONFIG_SERIAL) += samd21/serial.c generic/serial_irq.c +src-$(CONFIG_HAVE_GPIO_I2C) += samd21/i2c.c # Support bootloader offset address target-y := $(OUT)samd21.ld $(target-y) diff --git a/src/samd21/gpio.h b/src/samd21/gpio.h index ebdeb412..f423f8ff 100644 --- a/src/samd21/gpio.h +++ b/src/samd21/gpio.h @@ -21,4 +21,13 @@ 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 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/samd21/i2c.c b/src/samd21/i2c.c new file mode 100644 index 00000000..696cba8a --- /dev/null +++ b/src/samd21/i2c.c @@ -0,0 +1,114 @@ +// i2c support on samd21 +// +// Copyright (C) 2018 Kevin O'Connor +// +// 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 }; +} + +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"); +}