diff --git a/multi/stm32l4-multi/i2c.c b/multi/stm32l4-multi/i2c.c index b297fa32..10410b12 100644 --- a/multi/stm32l4-multi/i2c.c +++ b/multi/stm32l4-multi/i2c.c @@ -101,6 +101,20 @@ ssize_t i2c_writeReg(int i2c, unsigned char addr, unsigned char reg, const void } +int i2c_setSpeed(int i2c, uint32_t speed, int rise_time) +{ + if ((N_I2C_ACTIVE == 0) || (i2c < i2c1) || (i2c > MAX_I2C) || (i2cConfig[i2c] == 0)) { + return -EINVAL; + } + + mutexLock(i2c_lock[i2cPos[i2c]]); + int ret = libi2c_setSpeed(&i2c_ctx[i2cPos[i2c]], speed, rise_time); + mutexUnlock(i2c_lock[i2cPos[i2c]]); + + return ret; +} + + void i2c_init(void) { int i2c; diff --git a/multi/stm32l4-multi/i2c.h b/multi/stm32l4-multi/i2c.h index dd826aca..c0a12a2c 100644 --- a/multi/stm32l4-multi/i2c.h +++ b/multi/stm32l4-multi/i2c.h @@ -31,6 +31,9 @@ ssize_t i2c_write(int i2c, unsigned char addr, const void *buff, size_t len); ssize_t i2c_writeReg(int i2c, unsigned char addr, unsigned char reg, const void *buff, size_t len); +int i2c_setSpeed(int i2c, uint32_t speed, int rise_time); + + void i2c_init(void); diff --git a/multi/stm32l4-multi/libmulti/include/libmulti/libi2c.h b/multi/stm32l4-multi/libmulti/include/libmulti/libi2c.h index 462ac08d..b0a5b332 100644 --- a/multi/stm32l4-multi/libmulti/include/libmulti/libi2c.h +++ b/multi/stm32l4-multi/libmulti/include/libmulti/libi2c.h @@ -23,6 +23,7 @@ typedef struct { volatile unsigned int *base; volatile int err; int clk; + uint32_t refclk_freq; handle_t irqlock; handle_t irqcond; @@ -32,6 +33,13 @@ typedef struct { enum { i2c1 = 0, i2c2, i2c3, i2c4 }; +enum libi2c_speed { + libi2c_speed_standard = 0, /* Standard mode, 100 kHz */ + libi2c_speed_fast, /* Fast mode, 400 kHz */ + libi2c_speed_fastplus, /* Fast Plus mode, 1 MHz */ +}; + + ssize_t libi2c_read(libi2c_ctx_t *ctx, unsigned char addr, void *buff, size_t len); @@ -44,6 +52,9 @@ ssize_t libi2c_write(libi2c_ctx_t *ctx, unsigned char addr, const void *buff, si ssize_t libi2c_writeReg(libi2c_ctx_t *ctx, unsigned char addr, unsigned char reg, const void *buff, size_t len); +int libi2c_setSpeed(libi2c_ctx_t *ctx, enum libi2c_speed speed, int rise_time); + + int libi2c_init(libi2c_ctx_t *ctx, int i2c); diff --git a/multi/stm32l4-multi/libmulti/libi2c.c b/multi/stm32l4-multi/libmulti/libi2c.c index ad864cf5..3b88dc8c 100644 --- a/multi/stm32l4-multi/libmulti/libi2c.c +++ b/multi/stm32l4-multi/libmulti/libi2c.c @@ -25,6 +25,13 @@ #define TIMEOUT (100 * 1000) +#define ANALOG_NOISE_FILTER 0 +/* Regardless of reference clock frequency, the low period seems to be 3 cycles too long and high period 2 cycles too long. + * These extra delays were measured empirically, they are not mentioned in the documentation nor in the errata, + * and according to docs it seems they should not be necessary at all. */ +#define SCLL_EXTRA_DELAY 3 +#define SCLH_EXTRA_DELAY 2 + static const struct libi2c_peripheralInfo { void *base; @@ -276,14 +283,138 @@ ssize_t libi2c_writeReg(libi2c_ctx_t *ctx, unsigned char addr, unsigned char reg } +static uint32_t divide_ceil(uint32_t n, uint32_t d) +{ + uint32_t res = (n + d - 1) / d; + return (res == 0) ? 1 : res; +} + +/* Calculate values for TIMINGR register and DNF field of CR1 register. + * `refclk` - I2C reference clock in Hz + * `speed` - I2C speed mode + * `rise_time` - Rise time compensation. + * Should be set to time taken by the rising edge of SCLK to reach 70% amplitude. + * The unit is 1/16 microseconds (62.5 ns). + * If rise time is unknown, set to 0 - the clock speed will be in spec, but may be slower than optimal. + * Can be set to < 0 to slow down the clock more than required by spec. + * `timingr_val` - output for TIMINGR + * `digifilter` - output for DNF field + */ +static int libi2c_calculateTiming(uint32_t refclk, enum libi2c_speed speed, int rise_time, uint32_t *timingr_val, uint32_t *digifilter) +{ + /* In constants below, each tick represents 0.0625 us, corresponding to frequency of 16 MHz */ + /* Ticks per one clock period (from I2C protocol documentation) */ + static const uint8_t lookup_tCLK[] = { + [libi2c_speed_standard] = 160, /* 10 us => 100 kHz */ + [libi2c_speed_fast] = 40, /* 2.5 us => 400 kHz */ + [libi2c_speed_fastplus] = 16, /* 1 us => 1 MHz */ + }; + /* Ticks to hold high state (from I2C protocol documentation) */ + static const uint8_t lookup_tHIGH[] = { + [libi2c_speed_standard] = 65, /* 4 us minimum + 0.0625 safety margin */ + [libi2c_speed_fast] = 11, /* 0.6 us minimum + 0.0625 safety margin */ + [libi2c_speed_fastplus] = 5, /* 0.25 us minimum + 0.0625 safety margin */ + }; + /* Data set-up time (tSU;DAT from I2C protocol documentation) */ + static const uint8_t lookup_tSCLDEL[] = { + [libi2c_speed_standard] = 4, /* 0.25 us */ + [libi2c_speed_fast] = 2, /* 0.125 us >= 0.1 us minimum */ + [libi2c_speed_fastplus] = 1, /* 0.625 us >= 0.05 us minimum */ + }; + /* Analog noise filter delays clock transitions by another 0.0625 us. */ + static const uint32_t tANF = (ANALOG_NOISE_FILTER != 0) ? 1 : 0; + + if ((speed < libi2c_speed_standard) || (speed > libi2c_speed_fastplus)) { + return -EINVAL; + } + + /* Digital noise filter period - 0.125 us */ + uint32_t tDNF = 2; + + const int low_subtract = lookup_tHIGH[speed] + rise_time + tANF; + uint32_t tSCLL = (lookup_tCLK[speed] > low_subtract) ? (lookup_tCLK[speed] - low_subtract) : 1; + uint32_t tSCLH = lookup_tHIGH[speed] - tANF; + uint32_t tSCLDEL = lookup_tSCLDEL[speed]; + uint32_t prescaler = 1; + + /* Round the clock frequency to 1 MHz */ + uint32_t m = divide_ceil(refclk, 1000000); + /* If actual frequency is different from our "base frequency", scale the results. */ + if (m != 16) { + tDNF = divide_ceil(tDNF * m, 16); + if (tDNF > 15) { + tDNF = 15; + } + + tSCLDEL = divide_ceil(tSCLDEL * m, 16); + tSCLL = divide_ceil(tSCLL * m, 16); + tSCLH = divide_ceil(tSCLH * m, 16); + } + + tSCLL = (tSCLL > (tDNF + SCLL_EXTRA_DELAY)) ? (tSCLL - (tDNF + SCLL_EXTRA_DELAY)) : 1; + tSCLH = (tSCLH > (tDNF + SCLH_EXTRA_DELAY)) ? (tSCLH - (tDNF + SCLH_EXTRA_DELAY)) : 1; + + /* tSCLDEL has a range of [1:16], but we can save some calculations by scaling it up to [16:256] + * just for the purpose of calculating prescaler. */ + uint32_t longest = max(tSCLDEL * 16, max(tSCLL, tSCLH)); + if (longest > 256) { + prescaler = divide_ceil(longest, 256); + } + + if (prescaler > 16) { + /* Input frequency too fast */ + return -EINVAL; + } + + if (prescaler > 1) { + tSCLDEL = divide_ceil(tSCLDEL, prescaler); + tSCLL = divide_ceil(tSCLL, prescaler); + tSCLH = divide_ceil(tSCLH, prescaler); + } + + *timingr_val = + (((prescaler - 1) & 0xf) << 28) | + (((tSCLDEL - 1) & 0xf) << 20) | + (((tSCLH - 1) & 0xff) << 8) | + (((tSCLL - 1) & 0xff) << 0); + *digifilter = tDNF; + return 0; +} + + +static int _libi2c_setSpeedInternal(libi2c_ctx_t *ctx, enum libi2c_speed speed, int rise_time) +{ + int ret; + uint32_t timingr_val, digifilter; + dataBarier(); + ret = libi2c_calculateTiming(ctx->refclk_freq, speed, rise_time, &timingr_val, &digifilter); + if (ret < 0) { + return ret; + } + + uint32_t t = *(ctx->base + cr1) & ~(0xf << 8); + *(ctx->base + cr1) = t | (digifilter << 8); + *(ctx->base + timingr) = timingr_val; + dataBarier(); + return 0; +} + + +int libi2c_setSpeed(libi2c_ctx_t *ctx, enum libi2c_speed speed, int rise_time) +{ + devClk(ctx->clk, 1); + int ret = _libi2c_setSpeedInternal(ctx, speed, rise_time); + devClk(ctx->clk, 0); + return ret; +} + + int libi2c_init(libi2c_ctx_t *ctx, int i2c) { - int presc; - uint32_t refclk; unsigned int t; if (i2c < i2c1 || i2c > i2c4 || ctx == NULL) { - return -1; + return -EINVAL; } i2c -= i2c1; @@ -292,8 +423,8 @@ int libi2c_init(libi2c_ctx_t *ctx, int i2c) ctx->clk = i2cinfo[i2c].clk; devClk(ctx->clk, 1); - if (libi2c_clockSetup(&i2cinfo[i2c], &refclk) < 0) { - return -1; + if (libi2c_clockSetup(&i2cinfo[i2c], &ctx->refclk_freq) < 0) { + return -EIO; } mutexCreate(&ctx->irqlock); @@ -306,14 +437,11 @@ int libi2c_init(libi2c_ctx_t *ctx, int i2c) dataBarier(); t = *(ctx->base + cr1) & ~0xfffdff; - *(ctx->base + cr1) = t | (1 << 12) | (0x7 << 8) | (1 << 7) | (1 << 6) | (1 << 4) | (1 << 2) | (1 << 1); - - presc = ((refclk + 500 * 1000) / (1000 * 1000)) / 4; - t = *(ctx->base + timingr) & ~((0xf << 18) | 0xffffff); - *(ctx->base + timingr) = t | (((presc & 0xf) << 28) | (0x4 << 20) | (0x2 << 16) | (0xf << 8) | 0x13); - dataBarier(); - + /* Enable analog noise filter (if requested) and interrupts (ERR, TC, NACK, RX, TX). + * Note: Analog noise filter is 1 to disable. */ + *(ctx->base + cr1) = t | ((ANALOG_NOISE_FILTER != 0) ? 0 : (1 << 12)) | (1 << 7) | (1 << 6) | (1 << 4) | (1 << 2) | (1 << 1); + int ret = _libi2c_setSpeedInternal(ctx, libi2c_speed_standard, 0); devClk(ctx->clk, 0); - return 0; + return ret; } diff --git a/multi/stm32l4-multi/stm32l4-multi.c b/multi/stm32l4-multi/stm32l4-multi.c index e775de1a..dfbef81f 100644 --- a/multi/stm32l4-multi/stm32l4-multi.c +++ b/multi/stm32l4-multi/stm32l4-multi.c @@ -119,6 +119,10 @@ static void handleMsg(msg_t *msg) err = i2c_writeReg(imsg->i2c_msg.i2c, imsg->i2c_msg.addr, imsg->i2c_msg.reg, msg->i.data, msg->i.size); break; + case i2c_def: + err = i2c_setSpeed(imsg->i2c_def.i2c, imsg->i2c_def.speed, imsg->i2c_def.riseTime); + break; + case exti_def: err = exti_configure(imsg->exti_def.line, imsg->exti_def.mode, imsg->exti_def.edge); break; diff --git a/multi/stm32l4-multi/stm32l4-multi.h b/multi/stm32l4-multi/stm32l4-multi.h index 8e3a183a..bef6856d 100644 --- a/multi/stm32l4-multi/stm32l4-multi.h +++ b/multi/stm32l4-multi/stm32l4-multi.h @@ -29,7 +29,8 @@ enum { adc_get = 0, rtc_setcal, rtc_get, rtc_set, rtc_setalarm, i2c_get, i2c_get i2c_set, i2c_setwreg, gpio_def, gpio_get, gpio_set, uart_def, uart_get, uart_set, flash_get, flash_set, flash_info, spi_get, spi_set, spi_rw, spi_def, exti_def, exti_map, otp_get, otp_set, rtc_setBackup, rtc_getBackup, flash_setRaw, flash_erase, - rng_get, pwm_def, pwm_setm, pwm_getm, pwm_getfreq, pwm_distim, pwm_dischn, pwm_bitseq }; + rng_get, pwm_def, pwm_setm, pwm_getm, pwm_getfreq, pwm_distim, pwm_dischn, pwm_bitseq, + i2c_def }; /* clang-format on */ /* RTC */ @@ -58,6 +59,13 @@ typedef struct { } __attribute__((packed)) i2cmsg_t; +typedef struct { + int i2c; + unsigned char speed; + int riseTime; +} __attribute__((packed)) i2cdef_t; + + /* GPIO */ @@ -292,6 +300,7 @@ typedef struct { int rtc_calib; rtctimestamp_t rtc_timestamp; i2cmsg_t i2c_msg; + i2cdef_t i2c_def; uartget_t uart_get; uartset_t uart_set; uartdef_t uart_def;