Commit | Line | Data |
---|---|---|
1c1e45d1 HV |
1 | /* |
2 | * cx18 ADEC audio functions | |
3 | * | |
4 | * Derived from cx25840-audio.c | |
5 | * | |
6 | * Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License | |
10 | * as published by the Free Software Foundation; either version 2 | |
11 | * of the License, or (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
21 | * 02110-1301, USA. | |
22 | */ | |
23 | ||
24 | #include "cx18-driver.h" | |
25 | ||
26 | static int set_audclk_freq(struct cx18 *cx, u32 freq) | |
27 | { | |
28 | struct cx18_av_state *state = &cx->av_state; | |
29 | ||
30 | if (freq != 32000 && freq != 44100 && freq != 48000) | |
31 | return -EINVAL; | |
32 | ||
33 | /* common for all inputs and rates */ | |
34 | /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x10 */ | |
35 | cx18_av_write(cx, 0x127, 0x50); | |
36 | ||
81cb727d | 37 | if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { |
1c1e45d1 HV |
38 | switch (freq) { |
39 | case 32000: | |
40 | /* VID_PLL and AUX_PLL */ | |
41 | cx18_av_write4(cx, 0x108, 0x1006040f); | |
42 | ||
43 | /* AUX_PLL_FRAC */ | |
44 | cx18_av_write4(cx, 0x110, 0x01bb39ee); | |
45 | ||
46 | /* src3/4/6_ctl = 0x0801f77f */ | |
47 | cx18_av_write4(cx, 0x900, 0x0801f77f); | |
48 | cx18_av_write4(cx, 0x904, 0x0801f77f); | |
49 | cx18_av_write4(cx, 0x90c, 0x0801f77f); | |
50 | break; | |
51 | ||
52 | case 44100: | |
53 | /* VID_PLL and AUX_PLL */ | |
54 | cx18_av_write4(cx, 0x108, 0x1009040f); | |
55 | ||
56 | /* AUX_PLL_FRAC */ | |
57 | cx18_av_write4(cx, 0x110, 0x00ec6bd6); | |
58 | ||
59 | /* src3/4/6_ctl = 0x08016d59 */ | |
60 | cx18_av_write4(cx, 0x900, 0x08016d59); | |
61 | cx18_av_write4(cx, 0x904, 0x08016d59); | |
62 | cx18_av_write4(cx, 0x90c, 0x08016d59); | |
63 | break; | |
64 | ||
65 | case 48000: | |
66 | /* VID_PLL and AUX_PLL */ | |
67 | cx18_av_write4(cx, 0x108, 0x100a040f); | |
68 | ||
69 | /* AUX_PLL_FRAC */ | |
70 | cx18_av_write4(cx, 0x110, 0x0098d6e5); | |
71 | ||
72 | /* src3/4/6_ctl = 0x08014faa */ | |
73 | cx18_av_write4(cx, 0x900, 0x08014faa); | |
74 | cx18_av_write4(cx, 0x904, 0x08014faa); | |
75 | cx18_av_write4(cx, 0x90c, 0x08014faa); | |
76 | break; | |
77 | } | |
78 | } else { | |
79 | switch (freq) { | |
80 | case 32000: | |
81 | /* VID_PLL and AUX_PLL */ | |
82 | cx18_av_write4(cx, 0x108, 0x1e08040f); | |
83 | ||
84 | /* AUX_PLL_FRAC */ | |
85 | cx18_av_write4(cx, 0x110, 0x012a0869); | |
86 | ||
87 | /* src1_ctl = 0x08010000 */ | |
88 | cx18_av_write4(cx, 0x8f8, 0x08010000); | |
89 | ||
90 | /* src3/4/6_ctl = 0x08020000 */ | |
91 | cx18_av_write4(cx, 0x900, 0x08020000); | |
92 | cx18_av_write4(cx, 0x904, 0x08020000); | |
93 | cx18_av_write4(cx, 0x90c, 0x08020000); | |
94 | ||
95 | /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x14 */ | |
96 | cx18_av_write(cx, 0x127, 0x54); | |
97 | break; | |
98 | ||
99 | case 44100: | |
100 | /* VID_PLL and AUX_PLL */ | |
101 | cx18_av_write4(cx, 0x108, 0x1809040f); | |
102 | ||
103 | /* AUX_PLL_FRAC */ | |
104 | cx18_av_write4(cx, 0x110, 0x00ec6bd6); | |
105 | ||
106 | /* src1_ctl = 0x08010000 */ | |
107 | cx18_av_write4(cx, 0x8f8, 0x080160cd); | |
108 | ||
109 | /* src3/4/6_ctl = 0x08020000 */ | |
110 | cx18_av_write4(cx, 0x900, 0x08017385); | |
111 | cx18_av_write4(cx, 0x904, 0x08017385); | |
112 | cx18_av_write4(cx, 0x90c, 0x08017385); | |
113 | break; | |
114 | ||
115 | case 48000: | |
116 | /* VID_PLL and AUX_PLL */ | |
117 | cx18_av_write4(cx, 0x108, 0x180a040f); | |
118 | ||
119 | /* AUX_PLL_FRAC */ | |
120 | cx18_av_write4(cx, 0x110, 0x0098d6e5); | |
121 | ||
122 | /* src1_ctl = 0x08010000 */ | |
123 | cx18_av_write4(cx, 0x8f8, 0x08018000); | |
124 | ||
125 | /* src3/4/6_ctl = 0x08020000 */ | |
126 | cx18_av_write4(cx, 0x900, 0x08015555); | |
127 | cx18_av_write4(cx, 0x904, 0x08015555); | |
128 | cx18_av_write4(cx, 0x90c, 0x08015555); | |
129 | break; | |
130 | } | |
131 | } | |
132 | ||
133 | state->audclk_freq = freq; | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
138 | void cx18_av_audio_set_path(struct cx18 *cx) | |
139 | { | |
140 | struct cx18_av_state *state = &cx->av_state; | |
141 | ||
142 | /* stop microcontroller */ | |
143 | cx18_av_and_or(cx, 0x803, ~0x10, 0); | |
144 | ||
145 | /* assert soft reset */ | |
146 | cx18_av_and_or(cx, 0x810, ~0x1, 0x01); | |
147 | ||
148 | /* Mute everything to prevent the PFFT! */ | |
149 | cx18_av_write(cx, 0x8d3, 0x1f); | |
150 | ||
81cb727d | 151 | if (state->aud_input <= CX18_AV_AUDIO_SERIAL2) { |
1c1e45d1 HV |
152 | /* Set Path1 to Serial Audio Input */ |
153 | cx18_av_write4(cx, 0x8d0, 0x01011012); | |
154 | ||
155 | /* The microcontroller should not be started for the | |
156 | * non-tuner inputs: autodetection is specific for | |
157 | * TV audio. */ | |
158 | } else { | |
159 | /* Set Path1 to Analog Demod Main Channel */ | |
160 | cx18_av_write4(cx, 0x8d0, 0x1f063870); | |
161 | } | |
162 | ||
163 | set_audclk_freq(cx, state->audclk_freq); | |
164 | ||
165 | /* deassert soft reset */ | |
166 | cx18_av_and_or(cx, 0x810, ~0x1, 0x00); | |
167 | ||
81cb727d | 168 | if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { |
1c1e45d1 HV |
169 | /* When the microcontroller detects the |
170 | * audio format, it will unmute the lines */ | |
171 | cx18_av_and_or(cx, 0x803, ~0x10, 0x10); | |
172 | } | |
173 | } | |
174 | ||
175 | static int get_volume(struct cx18 *cx) | |
176 | { | |
177 | /* Volume runs +18dB to -96dB in 1/2dB steps | |
178 | * change to fit the msp3400 -114dB to +12dB range */ | |
179 | ||
180 | /* check PATH1_VOLUME */ | |
181 | int vol = 228 - cx18_av_read(cx, 0x8d4); | |
182 | vol = (vol / 2) + 23; | |
183 | return vol << 9; | |
184 | } | |
185 | ||
186 | static void set_volume(struct cx18 *cx, int volume) | |
187 | { | |
188 | /* First convert the volume to msp3400 values (0-127) */ | |
189 | int vol = volume >> 9; | |
190 | /* now scale it up to cx18_av values | |
191 | * -114dB to -96dB maps to 0 | |
192 | * this should be 19, but in my testing that was 4dB too loud */ | |
193 | if (vol <= 23) | |
194 | vol = 0; | |
195 | else | |
196 | vol -= 23; | |
197 | ||
198 | /* PATH1_VOLUME */ | |
199 | cx18_av_write(cx, 0x8d4, 228 - (vol * 2)); | |
200 | } | |
201 | ||
202 | static int get_bass(struct cx18 *cx) | |
203 | { | |
204 | /* bass is 49 steps +12dB to -12dB */ | |
205 | ||
206 | /* check PATH1_EQ_BASS_VOL */ | |
207 | int bass = cx18_av_read(cx, 0x8d9) & 0x3f; | |
208 | bass = (((48 - bass) * 0xffff) + 47) / 48; | |
209 | return bass; | |
210 | } | |
211 | ||
212 | static void set_bass(struct cx18 *cx, int bass) | |
213 | { | |
214 | /* PATH1_EQ_BASS_VOL */ | |
215 | cx18_av_and_or(cx, 0x8d9, ~0x3f, 48 - (bass * 48 / 0xffff)); | |
216 | } | |
217 | ||
218 | static int get_treble(struct cx18 *cx) | |
219 | { | |
220 | /* treble is 49 steps +12dB to -12dB */ | |
221 | ||
222 | /* check PATH1_EQ_TREBLE_VOL */ | |
223 | int treble = cx18_av_read(cx, 0x8db) & 0x3f; | |
224 | treble = (((48 - treble) * 0xffff) + 47) / 48; | |
225 | return treble; | |
226 | } | |
227 | ||
228 | static void set_treble(struct cx18 *cx, int treble) | |
229 | { | |
230 | /* PATH1_EQ_TREBLE_VOL */ | |
231 | cx18_av_and_or(cx, 0x8db, ~0x3f, 48 - (treble * 48 / 0xffff)); | |
232 | } | |
233 | ||
234 | static int get_balance(struct cx18 *cx) | |
235 | { | |
236 | /* balance is 7 bit, 0 to -96dB */ | |
237 | ||
238 | /* check PATH1_BAL_LEVEL */ | |
239 | int balance = cx18_av_read(cx, 0x8d5) & 0x7f; | |
240 | /* check PATH1_BAL_LEFT */ | |
241 | if ((cx18_av_read(cx, 0x8d5) & 0x80) == 0) | |
242 | balance = 0x80 - balance; | |
243 | else | |
244 | balance = 0x80 + balance; | |
245 | return balance << 8; | |
246 | } | |
247 | ||
248 | static void set_balance(struct cx18 *cx, int balance) | |
249 | { | |
250 | int bal = balance >> 8; | |
251 | if (bal > 0x80) { | |
252 | /* PATH1_BAL_LEFT */ | |
253 | cx18_av_and_or(cx, 0x8d5, 0x7f, 0x80); | |
254 | /* PATH1_BAL_LEVEL */ | |
255 | cx18_av_and_or(cx, 0x8d5, ~0x7f, bal & 0x7f); | |
256 | } else { | |
257 | /* PATH1_BAL_LEFT */ | |
258 | cx18_av_and_or(cx, 0x8d5, 0x7f, 0x00); | |
259 | /* PATH1_BAL_LEVEL */ | |
260 | cx18_av_and_or(cx, 0x8d5, ~0x7f, 0x80 - bal); | |
261 | } | |
262 | } | |
263 | ||
264 | static int get_mute(struct cx18 *cx) | |
265 | { | |
266 | /* check SRC1_MUTE_EN */ | |
267 | return cx18_av_read(cx, 0x8d3) & 0x2 ? 1 : 0; | |
268 | } | |
269 | ||
270 | static void set_mute(struct cx18 *cx, int mute) | |
271 | { | |
272 | struct cx18_av_state *state = &cx->av_state; | |
273 | ||
81cb727d | 274 | if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { |
1c1e45d1 HV |
275 | /* Must turn off microcontroller in order to mute sound. |
276 | * Not sure if this is the best method, but it does work. | |
277 | * If the microcontroller is running, then it will undo any | |
278 | * changes to the mute register. */ | |
279 | if (mute) { | |
280 | /* disable microcontroller */ | |
281 | cx18_av_and_or(cx, 0x803, ~0x10, 0x00); | |
282 | cx18_av_write(cx, 0x8d3, 0x1f); | |
283 | } else { | |
284 | /* enable microcontroller */ | |
285 | cx18_av_and_or(cx, 0x803, ~0x10, 0x10); | |
286 | } | |
287 | } else { | |
288 | /* SRC1_MUTE_EN */ | |
289 | cx18_av_and_or(cx, 0x8d3, ~0x2, mute ? 0x02 : 0x00); | |
290 | } | |
291 | } | |
292 | ||
293 | int cx18_av_audio(struct cx18 *cx, unsigned int cmd, void *arg) | |
294 | { | |
295 | struct cx18_av_state *state = &cx->av_state; | |
296 | struct v4l2_control *ctrl = arg; | |
297 | int retval; | |
298 | ||
299 | switch (cmd) { | |
300 | case VIDIOC_INT_AUDIO_CLOCK_FREQ: | |
81cb727d | 301 | if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { |
1c1e45d1 HV |
302 | cx18_av_and_or(cx, 0x803, ~0x10, 0); |
303 | cx18_av_write(cx, 0x8d3, 0x1f); | |
304 | } | |
305 | cx18_av_and_or(cx, 0x810, ~0x1, 1); | |
306 | retval = set_audclk_freq(cx, *(u32 *)arg); | |
307 | cx18_av_and_or(cx, 0x810, ~0x1, 0); | |
81cb727d | 308 | if (state->aud_input > CX18_AV_AUDIO_SERIAL2) |
1c1e45d1 HV |
309 | cx18_av_and_or(cx, 0x803, ~0x10, 0x10); |
310 | return retval; | |
311 | ||
312 | case VIDIOC_G_CTRL: | |
313 | switch (ctrl->id) { | |
314 | case V4L2_CID_AUDIO_VOLUME: | |
315 | ctrl->value = get_volume(cx); | |
316 | break; | |
317 | case V4L2_CID_AUDIO_BASS: | |
318 | ctrl->value = get_bass(cx); | |
319 | break; | |
320 | case V4L2_CID_AUDIO_TREBLE: | |
321 | ctrl->value = get_treble(cx); | |
322 | break; | |
323 | case V4L2_CID_AUDIO_BALANCE: | |
324 | ctrl->value = get_balance(cx); | |
325 | break; | |
326 | case V4L2_CID_AUDIO_MUTE: | |
327 | ctrl->value = get_mute(cx); | |
328 | break; | |
329 | default: | |
330 | return -EINVAL; | |
331 | } | |
332 | break; | |
333 | ||
334 | case VIDIOC_S_CTRL: | |
335 | switch (ctrl->id) { | |
336 | case V4L2_CID_AUDIO_VOLUME: | |
337 | set_volume(cx, ctrl->value); | |
338 | break; | |
339 | case V4L2_CID_AUDIO_BASS: | |
340 | set_bass(cx, ctrl->value); | |
341 | break; | |
342 | case V4L2_CID_AUDIO_TREBLE: | |
343 | set_treble(cx, ctrl->value); | |
344 | break; | |
345 | case V4L2_CID_AUDIO_BALANCE: | |
346 | set_balance(cx, ctrl->value); | |
347 | break; | |
348 | case V4L2_CID_AUDIO_MUTE: | |
349 | set_mute(cx, ctrl->value); | |
350 | break; | |
351 | default: | |
352 | return -EINVAL; | |
353 | } | |
354 | break; | |
355 | ||
356 | default: | |
357 | return -EINVAL; | |
358 | } | |
359 | ||
360 | return 0; | |
361 | } |