/* * linux/drivers/sound/vidc.c * * Copyright (C) 1997-2000 by Russell King <rmk@arm.linux.org.uk> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * VIDC20 audio driver. * * The VIDC20 sound hardware consists of the VIDC20 itself, a DAC and a DMA * engine. The DMA transfers fixed-format (16-bit little-endian linear) * samples to the VIDC20, which then transfers this data serially to the * DACs. The samplerate is controlled by the VIDC. * * We currently support a mixer device, but it is currently non-functional. */ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/interrupt.h> #include <asm/hardware.h> #include <asm/dma.h> #include <asm/io.h> #include <asm/hardware/iomd.h> #include <asm/irq.h> #include <asm/system.h> #include "sound_config.h" #include "vidc.h" #ifndef _SIOC_TYPE #define _SIOC_TYPE(x) _IOC_TYPE(x) #endif #ifndef _SIOC_NR #define _SIOC_NR(x) _IOC_NR(x) #endif #define VIDC_SOUND_CLOCK (250000) #define VIDC_SOUND_CLOCK_EXT (176400) /* * When using SERIAL SOUND mode (external DAC), the number of physical * channels is fixed at 2. */ static int vidc_busy; static int vidc_adev; static int vidc_audio_rate; static char vidc_audio_format; static char vidc_audio_channels; static unsigned char vidc_level_l[SOUND_MIXER_NRDEVICES] = { 85, /* master */ 50, /* bass */ 50, /* treble */ 0, /* synth */ 75, /* pcm */ 0, /* speaker */ 100, /* ext line */ 0, /* mic */ 100, /* CD */ 0, }; static unsigned char vidc_level_r[SOUND_MIXER_NRDEVICES] = { 85, /* master */ 50, /* bass */ 50, /* treble */ 0, /* synth */ 75, /* pcm */ 0, /* speaker */ 100, /* ext line */ 0, /* mic */ 100, /* CD */ 0, }; static unsigned int vidc_audio_volume_l; /* left PCM vol, 0 - 65536 */ static unsigned int vidc_audio_volume_r; /* right PCM vol, 0 - 65536 */ extern void vidc_update_filler(int bits, int channels); extern int softoss_dev; static void vidc_mixer_set(int mdev, unsigned int level) { unsigned int lev_l = level & 0x007f; unsigned int lev_r = (level & 0x7f00) >> 8; unsigned int mlev_l, mlev_r; if (lev_l > 100) lev_l = 100; if (lev_r > 100) lev_r = 100; #define SCALE(lev,master) ((lev) * (master) * 65536 / 10000) mlev_l = vidc_level_l[SOUND_MIXER_VOLUME]; mlev_r = vidc_level_r[SOUND_MIXER_VOLUME]; switch (mdev) { case SOUND_MIXER_VOLUME: case SOUND_MIXER_PCM: vidc_level_l[mdev] = lev_l; vidc_level_r[mdev] = lev_r; vidc_audio_volume_l = SCALE(lev_l, mlev_l); vidc_audio_volume_r = SCALE(lev_r, mlev_r); /*printk("VIDC: PCM vol %05X %05X\n", vidc_audio_volume_l, vidc_audio_volume_r);*/ break; } #undef SCALE } static int vidc_mixer_ioctl(int dev, unsigned int cmd, void __user *arg) { unsigned int val; unsigned int mdev; if (_SIOC_TYPE(cmd) != 'M') return -EINVAL; mdev = _SIOC_NR(cmd); if (_SIOC_DIR(cmd) & _SIOC_WRITE) { if (get_user(val, (unsigned int __user *)arg)) return -EFAULT; if (mdev < SOUND_MIXER_NRDEVICES) vidc_mixer_set(mdev, val); else return -EINVAL; } /* * Return parameters */ switch (mdev) { case SOUND_MIXER_RECSRC: val = 0; break; case SOUND_MIXER_DEVMASK: val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH; break; case SOUND_MIXER_STEREODEVS: val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH; break; case SOUND_MIXER_RECMASK: val = 0; break; case SOUND_MIXER_CAPS: val = 0; break; default: if (mdev < SOUND_MIXER_NRDEVICES) val = vidc_level_l[mdev] | vidc_level_r[mdev] << 8; else return -EINVAL; } return put_user(val, (unsigned int __user *)arg) ? -EFAULT : 0; } static unsigned int vidc_audio_set_format(int dev, unsigned int fmt) { switch (fmt) { default: fmt = AFMT_S16_LE; case AFMT_U8: case AFMT_S8: case AFMT_S16_LE: vidc_audio_format = fmt; vidc_update_filler(vidc_audio_format, vidc_audio_channels); case AFMT_QUERY: break; } return vidc_audio_format; } #define my_abs(i) ((i)<0 ? -(i) : (i)) static int vidc_audio_set_speed(int dev, int rate) { if (rate) { unsigned int hwctrl, hwrate, hwrate_ext, rate_int, rate_ext; unsigned int diff_int, diff_ext; unsigned int newsize, new2size; hwctrl = 0x00000003; /* Using internal clock */ hwrate = (((VIDC_SOUND_CLOCK * 2) / rate) + 1) >> 1; if (hwrate < 3) hwrate = 3; if (hwrate > 255) hwrate = 255; /* Using exernal clock */ hwrate_ext = (((VIDC_SOUND_CLOCK_EXT * 2) / rate) + 1) >> 1; if (hwrate_ext < 3) hwrate_ext = 3; if (hwrate_ext > 255) hwrate_ext = 255; rate_int = VIDC_SOUND_CLOCK / hwrate; rate_ext = VIDC_SOUND_CLOCK_EXT / hwrate_ext; /* Chose between external and internal clock */ diff_int = my_abs(rate_ext-rate); diff_ext = my_abs(rate_int-rate); if (diff_ext < diff_int) { /*printk("VIDC: external %d %d %d\n", rate, rate_ext, hwrate_ext);*/ hwrate=hwrate_ext; hwctrl=0x00000002; /* Allow roughly 0.4% tolerance */ if (diff_ext > (rate/256)) rate=rate_ext; } else { /*printk("VIDC: internal %d %d %d\n", rate, rate_int, hwrate);*/ hwctrl=0x00000003; /* Allow rougly 0.4% tolerance */ if (diff_int > (rate/256)) rate=rate_int; } vidc_writel(0xb0000000 | (hwrate - 2)); vidc_writel(0xb1000000 | hwctrl); newsize = (10000 / hwrate) & ~3; if (newsize < 208) newsize = 208; if (newsize > 4096) newsize = 4096; for (new2size = 128; new2size < newsize; new2size <<= 1); if (new2size - newsize > newsize - (new2size >> 1)) new2size >>= 1; if (new2size > 4096) { printk(KERN_ERR "VIDC: error: dma buffer (%d) %d > 4K\n", newsize, new2size); new2size = 4096; } /*printk("VIDC: dma size %d\n", new2size);*/ dma_bufsize = new2size; vidc_audio_rate = rate; } return vidc_audio_rate; } static short vidc_audio_set_channels(int dev, short channels) { switch (channels) { default: channels = 2; case 1: case 2: vidc_audio_channels = channels; vidc_update_filler(vidc_audio_format, vidc_audio_channels); case 0: break; } return vidc_audio_channels; } /* * Open the device */ static int vidc_audio_open(int dev, int mode) { /* This audio device does not have recording capability */ if (mode == OPEN_READ) return -EPERM; if (vidc_busy) return -EBUSY; vidc_busy = 1; return 0; } /* * Close the device */ static void vidc_audio_close(int dev) { vidc_busy = 0; } /* * Output a block via DMA to sound device. * * We just set the DMA start and count; the DMA interrupt routine * will take care of formatting the samples (via the appropriate * vidc_filler routine), and flag via vidc_audio_dma_interrupt when * more data is required. */ static void vidc_audio_output_block(int dev, unsigned long buf, int total_count, int one) { struct dma_buffparms *dmap = audio_devs[dev]->dmap_out; unsigned long flags; local_irq_save(flags); dma_start = buf - (unsigned long)dmap->raw_buf_phys + (unsigned long)dmap->raw_buf; dma_count = total_count; local_irq_restore(flags); } static void vidc_audio_start_input(int dev, unsigned long buf, int count, int intrflag) { } static int vidc_audio_prepare_for_input(int dev, int bsize, int bcount) { return -EINVAL; } static irqreturn_t vidc_audio_dma_interrupt(void) { DMAbuf_outputintr(vidc_adev, 1); return IRQ_HANDLED; } /* * Prepare for outputting samples. * * Each buffer that will be passed will be `bsize' bytes long, * with a total of `bcount' buffers. */ static int vidc_audio_prepare_for_output(int dev, int bsize, int bcount) { struct audio_operations *adev = audio_devs[dev]; dma_interrupt = NULL; adev->dmap_out->flags |= DMA_NODMA; return 0; } /* * Stop our current operation. */ static void vidc_audio_reset(int dev) { dma_interrupt = NULL; } static int vidc_audio_local_qlen(int dev) { return /*dma_count !=*/ 0; } static void vidc_audio_trigger(int dev, int enable_bits) { struct audio_operations *adev = audio_devs[dev]; if (enable_bits & PCM_ENABLE_OUTPUT) { if (!(adev->flags & DMA_ACTIVE)) { unsigned long flags; local_irq_save(flags); /* prevent recusion */ adev->flags |= DMA_ACTIVE; dma_interrupt = vidc_audio_dma_interrupt; vidc_sound_dma_irq(0, NULL); iomd_writeb(DMA_CR_E | 0x10, IOMD_SD0CR); local_irq_restore(flags); } } } static struct audio_driver vidc_audio_driver = { .owner = THIS_MODULE, .open = vidc_audio_open, .close = vidc_audio_close, .output_block = vidc_audio_output_block, .start_input = vidc_audio_start_input, .prepare_for_input = vidc_audio_prepare_for_input, .prepare_for_output = vidc_audio_prepare_for_output, .halt_io = vidc_audio_reset, .local_qlen = vidc_audio_local_qlen, .trigger = vidc_audio_trigger, .set_speed = vidc_audio_set_speed, .set_bits = vidc_audio_set_format, .set_channels = vidc_audio_set_channels }; static struct mixer_operations vidc_mixer_operations = { .owner = THIS_MODULE, .id = "VIDC", .name = "VIDCsound", .ioctl = vidc_mixer_ioctl }; void vidc_update_filler(int format, int channels) { #define TYPE(fmt,ch) (((fmt)<<2) | ((ch)&3)) switch (TYPE(format, channels)) { default: case TYPE(AFMT_U8, 1): vidc_filler = vidc_fill_1x8_u; break; case TYPE(AFMT_U8, 2): vidc_filler = vidc_fill_2x8_u; break; case TYPE(AFMT_S8, 1): vidc_filler = vidc_fill_1x8_s; break; case TYPE(AFMT_S8, 2): vidc_filler = vidc_fill_2x8_s; break; case TYPE(AFMT_S16_LE, 1): vidc_filler = vidc_fill_1x16_s; break; case TYPE(AFMT_S16_LE, 2): vidc_filler = vidc_fill_2x16_s; break; } } static void __init attach_vidc(struct address_info *hw_config) { char name[32]; int i, adev; sprintf(name, "VIDC %d-bit sound", hw_config->card_subtype); conf_printf(name, hw_config); memset(dma_buf, 0, sizeof(dma_buf)); adev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, name, &vidc_audio_driver, sizeof(vidc_audio_driver), DMA_AUTOMODE, AFMT_U8 | AFMT_S8 | AFMT_S16_LE, NULL, hw_config->dma, hw_config->dma2); if (adev < 0) goto audio_failed; /* * 1024 bytes => 64 buffers */ audio_devs[adev]->min_fragment = 10; audio_devs[adev]->mixer_dev = num_mixers; audio_devs[adev]->mixer_dev = sound_install_mixer(MIXER_DRIVER_VERSION, name, &vidc_mixer_operations, sizeof(vidc_mixer_operations), NULL); if (audio_devs[adev]->mixer_dev < 0) goto mixer_failed; for (i = 0; i < 2; i++) { dma_buf[i] = get_zeroed_page(GFP_KERNEL); if (!dma_buf[i]) { printk(KERN_ERR "%s: can't allocate required buffers\n", name); goto mem_failed; } dma_pbuf[i] = virt_to_phys((void *)dma_buf[i]); } if (sound_alloc_dma(hw_config->dma, hw_config->name)) { printk(KERN_ERR "%s: DMA %d is in use\n", name, hw_config->dma); goto dma_failed; } if (request_irq(hw_config->irq, vidc_sound_dma_irq, 0, hw_config->name, &dma_start)) { printk(KERN_ERR "%s: IRQ %d is in use\n", name, hw_config->irq); goto irq_failed; } vidc_adev = adev; vidc_mixer_set(SOUND_MIXER_VOLUME, (85 | 85 << 8)); #if defined(CONFIG_SOUND_SOFTOSS) || defined(CONFIG_SOUND_SOFTOSS_MODULE) softoss_dev = adev; #endif return; irq_failed: sound_free_dma(hw_config->dma); dma_failed: mem_failed: for (i = 0; i < 2; i++) free_page(dma_buf[i]); sound_unload_mixerdev(audio_devs[adev]->mixer_dev); mixer_failed: sound_unload_audiodev(adev); audio_failed: return; } static int __init probe_vidc(struct address_info *hw_config) { hw_config->irq = IRQ_DMAS0; hw_config->dma = DMA_VIRTUAL_SOUND; hw_config->dma2 = -1; hw_config->card_subtype = 16; hw_config->name = "VIDC20"; return 1; } static void __exit unload_vidc(struct address_info *hw_config) { int i, adev = vidc_adev; vidc_adev = -1; free_irq(hw_config->irq, &dma_start); sound_free_dma(hw_config->dma); if (adev >= 0) { sound_unload_mixerdev(audio_devs[adev]->mixer_dev); sound_unload_audiodev(adev); for (i = 0; i < 2; i++) free_page(dma_buf[i]); } } static struct address_info cfg; static int __init init_vidc(void) { if (probe_vidc(&cfg) == 0) return -ENODEV; attach_vidc(&cfg); return 0; } static void __exit cleanup_vidc(void) { unload_vidc(&cfg); } module_init(init_vidc); module_exit(cleanup_vidc); MODULE_AUTHOR("Russell King"); MODULE_DESCRIPTION("VIDC20 audio driver"); MODULE_LICENSE("GPL");