| /* |
| * 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" |
| |
| /* ====================================================================== |
| * 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. |
| */ |
| 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. |
| */ |
| static void usb_ctrl_complete(struct urb *urb, struct pt_regs *regs) |
| { |
| struct st5481_adapter *adapter = urb->context; |
| struct st5481_ctrl *ctrl = &adapter->ctrl; |
| struct ctrl_msg *ctrl_msg; |
| |
| if (unlikely(urb->status < 0)) { |
| if (urb->status != -ENOENT && urb->status != -ESHUTDOWN) { |
| WARN("urb status %d",urb->status); |
| } else { |
| DBG(1,"urb killed"); |
| return; // Give up |
| } |
| } |
| |
| 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. |
| */ |
| static void usb_int_complete(struct urb *urb, struct pt_regs *regs) |
| { |
| 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(1, "urb shutting down with status: %d", urb->status); |
| return; |
| default: |
| WARN("nonzero urb status received: %d", urb->status); |
| goto exit; |
| } |
| |
| |
| DBG_PACKET(1, 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(1,""); |
| |
| 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_unlink_urb(ctrl->urb); |
| if (ctrl->urb->transfer_buffer) |
| kfree(ctrl->urb->transfer_buffer); |
| usb_free_urb(ctrl->urb); |
| |
| usb_unlink_urb(intr->urb); |
| if (intr->urb->transfer_buffer) |
| kfree(intr->urb->transfer_buffer); |
| usb_free_urb(intr->urb); |
| } |
| |
| /* |
| * 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; |
| |
| spin_lock_init(&urb->lock); |
| urb->dev=dev; |
| urb->pipe=pipe; |
| 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]) { |
| if (urb[j]->transfer_buffer) |
| kfree(urb[j]->transfer_buffer); |
| usb_free_urb(urb[j]); |
| } |
| } |
| return retval; |
| } |
| |
| void st5481_release_isocpipes(struct urb* urb[2]) |
| { |
| int j; |
| |
| for (j = 0; j < 2; j++) { |
| usb_unlink_urb(urb[j]); |
| if (urb[j]->transfer_buffer) |
| kfree(urb[j]->transfer_buffer); |
| usb_free_urb(urb[j]); |
| } |
| } |
| |
| /* |
| * 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. |
| */ |
| static void usb_in_complete(struct urb *urb, struct pt_regs *regs) |
| { |
| struct st5481_in *in = urb->context; |
| unsigned char *ptr; |
| struct sk_buff *skb; |
| int len, count, status; |
| |
| if (unlikely(urb->status < 0)) { |
| if (urb->status != -ENOENT && urb->status != -ESHUTDOWN) { |
| WARN("urb status %d",urb->status); |
| } else { |
| DBG(1,"urb killed"); |
| return; // Give up |
| } |
| } |
| |
| 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. |
| */ |
| 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); |
| } |
| } |
| |