patch-2.1.29 linux/drivers/sbus/audio/amd7930.c
Next file: linux/drivers/sbus/audio/amd7930.h
Previous file: linux/drivers/sbus/audio/Makefile
Back to the patch index
Back to the overall index
- Lines: 606
- Date:
Wed Mar 5 17:04:32 1997
- Orig file:
v2.1.28/linux/drivers/sbus/audio/amd7930.c
- Orig date:
Sun Jan 26 02:07:17 1997
diff -u --recursive --new-file v2.1.28/linux/drivers/sbus/audio/amd7930.c linux/drivers/sbus/audio/amd7930.c
@@ -15,35 +15,42 @@
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
+#include <linux/malloc.h>
#include <asm/openprom.h>
#include <asm/oplib.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/io.h>
+#include <asm/sbus.h>
+
#include "audio.h"
#include "amd7930.h"
-/*
- * Chip interface
- */
-struct mapreg {
- u_short mr_x[8];
- u_short mr_r[8];
- u_short mr_gx;
- u_short mr_gr;
- u_short mr_ger;
- u_short mr_stgr;
- u_short mr_ftgr;
- u_short mr_atgr;
- u_char mr_mmr1;
- u_char mr_mmr2;
-} map;
+#define MAX_DRIVERS 1
+/* Private information we store for each amd7930 chip. */
+struct amd7930_info {
+ /* Current buffer that the driver is playing. */
+ volatile __u8 * output_ptr;
+ volatile unsigned long output_count;
+
+ /* Current record buffer. */
+ volatile __u8 * input_ptr;
+ volatile unsigned long input_count;
+
+ /* Device registers information. */
+ struct amd7930 *regs;
+ unsigned long regs_size;
+ struct amd7930_map map;
+
+ /* Device interrupt information. */
+ int irq;
+ volatile int ints_on;
+};
-/* Write 16 bits of data from variable v to the data port of the audio chip */
-#define WAMD16(amd, v) ((amd)->dr = (v), (amd)->dr = (v) >> 8)
+/* Output a 16-bit quantity in the order that the amd7930 expects. */
+#define amd7930_out16(regs,v) ({ regs->dr = v & 0xFF; regs->dr = (v >> 8) & 0xFF; })
-/* The following tables stolen from former (4.4Lite's) sys/sparc/bsd_audio.c */
/*
* gx, gr & stg gains. this table must contain 256 elements with
@@ -51,7 +58,7 @@
* elements match sun's gain curve (but with higher resolution):
* -18 to 0dB in .16dB steps then 0 to 12dB in .08dB steps.
*/
-static const u_short gx_coeff[256] = {
+static __const__ __u16 gx_coeff[256] = {
0x9008, 0x8b7c, 0x8b51, 0x8b45, 0x8b42, 0x8b3b, 0x8b36, 0x8b33,
0x8b32, 0x8b2a, 0x8b2b, 0x8b2c, 0x8b25, 0x8b23, 0x8b22, 0x8b22,
0x9122, 0x8b1a, 0x8aa3, 0x8aa3, 0x8b1c, 0x8aa6, 0x912d, 0x912b,
@@ -86,10 +93,7 @@
0x000a, 0x000a, 0x0011, 0x0011, 0x000b, 0x000b, 0x000c, 0x000e,
};
-/*
- * second stage play gain.
- */
-static const u_short ger_coeff[] = {
+static __const__ __u16 ger_coeff[] = {
0x431f, /* 5. dB */
0x331f, /* 5.5 dB */
0x40dd, /* 6. dB */
@@ -111,262 +115,362 @@
0x2200, /* 15.9 dB */
0x000b, /* 16.9 dB */
0x000f /* 18. dB */
-#define NGER (sizeof(ger_coeff) / sizeof(ger_coeff[0]))
};
-
-#if 0
-int
-amd7930_commit_settings(addr)
- void *addr;
-{
- register struct amd7930_softc *sc = addr;
- register struct mapreg *map;
- register volatile struct amd7930 *amd;
- register int s, level;
-
- DPRINTF(("sa_commit.\n"));
-
- map = &sc->sc_map;
- amd = sc->sc_au.au_amd;
-
- map->mr_gx = gx_coeff[sc->sc_rlevel];
- map->mr_stgr = gx_coeff[sc->sc_mlevel];
-
- level = (sc->sc_plevel * (256 + NGER)) >> 8;
- if (level >= 256) {
- map->mr_ger = ger_coeff[level - 256];
- map->mr_gr = gx_coeff[255];
- } else {
- map->mr_ger = ger_coeff[0];
- map->mr_gr = gx_coeff[level];
- }
-
- if (sc->sc_out_port == SUNAUDIO_SPEAKER)
- map->mr_mmr2 |= AMD_MMR2_LS;
- else
- map->mr_mmr2 &= ~AMD_MMR2_LS;
-
- s = splaudio();
-
- amd->cr = AMDR_MAP_MMR1;
- amd->dr = map->mr_mmr1;
- amd->cr = AMDR_MAP_GX;
- WAMD16(amd, map->mr_gx);
- amd->cr = AMDR_MAP_STG;
- WAMD16(amd, map->mr_stgr);
- amd->cr = AMDR_MAP_GR;
- WAMD16(amd, map->mr_gr);
- amd->cr = AMDR_MAP_GER;
- WAMD16(amd, map->mr_ger);
- amd->cr = AMDR_MAP_MMR2;
- amd->dr = map->mr_mmr2;
-
- splx(s);
- return(0);
-}
-#endif
-
-static int amd7930_node, amd7930_irq, amd7930_regs_size, amd7930_ints_on = 0;
-static struct amd7930 *amd7930_regs = NULL;
-static __u8 * ptr;
-static size_t count;
+#define NR_GER_COEFFS (sizeof(ger_coeff) / sizeof(ger_coeff[0]))
/* Enable amd7930 interrupts atomically. */
-static __inline__ void amd7930_enable_ints(void)
+static __inline__ void amd7930_enable_ints(struct sparcaudio_driver *drv)
{
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
register unsigned long flags;
- if (amd7930_ints_on)
+ if (info->ints_on)
return;
save_and_cli(flags);
- amd7930_regs->cr = AMR_INIT;
- amd7930_regs->dr = AM_INIT_ACTIVE;
+ info->regs->cr = AMR_INIT;
+ info->regs->dr = AM_INIT_ACTIVE;
restore_flags(flags);
- amd7930_ints_on = 1;
+ info->ints_on = 1;
}
/* Disable amd7930 interrupts atomically. */
-static __inline__ void amd7930_disable_ints(void)
+static __inline__ void amd7930_disable_ints(struct sparcaudio_driver *drv)
{
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
register unsigned long flags;
- if (!amd7930_ints_on)
+ if (!info->ints_on)
return;
save_and_cli(flags);
- amd7930_regs->cr = AMR_INIT;
- amd7930_regs->dr = AM_INIT_ACTIVE | AM_INIT_DISABLE_INTS;
+ info->regs->cr = AMR_INIT;
+ info->regs->dr = AM_INIT_ACTIVE | AM_INIT_DISABLE_INTS;
restore_flags(flags);
- amd7930_ints_on = 0;
+ info->ints_on = 0;
}
+/* Commit the local copy of the MAP registers to the amd7930. */
+static void amd7930_commit_map(struct sparcaudio_driver *drv)
+{
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
+ struct amd7930 *regs = info->regs;
+ struct amd7930_map *map = &info->map;
+ unsigned long flags;
+
+ save_and_cli(flags);
+
+ regs->cr = AMR_MAP_GX;
+ amd7930_out16(regs, map->gx);
+
+ regs->cr = AMR_MAP_GR;
+ amd7930_out16(regs, map->gr);
+
+ regs->cr = AMR_MAP_STGR;
+ amd7930_out16(regs, map->stgr);
+
+ regs->cr = AMR_MAP_GER;
+ amd7930_out16(regs, map->ger);
+
+ regs->cr = AMR_MAP_MMR1;
+ regs->dr = map->mmr1;
+
+ regs->cr = AMR_MAP_MMR2;
+ regs->dr = map->mmr2;
-/* Audio interrupt handler. */
-static void amd7930_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+ restore_flags(flags);
+}
+
+/* Interrupt handler (The chip takes only one byte per interrupt. Grrr!) */
+static void amd7930_interrupt(int irq, void *dev_id, struct pt_regs *intr_regs)
{
+ struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id;
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
+ struct amd7930 *regs = info->regs;
__u8 dummy;
/* Clear the interrupt. */
- dummy = amd7930_regs->ir;
+ dummy = regs->ir;
/* Send the next byte of outgoing data. */
- if (ptr && count > 0) {
- /* Send the next byte and advance the head pointer. */
- amd7930_regs->bbtb = *ptr;
- ptr++;
- count--;
-
- /* Empty buffer? Notify the midlevel driver. */
- if (count == 0)
- sparcaudio_output_done();
+ if (info->output_ptr && info->output_count > 0) {
+ /* Send the next byte and advance buffer pointer. */
+ regs->bbtb = *(info->output_ptr);
+ info->output_ptr++;
+ info->output_count--;
+
+ /* Done with the buffer? Notify the midlevel driver. */
+ if (info->output_count == 0) {
+ info->output_ptr = NULL;
+ info->output_count = 0;
+ sparcaudio_output_done(drv);
+ }
+ }
+
+ /* Read the next byte of incoming data. */
+ if (info->input_ptr && info->input_count > 0) {
+ /* Get the next byte and advance buffer pointer. */
+ *(info->input_ptr) = regs->bbrb;
+ info->input_ptr++;
+ info->input_count--;
+
+ /* Done with the buffer? Notify the midlevel driver. */
+ if (info->input_count == 0) {
+ info->input_ptr = NULL;
+ info->input_count = 0;
+ sparcaudio_input_done(drv);
+ }
}
}
-static int amd7930_open(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
+
+static int amd7930_open(struct inode * inode, struct file * file,
+ struct sparcaudio_driver *drv)
{
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
int level;
/* Set the default audio parameters. */
- map.mr_gx = gx_coeff[128];
- map.mr_stgr = gx_coeff[0];
+ info->map.gx = gx_coeff[128];
+ info->map.stgr = gx_coeff[0];
- level = (128 * (256 + NGER)) >> 8;
+ level = (128 * (256 + NR_GER_COEFFS)) >> 8;
if (level >= 256) {
- map.mr_ger = ger_coeff[level-256];
- map.mr_gr = gx_coeff[255];
+ info->map.ger = ger_coeff[level-256];
+ info->map.gr = gx_coeff[255];
} else {
- map.mr_ger = ger_coeff[0];
- map.mr_gr = gx_coeff[level];
+ info->map.ger = ger_coeff[0];
+ info->map.gr = gx_coeff[level];
}
- map.mr_mmr2 |= AM_MAP_MMR2_LS;
+ info->map.mmr2 |= AM_MAP_MMR2_LS;
- cli();
-
- amd7930_regs->cr = AMR_MAP_MMR1;
- amd7930_regs->dr = map.mr_mmr1;
- amd7930_regs->cr = AMR_MAP_GX;
- WAMD16(amd7930_regs,map.mr_gx);
- amd7930_regs->cr = AMR_MAP_STG;
- WAMD16(amd7930_regs,map.mr_stgr);
- amd7930_regs->cr = AMR_MAP_GR;
- WAMD16(amd7930_regs,map.mr_gr);
- amd7930_regs->cr = AMR_MAP_GER;
- WAMD16(amd7930_regs,map.mr_ger);
- amd7930_regs->cr = AMR_MAP_MMR2;
- amd7930_regs->dr = map.mr_mmr2;
-
- sti();
+ amd7930_commit_map(drv);
MOD_INC_USE_COUNT;
return 0;
}
-static void amd7930_release(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
+static void amd7930_release(struct inode * inode, struct file * file,
+ struct sparcaudio_driver *drv)
{
- amd7930_disable_ints();
+ amd7930_disable_ints(drv);
MOD_DEC_USE_COUNT;
}
-static void amd7930_start_output(struct sparcaudio_driver *drv, __u8 * buffer, size_t the_count)
+static void amd7930_start_output(struct sparcaudio_driver *drv,
+ __u8 * buffer, unsigned long count)
{
- count = the_count;
- ptr = buffer;
- amd7930_enable_ints();
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+ info->output_ptr = buffer;
+ info->output_count = count;
+ amd7930_enable_ints(drv);
}
static void amd7930_stop_output(struct sparcaudio_driver *drv)
{
- amd7930_disable_ints();
- ptr = NULL;
- count = 0;
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+ info->output_ptr = NULL;
+ info->output_count = 0;
+
+ if (!info->input_ptr)
+ amd7930_disable_ints(drv);
+}
+
+static void amd7930_start_input(struct sparcaudio_driver *drv,
+ __u8 * buffer, unsigned long count)
+{
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+ info->input_ptr = buffer;
+ info->input_count = count;
+ amd7930_enable_ints(drv);
+}
+
+static void amd7930_stop_input(struct sparcaudio_driver *drv)
+{
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+ info->input_ptr = NULL;
+ info->input_count = 0;
+
+ if (!info->output_ptr)
+ amd7930_disable_ints(drv);
+}
+
+static void amd7930_sunaudio_getdev(struct sparcaudio_driver *drv,
+ audio_device_t * audinfo)
+{
+ strncpy(audinfo->name, "amd7930", sizeof(audinfo->name) - 1);
+ strncpy(audinfo->version, "x", sizeof(audinfo->version) - 1);
+ strncpy(audinfo->config, "audio", sizeof(audinfo->config) - 1);
}
+/*
+ * Device detection and initialization.
+ */
+
+static struct sparcaudio_driver drivers[MAX_DRIVERS];
+static int num_drivers;
+
static struct sparcaudio_operations amd7930_ops = {
amd7930_open,
amd7930_release,
NULL, /* amd7930_ioctl */
amd7930_start_output,
amd7930_stop_output,
+ amd7930_start_input,
+ amd7930_stop_input,
+ amd7930_sunaudio_getdev,
};
-static struct sparcaudio_driver amd7930_drv = {
- "amd7930",
- &amd7930_ops,
-};
-
-/* Probe for the amd7930 chip and then attach the driver. */
-#ifdef MODULE
-int init_module(void)
-#else
-__initfunc(int amd7930_init(void))
-#endif
+/* Attach to an amd7930 chip given its PROM node. */
+static int amd7930_attach(struct sparcaudio_driver *drv, int node,
+ struct linux_sbus *sbus, struct linux_sbus_device *sdev)
{
- struct linux_prom_registers regs[1];
+ struct linux_prom_registers regs;
struct linux_prom_irqs irq;
+ struct amd7930_info *info;
int err;
-#ifdef MODULE
- register_symtab(0);
-#endif
-
- /* Find the PROM "audio" node. */
- amd7930_node = prom_getchild(prom_root_node);
- amd7930_node = prom_searchsiblings(amd7930_node, "audio");
- if (!amd7930_node)
- return -EIO;
+ /* Allocate our private information structure. */
+ drv->private = kmalloc(sizeof(struct amd7930_info), GFP_KERNEL);
+ if (!drv->private)
+ return -ENOMEM;
+
+ /* Point at the information structure and initialize it. */
+ drv->ops = &amd7930_ops;
+ info = (struct amd7930_info *)drv->private;
+ info->output_ptr = info->input_ptr = NULL;
+ info->output_count = info->input_count = 0;
+ info->ints_on = 1; /* force disable below */
/* Map the registers into memory. */
- prom_getproperty(amd7930_node, "reg", (char *)regs, sizeof(regs));
- amd7930_regs_size = regs[0].reg_size;
- amd7930_regs = sparc_alloc_io(regs[0].phys_addr, 0, regs[0].reg_size,
- "amd7930", regs[0].which_io, 0);
- if (!amd7930_regs) {
+ prom_getproperty(node, "reg", (char *)®s, sizeof(regs));
+ if (sbus && sdev)
+ prom_apply_sbus_ranges(sbus, ®s, 1, sdev);
+ info->regs_size = regs.reg_size;
+ info->regs = sparc_alloc_io(regs.phys_addr, 0, regs.reg_size,
+ "amd7930", regs.which_io, 0);
+ if (!info->regs) {
printk(KERN_ERR "amd7930: could not allocate registers\n");
+ kfree(drv->private);
return -EIO;
}
/* Disable amd7930 interrupt generation. */
- amd7930_disable_ints();
+ amd7930_disable_ints(drv);
/* Initialize the MUX unit to connect the MAP to the CPU. */
- amd7930_regs->cr = AMR_MUX_1_4;
- amd7930_regs->dr = (AM_MUX_CHANNEL_Bb << 4) | AM_MUX_CHANNEL_Ba;
- amd7930_regs->dr = 0;
- amd7930_regs->dr = 0;
- amd7930_regs->dr = AM_MUX_MCR4_ENABLE_INTS;
+ info->regs->cr = AMR_MUX_1_4;
+ info->regs->dr = (AM_MUX_CHANNEL_Bb << 4) | AM_MUX_CHANNEL_Ba;
+ info->regs->dr = 0;
+ info->regs->dr = 0;
+ info->regs->dr = AM_MUX_MCR4_ENABLE_INTS;
/* Attach the interrupt handler to the audio interrupt. */
- prom_getproperty(amd7930_node, "intr", (char *)&irq, sizeof(irq));
- amd7930_irq = irq.pri;
- request_irq(amd7930_irq, amd7930_interrupt, SA_INTERRUPT, "amd7930", NULL);
- enable_irq(amd7930_irq);
-
- memset(&map, 0, sizeof(map));
- map.mr_mmr1 = AM_MAP_MMR1_GX | AM_MAP_MMR1_GER | AM_MAP_MMR1_GR | AM_MAP_MMR1_STG;
+ prom_getproperty(node, "intr", (char *)&irq, sizeof(irq));
+ info->irq = irq.pri;
+ request_irq(info->irq, amd7930_interrupt,
+ SA_INTERRUPT, "amd7930", drv);
+ enable_irq(info->irq);
+
+ /* Initalize the local copy of the MAP registers. */
+ memset(&info->map, 0, sizeof(info->map));
+ info->map.mmr1 = AM_MAP_MMR1_GX | AM_MAP_MMR1_GER
+ | AM_MAP_MMR1_GR | AM_MAP_MMR1_STG;
- /* Register ourselves with the midlevel audio driver. */
- err = register_sparcaudio_driver(&amd7930_drv);
+ /* Register the amd7930 with the midlevel audio driver. */
+ err = register_sparcaudio_driver(drv);
if (err < 0) {
- /* XXX We should do something. Complain for now. */
- printk(KERN_ERR "amd7930: really screwed now\n");
+ printk(KERN_ERR "amd7930: unable to register\n");
+ disable_irq(info->irq);
+ free_irq(info->irq, drv);
+ sparc_free_io(info->regs, info->regs_size);
+ kfree(drv->private);
return -EIO;
}
+ /* Announce the hardware to the user. */
+ printk(KERN_INFO "amd7930 at 0x%lx irq %d\n",
+ (unsigned long)info->regs, info->irq);
+
+ /* Success! */
return 0;
}
#ifdef MODULE
+/* Detach from an amd7930 chip given the device structure. */
+static void amd7930_detach(struct sparcaudio_driver *drv)
+{
+ struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+ unregister_sparcaudio_driver(drv);
+ amd7930_disable_ints(drv);
+ disable_irq(info->irq);
+ free_irq(info->irq, drv);
+ sparc_free_io(info->regs, info->regs_size);
+ kfree(drv->private);
+}
+#endif
+
+
+/* Probe for the amd7930 chip and then attach the driver. */
+#ifdef MODULE
+int init_module(void)
+#else
+__initfunc(int amd7930_init(void))
+#endif
+{
+ struct linux_sbus *bus;
+ struct linux_sbus_device *sdev;
+ int node;
+
+#if 0
+#ifdef MODULE
+ register_symtab(0);
+#endif
+#endif
+
+ /* Try to find the sun4c "audio" node first. */
+ node = prom_getchild(prom_root_node);
+ node = prom_searchsiblings(node, "audio");
+ if (node && amd7930_attach(&drivers[0], node, NULL, NULL) == 0)
+ num_drivers = 1;
+ else
+ num_drivers = 0;
+
+ /* Probe each SBUS for amd7930 chips. */
+ for_all_sbusdev(sdev,bus) {
+ if (!strcmp(sdev->prom_name, "audio")) {
+ /* Don't go over the max number of drivers. */
+ if (num_drivers >= MAX_DRIVERS)
+ continue;
+
+ if (amd7930_attach(&drivers[num_drivers],
+ sdev->prom_node, sdev->my_bus, sdev) == 0)
+ num_drivers++;
+ }
+ }
+
+ /* Only return success if we found some amd7930 chips. */
+ return (num_drivers > 0) ? 0 : -EIO;
+}
+
+#ifdef MODULE
void cleanup_module(void)
{
- amd7930_disable_ints();
- disable_irq(amd7930_irq);
- free_irq(amd7930_irq, NULL);
- sparc_free_io(amd7930_regs, amd7930_regs_size);
+ register int i;
+
+ for (i = 0; i < num_drivers; i++) {
+ amd7930_detach(&drivers[i]);
+ num_drivers--;
+ }
}
#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov