summaryrefslogtreecommitdiff
path: root/Kernel/Devices/SB16.cpp
blob: e376b17f7e0fdf853ce2e6687418d9d3a22059be (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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#include <Kernel/Devices/SB16.h>
#include <Kernel/IO.h>
#include <Kernel/Thread.h>
#include <Kernel/VM/MemoryManager.h>

//#define SB16_DEBUG

enum class SampleFormat : u8 {
    Signed = 0x10,
    Stereo = 0x20,
};

const u16 DSP_READ = 0x22A;
const u16 DSP_WRITE = 0x22C;
const u16 DSP_STATUS = 0x22E;
const u16 DSP_R_ACK = 0x22F;

/* Write a value to the DSP write register */
void SB16::dsp_write(u8 value)
{
    while (IO::in8(DSP_WRITE) & 0x80) {
        ;
    }
    IO::out8(DSP_WRITE, value);
}

/* Reads the value of the DSP read register */
u8 SB16::dsp_read()
{
    while (!(IO::in8(DSP_STATUS) & 0x80)) {
        ;
    }
    return IO::in8(DSP_READ);
}

/* Changes the sample rate of sound output */
void SB16::set_sample_rate(uint16_t hz)
{
    dsp_write(0x41); // output
    dsp_write((u8)(hz >> 8));
    dsp_write((u8)hz);
    dsp_write(0x42); // input
    dsp_write((u8)(hz >> 8));
    dsp_write((u8)hz);
}

static SB16* s_the;

SB16::SB16()
    : IRQHandler(5)
    , CharacterDevice(42, 42) // ### ?
{
    s_the = this;
    initialize();
}

SB16::~SB16()
{
}

SB16& SB16::the()
{
    return *s_the;
}

void SB16::initialize()
{
    IO::out8(0x226, 1);
    IO::delay();
    IO::out8(0x226, 0);

    auto data = dsp_read();
    if (data != 0xaa) {
        kprintf("SB16: sb not ready");
        return;
    }

    // Get the version info
    dsp_write(0xe1);
    m_major_version = dsp_read();
    auto vmin = dsp_read();

    kprintf("SB16: found version %d.%d\n", m_major_version, vmin);
    enable_irq();
}

bool SB16::can_read(FileDescription&) const
{
    return false;
}

ssize_t SB16::read(FileDescription&, u8*, ssize_t)
{
    return 0;
}

void SB16::dma_start(uint32_t length)
{
    const auto addr = m_dma_buffer_page->paddr().get();
    const u8 channel = 5; // 16-bit samples use DMA channel 5 (on the master DMA controller)
    const u8 mode = 0;

    // Disable the DMA channel
    IO::out8(0xd4, 4 + (channel % 4));

    // Clear the byte pointer flip-flop
    IO::out8(0xd8, 0);

    // Write the DMA mode for the transfer
    IO::out8(0xd6, (channel % 4) | mode);

    // Write the offset of the buffer
    u16 offset = (addr / 2) % 65536;
    IO::out8(0xc4, (u8)offset);
    IO::out8(0xc4, (u8)(offset >> 8));

    // Write the transfer length
    IO::out8(0xc6, (u8)(length - 1));
    IO::out8(0xc6, (u8)((length - 1) >> 8));

    // Write the buffer
    IO::out8(0x8b, addr >> 16);

    // Enable the DMA channel
    IO::out8(0xd4, (channel % 4));
}

void SB16::handle_irq()
{
    // Stop sound output ready for the next block.
    dsp_write(0xd5);

    IO::in8(DSP_STATUS); // 8 bit interrupt
    if (m_major_version >= 4)
        IO::in8(DSP_R_ACK); // 16 bit interrupt

    m_interrupted = true;
}

void SB16::wait_for_irq()
{
    // Well, we have no way of knowing how much got written. So just hope all of
    // it did, even if we're interrupted.
    (void)current->block_until("Interrupting", [this] {
        return m_interrupted;
    });
}

ssize_t SB16::write(FileDescription&, const u8* data, ssize_t length)
{
    if (!m_dma_buffer_page)
        m_dma_buffer_page = MM.allocate_supervisor_physical_page();

#ifdef SB16_DEBUG
    kprintf("SB16: Writing buffer of %d bytes\n", length);
#endif
    ASSERT(length <= PAGE_SIZE);
    const int BLOCK_SIZE = 32 * 1024;
    if (length > BLOCK_SIZE) {
        return -ENOSPC;
    }

    u8 mode = (u8)SampleFormat::Signed | (u8)SampleFormat::Stereo;

    disable_irq();
    const int sample_rate = 44100;
    set_sample_rate(sample_rate);
    memcpy(m_dma_buffer_page->paddr().as_ptr(), data, length);
    dma_start(length);

    // 16-bit single-cycle output.
    // FIXME: Implement auto-initialized output.
    u8 command = 0xb0;

    u16 sample_count = length / sizeof(i16);
    if (mode & (u8)SampleFormat::Stereo)
        sample_count /= 2;

    sample_count -= 1;

    dsp_write(command);
    dsp_write(mode);
    dsp_write((u8)sample_count);
    dsp_write((u8)(sample_count >> 8));

    m_interrupted = false;
    enable_irq();
    wait_for_irq();
    return length;
}