From e51734823207aaae7ec282ccfd2a8af1281c690b Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Sun, 8 Aug 2021 01:09:25 +0300 Subject: [PATCH] feat(i2c): implement i2c master --- src/index.ts | 1 + src/peripherals/i2c.ts | 485 +++++++++++++++++++++++++++++++++++++++++ src/rp2040.ts | 6 +- src/utils/fifo.spec.ts | 25 +++ src/utils/fifo.ts | 14 ++ 5 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 src/peripherals/i2c.ts diff --git a/src/index.ts b/src/index.ts index 11d90cd..70b4a32 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export { GDBConnection } from './gdb/gdb-connection'; export { GDBServer } from './gdb/gdb-server'; export { GPIOPin, GPIOPinState } from './gpio-pin'; export { BasePeripheral, Peripheral } from './peripherals/peripheral'; +export { RPI2C, I2CSpeed, I2CMode } from './peripherals/i2c'; export { RPUSBController } from './peripherals/usb'; export { RP2040 } from './rp2040'; export { USBCDC } from './usb/cdc'; diff --git a/src/peripherals/i2c.ts b/src/peripherals/i2c.ts new file mode 100644 index 0000000..22c3513 --- /dev/null +++ b/src/peripherals/i2c.ts @@ -0,0 +1,485 @@ +import { RP2040 } from '../rp2040'; +import { FIFO } from '../utils/fifo'; +import { BasePeripheral, Peripheral } from './peripheral'; + +const IC_CON = 0x00; // I2C Control Register +const IC_TAR = 0x04; // I2C Target Address Register +const IC_SAR = 0x08; // I2C Slave Address Register +const IC_DATA_CMD = 0x10; // I2C Rx/Tx Data Buffer and Command Register +const IC_SS_SCL_HCNT = 0x14; // Standard Speed I2C Clock SCL High Count Register +const IC_SS_SCL_LCNT = 0x18; // Standard Speed I2C Clock SCL Low Count Register +const IC_FS_SCL_HCNT = 0x1c; // Fast Mode or Fast Mode Plus I2C Clock SCL High Count Register +const IC_FS_SCL_LCNT = 0x20; // Fast Mode or Fast Mode Plus I2C Clock SCL Low Count Register +const IC_INTR_STAT = 0x2c; // I2C Interrupt Status Register +const IC_INTR_MASK = 0x30; // I2C Interrupt Mask Register +const IC_RAW_INTR_STAT = 0x34; // I2C Raw Interrupt Status Register +const IC_RX_TL = 0x38; // I2C Receive FIFO Threshold Register +const IC_TX_TL = 0x3c; // I2C Transmit FIFO Threshold Register +const IC_CLR_INTR = 0x40; // Clear Combined and Individual Interrupt Register +const IC_CLR_RX_UNDER = 0x44; // Clear RX_UNDER Interrupt Register +const IC_CLR_RX_OVER = 0x48; // Clear RX_OVER Interrupt Register +const IC_CLR_TX_OVER = 0x4c; // Clear TX_OVER Interrupt Register +const IC_CLR_RD_REQ = 0x50; // Clear RD_REQ Interrupt Register +const IC_CLR_TX_ABRT = 0x54; // Clear TX_ABRT Interrupt Register +const IC_CLR_RX_DONE = 0x58; // Clear RX_DONE Interrupt Register +const IC_CLR_ACTIVITY = 0x5c; // Clear ACTIVITY Interrupt Register +const IC_CLR_STOP_DET = 0x60; // Clear STOP_DET Interrupt Register +const IC_CLR_START_DET = 0x64; // Clear START_DET Interrupt Register +const IC_CLR_GEN_CALL = 0x68; // Clear GEN_CALL Interrupt Register +const IC_ENABLE = 0x6c; // I2C ENABLE Register +const IC_STATUS = 0x70; // I2C STATUS Register +const IC_TXFLR = 0x74; // I2C Transmit FIFO Level Register +const IC_RXFLR = 0x78; // I2C Receive FIFO Level Register +const IC_SDA_HOLD = 0x7c; // I2C SDA Hold Time Length Register +const IC_TX_ABRT_SOURCE = 0x80; // I2C Transmit Abort Source Register +const IC_SLV_DATA_NACK_ONLY = 0x84; // Generate Slave Data NACK Register +const IC_DMA_CR = 0x88; // DMA Control Register +const IC_DMA_TDLR = 0x8c; // DMA Transmit Data Level Register +const IC_DMA_RDLR = 0x90; // DMA Transmit Data Level Register +const IC_SDA_SETUP = 0x94; // I2C SDA Setup Register +const IC_ACK_GENERAL_CALL = 0x98; // I2C ACK General Call Register +const IC_ENABLE_STATUS = 0x9c; // I2C Enable Status Register +const IC_FS_SPKLEN = 0xa0; // I2C SS, FS or FM+ spike suppression limit +const IC_CLR_RESTART_DET = 0xa8; // Clear RESTART_DET Interrupt Register +const IC_COMP_PARAM_1 = 0xf4; // Component Parameter Register 1 +const IC_COMP_VERSION = 0xf8; // I2C Component Version Register +const IC_COMP_TYPE = 0xfc; // I2C Component Type Register + +// IC_CON bits: +const STOP_DET_IF_MASTER_ACTIVE = 1 << 10; +const RX_FIFO_FULL_HLD_CTRL = 1 << 9; +const TX_EMPTY_CTRL = 1 << 8; +const STOP_DET_IFADDRESSED = 1 << 7; +const IC_SLAVE_DISABLE = 1 << 6; +const IC_RESTART_EN = 1 << 5; +const IC_10BITADDR_MASTER = 1 << 4; +const IC_10BITADDR_SLAVE = 1 << 3; +const SPEED_SHIFT = 1; +const SPEED_MASK = 0x3; +const MASTER_MODE = 1 << 0; + +// IC_TAR bits: +const SPECIAL = 1 << 11; +const GC_OR_START = 1 << 10; + +// IC_STATUS bits: +const SLV_ACTIVITY = 1 << 6; +const MST_ACTIVITY = 1 << 5; +const RFF = 1 << 4; +const RFNE = 1 << 3; +const TFE = 1 << 2; +const TFNF = 1 << 1; +const ACTIVITY = 1 << 0; + +// IC_ENABLE bits: +const TX_CMD_BLOCK = 1 << 2; +const ABORT = 1 << 1; +const ENABLE = 1 << 0; + +// IC_TX_ABRT_SOURCE bits: +const TX_FLUSH_CNT_MASK = 0x1ff; +const TX_FLUSH_CNT_SHIFT = 23; +const ABRT_USER_ABRT = 1 << 16; +const ABRT_SLVRD_INT = 1 << 15; +const ABRT_SLV_ARBLOST = 1 << 14; +const ABRT_SLVFLUSH_TXFIFO = 1 << 13; +const ARB_LOST = 1 << 12; +const ABRT_MASTER_DIS = 1 << 11; +const ABRT_10B_RD_NORSTRT = 1 << 10; +const ABRT_SBYTE_NORSTRT = 1 << 9; +const ABRT_HS_NORSTRT = 1 << 8; +const ABRT_SBYTE_ACKDET = 1 << 7; +const ABRT_HS_ACKDET = 1 << 6; +const ABRT_GCALL_READ = 1 << 5; +const ABRT_GCALL_NOACK = 1 << 4; +const ABRT_TXDATA_NOACK = 1 << 3; +const ABRT_10ADDR2_NOACK = 1 << 2; +const ABRT_10ADDR1_NOACK = 1 << 1; +const ABRT_7B_ADDR_NOACK = 1 << 0; + +/* Connection parameters */ +export enum I2CMode { + Write, + Read, +} + +export enum I2CSpeed { + Invalid, + /* standard mode (100 kbit/s) */ + Standard, + /* fast mode (<=400 kbit/s) or fast mode plus (<=1000Kbit/s) */ + FastMode, + /* high speed mode (3.4 Mbit/s) */ + HighSpeedMode, +} + +enum I2CState { + Idle, + Start, + Connect, + Connected, + Stop, +} + +// Interrupts +const R_RESTART_DET = 1 << 12; // Slave mode only +const R_GEN_CALL = 1 << 11; +const R_START_DET = 1 << 10; +const R_STOP_DET = 1 << 9; +const R_ACTIVITY = 1 << 8; +const R_RX_DONE = 1 << 7; +const R_TX_ABRT = 1 << 6; +const R_RD_REQ = 1 << 5; +const R_TX_EMPTY = 1 << 4; +const R_TX_OVER = 1 << 3; +const R_RX_FULL = 1 << 2; +const R_RX_OVER = 1 << 1; +const R_RX_UNDER = 1 << 0; + +// FIFO entry bits +const FIRST_DATA_BYTE = 1 << 10; +const RESTART = 1 << 10; +const STOP = 1 << 9; +const CMD = 1 << 8; // 0 for write, 1 for read + +export class RPI2C extends BasePeripheral implements Peripheral { + private state = I2CState.Idle; + private busy = false; + private stop = false; + private pendingRestart = false; + private firstByte = false; + private rxFIFO = new FIFO(16); + private txFIFO = new FIFO(16); + + // user provided callbacks + onStart: (repeatedStart: boolean) => void = () => this.completeStart(); + onConnect: (address: number, mode: I2CMode) => void = () => this.completeConnect(false); + onWriteByte: (value: number) => void = () => this.completeWrite(false); + onReadByte: (ack: boolean) => void = () => this.completeRead(0xff); + onStop: () => void = () => this.completeStop(); + + enable = 0; + rxThreshold = 0; + txThreshold = 0; + control = IC_SLAVE_DISABLE | IC_RESTART_EN | (I2CSpeed.FastMode << SPEED_SHIFT) | MASTER_MODE; + targetAddress = 0x55; + slaveAddress = 0x55; + abortSource = 0; + intRaw = 0; + intEnable = 0; + + get intStatus() { + return this.intRaw & this.intEnable; + } + + get speed() { + return ((this.control >> SPEED_SHIFT) & SPEED_MASK) as I2CSpeed; + } + + get masterBits() { + return this.control & IC_10BITADDR_MASTER ? 10 : 7; + } + + constructor(rp2040: RP2040, name: string, readonly irq: number) { + super(rp2040, name); + } + + checkInterrupts() { + this.rp2040.setInterrupt(this.irq, !!this.intStatus); + } + + protected clearInterrupts(mask: number) { + if (this.intRaw & mask) { + this.intRaw &= ~mask; + this.checkInterrupts(); + return 1; + } else { + return 0; + } + } + + protected setInterrupts(mask: number) { + if (!(this.intRaw & mask)) { + this.intRaw |= mask; + this.checkInterrupts(); + } + } + + protected abort(reason: number) { + this.abortSource &= ~TX_FLUSH_CNT_MASK; + this.abortSource |= reason | (this.txFIFO.itemCount << TX_FLUSH_CNT_SHIFT); + this.txFIFO.reset(); + this.setInterrupts(R_TX_ABRT); + } + + protected nextCommand() { + const enabled = this.enable & ENABLE; + const blocked = this.enable & TX_CMD_BLOCK; + if (this.txFIFO.empty || this.busy || blocked || !enabled) { + return; + } + this.busy = true; + const restart = !!(this.txFIFO.peek() & RESTART) && !this.pendingRestart && !this.stop; + if (this.state === I2CState.Idle || restart) { + this.pendingRestart = restart; + this.stop = false; + this.state = I2CState.Start; + this.onStart(restart); + return; + } + this.pendingRestart = false; + const cmd = this.txFIFO.pull(); + const readMode = !!(cmd & CMD); + this.stop = !!(cmd & STOP); + if (readMode) { + this.onReadByte(!this.stop); + } else { + this.onWriteByte(cmd & 0xff); + } + if (this.txFIFO.itemCount <= this.txThreshold) { + this.setInterrupts(R_TX_EMPTY); + } + } + + protected pushRX(value: number) { + if (this.rxFIFO.full) { + this.setInterrupts(R_RX_OVER); + return; + } + this.rxFIFO.push(value); + if (this.rxFIFO.itemCount > this.rxThreshold) { + this.setInterrupts(R_RX_FULL); + } + } + + completeStart() { + if (this.txFIFO.empty || this.state !== I2CState.Start || this.stop) { + this.onStop(); + return; + } + const mode = this.txFIFO.peek() & CMD ? I2CMode.Read : I2CMode.Write; + this.state = I2CState.Connect; + this.setInterrupts(R_START_DET); + const addressMask = this.masterBits === 10 ? 0x3ff : 0xff; + this.onConnect(this.targetAddress & addressMask, mode); + } + + completeConnect(ack: boolean, nackByte = 0) { + if (!ack || this.stop) { + if (!ack) { + if (!this.targetAddress) { + this.abort(ABRT_GCALL_NOACK); + } else if (this.control & IC_10BITADDR_MASTER) { + this.abort(nackByte === 0 ? ABRT_10ADDR1_NOACK : ABRT_10ADDR2_NOACK); + } else { + this.abort(ABRT_7B_ADDR_NOACK); + } + } + this.state = I2CState.Stop; + this.onStop(); + return; + } + + this.state = I2CState.Connected; + this.busy = false; + this.firstByte = true; + this.nextCommand(); + } + + completeWrite(ack: boolean) { + if (!ack || this.stop) { + if (!ack) { + this.abort(ABRT_TXDATA_NOACK); + } + this.state = I2CState.Stop; + this.onStop(); + return; + } + + this.busy = false; + this.nextCommand(); + } + + completeRead(value: number) { + this.pushRX(value | (this.firstByte ? FIRST_DATA_BYTE : 0)); + if (this.stop) { + this.state = I2CState.Stop; + this.onStop(); + return; + } + this.firstByte = false; + this.busy = false; + this.nextCommand(); + } + + completeStop() { + this.state = I2CState.Idle; + this.setInterrupts(R_STOP_DET); + this.busy = false; + this.pendingRestart = false; + this.enable &= ~ABORT; // Clean the abort bit, in case user aborted + } + + arbitrationLost() { + this.state = I2CState.Idle; + this.busy = false; + this.abort(ARB_LOST); + } + + readUint32(offset: number) { + switch (offset) { + case IC_CON: + return this.control; + case IC_TAR: + return this.targetAddress; + case IC_SAR: + return this.slaveAddress; + case IC_DATA_CMD: + if (this.rxFIFO.empty) { + this.setInterrupts(R_RX_UNDER); + return 0; + } + this.clearInterrupts(R_RX_FULL); + return this.rxFIFO.pull(); + case IC_INTR_STAT: + return this.intStatus; + case IC_INTR_MASK: + return this.intEnable; + case IC_RAW_INTR_STAT: + return this.intRaw; + case IC_RX_TL: + return this.rxThreshold; + case IC_TX_TL: + return this.txThreshold; + case IC_CLR_INTR: + this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9 + return this.clearInterrupts( + R_RX_UNDER | + R_RX_OVER | + R_TX_OVER | + R_RD_REQ | + R_TX_ABRT | + R_RX_DONE | + R_ACTIVITY | + R_STOP_DET | + R_START_DET | + R_GEN_CALL + ); + case IC_CLR_RX_UNDER: + return this.clearInterrupts(R_RX_UNDER); + case IC_CLR_RX_OVER: + return this.clearInterrupts(R_RX_OVER); + case IC_CLR_TX_OVER: + return this.clearInterrupts(R_TX_OVER); + case IC_CLR_RD_REQ: + return this.clearInterrupts(R_RD_REQ); + case IC_CLR_TX_ABRT: + this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9 + return this.clearInterrupts(R_TX_ABRT); + case IC_CLR_RX_DONE: + return this.clearInterrupts(R_RX_DONE); + case IC_CLR_ACTIVITY: + return this.clearInterrupts(R_ACTIVITY); + case IC_CLR_STOP_DET: + return this.clearInterrupts(R_STOP_DET); + case IC_CLR_START_DET: + return this.clearInterrupts(R_START_DET); + case IC_CLR_GEN_CALL: + return this.clearInterrupts(R_GEN_CALL); + case IC_ENABLE: + return this.enable; + case IC_STATUS: + return ( + (this.state !== I2CState.Idle ? MST_ACTIVITY | ACTIVITY : 0) | + (this.rxFIFO.full ? RFF : 0) | + (!this.rxFIFO.empty ? RFNE : 0) | + (this.txFIFO.empty ? TFE : 0) | + (!this.txFIFO.full ? TFNF : 0) + ); + case IC_TXFLR: + return this.txFIFO.itemCount; + case IC_RXFLR: + return this.rxFIFO.itemCount; + case IC_TX_ABRT_SOURCE: { + const value = this.abortSource; + this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9 + return value; + } + case IC_COMP_PARAM_1: + // From the datasheet: + // Note This register is not implemented and therefore reads as 0. If it was implemented it would be a constant read-only + // register that contains encoded information about the component's parameter settings. + return 0; + case IC_COMP_VERSION: + return 0x3230312a; + case IC_COMP_TYPE: + return 0x44570140; + } + return super.readUint32(offset); + } + + writeUint32(offset: number, value: number) { + switch (offset) { + case IC_CON: + if (((value >> SPEED_SHIFT) & SPEED_MASK) === I2CSpeed.Invalid) { + value = (value & ~(SPEED_MASK << SPEED_SHIFT)) | (I2CSpeed.HighSpeedMode << SPEED_SHIFT); + } + this.control = value; + return; + + case IC_TAR: + this.targetAddress = value & 0x3ff; + return; + + case IC_SAR: + this.slaveAddress = value & 0x3ff; + return; + + case IC_DATA_CMD: + if (this.txFIFO.full) { + this.setInterrupts(R_TX_OVER); + } else { + this.txFIFO.push(value); + this.clearInterrupts(R_TX_EMPTY); + this.nextCommand(); + } + return; + + case IC_RX_TL: + this.rxThreshold = value & 0xff; + if (this.rxThreshold > this.rxFIFO.size) { + this.rxThreshold = this.rxFIFO.size; + } + return; + + case IC_TX_TL: + this.txThreshold = value & 0xff; + if (this.txThreshold > this.txFIFO.size) { + this.txThreshold = this.txFIFO.size; + } + return; + + case IC_ENABLE: + // ABORT bit can only be set by software, not cleared. + value |= this.enable & ABORT; + if (value & ABORT) { + if (this.state === I2CState.Idle) { + value &= ~ABORT; + } else { + this.abort(ABRT_USER_ABRT); + this.stop = true; + } + } + if (!(value & ENABLE)) { + this.txFIFO.reset(); + this.rxFIFO.reset(); + } + this.enable = value; + this.nextCommand(); // TX_CMD_BLOCK may have changed + return; + + default: + super.writeUint32(offset, value); + } + } +} diff --git a/src/rp2040.ts b/src/rp2040.ts index ebef0f0..4655d20 100644 --- a/src/rp2040.ts +++ b/src/rp2040.ts @@ -3,6 +3,7 @@ import { RealtimeClock } from './clock/realtime-clock'; import { GPIOPin } from './gpio-pin'; import { IRQ, MAX_HARDWARE_IRQ } from './irq'; import { RPClocks } from './peripherals/clocks'; +import { RPI2C } from './peripherals/i2c'; import { RPIO } from './peripherals/io'; import { RPPADS } from './peripherals/pads'; import { Peripheral, UnimplementedPeripheral } from './peripherals/peripheral'; @@ -95,6 +96,7 @@ export class RP2040 { readonly sio = new RPSIO(this); readonly uart = [new RPUART(this, 'UART0', IRQ.UART0), new RPUART(this, 'UART1', IRQ.UART1)]; + readonly i2c = [new RPI2C(this, 'I2C0', IRQ.I2C0), new RPI2C(this, 'I2C1', IRQ.I2C1)]; readonly gpio = [ new GPIOPin(this, 0), @@ -197,8 +199,8 @@ export class RP2040 { 0x40038: this.uart[1], 0x4003c: new UnimplementedPeripheral(this, 'SPI0_BASE'), 0x40040: new UnimplementedPeripheral(this, 'SPI1_BASE'), - 0x40044: new UnimplementedPeripheral(this, 'I2C0_BASE'), - 0x40048: new UnimplementedPeripheral(this, 'I2C1_BASE'), + 0x40044: this.i2c[0], + 0x40048: this.i2c[1], 0x4004c: new UnimplementedPeripheral(this, 'ADC_BASE'), 0x40050: new UnimplementedPeripheral(this, 'PWM_BASE'), 0x40054: new RPTimer(this, 'TIMER_BASE'), diff --git a/src/utils/fifo.spec.ts b/src/utils/fifo.spec.ts index 58c9afd..2bddf7d 100644 --- a/src/utils/fifo.spec.ts +++ b/src/utils/fifo.spec.ts @@ -23,4 +23,29 @@ describe('FIFO', () => { expect(fifo.full).toBe(false); expect(fifo.empty).toBe(true); }); + + describe('peek()', () => { + it(`should return the next item in the FIFO without affecting the FIFO's content`, () => { + const fifo = new FIFO(3); + expect(fifo.empty).toBe(true); + fifo.push(10); + expect(fifo.empty).toBe(false); + fifo.push(20); + expect(fifo.peek()).toBe(10); + expect(fifo.itemCount).toBe(2); + expect(fifo.pull()).toBe(10); + }); + }); + + describe('items', () => { + it(`should return an array with all the FIFO's content`, () => { + const fifo = new FIFO(3); + expect(fifo.empty).toBe(true); + fifo.push(10); + fifo.push(20); + fifo.push(30); + fifo.pull(); + expect(fifo.items).toEqual([20, 30]); + }); + }); }); diff --git a/src/utils/fifo.ts b/src/utils/fifo.ts index 898be13..cf345f6 100644 --- a/src/utils/fifo.ts +++ b/src/utils/fifo.ts @@ -36,6 +36,10 @@ export class FIFO { return 0; } + peek() { + return this.used ? this.buffer[this.start] : 0; + } + reset() { this.used = 0; } @@ -47,4 +51,14 @@ export class FIFO { get full() { return this.used === this.buffer.length; } + + get items() { + const { start, used, buffer } = this; + const { length } = buffer; + const result = []; + for (let i = 0; i < used; i++) { + result[i] = buffer[(start + i) % length]; + } + return result; + } }