1
0
Fork 0
alistair23-linux/drivers/isdn/hisax/st5481_usb.c

662 lines
15 KiB
C
Raw Normal View History

/*
* Driver for ST5481 USB ISDN modem
*
* Author Frode Isaksen
* Copyright 2001 by Frode Isaksen <fisaksen@bewan.com>
* 2001 by Kai Germaschewski <kai.germaschewski@gmx.de>
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
*/
#include <linux/init.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include "st5481.h"
static int st5481_isoc_flatten(struct urb *urb);
/* ======================================================================
* control pipe
*/
/*
* Send the next endpoint 0 request stored in the FIFO.
* Called either by the completion or by usb_ctrl_msg.
*/
static void usb_next_ctrl_msg(struct urb *urb,
struct st5481_adapter *adapter)
{
struct st5481_ctrl *ctrl = &adapter->ctrl;
int r_index;
if (test_and_set_bit(0, &ctrl->busy)) {
return;
}
if ((r_index = fifo_remove(&ctrl->msg_fifo.f)) < 0) {
test_and_clear_bit(0,&ctrl->busy);
return;
}
urb->setup_packet =
(unsigned char *)&ctrl->msg_fifo.data[r_index];
DBG(1,"request=0x%02x,value=0x%04x,index=%x",
((struct ctrl_msg *)urb->setup_packet)->dr.bRequest,
((struct ctrl_msg *)urb->setup_packet)->dr.wValue,
((struct ctrl_msg *)urb->setup_packet)->dr.wIndex);
// Prepare the URB
urb->dev = adapter->usb_dev;
SUBMIT_URB(urb, GFP_ATOMIC);
}
/*
* Asynchronous endpoint 0 request (async version of usb_control_msg).
* The request will be queued up in a FIFO if the endpoint is busy.
*/
static void usb_ctrl_msg(struct st5481_adapter *adapter,
u8 request, u8 requesttype, u16 value, u16 index,
ctrl_complete_t complete, void *context)
{
struct st5481_ctrl *ctrl = &adapter->ctrl;
int w_index;
struct ctrl_msg *ctrl_msg;
if ((w_index = fifo_add(&ctrl->msg_fifo.f)) < 0) {
WARN("control msg FIFO full");
return;
}
ctrl_msg = &ctrl->msg_fifo.data[w_index];
ctrl_msg->dr.bRequestType = requesttype;
ctrl_msg->dr.bRequest = request;
ctrl_msg->dr.wValue = cpu_to_le16p(&value);
ctrl_msg->dr.wIndex = cpu_to_le16p(&index);
ctrl_msg->dr.wLength = 0;
ctrl_msg->complete = complete;
ctrl_msg->context = context;
usb_next_ctrl_msg(ctrl->urb, adapter);
}
/*
* Asynchronous endpoint 0 device request.
*/
void st5481_usb_device_ctrl_msg(struct st5481_adapter *adapter,
u8 request, u16 value,
ctrl_complete_t complete, void *context)
{
usb_ctrl_msg(adapter, request,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
value, 0, complete, context);
}
/*
* Asynchronous pipe reset (async version of usb_clear_halt).
*/
void st5481_usb_pipe_reset(struct st5481_adapter *adapter,
u_char pipe,
ctrl_complete_t complete, void *context)
{
DBG(1,"pipe=%02x",pipe);
usb_ctrl_msg(adapter,
USB_REQ_CLEAR_FEATURE, USB_DIR_OUT | USB_RECIP_ENDPOINT,
0, pipe, complete, context);
}
/*
Physical level functions
*/
void st5481_ph_command(struct st5481_adapter *adapter, unsigned int command)
{
DBG(8,"command=%s", ST5481_CMD_string(command));
st5481_usb_device_ctrl_msg(adapter, TXCI, command, NULL, NULL);
}
/*
* The request on endpoint 0 has completed.
* Call the user provided completion routine and try
* to send the next request.
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
static void usb_ctrl_complete(struct urb *urb)
{
struct st5481_adapter *adapter = urb->context;
struct st5481_ctrl *ctrl = &adapter->ctrl;
struct ctrl_msg *ctrl_msg;
if (unlikely(urb->status < 0)) {
switch (urb->status) {
case -ENOENT:
case -ESHUTDOWN:
case -ECONNRESET:
DBG(1,"urb killed status %d", urb->status);
return; // Give up
default:
WARN("urb status %d",urb->status);
break;
}
}
ctrl_msg = (struct ctrl_msg *)urb->setup_packet;
if (ctrl_msg->dr.bRequest == USB_REQ_CLEAR_FEATURE) {
/* Special case handling for pipe reset */
le16_to_cpus(&ctrl_msg->dr.wIndex);
/* toggle is reset on clear */
usb_settoggle(adapter->usb_dev,
ctrl_msg->dr.wIndex & ~USB_DIR_IN,
(ctrl_msg->dr.wIndex & USB_DIR_IN) == 0,
0);
}
if (ctrl_msg->complete)
ctrl_msg->complete(ctrl_msg->context);
clear_bit(0, &ctrl->busy);
// Try to send next control message
usb_next_ctrl_msg(urb, adapter);
return;
}
/* ======================================================================
* interrupt pipe
*/
/*
* The interrupt endpoint will be called when any
* of the 6 registers changes state (depending on masks).
* Decode the register values and schedule a private event.
* Called at interrupt.
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
static void usb_int_complete(struct urb *urb)
{
u8 *data = urb->transfer_buffer;
u8 irqbyte;
struct st5481_adapter *adapter = urb->context;
int j;
int status;
switch (urb->status) {
case 0:
/* success */
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
/* this urb is terminated, clean up */
DBG(2, "urb shutting down with status: %d", urb->status);
return;
default:
WARN("nonzero urb status received: %d", urb->status);
goto exit;
}
DBG_PACKET(2, data, INT_PKT_SIZE);
if (urb->actual_length == 0) {
goto exit;
}
irqbyte = data[MPINT];
if (irqbyte & DEN_INT)
FsmEvent(&adapter->d_out.fsm, EV_DOUT_DEN, NULL);
if (irqbyte & DCOLL_INT)
FsmEvent(&adapter->d_out.fsm, EV_DOUT_COLL, NULL);
irqbyte = data[FFINT_D];
if (irqbyte & OUT_UNDERRUN)
FsmEvent(&adapter->d_out.fsm, EV_DOUT_UNDERRUN, NULL);
if (irqbyte & OUT_DOWN)
;// printk("OUT_DOWN\n");
irqbyte = data[MPINT];
if (irqbyte & RXCI_INT)
FsmEvent(&adapter->l1m, data[CCIST] & 0x0f, NULL);
for (j = 0; j < 2; j++)
adapter->bcs[j].b_out.flow_event |= data[FFINT_B1 + j];
urb->actual_length = 0;
exit:
status = usb_submit_urb (urb, GFP_ATOMIC);
if (status)
WARN("usb_submit_urb failed with result %d", status);
}
/* ======================================================================
* initialization
*/
int st5481_setup_usb(struct st5481_adapter *adapter)
{
struct usb_device *dev = adapter->usb_dev;
struct st5481_ctrl *ctrl = &adapter->ctrl;
struct st5481_intr *intr = &adapter->intr;
struct usb_interface *intf;
struct usb_host_interface *altsetting = NULL;
struct usb_host_endpoint *endpoint;
int status;
struct urb *urb;
u8 *buf;
DBG(2,"");
if ((status = usb_reset_configuration (dev)) < 0) {
WARN("reset_configuration failed,status=%d",status);
return status;
}
intf = usb_ifnum_to_if(dev, 0);
if (intf)
altsetting = usb_altnum_to_altsetting(intf, 3);
if (!altsetting)
return -ENXIO;
// Check if the config is sane
if ( altsetting->desc.bNumEndpoints != 7 ) {
WARN("expecting 7 got %d endpoints!", altsetting->desc.bNumEndpoints);
return -EINVAL;
}
// The descriptor is wrong for some early samples of the ST5481 chip
altsetting->endpoint[3].desc.wMaxPacketSize = __constant_cpu_to_le16(32);
altsetting->endpoint[4].desc.wMaxPacketSize = __constant_cpu_to_le16(32);
// Use alternative setting 3 on interface 0 to have 2B+D
if ((status = usb_set_interface (dev, 0, 3)) < 0) {
WARN("usb_set_interface failed,status=%d",status);
return status;
}
// Allocate URB for control endpoint
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
return -ENOMEM;
}
ctrl->urb = urb;
// Fill the control URB
usb_fill_control_urb (urb, dev,
usb_sndctrlpipe(dev, 0),
NULL, NULL, 0, usb_ctrl_complete, adapter);
fifo_init(&ctrl->msg_fifo.f, ARRAY_SIZE(ctrl->msg_fifo.data));
// Allocate URBs and buffers for interrupt endpoint
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
return -ENOMEM;
}
intr->urb = urb;
buf = kmalloc(INT_PKT_SIZE, GFP_KERNEL);
if (!buf) {
return -ENOMEM;
}
endpoint = &altsetting->endpoint[EP_INT-1];
// Fill the interrupt URB
usb_fill_int_urb(urb, dev,
usb_rcvintpipe(dev, endpoint->desc.bEndpointAddress),
buf, INT_PKT_SIZE,
usb_int_complete, adapter,
endpoint->desc.bInterval);
return 0;
}
/*
* Release buffers and URBs for the interrupt and control
* endpoint.
*/
void st5481_release_usb(struct st5481_adapter *adapter)
{
struct st5481_intr *intr = &adapter->intr;
struct st5481_ctrl *ctrl = &adapter->ctrl;
DBG(1,"");
// Stop and free Control and Interrupt URBs
usb_kill_urb(ctrl->urb);
kfree(ctrl->urb->transfer_buffer);
usb_free_urb(ctrl->urb);
ctrl->urb = NULL;
usb_kill_urb(intr->urb);
kfree(intr->urb->transfer_buffer);
usb_free_urb(intr->urb);
intr->urb = NULL;
}
/*
* Initialize the adapter.
*/
void st5481_start(struct st5481_adapter *adapter)
{
static const u8 init_cmd_table[]={
SET_DEFAULT,0,
STT,0,
SDA_MIN,0x0d,
SDA_MAX,0x29,
SDELAY_VALUE,0x14,
GPIO_DIR,0x01,
GPIO_OUT,RED_LED,
// FFCTRL_OUT_D,4,
// FFCTRH_OUT_D,12,
FFCTRL_OUT_B1,6,
FFCTRH_OUT_B1,20,
FFCTRL_OUT_B2,6,
FFCTRH_OUT_B2,20,
MPMSK,RXCI_INT+DEN_INT+DCOLL_INT,
0
};
struct st5481_intr *intr = &adapter->intr;
int i = 0;
u8 request,value;
DBG(8,"");
adapter->leds = RED_LED;
// Start receiving on the interrupt endpoint
SUBMIT_URB(intr->urb, GFP_KERNEL);
while ((request = init_cmd_table[i++])) {
value = init_cmd_table[i++];
st5481_usb_device_ctrl_msg(adapter, request, value, NULL, NULL);
}
st5481_ph_command(adapter, ST5481_CMD_PUP);
}
/*
* Reset the adapter to default values.
*/
void st5481_stop(struct st5481_adapter *adapter)
{
DBG(8,"");
st5481_usb_device_ctrl_msg(adapter, SET_DEFAULT, 0, NULL, NULL);
}
/* ======================================================================
* isochronous USB helpers
*/
static void
fill_isoc_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, void *buf, int num_packets,
int packet_size, usb_complete_t complete,
void *context)
{
int k;
urb->dev=dev;
urb->pipe=pipe;
urb->interval = 1;
urb->transfer_buffer=buf;
urb->number_of_packets = num_packets;
urb->transfer_buffer_length=num_packets*packet_size;
urb->actual_length = 0;
urb->complete=complete;
urb->context=context;
urb->transfer_flags=URB_ISO_ASAP;
for (k = 0; k < num_packets; k++) {
urb->iso_frame_desc[k].offset = packet_size * k;
urb->iso_frame_desc[k].length = packet_size;
urb->iso_frame_desc[k].actual_length = 0;
}
}
int
st5481_setup_isocpipes(struct urb* urb[2], struct usb_device *dev,
unsigned int pipe, int num_packets,
int packet_size, int buf_size,
usb_complete_t complete, void *context)
{
int j, retval;
unsigned char *buf;
for (j = 0; j < 2; j++) {
retval = -ENOMEM;
urb[j] = usb_alloc_urb(num_packets, GFP_KERNEL);
if (!urb[j])
goto err;
// Allocate memory for 2000bytes/sec (16Kb/s)
buf = kmalloc(buf_size, GFP_KERNEL);
if (!buf)
goto err;
// Fill the isochronous URB
fill_isoc_urb(urb[j], dev, pipe, buf,
num_packets, packet_size, complete,
context);
}
return 0;
err:
for (j = 0; j < 2; j++) {
if (urb[j]) {
kfree(urb[j]->transfer_buffer);
urb[j]->transfer_buffer = NULL;
usb_free_urb(urb[j]);
urb[j] = NULL;
}
}
return retval;
}
void st5481_release_isocpipes(struct urb* urb[2])
{
int j;
for (j = 0; j < 2; j++) {
usb_kill_urb(urb[j]);
kfree(urb[j]->transfer_buffer);
usb_free_urb(urb[j]);
urb[j] = NULL;
}
}
/*
* Decode frames received on the B/D channel.
* Note that this function will be called continously
* with 64Kbit/s / 16Kbit/s of data and hence it will be
* called 50 times per second with 20 ISOC descriptors.
* Called at interrupt.
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
static void usb_in_complete(struct urb *urb)
{
struct st5481_in *in = urb->context;
unsigned char *ptr;
struct sk_buff *skb;
int len, count, status;
if (unlikely(urb->status < 0)) {
switch (urb->status) {
case -ENOENT:
case -ESHUTDOWN:
case -ECONNRESET:
DBG(1,"urb killed status %d", urb->status);
return; // Give up
default:
WARN("urb status %d",urb->status);
break;
}
}
DBG_ISO_PACKET(0x80,urb);
len = st5481_isoc_flatten(urb);
ptr = urb->transfer_buffer;
while (len > 0) {
if (in->mode == L1_MODE_TRANS) {
memcpy(in->rcvbuf, ptr, len);
status = len;
len = 0;
} else {
status = isdnhdlc_decode(&in->hdlc_state, ptr, len, &count,
in->rcvbuf, in->bufsize);
ptr += count;
len -= count;
}
if (status > 0) {
// Good frame received
DBG(4,"count=%d",status);
DBG_PACKET(0x400, in->rcvbuf, status);
if (!(skb = dev_alloc_skb(status))) {
WARN("receive out of memory\n");
break;
}
memcpy(skb_put(skb, status), in->rcvbuf, status);
in->hisax_if->l1l2(in->hisax_if, PH_DATA | INDICATION, skb);
} else if (status == -HDLC_CRC_ERROR) {
INFO("CRC error");
} else if (status == -HDLC_FRAMING_ERROR) {
INFO("framing error");
} else if (status == -HDLC_LENGTH_ERROR) {
INFO("length error");
}
}
// Prepare URB for next transfer
urb->dev = in->adapter->usb_dev;
urb->actual_length = 0;
SUBMIT_URB(urb, GFP_ATOMIC);
}
int st5481_setup_in(struct st5481_in *in)
{
struct usb_device *dev = in->adapter->usb_dev;
int retval;
DBG(4,"");
in->rcvbuf = kmalloc(in->bufsize, GFP_KERNEL);
retval = -ENOMEM;
if (!in->rcvbuf)
goto err;
retval = st5481_setup_isocpipes(in->urb, dev,
usb_rcvisocpipe(dev, in->ep),
in->num_packets, in->packet_size,
in->num_packets * in->packet_size,
usb_in_complete, in);
if (retval)
goto err_free;
return 0;
err_free:
kfree(in->rcvbuf);
err:
return retval;
}
void st5481_release_in(struct st5481_in *in)
{
DBG(2,"");
st5481_release_isocpipes(in->urb);
}
/*
* Make the transfer_buffer contiguous by
* copying from the iso descriptors if necessary.
*/
static int st5481_isoc_flatten(struct urb *urb)
{
struct usb_iso_packet_descriptor *pipd,*pend;
unsigned char *src,*dst;
unsigned int len;
if (urb->status < 0) {
return urb->status;
}
for (pipd = &urb->iso_frame_desc[0],
pend = &urb->iso_frame_desc[urb->number_of_packets],
dst = urb->transfer_buffer;
pipd < pend;
pipd++) {
if (pipd->status < 0) {
return (pipd->status);
}
len = pipd->actual_length;
pipd->actual_length = 0;
src = urb->transfer_buffer+pipd->offset;
if (src != dst) {
// Need to copy since isoc buffers not full
while (len--) {
*dst++ = *src++;
}
} else {
// No need to copy, just update destination buffer
dst += len;
}
}
// Return size of flattened buffer
return (dst - (unsigned char *)urb->transfer_buffer);
}
static void st5481_start_rcv(void *context)
{
struct st5481_in *in = context;
struct st5481_adapter *adapter = in->adapter;
DBG(4,"");
in->urb[0]->dev = adapter->usb_dev;
SUBMIT_URB(in->urb[0], GFP_KERNEL);
in->urb[1]->dev = adapter->usb_dev;
SUBMIT_URB(in->urb[1], GFP_KERNEL);
}
void st5481_in_mode(struct st5481_in *in, int mode)
{
if (in->mode == mode)
return;
in->mode = mode;
usb_unlink_urb(in->urb[0]);
usb_unlink_urb(in->urb[1]);
if (in->mode != L1_MODE_NULL) {
if (in->mode != L1_MODE_TRANS)
isdnhdlc_rcv_init(&in->hdlc_state,
in->mode == L1_MODE_HDLC_56K);
st5481_usb_pipe_reset(in->adapter, in->ep, NULL, NULL);
st5481_usb_device_ctrl_msg(in->adapter, in->counter,
in->packet_size,
NULL, NULL);
st5481_start_rcv(in);
} else {
st5481_usb_device_ctrl_msg(in->adapter, in->counter,
0, NULL, NULL);
}
}