From: M. R. B. <mr...@us...> - 2002-10-29 15:39:12
|
Update of /cvsroot/linuxdc/linux-sh-dc/sound/oss/aica In directory usw-pr-cvs1:/tmp/cvs-serv20099 Added Files: Tag: 1.9 main.c Log Message: Moved. --- NEW FILE: main.c --- /*======================================================= == OSS compliant sound driver for Dreamcast Linux == == New coding is == == Copyright (C) Adrian McMenamin 2001, 2002 == == ad...@mc... == == == == Substantially based on Dan Potter's Kallistios code == == Copyright Dan Potter and others, 2000, 2001 == == == == == == Licencsed under FSF's General Public Licence v2 == == http://www.gnu.org == == Absolutely no warranty is offered == ========================================================= == == == This drives the SH4 end of the sound driver ========================================================= ======================================================= $Id: main.c,v 1.9 2002/10/29 15:39:09 mrbrown Exp $ */ #include <linux/init.h> #include <linux/config.h> #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/kmod.h> #include <linux/mm.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/ioport.h> #include <linux/errno.h> #include <linux/ioctl.h> #include <asm/semaphore.h> #include <asm/dc_sysasic.h> #include "../sound_config.h" #include "arm7.h" /* Command values */ #define AICA_CMD_KICK 0x80000000 #define AICA_CMD_NONE 0 #define AICA_CMD_START 1 #define AICA_CMD_STOP 2 #define AICA_CMD_VOL 3 /* Sound modes */ #define SM_8BIT 1 #define SM_16BIT 0 #define SM_ADPCM 2 #ifdef _BUILD_DEBUG_ #define DEBGM(fmt, args...) (printk(KERN_ERR fmt, ##args)) #else #define DEBGM(fmt, args...) ((void) 0) #endif static char *BUFFER; static char *BUFFER2; static char *BUFFERL; static char *BUFFERR; static int buffer_size = 0x2000; static int total_count; static int samplelength; static int stereo; static int left_volume = 0xa0; static int right_volume = 0xa0; static int currentpoint; static struct semaphore dsp_mutex; static int sleeps = 0; /* default is not to sleep for hi-sample rate sounds */ MODULE_PARM(sleeps, "i"); typedef struct { uint32_t cmd; /* Command ID */ uint32_t pos; /* Sample position */ uint32_t length; /* Sample length */ uint32_t freq; /* Frequency */ uint32_t vol; /* Volume 0-255 */ uint32_t pan; /* Pan 0-255 */ uint32_t sfmt; /* Sound format */ uint32_t flags; /* Bit flags */ } aica_channel; /* Flags * 15 } * 14 } * 13 } * 12 } Right channel volume * 11 } in stereo * 10 } * 9 } * 8 } * 7 * 6 * 5 * 4 * 3 * 2 } Current active * 1 } buffer * 0 ->Stereo when set */ aica_channel *chanh; void *convert_u8tos8(void *buffer, int count) { int i; char *curpos = (char *) buffer; for (i = 0; i < count; i++) { char x = *curpos; x ^= 0x80; *curpos++ = x; } return buffer; } /* Waits for the sound FIFO to empty */ inline void spu_write_wait() { while (1) { if (!(readl(0xa05f688c) & 0x11)) break; } } void spu_memload(uint32_t toi, uint8_t * from, int length) { uint32_t *froml = (uint32_t *) from; uint32_t *to = (uint32_t *) (0xa0800000 + toi); int i, val; if (length % 4) length = (length / 4) + 1; else length = length / 4; for (i = 0; i < length; i++) { val = *froml; writel(val, to); froml++; to++; if (i && !(i % 8)) spu_write_wait(); } } void spu_memset(uint32_t toi, unsigned long what, int length) { uint32_t *to = (uint32_t *) (0xa0800000 + toi); int i; if (length % 4) length = (length / 4) + 1; else length = length / 4; for (i = 0; i < length; i++) { writel(what, to); to++; if (i && !(i % 8)) spu_write_wait(); } } /* Enable/disable the SPU; note that disable implies reset of the ARM CPU core. */ void spu_enable() { uint32_t regval = readl(0xa0702c00); regval &= ~1; spu_write_wait(); writel(regval, 0xa0702c00); } /* Stop the ARM7 processor and clear registers for all channels */ void spu_disable() { int i; spu_write_wait(); uint32_t regval = readl(0xa0702c00); regval |= 1; spu_write_wait(); writel(regval, 0xa0702c00); for (i = 0; i < 64; i++) { spu_write_wait(); regval = readl(0xa0700000 + (i * 0x80)); regval = (regval & ~0x4000) | 0x8000; spu_write_wait(); writel(regval, 0xa0700000 + (i * 0x80)); } } /* Halt the sound processor, clear the memory, load some default ARM7 code, and then restart ARM7 */ int spu_init() { spu_disable(); spu_memset(0, 0, 0x200000 / 4); *(uint32_t *) 0xa0800000 = 0xea000002; spu_enable(); schedule(); return 0; } /* Shutdown SPU */ int spu_shutdown() { spu_disable(); spu_memset(0, 0, 0x200000 / 4); return 0; } inline static void chn_halt() { spu_write_wait(); writel(AICA_CMD_KICK | AICA_CMD_STOP, (uint32_t *) 0xa0810000); } inline static void chn_start() { spu_write_wait(); writel(AICA_CMD_KICK | AICA_CMD_START, (uint32_t *) 0xa0810000); } typedef struct aica_dev { struct aica_dev *next_dev; int channel; /* which one are we using here? */ int audio_minor; /* device number */ int mixer_minor; /* device number */ wait_queue_head_t open_wait; /* wait queue */ int last_write_length; /* length of data last written to device */ int sformat; /* format of data being written */ } aica_dev_t; /* List of all open devices */ static aica_dev_t *aica_dev_list; /* open the device file, locking AICA registers as we go */ static int aica_audio_open(struct inode *inode, struct file *file) { aica_dev_t *devc; dev_t minor = MINOR(inode->i_rdev); MOD_INC_USE_COUNT; devc = aica_dev_list; if (down_interruptible(&dsp_mutex)) return -ERESTARTSYS; request_mem_region(0xa0700000, 0x100, "AICA Channels"); chanh = kmalloc(sizeof(aica_channel), GFP_KERNEL); if (chanh == NULL) return -ENOMEM; chanh->flags = 0; if ((minor & 0xF) == SND_DEV_DSP) { chanh->sfmt = SM_8BIT; devc->sformat = AFMT_U8; } else if ((minor & 0xF) == SND_DEV_DSP16) { chanh->sfmt = SM_16BIT; devc->sformat = AFMT_S16_LE; } else { DEBGM("Attempting to open an unsupported device file\n"); release_mem_region(0xa0700000, 0x100); up(&dsp_mutex); MOD_DEC_USE_COUNT; return -EINVAL; } devc->last_write_length = 0; file->private_data = devc; spu_disable(); spu_memset(0, 0, 0x31000); spu_memload(0, bin_arm7, sizeof(bin_arm7)); spu_enable(); /* Load default channel values */ chanh->cmd = AICA_CMD_START; chanh->vol = left_volume; chanh->pan = 0x80; chanh->pos = 0; chanh->freq = 8000; total_count = 0; currentpoint = 0; return 0; } /* Release device, waiting for queued audio to play out */ static int aica_audio_release(struct inode *inode, struct file *file) { aica_dev_t *devc = (aica_dev_t *) (file->private_data); int playpoint, i; if (chanh->freq < 23000) { interruptible_sleep_on_timeout(&(devc->open_wait), HZ / 5); } if (devc->last_write_length > 0) { spu_write_wait(); playpoint = readl(0xa0810004 + 4); if ((playpoint * samplelength) > currentpoint) { for (i = 0; i < (HZ * 2); i++) { interruptible_sleep_on_timeout(& (devc-> open_wait), 1); spu_write_wait(); playpoint = readl(0xa0810004 + 4); if ((playpoint * samplelength) < currentpoint) break; } } for (i = 0; i < (HZ * 2); i++) { if ((chanh->freq < 23000) || (sleeps > 0)) interruptible_sleep_on_timeout(& (devc-> open_wait), 1); spu_write_wait(); playpoint = readl(0xa0810004 + 4); if ((playpoint * samplelength) > currentpoint) break; } } spu_disable(); if (BUFFER) kfree(BUFFER); if (BUFFER2) kfree(BUFFER2); if (BUFFERL) kfree(BUFFERL); if (BUFFERR) kfree(BUFFERR); BUFFER = NULL; BUFFER2 = NULL; BUFFERL = NULL; BUFFERR = NULL; kfree(chanh); release_mem_region(0xa0700000, 0x100); MOD_DEC_USE_COUNT; total_count = 0; up(&dsp_mutex); return 0; } /* Perform stereo seperation for 16- and 8-bit samples */ static int aica_audio_process_stereo(int *count_out, int *count) { int z, y, x, w; *count_out = *count / 2; y = *count_out; BUFFER2 = kmalloc(y, GFP_KERNEL); if (BUFFER2 == NULL) return -1; char *BUFFER3 = kmalloc(y, GFP_KERNEL); if (BUFFER3 == NULL) return -1; if (samplelength == 1) { for (z = 0; z < y; z++) { x = z * 2; BUFFER3[z] = BUFFER[x]; BUFFER2[z] = BUFFER[x + 1]; } } else { y = y / samplelength; for (z = 0; z < y; z++) { x = z * 2; w = z * 4; BUFFER3[x] = BUFFER[w]; BUFFER3[x + 1] = BUFFER[w + 1]; BUFFER2[x] = BUFFER[w + 2]; BUFFER2[x + 1] = BUFFER[w + 3]; } } kfree(BUFFER); BUFFER = BUFFER3; return 0; } /* Block if buffer full until playing has freed up necessary space. Sleep while waiting for low sample freq samples to play, but lock processor for demanding samples */ inline static int aica_audio_tidy_buffers(aica_dev_t * devc) { int playpoint; do { if (chanh->freq < 23000) interruptible_sleep_on_timeout(&(devc->open_wait), 1); spu_write_wait(); playpoint = readl(0xa0810004 + 4); } while (((playpoint * samplelength) > currentpoint) && ((playpoint * samplelength) < (currentpoint + total_count))); if ((currentpoint + total_count) > 0x8000) { currentpoint = 0; do { if ((chanh->freq < 23000) || (sleeps > 0)) interruptible_sleep_on_timeout(& (devc-> open_wait), 1); spu_write_wait(); playpoint = readl(0xa0810004 + 4); } while ((playpoint * samplelength) <= total_count); } return 0; } static ssize_t aica_audio_write(struct file *file, const char *buffer, size_t count, loff_t * ppos) { aica_dev_t *devc = (aica_dev_t *) (file->private_data); stereo = chanh->flags & 1; BUFFER = kmalloc(count, GFP_KERNEL); copy_from_user(BUFFER, buffer, count); int count_out = count; samplelength = 1; if (chanh->sfmt == SM_16BIT) samplelength = 2; else if (devc->sformat == AFMT_U8) convert_u8tos8(BUFFER, count); if (stereo) { if (aica_audio_process_stereo(&count_out, &count) != 0) return -1; BUFFERR = BUFFER2; } total_count = count_out; BUFFERL = BUFFER; BUFFER = NULL; BUFFER2 = NULL; if (devc->last_write_length > 0) { aica_audio_tidy_buffers(devc); } spu_memload(0x11000 + currentpoint, BUFFERL, total_count); if (stereo) spu_memload(0x21000 + currentpoint, BUFFERR, total_count); if (BUFFERR) kfree(BUFFERR); kfree(BUFFERL); BUFFERL = NULL; BUFFERR = NULL; currentpoint += total_count; if (devc->last_write_length == 0) { chanh->pos = 0; spu_memload(0x10004, (uint8_t *) chanh, sizeof(aica_channel)); chn_start(); } devc->last_write_length = total_count; total_count = 0; return count; } static int aica_audio_ioctl(struct inode *inode, struct file *filip, unsigned int cmd, unsigned long arg) { aica_dev_t *devc = (aica_dev_t *) (filip->private_data); int newdata, left, right, s, m; audio_buf_info info; switch (cmd) { case SNDCTL_DSP_GETCAPS: put_user(DSP_CAP_TRIGGER | DSP_CAP_REALTIME, (int *) arg); return 0; case SNDCTL_DSP_SETFMT: get_user(newdata, (int *) arg); switch (newdata) { case AFMT_U8: devc->sformat = AFMT_U8; chanh->sfmt = SM_8BIT; break; case AFMT_S16_LE: devc->sformat = AFMT_S16_LE; chanh->sfmt = SM_16BIT; break; case AFMT_S8: devc->sformat = AFMT_S8; chanh->sfmt = SM_8BIT; break; case AFMT_IMA_ADPCM: devc->sformat = AFMT_IMA_ADPCM; chanh->sfmt = SM_ADPCM; break; default: devc->sformat = AFMT_U8; chanh->sfmt = SM_8BIT; /* have set a default format but should return an error */ return -ENOTTY; } put_user(devc->sformat, (int *) arg); return 0; case SNDCTL_DSP_GETFMTS: newdata = AFMT_S8 | AFMT_S16_LE | AFMT_IMA_ADPCM; put_user(newdata, (int *) arg); return 0; case SNDCTL_DSP_SPEED: get_user(newdata, (int *) arg); if (newdata < 10000) { chanh->freq = 8000; } else if (newdata < 11750) { chanh->freq = 11025; } else if (newdata < 15000) { chanh->freq = 12000; } else if (newdata < 20000) { chanh->freq = 16000; } else if (newdata < 23000) { chanh->freq = 22050; } else if (newdata < 30000) { chanh->freq = 24000; } else if (newdata < 41000) { chanh->freq = 32000; } else chanh->freq = 44100; put_user(chanh->freq, (int *) arg); return 0; case SNDCTL_DSP_RESET: chn_halt(); spu_memset(0x21000, 0, 0x8000); spu_memset(0x11000, 0, 0x8000); currentpoint = 0; return 0; case SNDCTL_DSP_GETBLKSIZE: put_user(buffer_size, (int *) arg); return 0; case SNDCTL_DSP_SETFRAGMENT: get_user(newdata, (int *) arg); s = newdata & 0xffff; if (s > 14) s = 14; else if (s < 4) s = 4; buffer_size = 1 << s; m = 0x7fff; arg = (m << 16) | s; put_user(0x010f, (int *) arg); return 0; /* bytes left to play */ case SNDCTL_DSP_GETODELAY: spu_write_wait(); s = readl(0xa0810004 + 4); if ((s * samplelength) > currentpoint) { newdata = currentpoint + 0x8000 - (s * samplelength); } else newdata = currentpoint - (s * samplelength); put_user(newdata, (int *) arg); return 0; /* How many fragments till writing blocks? */ case SNDCTL_DSP_GETOSPACE: spu_write_wait(); s = readl(0xa0810004 + 4); if ((s * samplelength) > currentpoint) { info.fragments = ((s * samplelength) - currentpoint) / buffer_size; } else info.fragments = ((0x8000 - currentpoint) + (s * samplelength)) / buffer_size; info.fragstotal = 0x8000 / buffer_size; info.fragsize = buffer_size; info.bytes = info.fragments * buffer_size; copy_to_user((audio_buf_info *) arg, &info, sizeof(info)); return 0; case SNDCTL_DSP_SYNC: case SNDCTL_DSP_POST: spu_write_wait(); s = readl(0xa0810004 + 4); if ((s * samplelength) > currentpoint) { newdata = currentpoint + 0x8000 - (s * samplelength); } else newdata = currentpoint - (s * samplelength); newdata = newdata / samplelength; /* delay in milliseconds */ newdata = (newdata * 1000) / chanh->freq; newdata = (newdata * HZ) / 1000; interruptible_sleep_on_timeout(&(devc->open_wait), newdata); chn_halt(); spu_memset(0x21000, 0, 0x8000); spu_memset(0x11000, 0, 0x8000); currentpoint = 0; return 0; case SNDCTL_DSP_STEREO: get_user(newdata, (int *) arg); if (newdata == 1) { chanh->flags |= 0x01; } else newdata = 0; put_user(newdata, (int *) arg); return 0; case SNDCTL_DSP_CHANNELS: get_user(newdata, (int *) arg); if (newdata >= 2) { newdata = 2; chanh->flags |= 0x01; } else newdata = 1; put_user(newdata, (int *) arg); return 0; /* Supported Mixer ioctls */ case SOUND_MIXER_READ_DEVMASK: newdata = SOUND_MASK_PCM | SOUND_MASK_VOLUME; put_user(newdata, (int *) arg); return 0; case SOUND_MIXER_WRITE_VOLUME: case SOUND_MIXER_WRITE_PCM: get_user(newdata, (int *) arg); left = newdata & 0xff; right = (newdata & 0xff00) >> 8; left_volume = left; right_volume = right; chanh->vol = (left * 255) / 100; newdata = ((right * 255) / 100) << 8; newdata = newdata | 0xff; chanh->flags &= newdata; right = right << 8; newdata = left | right; put_user(newdata, (int *) arg); return 0; default: return -ENOTTY; } } static struct file_operations aica_audio_fops = { owner:THIS_MODULE, open:aica_audio_open, release:aica_audio_release, write:aica_audio_write, ioctl:aica_audio_ioctl, }; /* Mixer code very basic currently */ static int aica_mixer_open(struct inode *inode, struct file *file) { MOD_INC_USE_COUNT; return 0; } static int aica_mixer_release(struct inode *inode, struct file *file) { MOD_DEC_USE_COUNT; return 0; } static int aica_mixer_ioctl(struct inode *inode, struct file *filip, unsigned int cmd, unsigned long arg) { aica_dev_t *devc = aica_dev_list; if (devc == NULL) return -1; int newdata, left, right; switch (cmd) { case SOUND_MIXER_READ_DEVMASK: newdata = SOUND_MASK_PCM | SOUND_MASK_VOLUME; put_user(newdata, (int *) arg); return 0; case SOUND_MIXER_WRITE_VOLUME: case SOUND_MIXER_WRITE_PCM: get_user(newdata, (int *) arg); left = newdata & 0xff; right = (newdata & 0xff00) >> 8; left_volume = left; right_volume = right; chanh->vol = (left * 255) / 100; newdata = ((right * 255) / 100) << 8; newdata = newdata | 0xff; chanh->flags &= newdata; right = right << 8; newdata = left | right; put_user(newdata, (int *) arg); return 0; default: return -ENOTTY; } } static struct file_operations aica_mixer_fops = { owner:THIS_MODULE, open:aica_mixer_open, release:aica_mixer_release, ioctl:aica_mixer_ioctl, }; /* Create holder for device, together with wait queue and then register device with underlying OSS code */ static int __init attach_aica(void) { printk ("AICA audio device driver for Dreamcast Linux - ver 0.1-pre15\n"); aica_dev_t *devc = NULL; sema_init(&dsp_mutex, 1); int err = -ENOMEM; devc = kmalloc(sizeof(aica_dev_t), GFP_KERNEL); if (devc == NULL) goto fail0; init_waitqueue_head(&devc->open_wait); devc->audio_minor = register_sound_dsp(&aica_audio_fops, -1); if (devc->audio_minor < 0) { DEBGM("attach_aica: register_sound_dsp error %d\n", err); err = devc->audio_minor; goto faildsp; } devc->mixer_minor = register_sound_mixer(&aica_mixer_fops, -1); if (devc->mixer_minor < 0) { DEBGM("attach_aica: register_sound_mixer error %d\n", err); err = devc->mixer_minor; goto failmixer; } devc->next_dev = NULL; devc->channel = 0; aica_dev_list = devc; return 0; failmixer: unregister_sound_dsp(devc->audio_minor); faildsp: kfree(devc); devc = NULL; fail0: DEBGM("AICA load failed\n"); return err; return 0; } static int __exit unload_aica(void) { aica_dev_t *devc; aica_dev_t *devcp; devcp = aica_dev_list; if (!devcp) return -ENODEV; while (1) { devc = devcp; unregister_sound_mixer(devc->mixer_minor); unregister_sound_dsp(devc->audio_minor); devcp = devc->next_dev; kfree(devc); if (!devcp) break; } return 0; } /* Lock iomem and initialise ARM7 processor */ static int __init init_aica(void) { BUFFER = NULL; BUFFER2 = NULL; BUFFERL = NULL; BUFFERR = NULL; if (request_mem_region(0xa0702c00, 4, "AICA ARM control") == NULL) { DEBGM ("Could not take ownership of SPU control register\n"); return -ENODEV; } if (request_mem_region(0xa0800000, 0x200000, "AICA Sound RAM") == NULL) { DEBGM("Could not take ownership of sound RAM\n"); return -ENOMEM; } spu_init(); if (attach_aica()) { DEBGM("Failed to initialise AICA sound driver\n"); return -EINVAL; } DEBGM("AICA sound driver has been initialised\n"); return 0; } static void __exit exit_aica(void) { DEBGM("AICA sound driver being unloaded...\n"); if (unload_aica()) DEBGM("Error in unloading AICA\n"); spu_init(); release_mem_region(0xa0702c00, 4); release_mem_region(0xa0800000, 0x200000); } module_init(init_aica); module_exit(exit_aica); MODULE_AUTHOR("Adrian McMenamin"); MODULE_DESCRIPTION("Basic OSS sound driver for Linux/DC"); |