Commit 8cc4723f authored by Kai Germaschewski's avatar Kai Germaschewski

ISDN/HiSax: Remove amd7930.c

This has been around for many years but was never finished. No
need to carry it on to yet another stable kernel.
parent 8de6defd
/* $Id: amd7930.c,v 1.5.6.4 2001/09/23 22:24:46 kai Exp $
*
* HiSax ISDN driver - chip specific routines for AMD 7930
*
* Author Brent Baccala
* Copyright by Brent Baccala <baccala@FreeSoft.org>
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
* - Existing ISDN HiSax driver provides all the smarts
* - it compiles, runs, talks to an isolated phone switch, connects
* to a Cisco, pings go through
* - AMD 7930 support only (no DBRI yet)
* - no US NI-1 support (may not work on US phone system - untested)
* - periodic packet loss, apparently due to lost interrupts
* - ISDN sometimes freezes, requiring reboot before it will work again
*
* The code is unreliable enough to be consider alpha
*
* This file is (c) under GNU General Public License
*
* Advanced Micro Devices' Am79C30A is an ISDN/audio chip used in the
* SparcStation 1+. The chip provides microphone and speaker interfaces
* which provide mono-channel audio at 8K samples per second via either
* 8-bit A-law or 8-bit mu-law encoding. Also, the chip features an
* ISDN BRI Line Interface Unit (LIU), I.430 S/T physical interface,
* which performs basic D channel LAPD processing and provides raw
* B channel data. The digital audio channel, the two ISDN B channels,
* and two 64 Kbps channels to the microprocessor are all interconnected
* via a multiplexer.
*
* This driver interfaces to the Linux HiSax ISDN driver, which performs
* all high-level Q.921 and Q.931 ISDN functions. The file is not
* itself a hardware driver; rather it uses functions exported by
* the AMD7930 driver in the sparcaudio subsystem (drivers/sbus/audio),
* allowing the chip to be simultaneously used for both audio and ISDN data.
* The hardware driver does _no_ buffering, but provides several callbacks
* which are called during interrupt service and should therefore run quickly.
*
* D channel transmission is performed by passing the hardware driver the
* address and size of an skb's data area, then waiting for a callback
* to signal successful transmission of the packet. A task is then
* queued to notify the HiSax driver that another packet may be transmitted.
*
* D channel reception is quite simple, mainly because of:
* 1) the slow speed of the D channel - 16 kbps, and
* 2) the presence of an 8- or 32-byte (depending on chip version) FIFO
* to buffer the D channel data on the chip
* Worst case scenario of back-to-back packets with the 8 byte buffer
* at 16 kbps yields an service time of 4 ms - long enough to preclude
* the need for fancy buffering. We queue a background task that copies
* data out of the receive buffer into an skb, and the hardware driver
* simply does nothing until we're done with the receive buffer and
* reset it for a new packet.
*
* B channel processing is more complex, because of:
* 1) the faster speed - 64 kbps,
* 2) the lack of any on-chip buffering (it interrupts for every byte), and
* 3) the lack of any chip support for HDLC encapsulation
*
* The HiSax driver can put each B channel into one of three modes -
* L1_MODE_NULL (channel disabled), L1_MODE_TRANS (transparent data relay),
* and L1_MODE_HDLC (HDLC encapsulation by low-level driver).
* L1_MODE_HDLC is the most common, used for almost all "pure" digital
* data sessions. L1_MODE_TRANS is used for ISDN audio.
*
* HDLC B channel transmission is performed via a large buffer into
* which the skb is copied while performing HDLC bit-stuffing. A CRC
* is computed and attached to the end of the buffer, which is then
* passed to the low-level routines for raw transmission. Once
* transmission is complete, the hardware driver is set to enter HDLC
* idle by successive transmission of mark (all 1) bytes, waiting for
* the ISDN driver to prepare another packet for transmission and
* deliver it.
*
* HDLC B channel reception is performed via an X-byte ring buffer
* divided into N sections of X/N bytes each. Defaults: X=256 bytes, N=4.
* As the hardware driver notifies us that each section is full, we
* hand it the next section and schedule a background task to peruse
* the received section, bit-by-bit, with an HDLC decoder. As
* packets are detected, they are copied into a large buffer while
* decoding HDLC bit-stuffing. The ending CRC is verified, and if
* it is correct, we alloc a new skb of the correct length (which we
* now know), copy the packet into it, and hand it to the upper layers.
* Optimization: for large packets, we hand the buffer (which also
* happens to be an skb) directly to the upper layer after an skb_trim,
* and alloc a new large buffer for future packets, thus avoiding a copy.
* Then we return to HDLC processing; state is saved between calls.
*
*/
#include "hisax.h"
#include "../../sbus/audio/amd7930.h"
#include "isac.h"
#include "isdnl1.h"
#include "rawhdlc.h"
#include <linux/interrupt.h>
static const char *amd7930_revision = "$Revision: 1.5.6.4 $";
#define RCV_BUFSIZE 1024 /* Size of raw receive buffer in bytes */
#define RCV_BUFBLKS 4 /* Number of blocks to divide buffer into
* (must divide RCV_BUFSIZE) */
static void Bchan_fill_fifo(struct BCState *, struct sk_buff *);
static void
Bchan_xmt_bh(void *data)
{
struct BCState *bcs = data;
struct sk_buff *skb;
if (bcs->hw.amd7930.tx_skb != NULL) {
dev_kfree_skb(bcs->hw.amd7930.tx_skb);
bcs->hw.amd7930.tx_skb = NULL;
}
xmit_ready_b(bcs);
}
static void
Bchan_xmit_callback(struct BCState *bcs)
{
schedule_work(&bcs->hw.amd7930.xmt_work);
}
/* B channel transmission: two modes (three, if you count L1_MODE_NULL)
*
* L1_MODE_HDLC - We need to do HDLC encapsulation before transmiting
* the packet (i.e. make_raw_hdlc_data). Since this can be a
* time-consuming operation, our completion callback just schedules
* a bottom half to do encapsulation for the next packet. In between,
* the link will just idle
*
* L1_MODE_TRANS - Data goes through, well, transparent. No HDLC encap,
* and we can't just let the link idle, so the "bottom half" actually
* gets called during the top half (it's our callback routine in this case),
* but it's a lot faster now since we don't call make_raw_hdlc_data
*/
static void
Bchan_fill_fifo(struct BCState *bcs, struct sk_buff *skb)
{
struct IsdnCardState *cs = bcs->cs;
int len;
if ((cs->debug & L1_DEB_HSCX) || (cs->debug & L1_DEB_HSCX_FIFO)) {
char tmp[1024];
char *t = tmp;
t += sprintf(t, "amd7930_fill_fifo %c cnt %d",
bcs->channel ? 'B' : 'A', skb->len);
if (cs->debug & L1_DEB_HSCX_FIFO)
QuickHex(t, skb->data, skb->len);
debugl1(cs, tmp);
}
if (bcs->mode == L1_MODE_HDLC) {
len = make_raw_hdlc_data(skb->data, skb->len,
bcs->hw.amd7930.tx_buff, RAW_BUFMAX);
if (len > 0)
amd7930_bxmit(0, bcs->channel,
bcs->hw.amd7930.tx_buff, len,
(void *) &Bchan_xmit_callback,
(void *) bcs);
dev_kfree_skb(skb);
} else if (bcs->mode == L1_MODE_TRANS) {
amd7930_bxmit(0, bcs->channel,
bcs->hw.amd7930.tx_buff, skb->len,
(void *) &Bchan_xmt_bh,
(void *) bcs);
bcs->hw.amd7930.tx_skb = skb;
} else {
dev_kfree_skb(skb);
}
}
static void
Bchan_mode(struct BCState *bcs, int mode, int bc)
{
struct IsdnCardState *cs = bcs->cs;
if (cs->debug & L1_DEB_HSCX) {
char tmp[40];
sprintf(tmp, "AMD 7930 mode %d bchan %d/%d",
mode, bc, bcs->channel);
debugl1(cs, tmp);
}
bcs->mode = mode;
}
/* Bchan_l2l1 is the entry point for upper layer routines that want to
* transmit on the B channel. PH_DATA_REQ is a normal packet that
* we either start transmitting (if idle) or queue (if busy).
* PH_PULL_REQ can be called to request a callback message (PH_PULL_CNF)
* once the link is idle. After a "pull" callback, the upper layer
* routines can use PH_PULL_IND to send data.
*/
static void
Bchan_l2l1(struct PStack *st, int pr, void *arg)
{
struct sk_buff *skb = arg;
switch (pr) {
case (PH_DATA_REQ):
if (test_bit(BC_FLG_BUSY, &st->l1.bcs->Flag)) {
skb_queue_tail(&st->l1.bcs->squeue, skb);
} else {
test_and_set_bit(BC_FLG_BUSY, &st->l1.bcs->Flag);
Bchan_fill_fifo(st->l1.bcs, skb);
}
break;
case (PH_PULL_IND):
if (test_bit(BC_FLG_BUSY, &st->l1.bcs->Flag)) {
printk(KERN_WARNING "amd7930: this shouldn't happen\n");
break;
}
test_and_set_bit(BC_FLG_BUSY, &st->l1.bcs->Flag);
Bchan_fill_fifo(st->l1.bcs, skb);
break;
case (PH_PULL_REQ):
if (!test_bit(BC_FLG_BUSY, &st->l1.bcs->Flag)) {
clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags);
st->l1.l1l2(st, PH_PULL_CNF, NULL);
} else
set_bit(FLG_L1_PULL_REQ, &st->l1.Flags);
break;
}
}
/* Receiver callback and bottom half - decodes HDLC at leisure (if
* L1_MODE_HDLC) and passes newly received skb on via bcs->rqueue. If
* a large packet is received, stick rv_skb (the buffer that the
* packet has been decoded into) on the receive queue and alloc a new
* (large) skb to act as buffer for future receives. If a small
* packet is received, leave rv_skb alone, alloc a new skb of the
* correct size, and copy the packet into it
*/
static void
Bchan_recv_callback(struct BCState *bcs)
{
struct amd7930_hw *hw = &bcs->hw.amd7930;
hw->rv_buff_in += RCV_BUFSIZE/RCV_BUFBLKS;
hw->rv_buff_in %= RCV_BUFSIZE;
if (hw->rv_buff_in != hw->rv_buff_out) {
amd7930_brecv(0, bcs->channel,
hw->rv_buff + hw->rv_buff_in,
RCV_BUFSIZE/RCV_BUFBLKS,
(void *) &Bchan_recv_callback, (void *) bcs);
}
schedule_work(&hw->rcv_work);
}
static void
Bchan_rcv_bh(void *data)
{
struct BCState *bcs = data;
struct IsdnCardState *cs = bcs->cs;
struct amd7930_hw *hw = &bcs->hw.amd7930;
struct sk_buff *skb;
int len;
if (cs->debug & L1_DEB_HSCX) {
char tmp[1024];
sprintf(tmp, "amd7930_Bchan_rcv (%d/%d)",
hw->rv_buff_in, hw->rv_buff_out);
debugl1(cs, tmp);
QuickHex(tmp, hw->rv_buff + hw->rv_buff_out,
RCV_BUFSIZE/RCV_BUFBLKS);
debugl1(cs, tmp);
}
do {
if (bcs->mode == L1_MODE_HDLC) {
while ((len = read_raw_hdlc_data(hw->hdlc_state,
hw->rv_buff + hw->rv_buff_out, RCV_BUFSIZE/RCV_BUFBLKS,
hw->rv_skb->tail, HSCX_BUFMAX))) {
if (len > 0 && (cs->debug & L1_DEB_HSCX_FIFO)) {
char tmp[1024];
char *t = tmp;
t += sprintf(t, "amd7930_Bchan_rcv %c cnt %d", bcs->channel ? 'B' : 'A', len);
QuickHex(t, hw->rv_skb->tail, len);
debugl1(cs, tmp);
}
if (len > HSCX_BUFMAX/2) {
/* Large packet received */
if (!(skb = dev_alloc_skb(HSCX_BUFMAX))) {
printk(KERN_WARNING "amd7930: receive out of memory");
} else {
skb_put(hw->rv_skb, len);
skb_queue_tail(&bcs->rqueue, hw->rv_skb);
hw->rv_skb = skb;
bcs->event |= 1 << B_RCVBUFREADY;
schedule_work(&bcs->work);
}
} else if (len > 0) {
/* Small packet received */
if (!(skb = dev_alloc_skb(len))) {
printk(KERN_WARNING "amd7930: receive out of memory\n");
} else {
memcpy(skb_put(skb, len), hw->rv_skb->tail, len);
skb_queue_tail(&bcs->rqueue, skb);
bcs->event |= 1 << B_RCVBUFREADY;
schedule_work(&bcs->work);
}
} else {
/* Reception Error */
/* printk("amd7930: B channel receive error\n"); */
}
}
} else if (bcs->mode == L1_MODE_TRANS) {
if (!(skb = dev_alloc_skb(RCV_BUFSIZE/RCV_BUFBLKS))) {
printk(KERN_WARNING "amd7930: receive out of memory\n");
} else {
memcpy(skb_put(skb, RCV_BUFSIZE/RCV_BUFBLKS),
hw->rv_buff + hw->rv_buff_out,
RCV_BUFSIZE/RCV_BUFBLKS);
skb_queue_tail(&bcs->rqueue, skb);
bcs->event |= 1 << B_RCVBUFREADY;
schedule_work(&bcs->work);
}
}
if (hw->rv_buff_in == hw->rv_buff_out) {
/* Buffer was filled up - need to restart receiver */
amd7930_brecv(0, bcs->channel,
hw->rv_buff + hw->rv_buff_in,
RCV_BUFSIZE/RCV_BUFBLKS,
(void *) &Bchan_recv_callback,
(void *) bcs);
}
hw->rv_buff_out += RCV_BUFSIZE/RCV_BUFBLKS;
hw->rv_buff_out %= RCV_BUFSIZE;
} while (hw->rv_buff_in != hw->rv_buff_out);
}
static void
Bchan_close(struct BCState *bcs)
{
struct sk_buff *skb;
Bchan_mode(bcs, 0, 0);
amd7930_bclose(0, bcs->channel);
if (test_bit(BC_FLG_INIT, &bcs->Flag)) {
skb_queue_purge(&bcs->rqueue);
skb_queue_purge(&bcs->squeue);
}
test_and_clear_bit(BC_FLG_INIT, &bcs->Flag);
}
static int
Bchan_open(struct BCState *bcs)
{
struct amd7930_hw *hw = &bcs->hw.amd7930;
if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) {
skb_queue_head_init(&bcs->rqueue);
skb_queue_head_init(&bcs->squeue);
}
test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag);
amd7930_bopen(0, bcs->channel, 0xff);
hw->rv_buff_in = 0;
hw->rv_buff_out = 0;
hw->tx_skb = NULL;
init_hdlc_state(hw->hdlc_state, 0);
amd7930_brecv(0, bcs->channel,
hw->rv_buff + hw->rv_buff_in, RCV_BUFSIZE/RCV_BUFBLKS,
(void *) &Bchan_recv_callback, (void *) bcs);
bcs->event = 0;
bcs->tx_cnt = 0;
return (0);
}
static void
Bchan_init(struct BCState *bcs)
{
if (!(bcs->hw.amd7930.tx_buff = kmalloc(RAW_BUFMAX, GFP_ATOMIC))) {
printk(KERN_WARNING
"HiSax: No memory for amd7930.tx_buff\n");
return;
}
if (!(bcs->hw.amd7930.rv_buff = kmalloc(RCV_BUFSIZE, GFP_ATOMIC))) {
printk(KERN_WARNING
"HiSax: No memory for amd7930.rv_buff\n");
return;
}
if (!(bcs->hw.amd7930.rv_skb = dev_alloc_skb(HSCX_BUFMAX))) {
printk(KERN_WARNING
"HiSax: No memory for amd7930.rv_skb\n");
return;
}
if (!(bcs->hw.amd7930.hdlc_state = kmalloc(sizeof(struct hdlc_state),
GFP_ATOMIC))) {
printk(KERN_WARNING
"HiSax: No memory for amd7930.hdlc_state\n");
return;
}
INIT_WORK(&bcs->hw.amd7930.rcv_work, &Bchan_rcv_bh, bcs);
INIT_WORK(&bcs->hw.amd7930.xmt_work, &Bchan_xmt_bh, bcs);
}
static void
Bchan_manl1(struct PStack *st, int pr,
void *arg)
{
switch (pr) {
case (PH_ACTIVATE_REQ):
test_and_set_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag);
Bchan_mode(st->l1.bcs, st->l1.mode, st->l1.bc);
st->l1.l1man(st, PH_ACTIVATE_CNF, NULL);
break;
case (PH_DEACTIVATE_REQ):
if (!test_bit(BC_FLG_BUSY, &st->l1.bcs->Flag))
Bchan_mode(st->l1.bcs, 0, 0);
test_and_clear_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag);
break;
}
}
int
setstack_amd7930(struct PStack *st, struct BCState *bcs)
{
if (Bchan_open(bcs))
return (-1);
st->l1.bcs = bcs;
st->l2.l2l1 = Bchan_l2l1;
st->ma.manl1 = Bchan_manl1;
setstack_manager(st);
bcs->st = st;
return (0);
}
static void
amd7930_drecv_callback(void *arg, int error, unsigned int count)
{
struct IsdnCardState *cs = (struct IsdnCardState *) arg;
static struct work_struct task;
struct sk_buff *skb;
/* NOTE: This function is called directly from an interrupt handler */
if (1) {
if (!(skb = alloc_skb(count, GFP_ATOMIC)))
printk(KERN_WARNING "HiSax: D receive out of memory\n");
else {
memcpy(skb_put(skb, count), cs->rcvbuf, count);
skb_queue_tail(&cs->rq, skb);
}
INIT_WORK(&task, (void *) DChannel_proc_rcv, (void *) cs);
schedule_work(&task);
}
if (cs->debug & L1_DEB_ISAC_FIFO) {
char tmp[128];
char *t = tmp;
t += sprintf(t, "amd7930 Drecv cnt %d", count);
if (error) t += sprintf(t, " ERR %x", error);
QuickHex(t, cs->rcvbuf, count);
debugl1(cs, tmp);
}
amd7930_drecv(0, cs->rcvbuf, MAX_DFRAME_LEN,
&amd7930_drecv_callback, cs);
}
static void
amd7930_dxmit_callback(void *arg, int error)
{
struct IsdnCardState *cs = (struct IsdnCardState *) arg;
static struct work_struct task;
/* NOTE: This function is called directly from an interrupt handler */
/* may wish to do retransmission here, if error indicates collision */
if (cs->debug & L1_DEB_ISAC_FIFO) {
char tmp[128];
char *t = tmp;
t += sprintf(t, "amd7930 Dxmit cnt %d", cs->tx_skb->len);
if (error) t += sprintf(t, " ERR %x", error);
QuickHex(t, cs->tx_skb->data, cs->tx_skb->len);
debugl1(cs, tmp);
}
cs->tx_skb = NULL;
INIT_WORK(&task, (void *) DChannel_proc_xmt, (void *) cs);
schedule_work(&task);
}
static void
amd7930_Dchan_l2l1(struct PStack *st, int pr, void *arg)
{
struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware;
struct sk_buff *skb = arg;
char str[64];
switch (pr) {
case (PH_DATA_REQ):
if (cs->tx_skb) {
skb_queue_tail(&cs->sq, skb);
#ifdef L2FRAME_DEBUG /* psa */
if (cs->debug & L1_DEB_LAPD)
Logl2Frame(cs, skb, "PH_DATA Queued", 0);
#endif
} else {
if ((cs->dlogflag) && (!(skb->data[2] & 1))) {
/* I-FRAME */
LogFrame(cs, skb->data, skb->len);
sprintf(str, "Q.931 frame user->network tei %d", st->l2.tei);
dlogframe(cs, skb->data+4, skb->len-4,
str);
}
cs->tx_skb = skb;
cs->tx_cnt = 0;
#ifdef L2FRAME_DEBUG /* psa */
if (cs->debug & L1_DEB_LAPD)
Logl2Frame(cs, skb, "PH_DATA", 0);
#endif
amd7930_dxmit(0, skb->data, skb->len,
&amd7930_dxmit_callback, cs);
}
break;
case (PH_PULL_IND):
if (cs->tx_skb) {
if (cs->debug & L1_DEB_WARN)
debugl1(cs, " l2l1 tx_skb exist this shouldn't happen");
skb_queue_tail(&cs->sq, skb);
break;
}
if ((cs->dlogflag) && (!(skb->data[2] & 1))) { /* I-FRAME */
LogFrame(cs, skb->data, skb->len);
sprintf(str, "Q.931 frame user->network tei %d", st->l2.tei);
dlogframe(cs, skb->data + 4, skb->len - 4,
str);
}
cs->tx_skb = skb;
cs->tx_cnt = 0;
#ifdef L2FRAME_DEBUG /* psa */
if (cs->debug & L1_DEB_LAPD)
Logl2Frame(cs, skb, "PH_DATA_PULLED", 0);
#endif
amd7930_dxmit(0, cs->tx_skb->data, cs->tx_skb->len,
&amd7930_dxmit_callback, cs);
break;
case (PH_PULL_REQ):
#ifdef L2FRAME_DEBUG /* psa */
if (cs->debug & L1_DEB_LAPD)
debugl1(cs, "-> PH_REQUEST_PULL");
#endif
if (!cs->tx_skb) {
test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags);
st->l1.l1l2(st, PH_PULL_CNF, NULL);
} else
test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags);
break;
}
}
int
setDstack_amd7930(struct PStack *st, struct IsdnCardState *cs)
{
st->l2.l2l1 = amd7930_Dchan_l2l1;
if (! cs->rcvbuf) {
printk("setDstack_amd7930: No cs->rcvbuf!\n");
} else {
amd7930_drecv(0, cs->rcvbuf, MAX_DFRAME_LEN,
&amd7930_drecv_callback, cs);
}
return (0);
}
static void
manl1_msg(struct IsdnCardState *cs, int msg, void *arg) {
struct PStack *st;
st = cs->stlist;
while (st) {
st->ma.manl1(st, msg, arg);
st = st->next;
}
}
static void
amd7930_new_ph(struct IsdnCardState *cs)
{
switch (amd7930_get_liu_state(0)) {
case 3:
manl1_msg(cs, PH_POWERUP_CNF, NULL);
break;
case 7:
manl1_msg(cs, PH_I4_P8_IND, NULL);
break;
case 8:
manl1_msg(cs, PH_RSYNC_IND, NULL);
break;
}
}
/* amd7930 LIU state change callback */
static void
amd7930_liu_callback(struct IsdnCardState *cs)
{
static struct work_struct task;
if (!cs)
return;
if (cs->debug & L1_DEB_ISAC) {
char tmp[32];
sprintf(tmp, "amd7930_liu state %d", amd7930_get_liu_state(0));
debugl1(cs, tmp);
}
INIT_WORK(&task, (void *) &amd7930_new_ph, (void *) cs);
schedule_work(&task);
}
void
amd7930_l1cmd(struct IsdnCardState *cs, int msg, void *arg)
{
u8 val;
char tmp[32];
if (cs->debug & L1_DEB_ISAC) {
char tmp[32];
sprintf(tmp, "amd7930_l1cmd msg %x", msg);
debugl1(cs, tmp);
}
switch(msg) {
case PH_RESET_REQ:
if (amd7930_get_liu_state(0) <= 3)
amd7930_liu_activate(0,0);
else
amd7930_liu_deactivate(0);
break;
case PH_ENABLE_REQ:
break;
case PH_INFO3_REQ:
amd7930_liu_activate(0,0);
break;
case PH_TESTLOOP_REQ:
break;
default:
if (cs->debug & L1_DEB_WARN) {
sprintf(tmp, "amd7930_l1cmd unknown %4x", msg);
debugl1(cs, tmp);
}
break;
}
}
static void init_amd7930(struct IsdnCardState *cs)
{
Bchan_init(&cs->bcs[0]);
Bchan_init(&cs->bcs[1]);
cs->bcs[0].BC_SetStack = setstack_amd7930;
cs->bcs[1].BC_SetStack = setstack_amd7930;
cs->bcs[0].BC_Close = Bchan_close;
cs->bcs[1].BC_Close = Bchan_close;
Bchan_mode(cs->bcs, 0, 0);
Bchan_mode(cs->bcs + 1, 0, 0);
}
static int
amd7930_init(struct IsdnCardState *cs)
{
cs->l1cmd = amd7930_l1cmd;
amd7930_liu_init(0, &amd7930_liu_callback, (void *)cs);
init_amd7930(cs);
return 0;
}
static struct card_ops amd7930_ops = {
.init = amd7930_init,
};
int __init
setup_amd7930(struct IsdnCard *card)
{
struct IsdnCardState *cs = card->cs;
char tmp[64];
strcpy(tmp, amd7930_revision);
printk(KERN_INFO "HiSax: AMD7930 driver Rev. %s\n", HiSax_getrev(tmp));
cs->irq = amd7930_get_irqnum(0);
if (cs->irq == 0)
return 0;
cs->card_ops = &amd7930_ops;
return 1;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment