aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/video/cx88/cx88-alsa.c
diff options
context:
space:
mode:
authorTrent Piepho <xyzzy@speakeasy.org>2007-08-24 00:06:34 -0400
committerMauro Carvalho Chehab <mchehab@infradead.org>2007-10-09 21:06:34 -0400
commit05b2723387cb4086535e935ee07cca19086308fc (patch)
tree369957184bb4e5203a46ba45e8b30de91863da2a /drivers/media/video/cx88/cx88-alsa.c
parent16cf1d0c5d7b8970aca2ca426166833642ce0544 (diff)
V4L/DVB (6083): cx88-alsa: Rework buffer handling
Rework the way the DMA buffer is handled and IRQs are generated. ALSA uses a ring-buffer of multiple periods. Each period is supposed to corrispond to one IRQ. The existing driver was generating one interrupt per ring-buffer, as opposed to per period. This meant that as soon as the IRQ was generated, the hardware was already starting to re-write the beginning of the buffer. Since the DMA happens on a per-line basis, there was only a narrow window to copy the data out before the buffer was overwritten. The cx88 core RISC program generator is modified so that it can set the IRQ and counter flags to count every X lines of DMA transfer. This way we can generate an interrupt every period instead of every full ring-buffer. Right now only period of one line are supported, but it should be possible to support longer periods. Note that a WRITE instruction generates an IRQ when it starts, not when the transfer is finished. Thus to generate an IRQ when line X is done, one must set the IRQ flag on the instruction that starts line X+1, not the one that ends line X. Change the line size so that there are four lines in the SRAM FIFO. If there are not four lines, the analog output from the cx88's internal DACs is full of clicks and pops. Try to handle FIFO sync errors. Sometimes the chip generates many of these errors before audio data starts. Up to 50 sync errors will be ignored and the counter reset. Have the IRQ handler save the RISC counter to the chip struct, and then have the pointer callback use this to calculate the pointer position. We could read the counter from the pointer callback, but sometimes the sync errors on start up cause the counter to go crazy. ALSA sees this and thinks there has been an overrun. The IRQ hander can avoid saving the counter position on sync errors. The chip "opened" flag wasn't necessary. ALSA won't try to open the same substream multiple times. Probably this code was cut&pasted from the bt87x driver, which has multiple sub-streams for one chip. Do error checking for the videobuf mapping functions. snd_card_cx88_runtime_free() is useless and can be deleted. Signed-off-by: Trent Piepho <xyzzy@speakeasy.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'drivers/media/video/cx88/cx88-alsa.c')
-rw-r--r--drivers/media/video/cx88/cx88-alsa.c168
1 files changed, 73 insertions, 95 deletions
diff --git a/drivers/media/video/cx88/cx88-alsa.c b/drivers/media/video/cx88/cx88-alsa.c
index 8d19d33c8b32..33dd4cb5e877 100644
--- a/drivers/media/video/cx88/cx88-alsa.c
+++ b/drivers/media/video/cx88/cx88-alsa.c
@@ -3,6 +3,7 @@
3 * Support for audio capture 3 * Support for audio capture
4 * PCI function #1 of the cx2388x. 4 * PCI function #1 of the cx2388x.
5 * 5 *
6 * (c) 2007 Trent Piepho <xyzzy@speakeasy.org>
6 * (c) 2005,2006 Ricardo Cerqueira <v4l@cerqueira.org> 7 * (c) 2005,2006 Ricardo Cerqueira <v4l@cerqueira.org>
7 * (c) 2005 Mauro Carvalho Chehab <mchehab@infradead.org> 8 * (c) 2005 Mauro Carvalho Chehab <mchehab@infradead.org>
8 * Based on a dummy cx88 module by Gerd Knorr <kraxel@bytesex.org> 9 * Based on a dummy cx88 module by Gerd Knorr <kraxel@bytesex.org>
@@ -47,7 +48,6 @@
47#define dprintk_core(level,fmt, arg...) if (debug >= level) \ 48#define dprintk_core(level,fmt, arg...) if (debug >= level) \
48 printk(KERN_DEBUG "%s/1: " fmt, chip->core->name , ## arg) 49 printk(KERN_DEBUG "%s/1: " fmt, chip->core->name , ## arg)
49 50
50
51/**************************************************************************** 51/****************************************************************************
52 Data type declarations - Can be moded to a header file later 52 Data type declarations - Can be moded to a header file later
53 ****************************************************************************/ 53 ****************************************************************************/
@@ -58,6 +58,7 @@
58struct cx88_audio_dev { 58struct cx88_audio_dev {
59 struct cx88_core *core; 59 struct cx88_core *core;
60 struct cx88_dmaqueue q; 60 struct cx88_dmaqueue q;
61 u64 starttime;
61 62
62 /* pci i/o */ 63 /* pci i/o */
63 struct pci_dev *pci; 64 struct pci_dev *pci;
@@ -68,24 +69,20 @@ struct cx88_audio_dev {
68 struct snd_card *card; 69 struct snd_card *card;
69 70
70 spinlock_t reg_lock; 71 spinlock_t reg_lock;
72 atomic_t count;
71 73
72 unsigned int dma_size; 74 unsigned int dma_size;
73 unsigned int period_size; 75 unsigned int period_size;
74 unsigned int num_periods; 76 unsigned int num_periods;
75 77
76 struct videobuf_dmabuf dma_risc; 78 struct videobuf_dmabuf dma_risc;
77 79
78 int mixer_volume[MIXER_ADDR_LAST+1][2]; 80 int mixer_volume[MIXER_ADDR_LAST+1][2];
79 int capture_source[MIXER_ADDR_LAST+1][2]; 81 int capture_source[MIXER_ADDR_LAST+1][2];
80 82
81 long int read_count; 83 struct cx88_buffer *buf;
82 long int read_offset;
83
84 struct cx88_buffer *buf;
85
86 long opened;
87 struct snd_pcm_substream *substream;
88 84
85 struct snd_pcm_substream *substream;
89}; 86};
90typedef struct cx88_audio_dev snd_cx88_card_t; 87typedef struct cx88_audio_dev snd_cx88_card_t;
91 88
@@ -146,16 +143,13 @@ static int _cx88_start_audio_dma(snd_cx88_card_t *chip)
146 cx_write(MO_AUDD_LNGTH, buf->bpl); 143 cx_write(MO_AUDD_LNGTH, buf->bpl);
147 144
148 /* reset counter */ 145 /* reset counter */
149 cx_write(MO_AUDD_GPCNTRL,GP_COUNT_CONTROL_RESET); 146 cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET);
147 atomic_set(&chip->count, 0);
150 148
151 dprintk(1, "Start audio DMA, %d B/line, %d lines/FIFO, %d lines/irq, " 149 dprintk(1, "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d "
152 "%d B/irq\n", buf->bpl, cx_read(audio_ch->cmds_start + 8)>>1, 150 "byte buffer\n", buf->bpl, cx_read(audio_ch->cmds_start + 8)>>1,
153 chip->num_periods, buf->bpl * chip->num_periods); 151 chip->num_periods, buf->bpl * chip->num_periods);
154 152
155 dprintk(1, "Enabling IRQ, setting mask from 0x%x to 0x%x\n",
156 chip->core->pci_irqmask,
157 chip->core->pci_irqmask | PCI_INT_AUDINT);
158
159 /* Enables corresponding bits at AUD_INT_STAT */ 153 /* Enables corresponding bits at AUD_INT_STAT */
160 cx_write(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | 154 cx_write(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC |
161 AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); 155 AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1);
@@ -198,7 +192,7 @@ static int _cx88_stop_audio_dma(snd_cx88_card_t *chip)
198 return 0; 192 return 0;
199} 193}
200 194
201#define MAX_IRQ_LOOP 10 195#define MAX_IRQ_LOOP 50
202 196
203/* 197/*
204 * BOARD Specific: IRQ dma bits 198 * BOARD Specific: IRQ dma bits
@@ -223,14 +217,11 @@ static void cx8801_aud_irq(snd_cx88_card_t *chip)
223{ 217{
224 struct cx88_core *core = chip->core; 218 struct cx88_core *core = chip->core;
225 u32 status, mask; 219 u32 status, mask;
226 u32 count;
227 220
228 status = cx_read(MO_AUD_INTSTAT); 221 status = cx_read(MO_AUD_INTSTAT);
229 mask = cx_read(MO_AUD_INTMSK); 222 mask = cx_read(MO_AUD_INTMSK);
230 if (0 == (status & mask)) { 223 if (0 == (status & mask))
231 spin_unlock(&chip->reg_lock);
232 return; 224 return;
233 }
234 cx_write(MO_AUD_INTSTAT, status); 225 cx_write(MO_AUD_INTSTAT, status);
235 if (debug > 1 || (status & mask & ~0xff)) 226 if (debug > 1 || (status & mask & ~0xff))
236 cx88_print_irqbits(core->name, "irq aud", 227 cx88_print_irqbits(core->name, "irq aud",
@@ -238,27 +229,20 @@ static void cx8801_aud_irq(snd_cx88_card_t *chip)
238 status, mask); 229 status, mask);
239 /* risc op code error */ 230 /* risc op code error */
240 if (status & AUD_INT_OPC_ERR) { 231 if (status & AUD_INT_OPC_ERR) {
241 printk(KERN_WARNING "%s/0: audio risc op code error\n",core->name); 232 printk(KERN_WARNING "%s/1: Audio risc op code error\n",core->name);
242 cx_clear(MO_AUD_DMACNTRL, 0x11); 233 cx_clear(MO_AUD_DMACNTRL, 0x11);
243 cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH25]); 234 cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH25]);
244 } 235 }
245 236 if (status & AUD_INT_DN_SYNC) {
237 dprintk(1, "Downstream sync error\n");
238 cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET);
239 return;
240 }
246 /* risc1 downstream */ 241 /* risc1 downstream */
247 if (status & AUD_INT_DN_RISCI1) { 242 if (status & AUD_INT_DN_RISCI1) {
248 spin_lock(&chip->reg_lock); 243 atomic_set(&chip->count, cx_read(MO_AUDD_GPCNT));
249 count = cx_read(MO_AUDD_GPCNT);
250 spin_unlock(&chip->reg_lock);
251 if (chip->read_count == 0)
252 chip->read_count += chip->dma_size;
253 }
254
255 if (chip->read_count >= chip->period_size) {
256 dprintk(2, "Elapsing period\n");
257 snd_pcm_period_elapsed(chip->substream); 244 snd_pcm_period_elapsed(chip->substream);
258 } 245 }
259
260 dprintk(3,"Leaving audio IRQ handler...\n");
261
262 /* FIXME: Any other status should deserve a special handling? */ 246 /* FIXME: Any other status should deserve a special handling? */
263} 247}
264 248
@@ -277,23 +261,20 @@ static irqreturn_t cx8801_irq(int irq, void *dev_id)
277 (core->pci_irqmask | PCI_INT_AUDINT); 261 (core->pci_irqmask | PCI_INT_AUDINT);
278 if (0 == status) 262 if (0 == status)
279 goto out; 263 goto out;
280 dprintk( 3, "cx8801_irq\n" ); 264 dprintk(3, "cx8801_irq loop %d/%d, status %x\n",
281 dprintk( 3, " loop: %d/%d\n", loop, MAX_IRQ_LOOP ); 265 loop, MAX_IRQ_LOOP, status);
282 dprintk( 3, " status: %d\n", status );
283 handled = 1; 266 handled = 1;
284 cx_write(MO_PCI_INTSTAT, status); 267 cx_write(MO_PCI_INTSTAT, status);
285 268
286 if (status & core->pci_irqmask) 269 if (status & core->pci_irqmask)
287 cx88_core_irq(core, status); 270 cx88_core_irq(core, status);
288 if (status & PCI_INT_AUDINT) { 271 if (status & PCI_INT_AUDINT)
289 dprintk( 2, " ALSA IRQ handling\n" );
290 cx8801_aud_irq(chip); 272 cx8801_aud_irq(chip);
291 } 273 }
292 };
293 274
294 if (MAX_IRQ_LOOP == loop) { 275 if (MAX_IRQ_LOOP == loop) {
295 dprintk( 0, "clearing mask\n" ); 276 printk(KERN_ERR
296 dprintk(1,"%s/0: irq loop -- clearing mask\n", 277 "%s/1: IRQ loop detected, disabling interrupts\n",
297 core->name); 278 core->name);
298 cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); 279 cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT);
299 } 280 }
@@ -315,7 +296,7 @@ static int dsp_buffer_free(snd_cx88_card_t *chip)
315 296
316 chip->dma_size = 0; 297 chip->dma_size = 0;
317 298
318 return 0; 299 return 0;
319} 300}
320 301
321/**************************************************************************** 302/****************************************************************************
@@ -325,6 +306,7 @@ static int dsp_buffer_free(snd_cx88_card_t *chip)
325/* 306/*
326 * Digital hardware definition 307 * Digital hardware definition
327 */ 308 */
309#define DEFAULT_FIFO_SIZE 4096
328static struct snd_pcm_hardware snd_cx88_digital_hw = { 310static struct snd_pcm_hardware snd_cx88_digital_hw = {
329 .info = SNDRV_PCM_INFO_MMAP | 311 .info = SNDRV_PCM_INFO_MMAP |
330 SNDRV_PCM_INFO_INTERLEAVED | 312 SNDRV_PCM_INFO_INTERLEAVED |
@@ -337,20 +319,16 @@ static struct snd_pcm_hardware snd_cx88_digital_hw = {
337 .rate_max = 48000, 319 .rate_max = 48000,
338 .channels_min = 2, 320 .channels_min = 2,
339 .channels_max = 2, 321 .channels_max = 2,
340 .buffer_bytes_max = (2*2048), 322 /* Analog audio output will be full of clicks and pops if there
341 .period_bytes_min = 2048, 323 are not exactly four lines in the SRAM FIFO buffer. */
342 .period_bytes_max = 2048, 324 .period_bytes_min = DEFAULT_FIFO_SIZE/4,
343 .periods_min = 2, 325 .period_bytes_max = DEFAULT_FIFO_SIZE/4,
344 .periods_max = 2, 326 .periods_min = 1,
327 .periods_max = 1024,
328 .buffer_bytes_max = (1024*1024),
345}; 329};
346 330
347/* 331/*
348 * audio pcm capture runtime free
349 */
350static void snd_card_cx88_runtime_free(struct snd_pcm_runtime *runtime)
351{
352}
353/*
354 * audio pcm capture open callback 332 * audio pcm capture open callback
355 */ 333 */
356static int snd_cx88_pcm_open(struct snd_pcm_substream *substream) 334static int snd_cx88_pcm_open(struct snd_pcm_substream *substream)
@@ -359,26 +337,24 @@ static int snd_cx88_pcm_open(struct snd_pcm_substream *substream)
359 struct snd_pcm_runtime *runtime = substream->runtime; 337 struct snd_pcm_runtime *runtime = substream->runtime;
360 int err; 338 int err;
361 339
362 if (test_and_set_bit(0, &chip->opened)) 340 err = snd_pcm_hw_constraint_pow2(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS);
363 return -EBUSY;
364
365 err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
366 if (err < 0) 341 if (err < 0)
367 goto _error; 342 goto _error;
368 343
369 chip->substream = substream; 344 chip->substream = substream;
370 345
371 chip->read_count = 0;
372 chip->read_offset = 0;
373
374 runtime->private_free = snd_card_cx88_runtime_free;
375 runtime->hw = snd_cx88_digital_hw; 346 runtime->hw = snd_cx88_digital_hw;
376 347
348 if (cx88_sram_channels[SRAM_CH25].fifo_size != DEFAULT_FIFO_SIZE) {
349 unsigned int bpl = cx88_sram_channels[SRAM_CH25].fifo_size / 4;
350 bpl &= ~7; /* must be multiple of 8 */
351 runtime->hw.period_bytes_min = bpl;
352 runtime->hw.period_bytes_max = bpl;
353 }
354
377 return 0; 355 return 0;
378_error: 356_error:
379 dprintk(1,"Error opening PCM!\n"); 357 dprintk(1,"Error opening PCM!\n");
380 clear_bit(0, &chip->opened);
381 smp_mb__after_clear_bit();
382 return err; 358 return err;
383} 359}
384 360
@@ -387,11 +363,6 @@ _error:
387 */ 363 */
388static int snd_cx88_close(struct snd_pcm_substream *substream) 364static int snd_cx88_close(struct snd_pcm_substream *substream)
389{ 365{
390 snd_cx88_card_t *chip = snd_pcm_substream_chip(substream);
391
392 clear_bit(0, &chip->opened);
393 smp_mb__after_clear_bit();
394
395 return 0; 366 return 0;
396} 367}
397 368
@@ -403,54 +374,61 @@ static int snd_cx88_hw_params(struct snd_pcm_substream * substream,
403{ 374{
404 snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); 375 snd_cx88_card_t *chip = snd_pcm_substream_chip(substream);
405 struct cx88_buffer *buf; 376 struct cx88_buffer *buf;
377 int ret;
406 378
407 if (substream->runtime->dma_area) { 379 if (substream->runtime->dma_area) {
408 dsp_buffer_free(chip); 380 dsp_buffer_free(chip);
409 substream->runtime->dma_area = NULL; 381 substream->runtime->dma_area = NULL;
410 } 382 }
411 383
412
413 chip->period_size = params_period_bytes(hw_params); 384 chip->period_size = params_period_bytes(hw_params);
414 chip->num_periods = params_periods(hw_params); 385 chip->num_periods = params_periods(hw_params);
415 chip->dma_size = chip->period_size * params_periods(hw_params); 386 chip->dma_size = chip->period_size * params_periods(hw_params);
416 387
417 BUG_ON(!chip->dma_size); 388 BUG_ON(!chip->dma_size);
389 BUG_ON(chip->num_periods & (chip->num_periods-1));
418 390
419 dprintk(1,"Setting buffer\n"); 391 buf = kzalloc(sizeof(*buf), GFP_KERNEL);
420
421 buf = kzalloc(sizeof(*buf),GFP_KERNEL);
422 if (NULL == buf) 392 if (NULL == buf)
423 return -ENOMEM; 393 return -ENOMEM;
424 394
425 buf->vb.memory = V4L2_MEMORY_MMAP; 395 buf->vb.memory = V4L2_MEMORY_MMAP;
396 buf->vb.field = V4L2_FIELD_NONE;
426 buf->vb.width = chip->period_size; 397 buf->vb.width = chip->period_size;
398 buf->bpl = chip->period_size;
427 buf->vb.height = chip->num_periods; 399 buf->vb.height = chip->num_periods;
428 buf->vb.size = chip->dma_size; 400 buf->vb.size = chip->dma_size;
429 buf->vb.field = V4L2_FIELD_NONE;
430 401
431 videobuf_dma_init(&buf->vb.dma); 402 videobuf_dma_init(&buf->vb.dma);
432 videobuf_dma_init_kernel(&buf->vb.dma,PCI_DMA_FROMDEVICE, 403 ret = videobuf_dma_init_kernel(&buf->vb.dma, PCI_DMA_FROMDEVICE,
433 (PAGE_ALIGN(buf->vb.size) >> PAGE_SHIFT)); 404 (PAGE_ALIGN(buf->vb.size) >> PAGE_SHIFT));
405 if (ret < 0)
406 goto error;
434 407
435 videobuf_pci_dma_map(chip->pci,&buf->vb.dma); 408 ret = videobuf_pci_dma_map(chip->pci,&buf->vb.dma);
436 409 if (ret < 0)
410 goto error;
437 411
438 cx88_risc_databuffer(chip->pci, &buf->risc, 412 ret = cx88_risc_databuffer(chip->pci, &buf->risc, buf->vb.dma.sglist,
439 buf->vb.dma.sglist, 413 buf->vb.width, buf->vb.height, 1);
440 buf->vb.width, buf->vb.height); 414 if (ret < 0)
415 goto error;
441 416
442 buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); 417 /* Loop back to start of program */
418 buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP|RISC_IRQ1|RISC_CNT_INC);
443 buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma); 419 buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
444 420
445 buf->vb.state = STATE_PREPARED; 421 buf->vb.state = STATE_PREPARED;
446 422
447 buf->bpl = chip->period_size;
448 chip->buf = buf; 423 chip->buf = buf;
449 chip->dma_risc = buf->vb.dma; 424 chip->dma_risc = buf->vb.dma;
450 425
451 dprintk(1,"Buffer ready at %u\n",chip->dma_risc.nr_pages);
452 substream->runtime->dma_area = chip->dma_risc.vmalloc; 426 substream->runtime->dma_area = chip->dma_risc.vmalloc;
453 return 0; 427 return 0;
428
429error:
430 kfree(buf);
431 return ret;
454} 432}
455 433
456/* 434/*
@@ -477,7 +455,6 @@ static int snd_cx88_prepare(struct snd_pcm_substream *substream)
477 return 0; 455 return 0;
478} 456}
479 457
480
481/* 458/*
482 * trigger callback 459 * trigger callback
483 */ 460 */
@@ -486,6 +463,7 @@ static int snd_cx88_card_trigger(struct snd_pcm_substream *substream, int cmd)
486 snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); 463 snd_cx88_card_t *chip = snd_pcm_substream_chip(substream);
487 int err; 464 int err;
488 465
466 /* Local interrupts are already disabled by ALSA */
489 spin_lock(&chip->reg_lock); 467 spin_lock(&chip->reg_lock);
490 468
491 switch (cmd) { 469 switch (cmd) {
@@ -512,17 +490,14 @@ static snd_pcm_uframes_t snd_cx88_pointer(struct snd_pcm_substream *substream)
512{ 490{
513 snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); 491 snd_cx88_card_t *chip = snd_pcm_substream_chip(substream);
514 struct snd_pcm_runtime *runtime = substream->runtime; 492 struct snd_pcm_runtime *runtime = substream->runtime;
493 u16 count;
515 494
516 if (chip->read_count) { 495 count = atomic_read(&chip->count);
517 chip->read_count -= snd_pcm_lib_period_bytes(substream);
518 chip->read_offset += snd_pcm_lib_period_bytes(substream);
519 if (chip->read_offset == chip->dma_size)
520 chip->read_offset = 0;
521 }
522
523 dprintk(2, "Pointer time, will return %li, read %li\n",chip->read_offset,chip->read_count);
524 return bytes_to_frames(runtime, chip->read_offset);
525 496
497// dprintk(2, "%s - count %d (+%u), period %d, frame %lu\n", __FUNCTION__,
498// count, new, count & (runtime->periods-1),
499// runtime->period_size * (count & (runtime->periods-1)));
500 return runtime->period_size * (count & (runtime->periods-1));
526} 501}
527 502
528/* 503/*
@@ -592,10 +567,13 @@ static int snd_cx88_capture_volume_put(struct snd_kcontrol *kcontrol,
592 int v; 567 int v;
593 u32 old_control; 568 u32 old_control;
594 569
570 /* Do we really know this will always be called with IRQs on? */
595 spin_lock_irq(&chip->reg_lock); 571 spin_lock_irq(&chip->reg_lock);
572
596 old_control = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f); 573 old_control = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f);
597 v = 0x3f - (value->value.integer.value[0] & 0x3f); 574 v = 0x3f - (value->value.integer.value[0] & 0x3f);
598 cx_andor(AUD_VOL_CTL, 0x3f, v); 575 cx_andor(AUD_VOL_CTL, 0x3f, v);
576
599 spin_unlock_irq(&chip->reg_lock); 577 spin_unlock_irq(&chip->reg_lock);
600 578
601 return v != old_control; 579 return v != old_control;