|
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");
|