Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <Kernel/Devices/SB16.h>
28#include <Kernel/Thread.h>
29#include <Kernel/VM/AnonymousVMObject.h>
30#include <Kernel/VM/MemoryManager.h>
31#include <LibBareMetal/IO.h>
32
33//#define SB16_DEBUG
34
35namespace Kernel {
36#define SB16_DEFAULT_IRQ 5
37
38enum class SampleFormat : u8 {
39 Signed = 0x10,
40 Stereo = 0x20,
41};
42
43const u16 DSP_READ = 0x22A;
44const u16 DSP_WRITE = 0x22C;
45const u16 DSP_STATUS = 0x22E;
46const u16 DSP_R_ACK = 0x22F;
47
48/* Write a value to the DSP write register */
49void SB16::dsp_write(u8 value)
50{
51 while (IO::in8(DSP_WRITE) & 0x80) {
52 ;
53 }
54 IO::out8(DSP_WRITE, value);
55}
56
57/* Reads the value of the DSP read register */
58u8 SB16::dsp_read()
59{
60 while (!(IO::in8(DSP_STATUS) & 0x80)) {
61 ;
62 }
63 return IO::in8(DSP_READ);
64}
65
66/* Changes the sample rate of sound output */
67void SB16::set_sample_rate(uint16_t hz)
68{
69 dsp_write(0x41); // output
70 dsp_write((u8)(hz >> 8));
71 dsp_write((u8)hz);
72 dsp_write(0x42); // input
73 dsp_write((u8)(hz >> 8));
74 dsp_write((u8)hz);
75}
76
77static SB16* s_the;
78
79SB16::SB16()
80 : IRQHandler(SB16_DEFAULT_IRQ)
81 , CharacterDevice(42, 42) // ### ?
82{
83 s_the = this;
84 initialize();
85}
86
87SB16::~SB16()
88{
89}
90
91SB16& SB16::the()
92{
93 return *s_the;
94}
95
96void SB16::initialize()
97{
98 disable_irq();
99
100 IO::out8(0x226, 1);
101 IO::delay();
102 IO::out8(0x226, 0);
103
104 auto data = dsp_read();
105 if (data != 0xaa) {
106 kprintf("SB16: sb not ready");
107 return;
108 }
109
110 // Get the version info
111 dsp_write(0xe1);
112 m_major_version = dsp_read();
113 auto vmin = dsp_read();
114
115 kprintf("SB16: found version %d.%d\n", m_major_version, vmin);
116 set_irq_register(SB16_DEFAULT_IRQ);
117 kprintf("SB16: IRQ %d\n", get_irq_line());
118}
119
120void SB16::set_irq_register(u8 irq_number)
121{
122 u8 bitmask;
123 switch (irq_number) {
124 case 2:
125 bitmask = 0;
126 break;
127 case 5:
128 bitmask = 0b10;
129 break;
130 case 7:
131 bitmask = 0b100;
132 break;
133 case 10:
134 bitmask = 0b1000;
135 break;
136 default:
137 ASSERT_NOT_REACHED();
138 }
139 IO::out8(0x224, 0x80);
140 IO::out8(0x225, bitmask);
141}
142
143u8 SB16::get_irq_line()
144{
145 IO::out8(0x224, 0x80);
146 u8 bitmask = IO::in8(0x225);
147 switch (bitmask) {
148 case 0:
149 return 2;
150 case 0b10:
151 return 5;
152 case 0b100:
153 return 7;
154 case 0b1000:
155 return 10;
156 }
157 return bitmask;
158}
159void SB16::set_irq_line(u8 irq_number)
160{
161 InterruptDisabler disabler;
162 if (irq_number == get_irq_line())
163 return;
164 set_irq_register(irq_number);
165 change_irq_number(irq_number);
166}
167
168bool SB16::can_read(const FileDescription&) const
169{
170 return false;
171}
172
173ssize_t SB16::read(FileDescription&, u8*, ssize_t)
174{
175 return 0;
176}
177
178void SB16::dma_start(uint32_t length)
179{
180 const auto addr = m_dma_region->vmobject().physical_pages()[0]->paddr().get();
181 const u8 channel = 5; // 16-bit samples use DMA channel 5 (on the master DMA controller)
182 const u8 mode = 0;
183
184 // Disable the DMA channel
185 IO::out8(0xd4, 4 + (channel % 4));
186
187 // Clear the byte pointer flip-flop
188 IO::out8(0xd8, 0);
189
190 // Write the DMA mode for the transfer
191 IO::out8(0xd6, (channel % 4) | mode);
192
193 // Write the offset of the buffer
194 u16 offset = (addr / 2) % 65536;
195 IO::out8(0xc4, (u8)offset);
196 IO::out8(0xc4, (u8)(offset >> 8));
197
198 // Write the transfer length
199 IO::out8(0xc6, (u8)(length - 1));
200 IO::out8(0xc6, (u8)((length - 1) >> 8));
201
202 // Write the buffer
203 IO::out8(0x8b, addr >> 16);
204
205 // Enable the DMA channel
206 IO::out8(0xd4, (channel % 4));
207}
208
209void SB16::handle_irq(RegisterState&)
210{
211 // Stop sound output ready for the next block.
212 dsp_write(0xd5);
213
214 IO::in8(DSP_STATUS); // 8 bit interrupt
215 if (m_major_version >= 4)
216 IO::in8(DSP_R_ACK); // 16 bit interrupt
217
218 m_irq_queue.wake_all();
219}
220
221void SB16::wait_for_irq()
222{
223 cli();
224 enable_irq();
225 Thread::current->wait_on(m_irq_queue);
226 disable_irq();
227}
228
229ssize_t SB16::write(FileDescription&, const u8* data, ssize_t length)
230{
231 if (!m_dma_region) {
232 auto page = MM.allocate_supervisor_physical_page();
233 auto vmobject = AnonymousVMObject::create_with_physical_page(*page);
234 m_dma_region = MM.allocate_kernel_region_with_vmobject(*vmobject, PAGE_SIZE, "SB16 DMA buffer", Region::Access::Write);
235 }
236
237#ifdef SB16_DEBUG
238 kprintf("SB16: Writing buffer of %d bytes\n", length);
239#endif
240 ASSERT(length <= PAGE_SIZE);
241 const int BLOCK_SIZE = 32 * 1024;
242 if (length > BLOCK_SIZE) {
243 return -ENOSPC;
244 }
245
246 u8 mode = (u8)SampleFormat::Signed | (u8)SampleFormat::Stereo;
247
248 const int sample_rate = 44100;
249 set_sample_rate(sample_rate);
250 memcpy(m_dma_region->vaddr().as_ptr(), data, length);
251 dma_start(length);
252
253 // 16-bit single-cycle output.
254 // FIXME: Implement auto-initialized output.
255 u8 command = 0xb0;
256
257 u16 sample_count = length / sizeof(i16);
258 if (mode & (u8)SampleFormat::Stereo)
259 sample_count /= 2;
260
261 sample_count -= 1;
262
263 dsp_write(command);
264 dsp_write(mode);
265 dsp_write((u8)sample_count);
266 dsp_write((u8)(sample_count >> 8));
267
268 wait_for_irq();
269 return length;
270}
271
272}