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