2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* 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"
|
|
|
|
|
2005-06-26 05:59:18 +08:00
|
|
|
static int st5481_isoc_flatten(struct urb *urb);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* ======================================================================
|
|
|
|
* 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.
|
|
|
|
*/
|
2005-06-26 05:59:18 +08:00
|
|
|
static void usb_ctrl_msg(struct st5481_adapter *adapter,
|
|
|
|
u8 request, u8 requesttype, u16 value, u16 index,
|
|
|
|
ctrl_complete_t complete, void *context)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
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 21:55:46 +08:00
|
|
|
static void usb_ctrl_complete(struct urb *urb)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct st5481_adapter *adapter = urb->context;
|
|
|
|
struct st5481_ctrl *ctrl = &adapter->ctrl;
|
|
|
|
struct ctrl_msg *ctrl_msg;
|
|
|
|
|
|
|
|
if (unlikely(urb->status < 0)) {
|
2005-09-18 05:52:42 +08:00
|
|
|
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;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 21:55:46 +08:00
|
|
|
static void usb_int_complete(struct urb *urb)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
u8 *data = urb->transfer_buffer;
|
|
|
|
u8 irqbyte;
|
|
|
|
struct st5481_adapter *adapter = urb->context;
|
|
|
|
int j;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
switch (urb->status) {
|
2005-09-18 05:52:42 +08:00
|
|
|
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;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-09-18 05:52:42 +08:00
|
|
|
DBG_PACKET(2, data, INT_PKT_SIZE);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2005-09-18 05:52:42 +08:00
|
|
|
DBG(2,"");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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
|
2005-09-18 05:52:42 +08:00
|
|
|
usb_kill_urb(ctrl->urb);
|
2005-11-07 17:01:29 +08:00
|
|
|
kfree(ctrl->urb->transfer_buffer);
|
2005-04-17 06:20:36 +08:00
|
|
|
usb_free_urb(ctrl->urb);
|
2005-09-18 05:52:42 +08:00
|
|
|
ctrl->urb = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-09-18 05:52:42 +08:00
|
|
|
usb_kill_urb(intr->urb);
|
2005-11-07 17:01:29 +08:00
|
|
|
kfree(intr->urb->transfer_buffer);
|
2005-04-17 06:20:36 +08:00
|
|
|
usb_free_urb(intr->urb);
|
2008-04-28 17:14:40 +08:00
|
|
|
intr->urb = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
2005-09-18 05:52:42 +08:00
|
|
|
urb->interval = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
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]) {
|
2005-11-07 17:01:29 +08:00
|
|
|
kfree(urb[j]->transfer_buffer);
|
2005-09-18 05:52:42 +08:00
|
|
|
urb[j]->transfer_buffer = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
usb_free_urb(urb[j]);
|
2005-09-18 05:52:42 +08:00
|
|
|
urb[j] = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void st5481_release_isocpipes(struct urb* urb[2])
|
|
|
|
{
|
|
|
|
int j;
|
|
|
|
|
|
|
|
for (j = 0; j < 2; j++) {
|
2005-09-18 05:52:42 +08:00
|
|
|
usb_kill_urb(urb[j]);
|
2005-11-07 17:01:29 +08:00
|
|
|
kfree(urb[j]->transfer_buffer);
|
2005-04-17 06:20:36 +08:00
|
|
|
usb_free_urb(urb[j]);
|
2005-09-18 05:52:42 +08:00
|
|
|
urb[j] = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 21:55:46 +08:00
|
|
|
static void usb_in_complete(struct urb *urb)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct st5481_in *in = urb->context;
|
|
|
|
unsigned char *ptr;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
int len, count, status;
|
|
|
|
|
|
|
|
if (unlikely(urb->status < 0)) {
|
2005-09-18 05:52:42 +08:00
|
|
|
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;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2005-06-26 05:59:18 +08:00
|
|
|
static int st5481_isoc_flatten(struct urb *urb)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|