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