aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals/spi.ts
blob: 0c03c3f7f36e323672ce4d0a7d9fe702897705e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { CPU } from '../cpu/cpu';
import { u8 } from '../types';
import { avrInterrupt } from '../cpu/interrupt';

export interface SPIConfig {
  spiInterrupt: u8;

  SPCR: u8;
  SPSR: u8;
  SPDR: u8;
}

// Register bits:
const SPCR_SPIE = 0x80; //  SPI Interrupt Enable
const SPCR_SPE = 0x40; // SPI Enable
const SPCR_DORD = 0x20; // Data Order
const SPCR_MSTR = 0x10; //  Master/Slave Select
const SPCR_CPOL = 0x8; // Clock Polarity
const SPCR_CPHA = 0x4; // Clock Phase
const SPCR_SPR1 = 0x2; // SPI Clock Rate Select 1
const SPCR_SPR0 = 0x1; // SPI Clock Rate Select 0
const SPSR_SPR_MASK = SPCR_SPR1 | SPCR_SPR0;

const SPSR_SPIF = 0x80; // SPI Interrupt Flag
const SPSR_WCOL = 0x40; // Write COLlision Flag
const SPSR_SPI2X = 0x1; // Double SPI Speed Bit

export const spiConfig: SPIConfig = {
  spiInterrupt: 0x22,
  SPCR: 0x4c,
  SPSR: 0x4d,
  SPDR: 0x4e,
};

export type SPITransferCallback = (value: u8) => u8;

const bitsPerByte = 8;

export class AVRSPI {
  public onTransfer: SPITransferCallback | null = null;

  private transmissionCompleteCycles = 0;
  private receivedByte: u8 = 0;

  constructor(private cpu: CPU, private config: SPIConfig, private freqMHz: number) {
    const { SPCR, SPSR, SPDR } = config;
    cpu.writeHooks[SPDR] = (value: u8) => {
      if (!(cpu.data[SPCR] & SPCR_SPE)) {
        // SPI not enabled, ignore write
        return;
      }

      // Write collision
      if (this.transmissionCompleteCycles > this.cpu.cycles) {
        cpu.data[SPSR] |= SPSR_WCOL;
        return true;
      }

      // Clear write collision / interrupt flags
      cpu.data[SPSR] &= ~SPSR_WCOL & ~SPSR_SPIF;

      this.receivedByte = this.onTransfer?.(value) ?? 0;
      this.transmissionCompleteCycles = this.cpu.cycles + this.clockDivider * bitsPerByte;
      return true;
    };
  }

  tick() {
    if (this.transmissionCompleteCycles && this.cpu.cycles >= this.transmissionCompleteCycles) {
      const { SPSR, SPDR } = this.config;
      this.cpu.data[SPSR] |= SPSR_SPIF;
      this.cpu.data[SPDR] = this.receivedByte;
      this.transmissionCompleteCycles = 0;
    }
    if (this.cpu.interruptsEnabled) {
      const { SPSR, SPCR, spiInterrupt } = this.config;
      if (this.cpu.data[SPCR] & SPCR_SPIE && this.cpu.data[SPSR] & SPSR_SPIF) {
        avrInterrupt(this.cpu, spiInterrupt);
        this.cpu.data[SPSR] &= ~SPSR_SPIF;
      }
    }
  }

  get isMaster() {
    return this.cpu.data[this.config.SPCR] & SPCR_MSTR ? true : false;
  }

  get dataOrder() {
    return this.cpu.data[this.config.SPCR] & SPCR_DORD ? 'lsbFirst' : 'msbFirst';
  }

  get spiMode() {
    const CPHA = this.cpu.data[this.config.SPCR] & SPCR_CPHA;
    const CPOL = this.cpu.data[this.config.SPCR] & SPCR_CPOL;
    return ((CPHA ? 2 : 0) | (CPOL ? 1 : 0)) as 0 | 1 | 2 | 3;
  }

  /**
   * The clock divider is only relevant for Master mode
   */
  get clockDivider() {
    const base = this.cpu.data[this.config.SPSR] & SPSR_SPI2X ? 2 : 4;
    switch (this.cpu.data[this.config.SPCR] & SPSR_SPR_MASK) {
      case 0b00:
        return base;

      case 0b01:
        return base * 4;

      case 0b10:
        return base * 16;

      case 0b11:
        return base * 32;
    }
    // We should never get here:
    throw new Error('Invalid divider value!');
  }

  /**
   * The SPI freqeuncy is only relevant to Master mode.
   * In slave mode, the frequency can be as high as F(osc) / 4.
   */
  get spiFrequency() {
    return this.freqMHz / this.clockDivider;
  }
}