📄 dbri.c
字号:
/* $Id: dbri.c,v 1.22 2000/10/27 07:01:38 uzi Exp $ * drivers/sbus/audio/dbri.c * * Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de) * Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org) * * This is the lowlevel driver for the DBRI & MMCODEC duo used for ISDN & AUDIO * on Sun SPARCstation 10, 20, LX and Voyager models. * * - DBRI: AT&T T5900FX Dual Basic Rates ISDN Interface. It is a 32 channel * data time multiplexer with ISDN support (aka T7259) * Interfaces: SBus,ISDN NT & TE, CHI, 4 bits parallel. * CHI: (spelled ki) Concentration Highway Interface (AT&T or Intel bus ?). * Documentation: * - "STP 4000SBus Dual Basic Rate ISDN (DBRI) Tranceiver" from * Sparc Technology Business (courtesy of Sun Support) * - Data sheet of the T7903, a newer but very similar ISA bus equivalent * available from the Lucent (formarly AT&T microelectronics) home * page. * - MMCODEC: Crystal Semiconductor CS4215 16 bit Multimedia Audio Codec * Interfaces: CHI, Audio In & Out, 2 bits parallel * Documentation: from the Crystal Semiconductor home page. * * The DBRI is a 32 pipe machine, each pipe can transfer some bits between * memory and a serial device (long pipes, nr 0-15) or between two serial * devices (short pipes, nr 16-31), or simply send a fixed data to a serial * device (short pipes). * A timeslot defines the bit-offset and nr of bits read from a serial device. * The timeslots are linked to 6 circular lists, one for each direction for * each serial device (NT,TE,CHI). A timeslot is associated to 1 or 2 pipes * (the second one is a monitor/tee pipe, valid only for serial input). * * The mmcodec is connected via the CHI bus and needs the data & some * parameters (volume, balance, output selection) timemultiplexed in 8 byte * chunks. It also has a control mode, which serves for audio format setting. * * Looking at the CS4215 data sheet it is easy to set up 2 or 4 codecs on * the same CHI bus, so I thought perhaps it is possible to use the onboard * & the speakerbox codec simultanously, giving 2 (not very independent :-) * audio devices. But the SUN HW group decided against it, at least on my * LX the speakerbox connector has at least 1 pin missing and 1 wrongly * connected. */#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 <linux/version.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 <asm/pgtable.h>#include <asm/audioio.h>#include "dbri.h"#if defined(DBRI_ISDN) || defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x200ff#include "../../isdn/hisax/hisax.h"#include "../../isdn/hisax/isdnl1.h"#include "../../isdn/hisax/foreign.h"#endif#define DBRI_DEBUG#ifdef DBRI_DEBUG#define dprintk(a, x) if(dbri_debug & a) printk x#define D_GEN (1<<0)#define D_INT (1<<1)#define D_CMD (1<<2)#define D_MM (1<<3)#define D_USR (1<<4)#define D_DESC (1<<5)static int dbri_debug = 0;MODULE_PARM(dbri_debug, "i");static int dbri_trace = 0;MODULE_PARM(dbri_trace, "i");#define tprintk(x) if(dbri_trace) printk xstatic char *cmds[] = { "WAIT", "PAUSE", "JUMP", "IIQ", "REX", "SDP", "CDP", "DTS", "SSP", "CHI", "NT", "TE", "CDEC", "TEST", "CDM", "RESRV"};#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | (1 << 27) | value)#else#define dprintk(a, x)#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | (intr << 27) | value)#endif /* DBRI_DEBUG */#define MAX_DRIVERS 2 /* Increase this if need more than 2 DBRI's */static struct sparcaudio_driver drivers[MAX_DRIVERS];static int num_drivers = 0;/******************************************************************************************* DBRI initialization and command synchronization *****************************************************************************************Commands are sent to the DBRI by building a list of them in memory,then writing the address of the first list item to DBRI register 8.The list is terminated with a WAIT command, which can generate aCPU interrupt if required.Since the DBRI can run in parallel with the CPU, several means ofsynchronization present themselves. The original scheme (Rudolf's)was to set a flag when we "cmdlock"ed the DBRI, clear the flag whenan interrupt signaled completion, and wait on a wait_queue if a routineattempted to cmdlock while the flag was set. The problems arose whenwe tried to cmdlock from inside an interrupt handler, which mightcause scheduling in an interrupt (if we waited), etc, etcA more sophisticated scheme might involve a circular command bufferor an array of command buffers. A routine could fill one withcommands and link it onto a list. When a interrupt signaledcompletion of the current command buffer, look on the list forthe next one.I've decided to implement something much simpler - after each command,the CPU waits for the DBRI to finish the command by polling the P bitin DBRI register 0. I've tried to implement this in such a waythat might make implementing a more sophisticated scheme easier.Every time a routine wants to write commands to the DBRI, it mustfirst call dbri_cmdlock() and get an initial pointer into dbri->dma->cmdin return. After the commands have been writen, dbri_cmdsend() iscalled with the final pointer value.Something a little more clever is required if this code is ever runon an SMP machine.*/static int dbri_locked = 0;static volatile s32 *dbri_cmdlock(struct dbri *dbri){ if (dbri_locked) printk("DBRI: Command buffer locked! (bug in driver)\n"); dbri_locked++; return &dbri->dma->cmd[0];}static void dbri_process_interrupt_buffer(struct dbri *);static void dbri_cmdsend(struct dbri *dbri, volatile s32 *cmd){ int MAXLOOPS = 1000000; int maxloops = MAXLOOPS; unsigned long flags; volatile s32 *ptr; for (ptr = &dbri->dma->cmd[0]; ptr < cmd; ptr++) { dprintk(D_CMD, ("DBRI cmd: %lx:%08x\n", (unsigned long) ptr, *ptr)); } save_and_cli(flags); dbri_locked--; if (dbri_locked != 0) { printk("DBRI: Command buffer improperly locked! (bug in driver)\n"); } else if ((cmd - &dbri->dma->cmd[0]) >= DBRI_NO_CMDS-1) { printk("DBRI: Command buffer overflow! (bug in driver)\n"); } else { *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); *(cmd++) = DBRI_CMD(D_WAIT, 1, 0); dbri->wait_seen = 0; sbus_writel(dbri->dma_dvma, dbri->regs + REG8); while ((--maxloops) > 0 && (sbus_readl(dbri->regs + REG0) & D_P)) barrier(); if (maxloops == 0) { printk("DBRI: Chip never completed command buffer\n"); } else { while ((--maxloops) > 0 && (! dbri->wait_seen)) dbri_process_interrupt_buffer(dbri); if (maxloops == 0) { printk("DBRI: Chip never acked WAIT\n"); } else { dprintk(D_INT, ("DBRI: Chip completed command " "buffer (%d)\n", MAXLOOPS - maxloops)); } } } restore_flags(flags);}static void dbri_reset(struct dbri *dbri){ int i; dprintk(D_GEN, ("DBRI: reset 0:%x 2:%x 8:%x 9:%x\n", sbus_readl(dbri->regs + REG0), sbus_readl(dbri->regs + REG2), sbus_readl(dbri->regs + REG8), sbus_readl(dbri->regs + REG9))); sbus_writel(D_R, dbri->regs + REG0); /* Soft Reset */ for(i = 0; (sbus_readl(dbri->regs + REG0) & D_R) && i < 64; i++) udelay(10);}static void dbri_detach(struct dbri *dbri){ dbri_reset(dbri); free_irq(dbri->irq, dbri); sbus_iounmap(dbri->regs, dbri->regs_size); sbus_free_consistent(dbri->sdev, sizeof(struct dbri_dma), (void *)dbri->dma, dbri->dma_dvma); kfree(dbri);}static void dbri_initialize(struct dbri *dbri){ volatile s32 *cmd; u32 dma_addr, tmp; int n; dbri_reset(dbri); dprintk(D_GEN, ("DBRI: init: cmd: %p, int: %p\n", &dbri->dma->cmd[0], &dbri->dma->intr[0])); /* * Initialize the interrupt ringbuffer. */ for(n = 0; n < DBRI_NO_INTS-1; n++) { dma_addr = dbri->dma_dvma; dma_addr += dbri_dma_off(intr, ((n+1) & DBRI_INT_BLK)); dbri->dma->intr[n * DBRI_INT_BLK] = dma_addr; } dma_addr = dbri->dma_dvma + dbri_dma_off(intr, 0); dbri->dma->intr[n * DBRI_INT_BLK] = dma_addr; dbri->dbri_irqp = 1; /* We should query the openprom to see what burst sizes this * SBus supports. For now, just disable all SBus bursts */ tmp = sbus_readl(dbri->regs + REG0); tmp &= ~(D_G | D_S | D_E); sbus_writel(tmp, dbri->regs + REG0); /* * Set up the interrupt queue */ cmd = dbri_cmdlock(dbri); dma_addr = dbri->dma_dvma + dbri_dma_off(intr, 0); *(cmd++) = DBRI_CMD(D_IIQ, 0, 0); *(cmd++) = dma_addr; dbri_cmdsend(dbri, cmd);}/******************************************************************************************************** DBRI interrupt handler *****************************************************************************************************The DBRI communicates with the CPU mainly via a circular interruptbuffer. When an interrupt is signaled, the CPU walks through thebuffer and calls dbri_process_one_interrupt() for each interrupt word.Complicated interrupts are handled by dedicated functions (whichappear first in this file). Any pending interrupts can be serviced bycalling dbri_process_interrupt_buffer(), which works even if the CPU'sinterrupts are disabled. This function is used by dbri_cmdsend()to make sure we're synced up with the chip after each command sequence,even if we're running cli'ed.*//* * Short data pipes transmit LSB first. The CS4215 receives MSB first. Grrr. * So we have to reverse the bits. Note: not all bit lengths are supported */static __u32 reverse_bytes(__u32 b, int len){ switch(len) { case 32: b = ((b & 0xffff0000) >> 16) | ((b & 0x0000ffff) << 16); case 16: b = ((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8); case 8: b = ((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4); case 4: b = ((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2); case 2: b = ((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1); case 1: case 0: break; default: printk("DBRI reverse_bytes: unsupported length\n"); }; return b;}/* transmission_complete_intr() * * Called by main interrupt handler when DBRI signals transmission complete * on a pipe (interrupt triggered by the B bit in a transmit descriptor). * * Walks through the pipe's list of transmit buffer descriptors, releasing * each one's DMA buffer (if present), flagging the descriptor available, * and signaling its callback routine (if present), before proceeding * to the next one. Stops when the first descriptor is found without * TBC (Transmit Buffer Complete) set, or we've run through them all. */static void transmission_complete_intr(struct dbri *dbri, int pipe){ int td; int status; void *buffer; void (*callback)(void *, int); void *callback_arg; td = dbri->pipes[pipe].desc; while (td >= 0) { if (td >= DBRI_NO_DESCS) { printk("DBRI: invalid td on pipe %d\n", pipe); return; } status = DBRI_TD_STATUS(dbri->dma->desc[td].word4); if (! (status & DBRI_TD_TBC)) { break; } dprintk(D_INT, ("DBRI: TD %d, status 0x%02x\n", td, status)); buffer = dbri->descs[td].buffer; if (buffer) sbus_unmap_single(dbri->sdev, dbri->descs[td].buffer_dvma, dbri->descs[td].len, SBUS_DMA_TODEVICE); callback = dbri->descs[td].output_callback; callback_arg = dbri->descs[td].output_callback_arg; dbri->descs[td].inuse = 0; td = dbri->descs[td].next; dbri->pipes[pipe].desc = td; if (callback != NULL) callback(callback_arg, status & 0xe); }}static void reception_complete_intr(struct dbri *dbri, int pipe){ int rd = dbri->pipes[pipe].desc; s32 status; void *buffer; void (*callback)(void *, int, unsigned int); if (rd < 0 || rd >= DBRI_NO_DESCS) { printk("DBRI: invalid rd on pipe %d\n", pipe); return; } dbri->descs[rd].inuse = 0; dbri->pipes[pipe].desc = dbri->descs[rd].next; status = dbri->dma->desc[rd].word1; buffer = dbri->descs[rd].buffer; if (buffer) sbus_unmap_single(dbri->sdev, dbri->descs[rd].buffer_dvma, dbri->descs[rd].len, SBUS_DMA_FROMDEVICE); callback = dbri->descs[rd].input_callback; if (callback != NULL) callback(dbri->descs[rd].input_callback_arg, DBRI_RD_STATUS(status), DBRI_RD_CNT(status)-2); dprintk(D_INT, ("DBRI: Recv RD %d, status 0x%02x, len %d\n", rd, DBRI_RD_STATUS(status), DBRI_RD_CNT(status)));}static void dbri_process_one_interrupt(struct dbri *dbri, int x){ int val = D_INTR_GETVAL(x); int channel = D_INTR_GETCHAN(x); int command = D_INTR_GETCMD(x); int code = D_INTR_GETCODE(x); int rval = D_INTR_GETRVAL(x); if (channel == D_INTR_CMD) { dprintk(D_INT,("DBRI: INTR: Command: %-5s Value:%d\n", cmds[command], val)); } else { dprintk(D_INT,("DBRI: INTR: Chan:%d Code:%d Val:%#x\n", channel, code, rval)); } if (channel == D_INTR_CMD && command == D_WAIT) dbri->wait_seen++; if (code == D_INTR_SBRI) { /* SBRI - BRI status change */ const int liu_states[] = {1, 0, 8, 3, 4, 5, 6, 7}; dbri->liu_state = liu_states[val & 0x7]; if (dbri->liu_callback) dbri->liu_callback(dbri->liu_callback_arg); } if (code == D_INTR_BRDY) reception_complete_intr(dbri, channel); if (code == D_INTR_XCMP) transmission_complete_intr(dbri, channel); if (code == D_INTR_UNDR) { /* UNDR - Transmission underrun * resend SDP command with clear pipe bit (C) set */ volatile s32 *cmd; int pipe = channel; int td = dbri->pipes[pipe].desc; dbri->dma->desc[td].word4 = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -