diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
commit | 19c9bba94152148523ba0f7ef7cffe3d45656b11 (patch) | |
tree | 40b1cb534496a7f1ca0f5c314a523c69f1fee464 /drivers/sbus/audio | |
parent | 7206675c40394c78a90e74812bbdbf8cf3cca1be (diff) |
Import of Linux/MIPS 2.1.36
Diffstat (limited to 'drivers/sbus/audio')
-rw-r--r-- | drivers/sbus/audio/Config.in | 12 | ||||
-rw-r--r-- | drivers/sbus/audio/Makefile | 54 | ||||
-rw-r--r-- | drivers/sbus/audio/amd7930.c | 476 | ||||
-rw-r--r-- | drivers/sbus/audio/amd7930.h | 163 | ||||
-rw-r--r-- | drivers/sbus/audio/audio.c | 379 | ||||
-rw-r--r-- | drivers/sbus/audio/audio.h | 287 | ||||
-rw-r--r-- | drivers/sbus/audio/cs4231.c | 755 | ||||
-rw-r--r-- | drivers/sbus/audio/cs4231.h | 230 |
8 files changed, 2356 insertions, 0 deletions
diff --git a/drivers/sbus/audio/Config.in b/drivers/sbus/audio/Config.in new file mode 100644 index 000000000..71a75c763 --- /dev/null +++ b/drivers/sbus/audio/Config.in @@ -0,0 +1,12 @@ +# +# Configuration script for sparcaudio subsystem +# + +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + + comment 'Linux/SPARC audio subsystem (EXPERIMENTAL)' + + tristate 'Audio support (EXPERIMENTAL)' CONFIG_SPARCAUDIO + dep_tristate ' AMD7930 Lowlevel Driver' CONFIG_SPARCAUDIO_AMD7930 $CONFIG_SPARCAUDIO + dep_tristate ' CS4231 Lowlevel Driver' CONFIG_SPARCAUDIO_CS4231 $CONFIG_SPARCAUDIO +fi diff --git a/drivers/sbus/audio/Makefile b/drivers/sbus/audio/Makefile new file mode 100644 index 000000000..f870e0da5 --- /dev/null +++ b/drivers/sbus/audio/Makefile @@ -0,0 +1,54 @@ +# +# Makefile for the linux kernel. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +# +# sbus audio drivers +# + +O_TARGET := sparcaudio.o +O_OBJS := +M_OBJS := + +ifeq ($(CONFIG_SPARCAUDIO),y) +M=y +else + ifeq ($(CONFIG_SPARCAUDIO),m) + MM=y + endif +endif + +ifeq ($(CONFIG_SPARCAUDIO_AMD7930),y) +M=y +O_OBJS += amd7930.o +else + ifeq ($(CONFIG_SPARCAUDIO_AMD7930),m) + MM=y + M_OBJS += amd7930.o + endif +endif + +ifeq ($(CONFIG_SPARCAUDIO_CS4231),y) +M=y +O_OBJS += cs4231.o +else + ifeq ($(CONFIG_SPARCAUDIO_CS4231),m) + MM=y + M_OBJS += cs4231.o + endif +endif + +ifdef M +OX_OBJS += audio.o +else + ifdef MM + MX_OBJS += audio.o + endif +endif + +include $(TOPDIR)/Rules.make diff --git a/drivers/sbus/audio/amd7930.c b/drivers/sbus/audio/amd7930.c new file mode 100644 index 000000000..388949d21 --- /dev/null +++ b/drivers/sbus/audio/amd7930.c @@ -0,0 +1,476 @@ +/* + * drivers/sbus/audio/amd7930.c + * + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * + * This is the lowlevel driver for the AMD7930 audio chip found on all + * sun4c machines and some sun4m machines. + * + * XXX Add note about the fun of getting the docs. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#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" + +#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; +}; + +/* 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; }) + + +/* + * gx, gr & stg gains. this table must contain 256 elements with + * the 0th being "infinity" (the magic value 9008). The remaining + * 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__ __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, + 0x8aab, 0x8b12, 0x8aaa, 0x8ab2, 0x9132, 0x8ab4, 0x913c, 0x8abb, + 0x9142, 0x9144, 0x9151, 0x8ad5, 0x8aeb, 0x8a79, 0x8a5a, 0x8a4a, + 0x8b03, 0x91c2, 0x91bb, 0x8a3f, 0x8a33, 0x91b2, 0x9212, 0x9213, + 0x8a2c, 0x921d, 0x8a23, 0x921a, 0x9222, 0x9223, 0x922d, 0x9231, + 0x9234, 0x9242, 0x925b, 0x92dd, 0x92c1, 0x92b3, 0x92ab, 0x92a4, + 0x92a2, 0x932b, 0x9341, 0x93d3, 0x93b2, 0x93a2, 0x943c, 0x94b2, + 0x953a, 0x9653, 0x9782, 0x9e21, 0x9d23, 0x9cd2, 0x9c23, 0x9baa, + 0x9bde, 0x9b33, 0x9b22, 0x9b1d, 0x9ab2, 0xa142, 0xa1e5, 0x9a3b, + 0xa213, 0xa1a2, 0xa231, 0xa2eb, 0xa313, 0xa334, 0xa421, 0xa54b, + 0xada4, 0xac23, 0xab3b, 0xaaab, 0xaa5c, 0xb1a3, 0xb2ca, 0xb3bd, + 0xbe24, 0xbb2b, 0xba33, 0xc32b, 0xcb5a, 0xd2a2, 0xe31d, 0x0808, + 0x72ba, 0x62c2, 0x5c32, 0x52db, 0x513e, 0x4cce, 0x43b2, 0x4243, + 0x41b4, 0x3b12, 0x3bc3, 0x3df2, 0x34bd, 0x3334, 0x32c2, 0x3224, + 0x31aa, 0x2a7b, 0x2aaa, 0x2b23, 0x2bba, 0x2c42, 0x2e23, 0x25bb, + 0x242b, 0x240f, 0x231a, 0x22bb, 0x2241, 0x2223, 0x221f, 0x1a33, + 0x1a4a, 0x1acd, 0x2132, 0x1b1b, 0x1b2c, 0x1b62, 0x1c12, 0x1c32, + 0x1d1b, 0x1e71, 0x16b1, 0x1522, 0x1434, 0x1412, 0x1352, 0x1323, + 0x1315, 0x12bc, 0x127a, 0x1235, 0x1226, 0x11a2, 0x1216, 0x0a2a, + 0x11bc, 0x11d1, 0x1163, 0x0ac2, 0x0ab2, 0x0aab, 0x0b1b, 0x0b23, + 0x0b33, 0x0c0f, 0x0bb3, 0x0c1b, 0x0c3e, 0x0cb1, 0x0d4c, 0x0ec1, + 0x079a, 0x0614, 0x0521, 0x047c, 0x0422, 0x03b1, 0x03e3, 0x0333, + 0x0322, 0x031c, 0x02aa, 0x02ba, 0x02f2, 0x0242, 0x0232, 0x0227, + 0x0222, 0x021b, 0x01ad, 0x0212, 0x01b2, 0x01bb, 0x01cb, 0x01f6, + 0x0152, 0x013a, 0x0133, 0x0131, 0x012c, 0x0123, 0x0122, 0x00a2, + 0x011b, 0x011e, 0x0114, 0x00b1, 0x00aa, 0x00b3, 0x00bd, 0x00ba, + 0x00c5, 0x00d3, 0x00f3, 0x0062, 0x0051, 0x0042, 0x003b, 0x0033, + 0x0032, 0x002a, 0x002c, 0x0025, 0x0023, 0x0022, 0x001a, 0x0021, + 0x001b, 0x001b, 0x001d, 0x0015, 0x0013, 0x0013, 0x0012, 0x0012, + 0x000a, 0x000a, 0x0011, 0x0011, 0x000b, 0x000b, 0x000c, 0x000e, +}; + +static __const__ __u16 ger_coeff[] = { + 0x431f, /* 5. dB */ + 0x331f, /* 5.5 dB */ + 0x40dd, /* 6. dB */ + 0x11dd, /* 6.5 dB */ + 0x440f, /* 7. dB */ + 0x411f, /* 7.5 dB */ + 0x311f, /* 8. dB */ + 0x5520, /* 8.5 dB */ + 0x10dd, /* 9. dB */ + 0x4211, /* 9.5 dB */ + 0x410f, /* 10. dB */ + 0x111f, /* 10.5 dB */ + 0x600b, /* 11. dB */ + 0x00dd, /* 11.5 dB */ + 0x4210, /* 12. dB */ + 0x110f, /* 13. dB */ + 0x7200, /* 14. dB */ + 0x2110, /* 15. dB */ + 0x2200, /* 15.9 dB */ + 0x000b, /* 16.9 dB */ + 0x000f /* 18. dB */ +}; +#define NR_GER_COEFFS (sizeof(ger_coeff) / sizeof(ger_coeff[0])) + +/* Enable amd7930 interrupts atomically. */ +static __inline__ void amd7930_enable_ints(struct sparcaudio_driver *drv) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + register unsigned long flags; + + if (info->ints_on) + return; + + save_and_cli(flags); + info->regs->cr = AMR_INIT; + info->regs->dr = AM_INIT_ACTIVE; + restore_flags(flags); + + info->ints_on = 1; +} + +/* Disable amd7930 interrupts atomically. */ +static __inline__ void amd7930_disable_ints(struct sparcaudio_driver *drv) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + register unsigned long flags; + + if (!info->ints_on) + return; + + save_and_cli(flags); + info->regs->cr = AMR_INIT; + info->regs->dr = AM_INIT_ACTIVE | AM_INIT_DISABLE_INTS; + restore_flags(flags); + + 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; + + 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 = regs->ir; + + /* Send the next byte of outgoing data. */ + 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) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + int level; + + /* Set the default audio parameters. */ + info->map.gx = gx_coeff[128]; + info->map.stgr = gx_coeff[0]; + + level = (128 * (256 + NR_GER_COEFFS)) >> 8; + if (level >= 256) { + info->map.ger = ger_coeff[level-256]; + info->map.gr = gx_coeff[255]; + } else { + info->map.ger = ger_coeff[0]; + info->map.gr = gx_coeff[level]; + } + + info->map.mmr2 |= AM_MAP_MMR2_LS; + + amd7930_commit_map(drv); + + MOD_INC_USE_COUNT; + + return 0; +} + +static void amd7930_release(struct inode * inode, struct file * file, + struct sparcaudio_driver *drv) +{ + amd7930_disable_ints(drv); + MOD_DEC_USE_COUNT; +} + +static void amd7930_start_output(struct sparcaudio_driver *drv, + __u8 * buffer, unsigned long count) +{ + 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) +{ + 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, +}; + +/* 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; + struct linux_prom_irqs irq; + struct amd7930_info *info; + int err; + + /* 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(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(drv); + + /* Initialize the MUX unit to connect the MAP to the CPU. */ + 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(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 the amd7930 with the midlevel audio driver. */ + err = register_sparcaudio_driver(drv); + if (err < 0) { + 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) +{ + register int i; + + for (i = 0; i < num_drivers; i++) { + amd7930_detach(&drivers[i]); + num_drivers--; + } +} +#endif diff --git a/drivers/sbus/audio/amd7930.h b/drivers/sbus/audio/amd7930.h new file mode 100644 index 000000000..be860d10c --- /dev/null +++ b/drivers/sbus/audio/amd7930.h @@ -0,0 +1,163 @@ +/* + * drivers/sbus/audio/amd7930.h + * + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * + * Definitions for the AMD79C30 Digital Subscriber Controller which is + * used as an audio chip in sun4c architecture machines. The + * information in this file is based on Advanced Micro Devices + * Publication 09893, Rev G, Amendment /0, Final (a.k.a. the data + * sheet). + */ + +#ifndef _AMD7930_H_ +#define _AMD7930_H_ + +#include <linux/types.h> + +/* Register interface presented to the CPU by the amd7930. */ +struct amd7930 +{ + __volatile__ __u8 cr; /* Command Register (W) */ +#define ir cr /* Interrupt Register (R) */ + __volatile__ __u8 dr; /* Data Register (R/W) */ + __volatile__ __u8 dsr1; /* D-channel Status Register 1 (R) */ + __volatile__ __u8 der; /* D-channel Error Register (R) */ + __volatile__ __u8 dctb; /* D-channel Transmit Buffer (W) */ +#define dcrb dctb /* D-channel Receive Buffer (R) */ + __volatile__ __u8 bbtb; /* Bb-channel Transmit Buffer (W) */ +#define bbrb bbtb /* Bb-channel Receive Buffer (R) */ + __volatile__ __u8 bctb; /* Bc-channel Transmit Buffer (W) */ +#define bcrb bctb /* Bc-channel Receive Buffer (R) */ + __volatile__ __u8 dsr2; /* D-channel Status Register 2 (R) */ +}; + + +/* Indirect registers in the Main Audio Processor. */ +struct amd7930_map { + __u16 x[8]; + __u16 r[8]; + __u16 gx; + __u16 gr; + __u16 ger; + __u16 stgr; + __u16 ftgr; + __u16 atgr; + __u8 mmr1; + __u8 mmr2; +}; + + +/* The amd7930 has "indirect registers" which are accessed by writing + * the register number into the Command Register and then reading or + * writing values from the Data Register as appropriate. We define the + * AMR_* macros to be the indirect register numbers and AM_* macros to + * be bits in whatever register is referred to. + */ + +/* Initialization */ +#define AMR_INIT 0x21 +#define AM_INIT_ACTIVE 0x01 +#define AM_INIT_DATAONLY 0x02 +#define AM_INIT_POWERDOWN 0x03 +#define AM_INIT_DISABLE_INTS 0x04 +#define AMR_INIT2 0x20 +#define AM_INIT2_ENABLE_POWERDOWN 0x20 +#define AM_INIT2_ENABLE_MULTIFRAME 0x10 + +/* Line Interface Unit */ +#define AMR_LIU_LSR 0xA1 +#define AMR_LIU_LPR 0xA2 +#define AMR_LIU_LMR1 0xA3 +#define AMR_LIU_LMR2 0xA4 +#define AMR_LIU_2_4 0xA5 +#define AMR_LIU_MF 0xA6 +#define AMR_LIU_MFSB 0xA7 +#define AMR_LIU_MFQB 0xA8 + +/* Multiplexor */ +#define AMR_MUX_MCR1 0x41 +#define AMR_MUX_MCR2 0x42 +#define AMR_MUX_MCR3 0x43 +#define AM_MUX_CHANNEL_B1 0x01 +#define AM_MUX_CHANNEL_B2 0x02 +#define AM_MUX_CHANNEL_Ba 0x03 +#define AM_MUX_CHANNEL_Bb 0x04 +#define AM_MUX_CHANNEL_Bc 0x05 +#define AM_MUX_CHANNEL_Bd 0x06 +#define AM_MUX_CHANNEL_Be 0x07 +#define AM_MUX_CHANNEL_Bf 0x08 +#define AMR_MUX_MCR4 0x44 +#define AM_MUX_MCR4_ENABLE_INTS 0x08 +#define AM_MUX_MCR4_REVERSE_Bb 0x10 +#define AM_MUX_MCR4_REVERSE_Bc 0x20 +#define AMR_MUX_1_4 0x45 + +/* Main Audio Processor */ +#define AMR_MAP_X 0x61 +#define AMR_MAP_R 0x62 +#define AMR_MAP_GX 0x63 +#define AMR_MAP_GR 0x64 +#define AMR_MAP_GER 0x65 +#define AMR_MAP_STGR 0x66 +#define AMR_MAP_FTGR_1_2 0x67 +#define AMR_MAP_ATGR_1_2 0x68 +#define AMR_MAP_MMR1 0x69 +#define AM_MAP_MMR1_ALAW 0x01 +#define AM_MAP_MMR1_GX 0x02 +#define AM_MAP_MMR1_GR 0x04 +#define AM_MAP_MMR1_GER 0x08 +#define AM_MAP_MMR1_X 0x10 +#define AM_MAP_MMR1_R 0x20 +#define AM_MAP_MMR1_STG 0x40 +#define AM_MAP_MMR1_LOOPBACK 0x80 +#define AMR_MAP_MMR2 0x6A +#define AM_MAP_MMR2_AINB 0x01 +#define AM_MAP_MMR2_LS 0x02 +#define AM_MAP_MMR2_ENABLE_DTMF 0x04 +#define AM_MAP_MMR2_ENABLE_TONEGEN 0x08 +#define AM_MAP_MMR2_ENABLE_TONERING 0x10 +#define AM_MAP_MMR2_DISABLE_HIGHPASS 0x20 +#define AM_MAP_MMR2_DISABLE_AUTOZERO 0x40 +#define AMR_MAP_1_10 0x6B +#define AMR_MAP_MMR3 0x6C +#define AMR_MAP_STRA 0x6D +#define AMR_MAP_STRF 0x6E +#define AMR_MAP_PEAKX 0x70 +#define AMR_MAP_PEAKR 0x71 +#define AMR_MAP_15_16 0x72 + +/* Data Link Controller */ +#define AMR_DLC_FRAR_1_2_3 0x81 +#define AMR_DLC_SRAR_1_2_3 0x82 +#define AMR_DLC_TAR 0x83 +#define AMR_DLC_DRLR 0x84 +#define AMR_DLC_DTCR 0x85 +#define AMR_DLC_DMR1 0x86 +#define AMR_DLC_DMR2 0x87 +#define AMR_DLC_1_7 0x88 +#define AMR_DLC_DRCR 0x89 +#define AMR_DLC_RNGR1 0x8A +#define AMR_DLC_RNGR2 0x8B +#define AMR_DLC_FRAR4 0x8C +#define AMR_DLC_SRAR4 0x8D +#define AMR_DLC_DMR3 0x8E +#define AMR_DLC_DMR4 0x8F +#define AMR_DLC_12_15 0x90 +#define AMR_DLC_ASR 0x91 +#define AMR_DLC_EFCR 0x92 + +/* Peripheral Port */ +#define AMR_PP_PPCR1 0xC0 +#define AMR_PP_PPSR 0xC1 +#define AMR_PP_PPIER 0xC2 +#define AMR_PP_MTDR 0xC3 +#define AMR_PP_MRDR 0xC3 +#define AMR_PP_CITDR0 0xC4 +#define AMR_PP_CIRDR0 0xC4 +#define AMR_PP_CITDR1 0xC5 +#define AMR_PP_CIRDR1 0xC5 +#define AMR_PP_PPCR2 0xC8 +#define AMR_PP_PPCR3 0xC9 + +#endif diff --git a/drivers/sbus/audio/audio.c b/drivers/sbus/audio/audio.c new file mode 100644 index 000000000..ded93c8e6 --- /dev/null +++ b/drivers/sbus/audio/audio.c @@ -0,0 +1,379 @@ +/* + * drivers/sbus/audio/audio.c + * + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * + * This is the audio midlayer that sits between the VFS character + * devices and the low-level audio hardware device drivers. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/tqueue.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/init.h> + +#include "audio.h" + + +/* + * Low-level driver interface. + */ + +/* We only support one low-level audio driver currently. */ +static struct sparcaudio_driver *driver = NULL; + +int register_sparcaudio_driver(struct sparcaudio_driver *drv) +{ + int i; + + /* If a driver is already present, don't allow the register. */ + if (driver) + return -EIO; + + /* Ensure that the driver has a proper operations structure. */ + if (!drv->ops || !drv->ops->start_output || !drv->ops->stop_output) + return -EINVAL; + + /* Setup the circular queue of output buffers. */ + drv->num_output_buffers = 32; + drv->output_front = 0; + drv->output_rear = 0; + drv->output_count = 0; + drv->output_active = 0; + drv->output_buffers = kmalloc(32 * sizeof(__u8 *), GFP_KERNEL); + drv->output_sizes = kmalloc(32 * sizeof(size_t), GFP_KERNEL); + if (!drv->output_buffers || !drv->output_sizes) { + if (drv->output_buffers) + kfree(drv->output_buffers); + if (drv->output_sizes) + kfree(drv->output_sizes); + return -ENOMEM; + } + + /* Allocate the pages for each output buffer. */ + for (i = 0; i < drv->num_output_buffers; i++) { + drv->output_buffers[i] = (void *) __get_free_page(GFP_KERNEL); + if (!drv->output_buffers[i]) { + int j; + for (j = 0; j < i; j++) + free_page((unsigned long) drv->output_buffers[j]); + kfree(drv->output_buffers); + kfree(drv->output_sizes); + return -ENOMEM; + } + } + + /* Ensure that the driver is marked as not being open. */ + drv->flags = 0; + + MOD_INC_USE_COUNT; + + driver = drv; + return 0; +} + +int unregister_sparcaudio_driver(struct sparcaudio_driver *drv) +{ + int i; + + /* Make sure that the current driver is unregistering. */ + if (driver != drv) + return -EIO; + + /* Deallocate the queue of output buffers. */ + for (i = 0; i < driver->num_output_buffers; i++) + free_page((unsigned long) driver->output_buffers[i]); + kfree(driver->output_buffers); + kfree(driver->output_sizes); + + MOD_DEC_USE_COUNT; + + driver = NULL; + return 0; +} + +static void sparcaudio_output_done_task(void * arg) +{ + struct sparcaudio_driver *drv = (struct sparcaudio_driver *)arg; + unsigned long flags; + + save_and_cli(flags); + drv->ops->start_output(drv, + drv->output_buffers[drv->output_front], + drv->output_sizes[drv->output_front]); + drv->output_active = 1; + restore_flags(flags); +} + +void sparcaudio_output_done(struct sparcaudio_driver * drv) +{ + /* Point the queue after the "done" buffer. */ + drv->output_front = (drv->output_front + 1) % drv->num_output_buffers; + drv->output_count--; + + /* If the output queue is empty, shutdown the driver. */ + if (drv->output_count == 0) { + /* Stop the lowlevel driver from outputing. */ + drv->ops->stop_output(drv); + drv->output_active = 0; + + /* Wake up any waiting writers or syncers and return. */ + wake_up_interruptible(&drv->output_write_wait); + wake_up_interruptible(&drv->output_drain_wait); + return; + } + + /* Otherwise, queue a task to give the driver the next buffer. */ + drv->tqueue.next = NULL; + drv->tqueue.sync = 0; + drv->tqueue.routine = sparcaudio_output_done_task; + drv->tqueue.data = drv; + + queue_task(&drv->tqueue, &tq_immediate); + mark_bh(IMMEDIATE_BH); + + /* Wake up any tasks that are waiting. */ + wake_up_interruptible(&drv->output_write_wait); +} + +void sparcaudio_input_done(struct sparcaudio_driver * drv) +{ + /* XXX Implement! */ +} + + + +/* + * VFS layer interface + */ + +static int sparcaudio_lseek(struct inode * inode, struct file * file, + off_t offset, int origin) +{ + return -ESPIPE; +} + +static int sparcaudio_read(struct inode * inode, struct file * file, + char *buf, int count) +{ + /* XXX Implement me! */ + return -EINVAL; +} + +static int sparcaudio_write(struct inode * inode, struct file * file, + const char *buf, int count) +{ + unsigned long flags; + int bytes_written = 0, bytes_to_copy, err; + + /* Ensure that we have something to write. */ + if (count < 1) + return 0; + + /* Loop until all output is written to device. */ + while (count > 0) { + /* Check to make sure that an output buffer is available. */ + if (driver->output_count == driver->num_output_buffers) { + interruptible_sleep_on(&driver->output_write_wait); + if (current->signal & ~current->blocked) + return bytes_written > 0 ? bytes_written : -EINTR; + } + + /* Determine how much we can copy in this iteration. */ + bytes_to_copy = count; + if (bytes_to_copy > PAGE_SIZE) + bytes_to_copy = PAGE_SIZE; + + copy_from_user_ret(driver->output_buffers[driver->output_rear], + buf, bytes_to_copy, -EFAULT); + + /* Update the queue pointers. */ + buf += bytes_to_copy; + count -= bytes_to_copy; + bytes_written += bytes_to_copy; + driver->output_sizes[driver->output_rear] = bytes_to_copy; + driver->output_rear = (driver->output_rear + 1) % driver->num_output_buffers; + driver->output_count++; + + /* If the low-level driver is not active, activate it. */ + save_and_cli(flags); + if (! driver->output_active) { + driver->ops->start_output(driver, driver->output_buffers[driver->output_front], + driver->output_sizes[driver->output_front]); + driver->output_active = 1; + } + restore_flags(flags); + } + + /* Return the number of bytes written to the caller. */ + return bytes_written; +} + +static int sparcaudio_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + switch (cmd) { + case AUDIO_DRAIN: + if (driver->output_count > 0) { + interruptible_sleep_on(&driver->output_drain_wait); + retval = (current->signal & ~current->blocked) ? -EINTR : 0; + } + break; + + case AUDIO_GETDEV: + if (driver->ops->sunaudio_getdev) { + audio_device_t tmp; + + driver->ops->sunaudio_getdev(driver, &tmp); + + copy_to_user_ret((audio_device_t *)arg, &tmp, sizeof(tmp), -EFAULT); + } else + retval = -EINVAL; + break; + + default: + if (driver->ops->ioctl) + retval = driver->ops->ioctl(inode,file,cmd,arg,driver); + else + retval = -EINVAL; + } + + return retval; +} + +static int sparcaudio_open(struct inode * inode, struct file * file) +{ + int err; + + /* A low-level audio driver must exist. */ + if (!driver) + return -ENODEV; + + /* We only support minor #4 (/dev/audio) right now. */ + if (MINOR(inode->i_rdev) != 4) + return -ENXIO; + + /* If the driver is busy, then wait to get through. */ + retry_open: + if (file->f_mode & FMODE_READ && driver->flags & SDF_OPEN_READ) { + if (file->f_flags & O_NONBLOCK) + return -EBUSY; + + interruptible_sleep_on(&driver->open_wait); + if (current->signal & ~current->blocked) + return -EINTR; + goto retry_open; + } + if (file->f_mode & FMODE_WRITE && driver->flags & SDF_OPEN_WRITE) { + if (file->f_flags & O_NONBLOCK) + return -EBUSY; + + interruptible_sleep_on(&driver->open_wait); + if (current->signal & ~current->blocked) + return -EINTR; + goto retry_open; + } + + /* Mark the driver as locked for read and/or write. */ + if (file->f_mode & FMODE_READ) + driver->flags |= SDF_OPEN_READ; + if (file->f_mode & FMODE_WRITE) { + driver->output_front = 0; + driver->output_rear = 0; + driver->output_count = 0; + driver->output_active = 0; + driver->flags |= SDF_OPEN_WRITE; + } + + /* Allow the low-level driver to initialize itself. */ + if (driver->ops->open) { + err = driver->ops->open(inode,file,driver); + if (err < 0) + return err; + } + + MOD_INC_USE_COUNT; + + /* Success! */ + return 0; +} + +static void sparcaudio_release(struct inode * inode, struct file * file) +{ + /* Wait for any output still in the queue to be played. */ + if (driver->output_count > 0) + interruptible_sleep_on(&driver->output_drain_wait); + + /* Force any output to be stopped. */ + driver->ops->stop_output(driver); + driver->output_active = 0; + + /* Let the low-level driver do any release processing. */ + if (driver->ops->release) + driver->ops->release(inode,file,driver); + + if (file->f_mode & FMODE_READ) + driver->flags &= ~(SDF_OPEN_READ); + + if (file->f_mode & FMODE_WRITE) + driver->flags &= ~(SDF_OPEN_WRITE); + + MOD_DEC_USE_COUNT; + + wake_up_interruptible(&driver->open_wait); +} + +static struct file_operations sparcaudio_fops = { + sparcaudio_lseek, + sparcaudio_read, + sparcaudio_write, + NULL, /* sparcaudio_readdir */ + NULL, /* sparcaudio_select */ + sparcaudio_ioctl, + NULL, /* sparcaudio_mmap */ + sparcaudio_open, + sparcaudio_release +}; + +EXPORT_SYMBOL(register_sparcaudio_driver); +EXPORT_SYMBOL(unregister_sparcaudio_driver); +EXPORT_SYMBOL(sparcaudio_output_done); +EXPORT_SYMBOL(sparcaudio_input_done); + +#ifdef MODULE +int init_module(void) +#else +__initfunc(int sparcaudio_init(void)) +#endif +{ + /* Register our character device driver with the VFS. */ + if (register_chrdev(SOUND_MAJOR, "sparcaudio", &sparcaudio_fops)) + return -EIO; + +#ifdef CONFIG_SPARCAUDIO_AMD7930 + amd7930_init(); +#endif + +#ifdef CONFIG_SPARCAUDIO_CS4231 + cs4231_init(); +#endif + + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + unregister_chrdev(SOUND_MAJOR, "sparcaudio"); +} +#endif diff --git a/drivers/sbus/audio/audio.h b/drivers/sbus/audio/audio.h new file mode 100644 index 000000000..127700ab5 --- /dev/null +++ b/drivers/sbus/audio/audio.h @@ -0,0 +1,287 @@ +/* + * drivers/sbus/audio/audio.h + * + * Sparc Audio Midlayer + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + */ + +#ifndef _AUDIO_H_ +#define _AUDIO_H_ + +/* + * SunOS/Solaris /dev/audio interface + */ + +#include <linux/types.h> +#include <linux/time.h> +#include <linux/ioctl.h> + +/* + * This structure contains state information for audio device IO streams. + */ +typedef struct audio_prinfo { + /* + * The following values describe the audio data encoding. + */ + unsigned int sample_rate; /* samples per second */ + unsigned int channels; /* number of interleaved channels */ + unsigned int precision; /* bit-width of each sample */ + unsigned int encoding; /* data encoding method */ + + /* + * The following values control audio device configuration + */ + unsigned int gain; /* gain level: 0 - 255 */ + unsigned int port; /* selected I/O port (see below) */ + unsigned int avail_ports; /* available I/O ports (see below) */ + unsigned int _xxx[2]; /* Reserved for future use */ + + unsigned int buffer_size; /* I/O buffer size */ + + /* + * The following values describe driver state + */ + unsigned int samples; /* number of samples converted */ + unsigned int eof; /* End Of File counter (play only) */ + + unsigned char pause; /* non-zero for pause, zero to resume */ + unsigned char error; /* non-zero if overflow/underflow */ + unsigned char waiting; /* non-zero if a process wants access */ + unsigned char balance; /* stereo channel balance */ + + unsigned short minordev; + + /* + * The following values are read-only state flags + */ + unsigned char open; /* non-zero if open access permitted */ + unsigned char active; /* non-zero if I/O is active */ +} audio_prinfo_t; + + +/* + * This structure describes the current state of the audio device. + */ +typedef struct audio_info { + /* + * Per-stream information + */ + audio_prinfo_t play; /* output status information */ + audio_prinfo_t record; /* input status information */ + + /* + * Per-unit/channel information + */ + unsigned int monitor_gain; /* input to output mix: 0 - 255 */ + unsigned char output_muted; /* non-zero if output is muted */ + unsigned char _xxx[3]; /* Reserved for future use */ + unsigned int _yyy[3]; /* Reserved for future use */ +} audio_info_t; + + +/* + * Audio encoding types + */ +#define AUDIO_ENCODING_NONE (0) /* no encoding assigned */ +#define AUDIO_ENCODING_ULAW (1) /* u-law encoding */ +#define AUDIO_ENCODING_ALAW (2) /* A-law encoding */ +#define AUDIO_ENCODING_LINEAR (3) /* Linear PCM encoding */ +#define AUDIO_ENCODING_DVI (104) /* DVI ADPCM */ +#define AUDIO_ENCODING_LINEAR8 (105) /* 8 bit UNSIGNED */ + +/* + * These ranges apply to record, play, and monitor gain values + */ +#define AUDIO_MIN_GAIN (0) /* minimum gain value */ +#define AUDIO_MAX_GAIN (255) /* maximum gain value */ + +/* + * These values apply to the balance field to adjust channel gain values + */ +#define AUDIO_LEFT_BALANCE (0) /* left channel only */ +#define AUDIO_MID_BALANCE (32) /* equal left/right channel */ +#define AUDIO_RIGHT_BALANCE (64) /* right channel only */ +#define AUDIO_BALANCE_SHIFT (3) + +/* + * Generic minimum/maximum limits for number of channels, both modes + */ +#define AUDIO_MIN_PLAY_CHANNELS (1) +#define AUDIO_MAX_PLAY_CHANNELS (4) +#define AUDIO_MIN_REC_CHANNELS (1) +#define AUDIO_MAX_REC_CHANNELS (4) + +/* + * Generic minimum/maximum limits for sample precision + */ +#define AUDIO_MIN_PLAY_PRECISION (8) +#define AUDIO_MAX_PLAY_PRECISION (32) +#define AUDIO_MIN_REC_PRECISION (8) +#define AUDIO_MAX_REC_PRECISION (32) + +/* + * Define some convenient names for typical audio ports + */ +/* + * output ports (several may be enabled simultaneously) + */ +#define AUDIO_SPEAKER 0x01 /* output to built-in speaker */ +#define AUDIO_HEADPHONE 0x02 /* output to headphone jack */ +#define AUDIO_LINE_OUT 0x04 /* output to line out */ + +/* + * input ports (usually only one at a time) + */ +#define AUDIO_MICROPHONE 0x01 /* input from microphone */ +#define AUDIO_LINE_IN 0x02 /* input from line in */ +#define AUDIO_CD 0x04 /* input from on-board CD inputs */ +#define AUDIO_INTERNAL_CD_IN AUDIO_CD /* input from internal CDROM */ + + +/* + * This macro initializes an audio_info structure to 'harmless' values. + * Note that (~0) might not be a harmless value for a flag that was + * a signed int. + */ +#define AUDIO_INITINFO(i) { \ + unsigned int *__x__; \ + for (__x__ = (unsigned int *)(i); \ + (char *) __x__ < (((char *)(i)) + sizeof (audio_info_t)); \ + *__x__++ = ~0); \ +} + + +/* + * Parameter for the AUDIO_GETDEV ioctl to determine current + * audio devices. + */ +#define MAX_AUDIO_DEV_LEN (16) +typedef struct audio_device { + char name[MAX_AUDIO_DEV_LEN]; + char version[MAX_AUDIO_DEV_LEN]; + char config[MAX_AUDIO_DEV_LEN]; +} audio_device_t; + + +/* + * Ioctl calls for the audio device. + */ + +/* + * AUDIO_GETINFO retrieves the current state of the audio device. + * + * AUDIO_SETINFO copies all fields of the audio_info structure whose + * values are not set to the initialized value (-1) to the device state. + * It performs an implicit AUDIO_GETINFO to return the new state of the + * device. Note that the record.samples and play.samples fields are set + * to the last value before the AUDIO_SETINFO took effect. This allows + * an application to reset the counters while atomically retrieving the + * last value. + * + * AUDIO_DRAIN suspends the calling process until the write buffers are + * empty. + * + * AUDIO_GETDEV returns a structure of type audio_device_t which contains + * three strings. The string "name" is a short identifying string (for + * example, the SBus Fcode name string), the string "version" identifies + * the current version of the device, and the "config" string identifies + * the specific configuration of the audio stream. All fields are + * device-dependent -- see the device specific manual pages for details. + */ +#define AUDIO_GETINFO _IOR('A', 1, audio_info_t) +#define AUDIO_SETINFO _IOWR('A', 2, audio_info_t) +#define AUDIO_DRAIN _IO('A', 3) +#define AUDIO_GETDEV _IOR('A', 4, audio_device_t) + +/* + * The following ioctl sets the audio device into an internal loopback mode, + * if the hardware supports this. The argument is TRUE to set loopback, + * FALSE to reset to normal operation. If the hardware does not support + * internal loopback, the ioctl should fail with EINVAL. + */ +#define AUDIO_DIAG_LOOPBACK _IOW('A', 101, int) + +#ifdef notneeded +/* + * Structure sent up as a M_PROTO message on trace streams + */ +typedef struct audtrace_hdr audtrace_hdr_t; +struct audtrace_hdr { + unsigned int seq; /* Sequence number (per-aud_stream) */ + int type; /* device-dependent */ + struct timeval timestamp; + char _f[8]; /* filler */ +}; +#endif + + + +/* + * Linux kernel internal implementation. + */ + +#ifdef __KERNEL__ + +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/tqueue.h> +#include <linux/wait.h> + +#define SDF_OPEN_WRITE 0x00000001 +#define SDF_OPEN_READ 0x00000002 + +struct sparcaudio_driver +{ + const char * name; + struct sparcaudio_operations *ops; + void *private; + unsigned long flags; + + /* Processes blocked on open() sit here. */ + struct wait_queue *open_wait; + + /* Task queue for this driver's bottom half. */ + struct tq_struct tqueue; + + /* Support for a circular queue of output buffers. */ + __u8 **output_buffers; + size_t *output_sizes; + int num_output_buffers, output_front, output_rear; + int output_count, output_active; + struct wait_queue *output_write_wait, *output_drain_wait; +}; + +struct sparcaudio_operations +{ + int (*open)(struct inode *, struct file *, struct sparcaudio_driver *); + void (*release)(struct inode *, struct file *, struct sparcaudio_driver *); + int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long, + struct sparcaudio_driver *); + + /* Ask driver to begin playing a buffer. */ + void (*start_output)(struct sparcaudio_driver *, __u8 *, unsigned long); + + /* Ask driver to stop playing a buffer. */ + void (*stop_output)(struct sparcaudio_driver *); + + /* Ask driver to begin recording into a buffer. */ + void (*start_input)(struct sparcaudio_driver *, __u8 *, unsigned long); + + /* Ask driver to stop recording. */ + void (*stop_input)(struct sparcaudio_driver *); + + /* Return driver name/version to caller. (/dev/audio specific) */ + void (*sunaudio_getdev)(struct sparcaudio_driver *, audio_device_t *); +}; + +extern int register_sparcaudio_driver(struct sparcaudio_driver *); +extern int unregister_sparcaudio_driver(struct sparcaudio_driver *); +extern void sparcaudio_output_done(struct sparcaudio_driver *); +extern void sparcaudio_input_done(struct sparcaudio_driver *); +extern int sparcaudio_init(void); +extern int amd7930_init(void); +extern int cs4231_init(void); + +#endif + +#endif diff --git a/drivers/sbus/audio/cs4231.c b/drivers/sbus/audio/cs4231.c new file mode 100644 index 000000000..ecb09a4d7 --- /dev/null +++ b/drivers/sbus/audio/cs4231.c @@ -0,0 +1,755 @@ +/* + * drivers/sbus/audio/cs4231.c + * + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * Copyright (C) 1996 Derrick J Brashear (shadow@andrew.cmu.edu) + * + * This is the lowlevel driver for the CS4231 audio chip found on some + * sun4m machines. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#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/delay.h> +#include <asm/sbus.h> + +#include "audio.h" +#include "cs4231.h" + +/* Stolen for now from compat.h */ +#ifndef MAX /* Usually found in <sys/param.h>. */ +#define MAX(_a,_b) ((_a)<(_b)?(_b):(_a)) +#endif +#ifndef MIN /* Usually found in <sys/param.h>. */ +#define MIN(_a,_b) ((_a)<(_b)?(_a):(_b)) +#endif + +#define MAX_DRIVERS 1 +static struct sparcaudio_driver drivers[MAX_DRIVERS]; +static int num_drivers; + +static int cs4231_playintr(struct sparcaudio_driver *drv); +static int cs4231_recintr(struct sparcaudio_driver *drv); +static void cs4231_output_muted(struct sparcaudio_driver *drv, unsigned int value); +static void cs4231_mute(struct sparcaudio_driver *drv); +static void cs4231_pollinput(struct sparcaudio_driver *drv); +static int cs4231_attach(struct sparcaudio_driver *drv, int node, + struct linux_sbus *sbus); + +#define CHIP_BUG udelay(100); cs4231_ready(drv); udelay(1000); + +/* Disable mode change, let chip auto-calibrate */ +static void cs4231_ready(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + unsigned int x = 0; + + cs4231_chip->pioregs->iar = (u_char)IAR_AUTOCAL_END; + while (cs4231_chip->pioregs->iar == IAR_NOT_READY && x <= CS_TIMEOUT) { + x++; + } + + x = 0; + cs4231_chip->pioregs->iar = 0x0b; + while (cs4231_chip->pioregs->idr == AUTOCAL_IN_PROGRESS && x <= CS_TIMEOUT) { + x++; + } +} + +/* Audio interrupt handler. */ +static void cs4231_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id; + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + __u8 dummy; + int ic = 1; + + /* Clear the interrupt. */ + dummy = cs4231_chip->dmaregs.dmacsr; + cs4231_chip->dmaregs.dmacsr = dummy; + + /* now go through and figure out what gets to claim the interrupt */ + if (dummy & CS_PLAY_INT) { + if (dummy & CS_XINT_PNVA) { + /* recalculate number of samples */ + cs4231_playintr(drv); + } + ic = 0; + } + if (dummy & CS_CAPT_INT) { + if (dummy & CS_XINT_CNVA) { + /* recalculate number of samples */ + cs4231_recintr(drv); + } + ic = 0; + } + if ((dummy & CS_XINT_CEMP) + && (cs4231_chip->perchip_info.record.active == 0)) + { + ic = 0; + } + if ((dummy & CS_XINT_EMPT) && (cs4231_chip->perchip_info.play.active == 0)) { + cs4231_chip->dmaregs.dmacsr |= (CS_PPAUSE); + cs4231_chip->pioregs->iar = 0x9; + cs4231_chip->pioregs->idr &= PEN_DISABLE; + + cs4231_mute(drv); + + /* recalculate number of samples */ + /* cleanup DMA */ + ic = 0; + } + if (dummy & CS_GENL_INT) { + ic = 0; + } +} + +/* Set output mute */ +static void cs4231_output_muted(struct sparcaudio_driver *drv, unsigned int value) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + if (!value) { + cs4231_chip->pioregs->iar = 0x7; + cs4231_chip->pioregs->idr &= OUTCR_UNMUTE; + cs4231_chip->pioregs->iar = 0x6; + cs4231_chip->pioregs->idr &= OUTCR_UNMUTE; + cs4231_chip->perchip_info.output_muted = 0; + } else { + cs4231_chip->pioregs->iar = 0x7; + cs4231_chip->pioregs->idr |= OUTCR_MUTE; + cs4231_chip->pioregs->iar = 0x6; + cs4231_chip->pioregs->idr |= OUTCR_MUTE; + cs4231_chip->perchip_info.output_muted = 1; + } + return /*(cs4231_chip->perchip_info.output_muted)*/; +} + +/* Set chip "output" port */ +static unsigned int cs4231_out_port(struct sparcaudio_driver *drv, unsigned int value) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + unsigned int r = 0; + + /* You can have any combo you want. Just don't tell anyone. */ + + cs4231_chip->pioregs->iar = 0x1a; + cs4231_chip->pioregs->idr |= MONO_IOCR_MUTE; + cs4231_chip->pioregs->iar = 0x0a; + cs4231_chip->pioregs->idr |= PINCR_LINE_MUTE; + cs4231_chip->pioregs->idr |= PINCR_HDPH_MUTE; + + if (value & AUDIO_SPEAKER) { + cs4231_chip->pioregs->iar = 0x1a; + cs4231_chip->pioregs->idr &= ~MONO_IOCR_MUTE; + r |= AUDIO_SPEAKER; + } + + if (value & AUDIO_HEADPHONE) { + cs4231_chip->pioregs->iar = 0x0a; + cs4231_chip->pioregs->idr &= ~PINCR_HDPH_MUTE; + r |= AUDIO_HEADPHONE; + } + + if (value & AUDIO_LINE_OUT) { + cs4231_chip->pioregs->iar = 0x0a; + cs4231_chip->pioregs->idr &= ~PINCR_LINE_MUTE; + r |= AUDIO_LINE_OUT; + } + + return (r); +} + +/* Set chip "input" port */ +static unsigned int cs4231_in_port(struct sparcaudio_driver *drv, unsigned int value) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + unsigned int r = 0; + + /* The order of these seems to matter. Can't tell yet why. */ + if (value & AUDIO_INTERNAL_CD_IN) { + cs4231_chip->pioregs->iar = 0x1; + cs4231_chip->pioregs->idr = CDROM_ENABLE(cs4231_chip->pioregs->idr); + cs4231_chip->pioregs->iar = 0x0; + cs4231_chip->pioregs->idr = CDROM_ENABLE(cs4231_chip->pioregs->idr); + r = AUDIO_INTERNAL_CD_IN; + } + if ((value & AUDIO_LINE_IN)) { + cs4231_chip->pioregs->iar = 0x1; + cs4231_chip->pioregs->idr = LINE_ENABLE(cs4231_chip->pioregs->idr); + cs4231_chip->pioregs->iar = 0x0; + cs4231_chip->pioregs->idr = LINE_ENABLE(cs4231_chip->pioregs->idr); + r = AUDIO_LINE_IN; + } else if (value & AUDIO_MICROPHONE) { + cs4231_chip->pioregs->iar = 0x1; + cs4231_chip->pioregs->idr = MIC_ENABLE(cs4231_chip->pioregs->idr); + cs4231_chip->pioregs->iar = 0x0; + cs4231_chip->pioregs->idr = MIC_ENABLE(cs4231_chip->pioregs->idr); + r = AUDIO_MICROPHONE; + } + + return (r); +} + +/* Set chip "monitor" gain */ +static unsigned int cs4231_monitor_gain(struct sparcaudio_driver *drv, unsigned int value) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + int a = 0; + + a = CS4231_MON_MAX_ATEN - (value * (CS4231_MON_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1)); + + cs4231_chip->pioregs->iar = 0x0d; + if (a >= CS4231_MON_MAX_ATEN) + cs4231_chip->pioregs->idr = LOOPB_OFF; + else + cs4231_chip->pioregs->idr = ((a << 2) | LOOPB_ON); + + if (value == AUDIO_MAX_GAIN) return AUDIO_MAX_GAIN; + + return ((CS4231_MAX_DEV_ATEN - a) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_DEV_ATEN + 1)); +} + +/* Set chip record gain */ +static unsigned int cs4231_record_gain(struct sparcaudio_driver *drv, unsigned int value, unsigned char balance) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + unsigned int tmp = 0, r, l, ra, la; + unsigned char old_gain; + + r = l = value; + + if (balance < AUDIO_MID_BALANCE) { + r = MAX(0, (int)(value - ((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT))); + } else if (balance > AUDIO_MID_BALANCE) { + l = MAX(0, (int)(value - ((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT))); + } + + la = l * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1); + ra = r * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1); + + cs4231_chip->pioregs->iar = 0x0; + old_gain = cs4231_chip->pioregs->idr; + cs4231_chip->pioregs->idr = RECGAIN_SET(old_gain, la); + cs4231_chip->pioregs->iar = 0x1; + old_gain = cs4231_chip->pioregs->idr; + cs4231_chip->pioregs->idr = RECGAIN_SET(old_gain, ra); + + if (l == value) { + (l == 0) ? (tmp = 0) : (tmp = ((la + 1) * AUDIO_MAX_GAIN) / (CS4231_MAX_GAIN + 1)); + } else if (r == value) { + (r == 0) ? (tmp = 0) : (tmp = ((ra + 1) * AUDIO_MAX_GAIN) / (CS4231_MAX_GAIN + 1)); + } + return (tmp); +} + +/* Set chip play gain */ +static unsigned int cs4231_play_gain(struct sparcaudio_driver *drv, unsigned int value, unsigned char balance) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + unsigned int tmp = 0, r, l, ra, la; + unsigned char old_gain; + + r = l = value; + if (balance < AUDIO_MID_BALANCE) { + r = MAX(0, (int)(value - ((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT))); + } else if (balance > AUDIO_MID_BALANCE) { + l = MAX(0, (int)(value - ((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT))); + } + + if (l == 0) { + la = CS4231_MAX_DEV_ATEN; + } else { + la = CS4231_MAX_ATEN - (l * (CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1)); + } + if (r == 0) { + ra = CS4231_MAX_DEV_ATEN; + } else { + ra = CS4231_MAX_ATEN - (r * (CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1)); + } + + cs4231_chip->pioregs->iar = 0x6; + old_gain = cs4231_chip->pioregs->idr; + cs4231_chip->pioregs->idr = GAIN_SET(old_gain, la); + cs4231_chip->pioregs->iar = 0x7; + old_gain = cs4231_chip->pioregs->idr; + cs4231_chip->pioregs->idr = GAIN_SET(old_gain, ra); + + if ((value == 0) || (value == AUDIO_MAX_GAIN)) { + tmp = value; + } else { + if (l == value) { + tmp = ((CS4231_MAX_ATEN - la) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_ATEN + 1)); + } else if (r == value) { + tmp = ((CS4231_MAX_ATEN - ra) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_ATEN + 1)); + } + } + return (tmp); +} + +/* Reset the audio chip to a sane state. */ +static void cs4231_reset(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + cs4231_chip->dmaregs.dmacsr = CS_CHIP_RESET; + cs4231_chip->dmaregs.dmacsr = 0x00; + cs4231_chip->dmaregs.dmacsr |= CS_CDC_RESET; + + udelay(100); + + cs4231_chip->dmaregs.dmacsr &= ~(CS_CDC_RESET); + cs4231_chip->pioregs->iar |= IAR_AUTOCAL_BEGIN; + + CHIP_BUG + + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x0c; + cs4231_chip->pioregs->idr = MISC_IR_MODE2; + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x08; + cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; /* Ulaw */ + + CHIP_BUG + + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1c; + cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; /* Ulaw */ + + CHIP_BUG + + cs4231_chip->pioregs->iar = 0x19; + + /* see what we can turn on */ + if (cs4231_chip->pioregs->idr & CS4231A) + cs4231_chip->status |= CS_STATUS_REV_A; + else + cs4231_chip->status &= ~CS_STATUS_REV_A; + + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x10; + cs4231_chip->pioregs->idr = OLB_ENABLE; + + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x11; + if (cs4231_chip->status & CS_STATUS_REV_A) + cs4231_chip->pioregs->idr = (HPF_ON | XTALE_ON); + else + cs4231_chip->pioregs->idr = (HPF_ON); + + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1a; + cs4231_chip->pioregs->idr = 0x00; + + /* Now set things up for defaults */ + cs4231_chip->perchip_info.play.port = cs4231_out_port(drv, AUDIO_SPEAKER); + cs4231_chip->perchip_info.record.port = cs4231_in_port(drv, AUDIO_MICROPHONE); + cs4231_chip->perchip_info.play.gain = cs4231_play_gain(drv, CS4231_DEFAULT_PLAYGAIN, AUDIO_MID_BALANCE); + cs4231_chip->perchip_info.record.gain = cs4231_record_gain(drv, CS4231_DEFAULT_RECGAIN, AUDIO_MID_BALANCE); + cs4231_chip->perchip_info.monitor_gain = cs4231_monitor_gain(drv, LOOPB_OFF); + + cs4231_chip->pioregs->iar = (u_char)IAR_AUTOCAL_END; + + cs4231_ready(drv); + + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x09; + cs4231_chip->pioregs->idr &= ACAL_DISABLE; + cs4231_chip->pioregs->iar = (u_char)IAR_AUTOCAL_END; + + cs4231_ready(drv); + + cs4231_output_muted(drv, 0); +} + +static void cs4231_mute(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + if (!(cs4231_chip->status & CS_STATUS_REV_A)) { + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN; + udelay(100); + cs4231_chip->pioregs->iar = IAR_AUTOCAL_END; + CHIP_BUG + } +} + +/* Not yet useful */ +#if 0 +static int cs4231_len_to_sample(struct sparcaudio_driver *drv, int length, int direction) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + int sample; + + if (/* number of channels == 2*/0) { + sample = (length/2); + } else { + sample = length; + } + if (/*encoding == AUDIO_ENCODING_LINEAR*/0) { + sample = sample/2; + } + return (sample); +} +#endif + +static int cs4231_open(struct inode * inode, struct file * file, struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + /* Set the default audio parameters. */ + + cs4231_chip->perchip_info.play.sample_rate = CS4231_RATE; + cs4231_chip->perchip_info.play.channels = CS4231_CHANNELS; + cs4231_chip->perchip_info.play.precision = CS4231_PRECISION; + cs4231_chip->perchip_info.play.encoding = AUDIO_ENCODING_ULAW; + + cs4231_chip->perchip_info.record.sample_rate = CS4231_RATE; + cs4231_chip->perchip_info.record.channels = CS4231_CHANNELS; + cs4231_chip->perchip_info.record.precision = CS4231_PRECISION; + cs4231_chip->perchip_info.record.encoding = AUDIO_ENCODING_ULAW; + + cs4231_ready(drv); + + cs4231_chip->status |= CS_STATUS_NEED_INIT; + + CHIP_BUG + + MOD_INC_USE_COUNT; + + return 0; +} + +static void cs4231_release(struct inode * inode, struct file * file, struct sparcaudio_driver *drv) +{ + /* zero out any info about what data we have as well */ + /* should insert init on close variable optionally calling cs4231_reset() */ + MOD_DEC_USE_COUNT; +} + +static int cs4231_playintr(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + /* Send the next byte of outgoing data. */ +#if 0 + if (cs4231_chip->output_ptr && cs4231_chip->output_count > 0) { + cs4231_chip->dmaregs.dmapnva = dma_handle; + cs4231_chip->dmaregs.dmapnc = length; + cs4231_chip->output_ptr++; + cs4231_chip->output_count--; + + /* Done with the buffer? Notify the midlevel driver. */ + if (cs4231_chip->output_count == 0) { + cs4231_chip->output_ptr = NULL; + cs4231_chip->output_count = 0; + sparcaudio_output_done(drv); + } + } +#endif +} + +static void cs4231_recmute(int fmt) +{ + switch (fmt) { + case AUDIO_ENCODING_LINEAR: + /* Insert 0x00 from "here" to end of data stream */ + break; + case AUDIO_ENCODING_ALAW: + /* Insert 0xd5 from "here" to end of data stream */ + break; + case AUDIO_ENCODING_ULAW: + /* Insert 0xff from "here" to end of data stream */ + break; + } +} + +static int cs4231_recintr(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + cs4231_recmute(cs4231_chip->perchip_info.record.encoding); + + if (cs4231_chip->perchip_info.record.active == 0) { + cs4231_pollinput(drv); + cs4231_chip->pioregs->iar = 0x9; + cs4231_chip->pioregs->idr &= CEN_DISABLE; + } + /* Read the next byte of incoming data. */ +#if 0 + if (cs4231_chip->input_ptr && cs4231_chip->input_count > 0) { + cs4231_chip->dmaregs.dmacnva = dma_handle; + cs4231_chip->dmaregs.dmacnc = length; + cs4231_chip->input_ptr++; + cs4231_chip->input_count--; + + /* Done with the buffer? Notify the midlevel driver. */ + if (cs4231_chip->input_count == 0) { + cs4231_chip->input_ptr = NULL; + cs4231_chip->input_count = 0; + sparcaudio_input_done(drv); + } + } +#endif +} + +static void cs4231_start_output(struct sparcaudio_driver *drv, __u8 * buffer, unsigned long count) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + if (cs4231_chip->perchip_info.play.active || (cs4231_chip->perchip_info.play.pause)) + return; + + cs4231_ready(drv); + + if (cs4231_chip->status & CS_STATUS_NEED_INIT) + { + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x08; + cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1c; + cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; + + CHIP_BUG + + cs4231_chip->status &= ~CS_STATUS_NEED_INIT; + } + + if (!cs4231_chip->perchip_info.play.pause) + { + /* init dma foo here */ + cs4231_chip->dmaregs.dmacsr &= ~CS_XINT_PLAY; + cs4231_chip->dmaregs.dmacsr &= ~CS_PPAUSE; + if (cs4231_playintr(drv)) { + cs4231_chip->dmaregs.dmacsr |= CS_PLAY_SETUP; + cs4231_chip->pioregs->iar = 0x9; + cs4231_chip->pioregs->idr |= PEN_ENABLE; + } + } + cs4231_chip->perchip_info.play.active = 1; +} + +static void cs4231_stop_output(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + cs4231_chip->perchip_info.play.active = 0; + cs4231_chip->dmaregs.dmacsr |= (CS_PPAUSE); +} + +static void cs4231_pollinput(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + int x = 0; + + while (!(cs4231_chip->dmaregs.dmacsr & CS_XINT_COVF) && x <= CS_TIMEOUT) { + x++; + } + cs4231_chip->dmaregs.dmacsr |= CS_XINT_CEMP; +} + +static void cs4231_start_input(struct sparcaudio_driver *drv, __u8 * buffer, unsigned long count) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + if (cs4231_chip->perchip_info.record.active || (cs4231_chip->perchip_info.record.pause)) + return; + + cs4231_ready(drv); + + if (cs4231_chip->status & CS_STATUS_NEED_INIT) + { + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x08; + cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; + cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1c; + cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; + + CHIP_BUG + + cs4231_chip->status &= ~CS_STATUS_NEED_INIT; + } + + if (!cs4231_chip->perchip_info.record.pause) + { + /* init dma foo here */ + cs4231_chip->dmaregs.dmacsr &= ~CS_XINT_CAPT; + cs4231_chip->dmaregs.dmacsr &= ~CS_CPAUSE; + cs4231_recintr(drv); + cs4231_chip->dmaregs.dmacsr |= CS_CAPT_SETUP; + cs4231_chip->pioregs->iar = 0x9; + cs4231_chip->pioregs->idr |= CEN_ENABLE; + } + cs4231_chip->perchip_info.record.active = 1; +} + +static void cs4231_stop_input(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private; + + cs4231_chip->perchip_info.record.active = 0; + cs4231_chip->dmaregs.dmacsr |= (CS_CPAUSE); + + cs4231_pollinput(drv); + + /* need adjust the end pointer, process the input, and clean up the dma */ + + cs4231_chip->pioregs->iar = 0x09; + cs4231_chip->pioregs->idr &= CEN_DISABLE; +} + +static void cs4231_audio_getdev(struct sparcaudio_driver *drv, + audio_device_t * audinfo) +{ + strncpy(audinfo->name, "cs4231", sizeof(audinfo->name) - 1); + strncpy(audinfo->version, "x", sizeof(audinfo->version) - 1); + strncpy(audinfo->config, "audio", sizeof(audinfo->config) - 1); +} + + +/* The ioctl handler should be expected to identify itself and handle loopback + mode */ +/* There will also be a handler for getinfo and setinfo */ + +static struct sparcaudio_operations cs4231_ops = { + cs4231_open, + cs4231_release, + NULL, /* cs4231_ioctl */ + cs4231_start_output, + cs4231_stop_output, + cs4231_start_input, + cs4231_stop_input, + cs4231_audio_getdev, +}; + +/* Probe for the cs4231 chip and then attach the driver. */ +#ifdef MODULE +int init_module(void) +#else +__initfunc(int cs4231_init(void)) +#endif +{ + struct linux_sbus *bus; + struct linux_sbus_device *sdev; + int cs4231_node; + + /* Find the PROM CS4231 node. */ + /* There's an easier way, and I should FIXME */ + cs4231_node = prom_getchild(prom_root_node); + cs4231_node = prom_searchsiblings(cs4231_node,"iommu"); + cs4231_node = prom_getchild(cs4231_node); + cs4231_node = prom_searchsiblings(cs4231_node,"sbus"); + cs4231_node = prom_getchild(cs4231_node); + cs4231_node = prom_searchsiblings(cs4231_node,"SUNW,CS4231"); + + if (cs4231_node && cs4231_attach(&drivers[0], cs4231_node, NULL) == 0) + num_drivers = 1; + else + num_drivers = 0; + + /* Probe each SBUS for cs4231 chips. */ + for_all_sbusdev(sdev,bus) { + if (!strcmp(sdev->prom_name, "SUNW,CS4231")) { + /* Don't go over the max number of drivers. */ + if (num_drivers >= MAX_DRIVERS) + continue; + + if (cs4231_attach(&drivers[num_drivers], + sdev->prom_node, sdev->my_bus) == 0) + num_drivers++; + } + } + + /* Only return success if we found some cs4231 chips. */ + return (num_drivers > 0) ? 0 : -EIO; +} + +/* Attach to an cs4231 chip given its PROM node. */ +static int cs4231_attach(struct sparcaudio_driver *drv, int node, + struct linux_sbus *sbus) +{ + struct linux_prom_registers regs; + struct linux_prom_irqs irq; + struct cs4231_chip *cs4231_chip; + int err; + + /* Allocate our private information structure. */ + drv->private = kmalloc(sizeof(struct cs4231_chip), GFP_KERNEL); + if (!drv->private) + return -ENOMEM; + + /* Point at the information structure and initialize it. */ + drv->ops = &cs4231_ops; + cs4231_chip = (struct cs4231_chip *)drv->private; +#if 0 + cs4231_chip->input_ptr = NULL; + cs4231_chip->input_count = 0; + cs4231_chip->output_ptr = NULL; + cs4231_chip->output_count = 0; +#endif + + /* Map the registers into memory. */ + prom_getproperty(node, "reg", (char *)®s, sizeof(regs)); + if (sbus) + prom_apply_sbus_ranges(sbus, ®s, 1); + cs4231_chip->regs_size = regs.reg_size; + cs4231_chip->pioregs = sparc_alloc_io(regs.phys_addr, 0, regs.reg_size, + "cs4231", regs.which_io, 0); + if (!cs4231_chip->pioregs) { + printk(KERN_ERR "cs4231: could not allocate registers\n"); + kfree(drv->private); + return -EIO; + } + + /* Reset the audio chip. */ + cs4231_reset(drv); + + /* Attach the interrupt handler to the audio interrupt. */ + prom_getproperty(node, "intr", (char *)&irq, sizeof(irq)); + cs4231_chip->irq = irq.pri; + request_irq(cs4231_chip->irq, cs4231_interrupt, SA_INTERRUPT, "cs4231", NULL); + enable_irq(cs4231_chip->irq); + + /* Register ourselves with the midlevel audio driver. */ + err = register_sparcaudio_driver(drv); + if (err < 0) { + printk(KERN_ERR "cs4231: unable to register\n"); + disable_irq(cs4231_chip->irq); + free_irq(cs4231_chip->irq, drv); + sparc_free_io(cs4231_chip->pioregs, cs4231_chip->regs_size); + kfree(drv->private); + return -EIO; + } + + /* Announce the hardware to the user. */ + printk(KERN_INFO "cs4231 at 0x%lx irq %d\n", + (unsigned long)cs4231_chip->pioregs, cs4231_chip->irq); + + /* Success! */ + return 0; +} + +#ifdef MODULE +/* Detach from an cs4231 chip given the device structure. */ +static void cs4231_detach(struct sparcaudio_driver *drv) +{ + struct cs4231_chip *info = (struct cs4231_chip *)drv->private; + + unregister_sparcaudio_driver(drv); + disable_irq(info->irq); + free_irq(info->irq, drv); + sparc_free_io(info->pioregs, info->regs_size); + kfree(drv->private); +} + +void cleanup_module(void) +{ + register int i; + + for (i = 0; i < num_drivers; i++) { + cs4231_detach(&drivers[i]); + num_drivers--; + } +} +#endif + diff --git a/drivers/sbus/audio/cs4231.h b/drivers/sbus/audio/cs4231.h new file mode 100644 index 000000000..cd1525051 --- /dev/null +++ b/drivers/sbus/audio/cs4231.h @@ -0,0 +1,230 @@ +/* + * drivers/sbus/audio/cs4231.h + * + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * Copyright (C) 1997 Derrick J. Brashear (shadow@dementia.org) + */ + +#ifndef _CS4231_H_ +#define _CS4231_H_ + +#include <linux/types.h> + +struct cs4231_regs { + u_char iar; /* Index Address Register */ + u_char pad0[3]; + u_char idr; /* Indexed Data Register */ + u_char pad1[3]; + u_char statr; /* Status Register */ + u_char pad2[3]; + u_char piodr; /* PIO Data Register I/O */ + u_char pad3[3]; +}; + +struct cs4231_dma { + u_long dmacsr; /* APC CSR */ + u_long dmapad[3]; + u_long dmacva; /* Capture Virtual Address */ + u_long dmacc; /* Capture Count */ + u_long dmacnva; /* Capture Next Virtual Address */ + u_long dmacnc; /* Capture Next Count */ + u_long dmapva; /* Playback Virtual Address */ + u_long dmapc; /* Playback Count */ + u_long dmapnva; /* Playback Next Virtual Address */ + u_long dmapnc; /* Playback Next Count */ +}; + +struct cs4231_chip { + struct cs4231_regs *pioregs; + struct cs4231_dma dmaregs; + struct audio_info perchip_info; + int irq; + unsigned long regs_size; + + /* Keep track of various info */ + volatile unsigned int status; + + int dma; + int dma2; +}; + +/* Status bits */ +#define CS_STATUS_NEED_INIT 0x01 +#define CS_STATUS_INIT_ON_CLOSE 0x02 +#define CS_STATUS_REV_A 0x04 + +#define CS_TIMEOUT 9000000 + +#define GAIN_SET(var, gain) ((var & ~(0x3f)) | gain) +#define RECGAIN_SET(var, gain) ((var & ~(0x1f)) | gain) + +#define IAR_AUTOCAL_BEGIN 0x40 /* IAR_MCE */ +#define IAR_AUTOCAL_END ~(0x40) /* IAR_MCD */ +#define IAR_NOT_READY 0x80 /* 80h not ready CODEC state */ + +/* Each register assumed mode 1 and 2 unless noted */ + +/* 0 - Left Input Control */ +/* 1 - Right Input Control */ +#define MIC_ENABLE(var) ((var & 0x2f) | 0x80) +#define LINE_ENABLE(var) (var & 0x2f) +#define CDROM_ENABLE(var) ((var & 0x2f) | 0x40) +#define INPUTCR_AUX1 0x40 + +/* 2 - Left Aux 1 Input Control */ +/* 3 - Right Aux 1 Input Control */ +/* 4 - Left Aux 2 Input Control */ +/* 5 - Right Aux 2 Input Control */ + +/* 6 - Left Output Control */ +/* 7 - Right Output Control */ +#define OUTCR_MUTE 0x80 +#define OUTCR_UNMUTE ~0x80 + +/* 8 - Playback Data Format (Mode 2) */ +#define CHANGE_DFR(var, val) ((var & ~(0xF)) | val) +#define CHANGE_ENCODING(var, val) ((var & ~(0xe0)) | val) +#define DEFAULT_DATA_FMAT CS4231_DFR_ULAW +#define CS4231_DFR_8000 0x00 +#define CS4231_DFR_9600 0x0e +#define CS4231_DFR_11025 0x03 +#define CS4231_DFR_16000 0x02 +#define CS4231_DFR_18900 0x05 +#define CS4231_DFR_22050 0x07 +#define CS4231_DFR_32000 0x06 +#define CS4231_DFR_37800 0x09 +#define CS4231_DFR_44100 0x0b +#define CS4231_DFR_48000 0x0c +#define CS4231_DFR_LINEAR8 0x00 +#define CS4231_DFR_ULAW 0x20 +#define CS4231_DFR_ALAW 0x60 +#define CS4231_DFR_ADPCM 0xa0 +#define CS4231_DFR_LINEARBE 0xc0 +#define CS4231_STEREO_ON(val) (val | 0x10) +#define CS4231_MONO_ON(val) (val & ~0x10) + +/* 9 - Interface Config. Register */ +#define CHIP_INACTIVE 0x08 +#define PEN_ENABLE (0x01) +#define PEN_DISABLE (~0x01) +#define CEN_ENABLE (0x02) +#define CEN_DISABLE (~0x02) +#define ACAL_DISABLE (~0x08) +#define ICR_AUTOCAL_INIT 0x01 + +/* 10 - Pin Control Register */ +#define INTR_ON 0x82 +#define INTR_OFF 0x80 +#define PINCR_LINE_MUTE 0x40 +#define PINCR_HDPH_MUTE 0x80 + +/* 11 - Test/Initialization */ +#define DRQ_STAT 0x10 +#define AUTOCAL_IN_PROGRESS 0x20 + +/* 12 - Misc Information */ +#define MISC_IR_MODE2 0x40 + +/* 13 - Loopback Control */ +#define LOOPB_ON 0x01 +#define LOOPB_OFF 0x00 + +/* 14 - Unused (mode 1) */ +/* 15 - Unused (mode 1) */ + +/* 14 - Playback Upper (mode 2) */ +/* 15 - Playback Lower (mode 2) */ + +/* The rest are mode 2 only */ + +/* 16 - Alternate Feature 1 Enable */ +#define OLB_ENABLE 0x80 + +/* 17 - Alternate Feature 2 Enable */ +#define HPF_ON 0x01 +#define XTALE_ON 0x20 + +/* 18 - Left Line Input Gain */ +/* 19 - Right Line Input Gain */ + +/* 20 - Timer High */ +/* 21 - Timer Low */ + +/* 22 - unused */ +/* 23 - unused */ + +/* 24 - Alternate Feature Status */ +#define CS_PU 0x01 /* Underrun */ +#define CS_PO 0x20 /* Overrun */ + +/* 25 - Version */ +#define CS4231A 0x20 +#define CS4231CDE 0x80 + +/* 26 - Mono I/O Control */ +#define CHANGE_MONO_GAIN(val) ((val & ~(0xFF)) | val) +#define MONO_IOCR_MUTE 0x40 + +/* 27 - Unused */ + +/* 28 - Capture Data Format */ +/* see register 8 */ + +/* 29 - Unused */ + +/* 30 - Capture Upper */ +/* 31 - Capture Lower */ + +/* Following are CSR register definitions for the Sparc */ +/* Also list "Solaris" equivs for now, not really useful tho */ +#define CS_INT_PENDING 0x800000 /* APC_IP */ /* Interrupt Pending */ +#define CS_PLAY_INT 0x400000 /* APC_PI */ /* Playback interrupt */ +#define CS_CAPT_INT 0x200000 /* APC_CI */ /* Capture interrupt */ +#define CS_GENL_INT 0x100000 /* APC_EI */ /* General interrupt */ +#define CS_XINT_ENA 0x80000 /* APC_IE */ /* General ext int. enable */ +#define CS_XINT_PLAY 0x40000 /* APC_PIE */ /* Playback ext intr */ +#define CS_XINT_CAPT 0x20000 /* APC_CIE */ /* Capture ext intr */ +#define CS_XINT_GENL 0x10000 /* APC_EIE */ /* Error ext intr */ +#define CS_XINT_EMPT 0x8000 /* APC_PMI */ /* Pipe empty interrupt */ +#define CS_XINT_PEMP 0x4000 /* APC_PM */ /* Play pipe empty */ +#define CS_XINT_PNVA 0x2000 /* APC_PD */ /* Playback NVA dirty */ +#define CS_XINT_PENA 0x1000 /* APC_PMIE */ /* play pipe empty Int enable */ +#define CS_XINT_COVF 0x800 /* APC_CM */ /* Cap data dropped on floor */ +#define CS_XINT_CNVA 0x400 /* APC_CD */ /* Capture NVA dirty */ +#define CS_XINT_CEMP 0x200 /* APC_CMI */ /* Capture pipe empty interrupt */ +#define CS_XINT_CENA 0x100 /* APC_CMIE */ /* Cap. pipe empty int enable */ +#define CS_PPAUSE 0x80 /* APC_PPAUSE */ /* Pause the play DMA */ +#define CS_CPAUSE 0x40 /* APC_CPAUSE */ /* Pause the capture DMA */ +#define CS_CDC_RESET 0x20 /* APC_CODEC_PDN */ /* CODEC RESET */ +#define PDMA_READY 0x08 /* PDMA_GO */ +#define CDMA_READY 0x04 /* CDMA_GO */ +#define CS_CHIP_RESET 0x01 /* APC_RESET */ /* Reset the chip */ + +#define CS_INIT_SETUP (CDMA_READY | PDMA_READY | CS_XINT_ENA | CS_XINT_PLAY | CS_XINT_GENL | CS_INT_PENDING | CS_PLAY_INT | CS_CAPT_INT | CS_GENL_INT) + +#define CS_PLAY_SETUP (CS_GENL_INT | CS_PLAY_INT | CS_XINT_ENA | CS_XINT_PLAY | CS_XINT_EMPT | CS_XINT_GENL | CS_XINT_PENA | PDMA_READY) + +#define CS_CAPT_SETUP (CS_GENL_INT | CS_CAPT_INT | CS_XINT_ENA | CS_XINT_CAPT | CS_XINT_CEMP | CS_XINT_GENL | CDMA_READY) + +#define CS4231_MIN_ATEN (0) +#define CS4231_MAX_ATEN (31) +#define CS4231_MAX_DEV_ATEN (63) + +#define CS4231_MON_MIN_ATEN (0) +#define CS4231_MON_MAX_ATEN (63) + +#define CS4231_DEFAULT_PLAYGAIN (132) +#define CS4231_DEFAULT_RECGAIN (126) + +#define CS4231_MIN_GAIN (0) +#define CS4231_MAX_GAIN (15) + +#define CS4231_PRECISION (8) /* # of bits/sample */ +#define CS4231_CHANNELS (1) /* channels/sample */ + +#define CS4231_RATE (8000) /* default sample rate */ +/* Other rates supported are: + * 9600, 11025, 16000, 18900, 22050, 32000, 37800, 44100, 48000 + */ + +#endif |