-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathaudio_i2s.c
187 lines (155 loc) · 5.22 KB
/
audio_i2s.c
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
#include "audio.h"
#include "ccu.h"
#include "system.h"
#include "interrupts.h"
#include <stdio.h>
#include <h3_codec.h>
int sample_count;
void __attribute__((weak)) hook_audio_get_sample(int16_t *l, int16_t *r)
{
// Create a pilot tone.
*l = ((sample_count / 50) & 1) * 0x007f;
*r = ((sample_count / 50) & 1) * 0x007f;
sample_count++;
}
void audio_queue_samples(void)
{
#ifdef AWBM_PLATFORM_h3
while (((I2S_FSTA(2) >> 16) & 0xff) > 2) {
int16_t l, r;
hook_audio_get_sample(&l, &r);
I2S_TXFIFO(2) = l;
I2S_TXFIFO(2) = r;
}
// TXEI clears itself when the FIFO is full again
#elif defined(AWBM_PLATFORM_h616)
while ((AHUB_APBIF_TXFIFO_STS(1) & 0x7f) > 2) {
int16_t l, r;
hook_audio_get_sample(&l, &r);
// Feed AHUB and analog codec FIFOs.
// We are running this off the AHUB interrupt. We can do
// that because the AHUB and the codec run off clocks
// derived from the same source (they are, aren't they?).
// The audio codec also has a larger FIFO than the AHUB (128
// vs. 64 samples).
// XXX: find out the TXDIF no. instead of hardcoding it
AHUB_APBIF_TXFIFO(1) = l;
AC_DAC_TXDATA = l;
AHUB_APBIF_TXFIFO(1) = r;
AC_DAC_TXDATA = r;
}
// TXnE clears itself when the FIFO is no longer empty.
#else
#warning unimplemented
#endif
}
#ifdef AWBM_PLATFORM_h616
#define DMA_BASE 0x3002000
#define DMA_CHAN_BASE(n) (DMA_BASE + (n) * 0x40)
#define DMA_PAUSE(n) MEM(DMA_CHAN_BASE(n) + 0x104)
#define DMA_CUR_DEST_REG(n) MEM(DMA_CHAN_BASE(n) + 0x114)
void audio_ahub_init(void)
{
// Hijack AHUB and codec by pausing DMA.
// We assume that the Linux cell has brought the devices up and
// keeps them running.
// Linux uses DMA to feed the AHUB and codec FIFOs. We cannot hijack
// the DMA controller because Linux needs it for other things.
// Instead, we pause the DMA channels that are feeding the FIFOs and
// use the AHUB's unused IRQ to feed them manually.
for (int ch = 0; ch < 16; ++ch) {
if ((DMA_CUR_DEST_REG(ch) & 0xfffff000) == AHUB_BASE_BASE) {
DMA_PAUSE(ch) |= 1;
// XXX: find out the TXDIF no. instead of hardcoding it
}
if ((DMA_CUR_DEST_REG(ch) & 0xfffff000) == AC_BASE) {
DMA_PAUSE(ch) |=1;
}
}
AHUB_APBIF_TXIRQ_CTRL(1) |= 1; // enable TXnEI_EN (FIFO empty interrupt)
irq_enable(HDMI_AUDIO_IRQ);
}
#endif
#ifdef AWBM_PLATFORM_h3
void audio_i2s2_init(void)
{
// enable audio PLL, default value (24.571 MHz)
// XXX: should be 24.576 MHz for 48 kHz
PLL_AUDIO_CTRL = 0x80035514;
while (!(PLL_AUDIO_CTRL & (1 << 28))) {
// wait
}
// enable clock, source PLL_AUDIO (no multiplier)
// XXX: correct?
I2S_PCM2_CLK = 0x80030000;
BUS_SOFT_RST3 &= ~(1 << 14); // assert reset
udelay(100);
BUS_SOFT_RST3 |= 1 << 14; // deassert reset
udelay(100);
BUS_CLK_GATING2 |= (1 << 14); // pass clock
udelay(100);
}
void audio_i2s2_on(void)
{
I2S_TX0CHSEL_H3(2) = I2S_TXnCHSEL_OFFSET_1 | I2S_TXnCHSEL_CHSEL_2 |
I2S_TXnCHSEL_CHEN_0 | I2S_TXnCHSEL_CHEN_1; // 0x00001031
I2S_TX0CHMAP_H3(2) = I2S_TXnCHMAP_CH0_SAMPLE(0) | I2S_TXnCHMAP_CH1_SAMPLE(1);
// Set clock dividers.
// BCLK/8 is good for both 44.1 and 48 kHz, must set input
// clock accordingly (22.5792 or 24.576 MHz).
I2S_CLKD(2) = I2S_CLKD_MCLKDIV(1) | I2S_CLKD_BCLKDIV(5) | I2S_CLKD_MCLKO_EN; // 0x00000151
// TX/RX mode PCM, no sign extension
I2S_FAT1(2) = 0; // 0x00000000
/* "normal bit clock + frame" (BCLK, LRCK polarity 0) */
// LRCK period 32 BCLKs, LRCKR period 1 BCLK
// slot width and sample resolution 16 bits
I2S_FAT0(2) = I2S_FAT0_SW(3) | I2S_FAT0_SR(3) |
I2S_FAT0_LRCK_PERIOD(31) | I2S_FAT0_LRCKR_PERIOD(0); // 0x00001F33
I2S_TXCHCFG_H3(2) = I2S_CHCFG_RX_SLOT_NUM(1) | I2S_CHCFG_TX_SLOT_NUM(1);
I2S_RXCHSEL(2) = I2S_RXCHSEL_OFFSET(1) | I2S_RXCHSEL_CHSEL(1); // 0x00001001
I2S_RXCHMAP(2) = I2S_RXCHMAP_CH_SAMPLE(0, 0) | I2S_RXCHMAP_CH_SAMPLE(1, 1); // 0x00000010
// TX trigger level 0x40
// RX trigger level 0x0f
// TX FIFO input mode 1 (valid data aligned with LSB of sample)
// RX FIFO output mode 1
// ("Expanding received sample sign bit at MSB of OWA_RXFIFO register")
I2S_FCTL(2) = I2S_FCTL_TXTL(0x40) | I2S_FCTL_RXTL(0x0f) |
I2S_FCTL_TXIM(1) | I2S_FCTL_RXOM(1) |
I2S_FCTL_FRX; // 0x010400F5
I2S_RXCNT(2) = 0; // clear RX counter
I2S_TXCNT(2) = 0; // clear TX counter
I2S_INT(2) = I2S_INT_TXEI_EN; // enable TX FIFO empty IRQ
irq_enable(HDMI_AUDIO_IRQ);
// BCLK_OUT | LRCK_OUT: "codec clk & frm slave,ap is master"
// MODE_SEL(1): "Left mode (offset 0: LJ mode; offset 1: I2S mode"
I2S_CTL(2) = I2S_CTL_BCLK_OUT | I2S_CTL_LRCK_OUT | I2S_CTL_MODE_SEL(1) |
I2S_CTL_RXEN | I2S_CTL_TXEN | I2S_CTL_GEN | I2S_CTL_SDO0_EN; // 0x00060117
}
void audio_i2s2_off(void)
{
irq_disable(HDMI_AUDIO_IRQ);
// disable RX
I2S_FCTL(2) |= I2S_FCTL_FRX; // flush RX FIFO
I2S_RXCNT(2) = 0; // clear RX count
// don't have to disable DRQ, we don't enable it
I2S_INT(2) = 0;
// XXX: should we do a flush?
// XXX: hub enable/disable? Not enabled in register trace.
}
#else // AWBM_PLATFORM_h3
#warning unimplemented
#endif // AWBM_PLATFORM_h3
void audio_start(int buf_len)
{
#ifdef AWBM_PLATFORM_h3
h3_codec_begin();
h3_codec_set_buffer_length(buf_len);
audio_i2s2_init();
audio_i2s2_on();
h3_codec_start();
#elif defined(AWBM_PLATFORM_h616)
audio_ahub_init();
#else
#warning unimplemented
#endif
}