Skip to content

Commit 3c7c5c7

Browse files
committed
feat(spi): implement SPI peripheral
not yet tested
1 parent 159f6fa commit 3c7c5c7

File tree

2 files changed

+246
-2
lines changed

2 files changed

+246
-2
lines changed

src/peripherals/spi.ts

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import { RP2040 } from '../rp2040';
2+
import { FIFO } from '../utils/fifo';
3+
import { BasePeripheral, Peripheral } from './peripheral';
4+
5+
const SSPCR0 = 0x000; // Control register 0, SSPCR0 on page 3-4
6+
const SSPCR1 = 0x004; // Control register 1, SSPCR1 on page 3-5
7+
const SSPDR = 0x008; // Data register, SSPDR on page 3-6
8+
const SSPSR = 0x00c; // Status register, SSPSR on page 3-7
9+
const SSPCPSR = 0x010; // Clock prescale register, SSPCPSR on page 3-8
10+
const SSPIMSC = 0x014; // Interrupt mask set or clear register, SSPIMSC on page 3-9
11+
const SSPRIS = 0x018; // Raw interrupt status register, SSPRIS on page 3-10
12+
const SSPMIS = 0x01c; // Masked interrupt status register, SSPMIS on page 3-11
13+
const SSPICR = 0x020; // Interrupt clear register, SSPICR on page 3-11
14+
const SSPDMACR = 0x024; // DMA control register, SSPDMACR on page 3-12
15+
const SSPPERIPHID0 = 0xfe0; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
16+
const SSPPERIPHID1 = 0xfe4; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
17+
const SSPPERIPHID2 = 0xfe8; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
18+
const SSPPERIPHID3 = 0xfec; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
19+
const SSPPCELLID0 = 0xff0; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
20+
const SSPPCELLID1 = 0xff4; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
21+
const SSPPCELLID2 = 0xff8; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
22+
const SSPPCELLID3 = 0xffc; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
23+
24+
// SSPCR0 bits:
25+
const SCR_MASK = 0xff;
26+
const SCR_SHIFT = 8;
27+
const SPH = 1 << 7;
28+
const SPO = 1 << 6;
29+
const FRF_MASK = 0x3;
30+
const FRF_SHIFT = 4;
31+
const DSS_MASK = 0xf;
32+
const DSS_SHIFT = 0;
33+
34+
// SSPCR1 bits:
35+
const SOD = 1 << 3;
36+
const MS = 1 << 2;
37+
const SSE = 1 << 1;
38+
const LBM = 1 << 0;
39+
40+
// SSPSR bits:
41+
const BSY = 1 << 4;
42+
const RFF = 1 << 3;
43+
const RNE = 1 << 2;
44+
const TNF = 1 << 1;
45+
const TFE = 1 << 0;
46+
47+
// SSPCPSR bits:
48+
const CPSDVSR_MASK = 0xfe;
49+
const CPSDVSR_SHIFT = 0;
50+
51+
// SSPDMACR bits:
52+
const TXDMAE = 1 << 1;
53+
const RXDMAE = 1 << 0;
54+
55+
// Interrupts:
56+
const SSPTXINTR = 1 << 3;
57+
const SSPRXINTR = 1 << 2;
58+
const SSPRTINTR = 1 << 1;
59+
const SSPRORINTR = 1 << 0;
60+
61+
export class RPSPI extends BasePeripheral implements Peripheral {
62+
readonly rxFIFO = new FIFO(8);
63+
readonly txFIFO = new FIFO(8);
64+
65+
// User provided callbacks
66+
onTransmit: (value: number) => void = () => this.completeTransmit(0);
67+
68+
private busy = false;
69+
private control0 = 0;
70+
private control1 = 0;
71+
private dmaControl = 0;
72+
private clockDivisor = 0;
73+
private intRaw = 0;
74+
private intEnable = 0;
75+
76+
get intStatus() {
77+
return this.intRaw & this.intEnable;
78+
}
79+
80+
get enabled() {
81+
return !!(this.control1 & SSE);
82+
}
83+
84+
/** Data size in bits: 4 to 16 bits */
85+
get dataBits() {
86+
return ((this.control0 >> DSS_SHIFT) & DSS_MASK) + 1;
87+
}
88+
89+
get masterMode() {
90+
return !(this.control0 & MS);
91+
}
92+
93+
get spiMode() {
94+
const cpol = this.control0 & SPO;
95+
const cpha = this.control0 & SPH;
96+
return cpol ? (cpha ? 2 : 3) : cpha ? 1 : 0;
97+
}
98+
99+
get clockFrequency() {
100+
if (!this.clockDivisor) {
101+
return 0;
102+
}
103+
104+
const scr = (this.control0 >> SCR_SHIFT) & SCR_MASK;
105+
return this.rp2040.clkPeri / (this.clockDivisor * (1 + scr));
106+
}
107+
108+
constructor(rp2040: RP2040, name: string, readonly irq: number) {
109+
super(rp2040, name);
110+
}
111+
112+
private doTX() {
113+
if (!this.busy && !this.txFIFO.empty) {
114+
const value = this.txFIFO.pull();
115+
this.onTransmit(value);
116+
this.busy = true;
117+
this.fifosUpdated();
118+
}
119+
}
120+
121+
completeTransmit(rxValue: number) {
122+
this.busy = false;
123+
if (!this.rxFIFO.full) {
124+
this.rxFIFO.push(rxValue);
125+
} else {
126+
this.intRaw |= SSPRORINTR;
127+
}
128+
this.fifosUpdated();
129+
this.doTX();
130+
}
131+
132+
checkInterrupts() {
133+
this.rp2040.setInterrupt(this.irq, !!this.intStatus);
134+
}
135+
136+
private fifosUpdated() {
137+
const prevStatus = this.intStatus;
138+
if (this.txFIFO.itemCount <= this.txFIFO.size / 2) {
139+
this.intRaw |= SSPTXINTR;
140+
} else {
141+
this.intRaw &= ~SSPTXINTR;
142+
}
143+
if (this.rxFIFO.itemCount >= this.rxFIFO.size / 2) {
144+
this.intRaw |= SSPRXINTR;
145+
} else {
146+
this.intRaw &= ~SSPRXINTR;
147+
}
148+
if (this.intStatus !== prevStatus) {
149+
this.checkInterrupts();
150+
}
151+
}
152+
153+
readUint32(offset: number) {
154+
switch (offset) {
155+
case SSPCR0:
156+
return this.control0;
157+
case SSPCR1:
158+
return this.control1;
159+
case SSPDR:
160+
if (!this.rxFIFO.empty) {
161+
const value = this.rxFIFO.pull();
162+
this.fifosUpdated();
163+
return value;
164+
}
165+
return 0;
166+
case SSPSR:
167+
return (
168+
(this.busy || !this.txFIFO.empty ? BSY : 0) |
169+
(this.rxFIFO.full ? RFF : 0) |
170+
(!this.rxFIFO.empty ? RNE : 0) |
171+
(!this.txFIFO.full ? TNF : 0) |
172+
(this.txFIFO.empty ? TFE : 0)
173+
);
174+
case SSPCPSR:
175+
return this.clockDivisor;
176+
case SSPIMSC:
177+
return this.intEnable;
178+
case SSPRIS:
179+
return this.intRaw;
180+
case SSPMIS:
181+
return this.intStatus;
182+
case SSPDMACR:
183+
return this.dmaControl;
184+
case SSPPERIPHID0:
185+
return 0x22;
186+
case SSPPERIPHID1:
187+
return 0x10;
188+
case SSPPERIPHID2:
189+
return 0x34;
190+
case SSPPERIPHID3:
191+
return 0x00;
192+
case SSPPCELLID0:
193+
return 0x0d;
194+
case SSPPCELLID1:
195+
return 0xf0;
196+
case SSPPCELLID2:
197+
return 0x05;
198+
case SSPPCELLID3:
199+
return 0xb1;
200+
}
201+
return super.readUint32(offset);
202+
}
203+
204+
writeUint32(offset: number, value: number) {
205+
switch (offset) {
206+
case SSPCR0:
207+
this.control0 = value;
208+
return;
209+
case SSPCR1:
210+
this.control1 = value;
211+
return;
212+
case SSPDR:
213+
if (!this.txFIFO.full) {
214+
this.txFIFO.push(value);
215+
this.doTX();
216+
this.fifosUpdated();
217+
}
218+
return;
219+
case SSPCPSR:
220+
this.clockDivisor = value & CPSDVSR_MASK;
221+
return;
222+
case SSPIMSC:
223+
this.intEnable = value;
224+
this.checkInterrupts();
225+
return;
226+
case SSPDMACR:
227+
this.dmaControl = value;
228+
return;
229+
case SSPICR:
230+
this.intRaw &= ~(value & (SSPRTINTR | SSPRORINTR));
231+
this.checkInterrupts();
232+
return;
233+
default:
234+
super.writeUint32(offset, value);
235+
}
236+
}
237+
}

src/rp2040.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { RPPIO } from './peripherals/pio';
1212
import { RPPPB } from './peripherals/ppb';
1313
import { RPReset } from './peripherals/reset';
1414
import { RP2040RTC } from './peripherals/rtc';
15+
import { RPSPI } from './peripherals/spi';
1516
import { RPSSI } from './peripherals/ssi';
1617
import { RP2040SysCfg } from './peripherals/syscfg';
1718
import { RPTimer } from './peripherals/timer';
@@ -79,6 +80,7 @@ const LOG_NAME = 'RP2040';
7980

8081
const KB = 1024;
8182
const MB = 1024 * KB;
83+
const MHz = 1_000_000;
8284

8385
export class RP2040 {
8486
readonly bootrom = new Uint32Array(4 * KB);
@@ -93,11 +95,16 @@ export class RP2040 {
9395
bankedSP: number = 0;
9496
cycles: number = 0;
9597

98+
/* Clocks */
99+
clkSys = 125 * MHz;
100+
clkPeri = 125 * MHz;
101+
96102
readonly ppb = new RPPPB(this, 'PPB');
97103
readonly sio = new RPSIO(this);
98104

99105
readonly uart = [new RPUART(this, 'UART0', IRQ.UART0), new RPUART(this, 'UART1', IRQ.UART1)];
100106
readonly i2c = [new RPI2C(this, 'I2C0', IRQ.I2C0), new RPI2C(this, 'I2C1', IRQ.I2C1)];
107+
readonly spi = [new RPSPI(this, 'SPI0', IRQ.SPI0), new RPSPI(this, 'SPI1', IRQ.SPI1)];
101108
readonly adc = new RPADC(this, 'ADC');
102109

103110
readonly gpio = [
@@ -199,8 +206,8 @@ export class RP2040 {
199206
0x40030: new UnimplementedPeripheral(this, 'BUSCTRL_BASE'),
200207
0x40034: this.uart[0],
201208
0x40038: this.uart[1],
202-
0x4003c: new UnimplementedPeripheral(this, 'SPI0_BASE'),
203-
0x40040: new UnimplementedPeripheral(this, 'SPI1_BASE'),
209+
0x4003c: this.spi[0],
210+
0x40040: this.spi[1],
204211
0x40044: this.i2c[0],
205212
0x40048: this.i2c[1],
206213
0x4004c: this.adc,

0 commit comments

Comments
 (0)