summaryrefslogtreecommitdiff
path: root/drivers/isdn/hisax/st5481_d.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/isdn/hisax/st5481_d.c')
-rw-r--r--drivers/isdn/hisax/st5481_d.c780
1 files changed, 780 insertions, 0 deletions
diff --git a/drivers/isdn/hisax/st5481_d.c b/drivers/isdn/hisax/st5481_d.c
new file mode 100644
index 000000000..e88c5c71f
--- /dev/null
+++ b/drivers/isdn/hisax/st5481_d.c
@@ -0,0 +1,780 @@
+/*
+ * 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/gfp.h>
+#include <linux/usb.h>
+#include <linux/netdevice.h>
+#include "st5481.h"
+
+static void ph_connect(struct st5481_adapter *adapter);
+static void ph_disconnect(struct st5481_adapter *adapter);
+
+static struct Fsm l1fsm;
+
+static char *strL1State[] =
+{
+ "ST_L1_F3",
+ "ST_L1_F4",
+ "ST_L1_F6",
+ "ST_L1_F7",
+ "ST_L1_F8",
+};
+
+static char *strL1Event[] =
+{
+ "EV_IND_DP",
+ "EV_IND_1",
+ "EV_IND_2",
+ "EV_IND_3",
+ "EV_IND_RSY",
+ "EV_IND_5",
+ "EV_IND_6",
+ "EV_IND_7",
+ "EV_IND_AP",
+ "EV_IND_9",
+ "EV_IND_10",
+ "EV_IND_11",
+ "EV_IND_AI8",
+ "EV_IND_AI10",
+ "EV_IND_AIL",
+ "EV_IND_DI",
+ "EV_PH_ACTIVATE_REQ",
+ "EV_PH_DEACTIVATE_REQ",
+ "EV_TIMER3",
+};
+
+static inline void D_L1L2(struct st5481_adapter *adapter, int pr, void *arg)
+{
+ struct hisax_if *ifc = (struct hisax_if *) &adapter->hisax_d_if;
+
+ ifc->l1l2(ifc, pr, arg);
+}
+
+static void
+l1_go_f3(struct FsmInst *fi, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fi->userdata;
+
+ if (fi->state == ST_L1_F7)
+ ph_disconnect(adapter);
+
+ FsmChangeState(fi, ST_L1_F3);
+ D_L1L2(adapter, PH_DEACTIVATE | INDICATION, NULL);
+}
+
+static void
+l1_go_f6(struct FsmInst *fi, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fi->userdata;
+
+ if (fi->state == ST_L1_F7)
+ ph_disconnect(adapter);
+
+ FsmChangeState(fi, ST_L1_F6);
+}
+
+static void
+l1_go_f7(struct FsmInst *fi, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fi->userdata;
+
+ FsmDelTimer(&adapter->timer, 0);
+ ph_connect(adapter);
+ FsmChangeState(fi, ST_L1_F7);
+ D_L1L2(adapter, PH_ACTIVATE | INDICATION, NULL);
+}
+
+static void
+l1_go_f8(struct FsmInst *fi, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fi->userdata;
+
+ if (fi->state == ST_L1_F7)
+ ph_disconnect(adapter);
+
+ FsmChangeState(fi, ST_L1_F8);
+}
+
+static void
+l1_timer3(struct FsmInst *fi, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fi->userdata;
+
+ st5481_ph_command(adapter, ST5481_CMD_DR);
+ FsmChangeState(fi, ST_L1_F3);
+ D_L1L2(adapter, PH_DEACTIVATE | INDICATION, NULL);
+}
+
+static void
+l1_ignore(struct FsmInst *fi, int event, void *arg)
+{
+}
+
+static void
+l1_activate(struct FsmInst *fi, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fi->userdata;
+
+ st5481_ph_command(adapter, ST5481_CMD_DR);
+ st5481_ph_command(adapter, ST5481_CMD_PUP);
+ FsmRestartTimer(&adapter->timer, TIMER3_VALUE, EV_TIMER3, NULL, 2);
+ st5481_ph_command(adapter, ST5481_CMD_AR8);
+ FsmChangeState(fi, ST_L1_F4);
+}
+
+static struct FsmNode L1FnList[] __initdata =
+{
+ {ST_L1_F3, EV_IND_DP, l1_ignore},
+ {ST_L1_F3, EV_IND_AP, l1_go_f6},
+ {ST_L1_F3, EV_IND_AI8, l1_go_f7},
+ {ST_L1_F3, EV_IND_AI10, l1_go_f7},
+ {ST_L1_F3, EV_PH_ACTIVATE_REQ, l1_activate},
+
+ {ST_L1_F4, EV_TIMER3, l1_timer3},
+ {ST_L1_F4, EV_IND_DP, l1_go_f3},
+ {ST_L1_F4, EV_IND_AP, l1_go_f6},
+ {ST_L1_F4, EV_IND_AI8, l1_go_f7},
+ {ST_L1_F4, EV_IND_AI10, l1_go_f7},
+
+ {ST_L1_F6, EV_TIMER3, l1_timer3},
+ {ST_L1_F6, EV_IND_DP, l1_go_f3},
+ {ST_L1_F6, EV_IND_AP, l1_ignore},
+ {ST_L1_F6, EV_IND_AI8, l1_go_f7},
+ {ST_L1_F6, EV_IND_AI10, l1_go_f7},
+ {ST_L1_F7, EV_IND_RSY, l1_go_f8},
+
+ {ST_L1_F7, EV_IND_DP, l1_go_f3},
+ {ST_L1_F7, EV_IND_AP, l1_go_f6},
+ {ST_L1_F7, EV_IND_AI8, l1_ignore},
+ {ST_L1_F7, EV_IND_AI10, l1_ignore},
+ {ST_L1_F7, EV_IND_RSY, l1_go_f8},
+
+ {ST_L1_F8, EV_TIMER3, l1_timer3},
+ {ST_L1_F8, EV_IND_DP, l1_go_f3},
+ {ST_L1_F8, EV_IND_AP, l1_go_f6},
+ {ST_L1_F8, EV_IND_AI8, l1_go_f8},
+ {ST_L1_F8, EV_IND_AI10, l1_go_f8},
+ {ST_L1_F8, EV_IND_RSY, l1_ignore},
+};
+
+static __printf(2, 3)
+ void l1m_debug(struct FsmInst *fi, char *fmt, ...)
+{
+ va_list args;
+ char buf[256];
+
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ DBG(8, "%s", buf);
+ va_end(args);
+}
+
+/* ======================================================================
+ * D-Channel out
+ */
+
+/*
+ D OUT state machine:
+ ====================
+
+ Transmit short frame (< 16 bytes of encoded data):
+
+ L1 FRAME D_OUT_STATE USB D CHANNEL
+ -------- ----------- --- ---------
+
+ FIXME
+
+ -> [xx..xx] SHORT_INIT -> [7Exx..xxC1C27EFF]
+ SHORT_WAIT_DEN <> OUT_D_COUNTER=16
+
+ END_OF_SHORT <- DEN_EVENT -> 7Exx
+ xxxx
+ xxxx
+ xxxx
+ xxxx
+ xxxx
+ C1C1
+ 7EFF
+ WAIT_FOR_RESET_IDLE <- D_UNDERRUN <- (8ms)
+ IDLE <> Reset pipe
+
+
+
+ Transmit long frame (>= 16 bytes of encoded data):
+
+ L1 FRAME D_OUT_STATE USB D CHANNEL
+ -------- ----------- --- ---------
+
+ -> [xx...xx] IDLE
+ WAIT_FOR_STOP <> OUT_D_COUNTER=0
+ WAIT_FOR_RESET <> Reset pipe
+ STOP
+ INIT_LONG_FRAME -> [7Exx..xx]
+ WAIT_DEN <> OUT_D_COUNTER=16
+ OUT_NORMAL <- DEN_EVENT -> 7Exx
+ END_OF_FRAME_BUSY -> [xxxx] xxxx
+ END_OF_FRAME_NOT_BUSY -> [xxxx] xxxx
+ -> [xxxx] xxxx
+ -> [C1C2] xxxx
+ -> [7EFF] xxxx
+ xxxx
+ xxxx
+ ....
+ xxxx
+ C1C2
+ 7EFF
+ <- D_UNDERRUN <- (> 8ms)
+ WAIT_FOR_STOP <> OUT_D_COUNTER=0
+ WAIT_FOR_RESET <> Reset pipe
+ STOP
+
+*/
+
+static struct Fsm dout_fsm;
+
+static char *strDoutState[] =
+{
+ "ST_DOUT_NONE",
+
+ "ST_DOUT_SHORT_INIT",
+ "ST_DOUT_SHORT_WAIT_DEN",
+
+ "ST_DOUT_LONG_INIT",
+ "ST_DOUT_LONG_WAIT_DEN",
+ "ST_DOUT_NORMAL",
+
+ "ST_DOUT_WAIT_FOR_UNDERRUN",
+ "ST_DOUT_WAIT_FOR_NOT_BUSY",
+ "ST_DOUT_WAIT_FOR_STOP",
+ "ST_DOUT_WAIT_FOR_RESET",
+};
+
+static char *strDoutEvent[] =
+{
+ "EV_DOUT_START_XMIT",
+ "EV_DOUT_COMPLETE",
+ "EV_DOUT_DEN",
+ "EV_DOUT_RESETED",
+ "EV_DOUT_STOPPED",
+ "EV_DOUT_COLL",
+ "EV_DOUT_UNDERRUN",
+};
+
+static __printf(2, 3)
+ void dout_debug(struct FsmInst *fi, char *fmt, ...)
+{
+ va_list args;
+ char buf[256];
+
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ DBG(0x2, "%s", buf);
+ va_end(args);
+}
+
+static void dout_stop_event(void *context)
+{
+ struct st5481_adapter *adapter = context;
+
+ FsmEvent(&adapter->d_out.fsm, EV_DOUT_STOPPED, NULL);
+}
+
+/*
+ * Start the transfer of a D channel frame.
+ */
+static void usb_d_out(struct st5481_adapter *adapter, int buf_nr)
+{
+ struct st5481_d_out *d_out = &adapter->d_out;
+ struct urb *urb;
+ unsigned int num_packets, packet_offset;
+ int len, buf_size, bytes_sent;
+ struct sk_buff *skb;
+ struct usb_iso_packet_descriptor *desc;
+
+ if (d_out->fsm.state != ST_DOUT_NORMAL)
+ return;
+
+ if (test_and_set_bit(buf_nr, &d_out->busy)) {
+ DBG(2, "ep %d urb %d busy %#lx", EP_D_OUT, buf_nr, d_out->busy);
+ return;
+ }
+ urb = d_out->urb[buf_nr];
+
+ skb = d_out->tx_skb;
+
+ buf_size = NUM_ISO_PACKETS_D * SIZE_ISO_PACKETS_D_OUT;
+
+ if (skb) {
+ len = isdnhdlc_encode(&d_out->hdlc_state,
+ skb->data, skb->len, &bytes_sent,
+ urb->transfer_buffer, buf_size);
+ skb_pull(skb, bytes_sent);
+ } else {
+ // Send flags or idle
+ len = isdnhdlc_encode(&d_out->hdlc_state,
+ NULL, 0, &bytes_sent,
+ urb->transfer_buffer, buf_size);
+ }
+
+ if (len < buf_size) {
+ FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_UNDERRUN);
+ }
+ if (skb && !skb->len) {
+ d_out->tx_skb = NULL;
+ D_L1L2(adapter, PH_DATA | CONFIRM, NULL);
+ dev_kfree_skb_any(skb);
+ }
+
+ // Prepare the URB
+ urb->transfer_buffer_length = len;
+ num_packets = 0;
+ packet_offset = 0;
+ while (packet_offset < len) {
+ desc = &urb->iso_frame_desc[num_packets];
+ desc->offset = packet_offset;
+ desc->length = SIZE_ISO_PACKETS_D_OUT;
+ if (len - packet_offset < desc->length)
+ desc->length = len - packet_offset;
+ num_packets++;
+ packet_offset += desc->length;
+ }
+ urb->number_of_packets = num_packets;
+
+ // Prepare the URB
+ urb->dev = adapter->usb_dev;
+ // Need to transmit the next buffer 2ms after the DEN_EVENT
+ urb->transfer_flags = 0;
+ urb->start_frame = usb_get_current_frame_number(adapter->usb_dev) + 2;
+
+ DBG_ISO_PACKET(0x20, urb);
+
+ if (usb_submit_urb(urb, GFP_KERNEL) < 0) {
+ // There is another URB queued up
+ urb->transfer_flags = URB_ISO_ASAP;
+ SUBMIT_URB(urb, GFP_KERNEL);
+ }
+}
+
+static void fifo_reseted(void *context)
+{
+ struct st5481_adapter *adapter = context;
+
+ FsmEvent(&adapter->d_out.fsm, EV_DOUT_RESETED, NULL);
+}
+
+static void usb_d_out_complete(struct urb *urb)
+{
+ struct st5481_adapter *adapter = urb->context;
+ struct st5481_d_out *d_out = &adapter->d_out;
+ long buf_nr;
+
+ DBG(2, "");
+
+ buf_nr = get_buf_nr(d_out->urb, urb);
+ test_and_clear_bit(buf_nr, &d_out->busy);
+
+ if (unlikely(urb->status < 0)) {
+ switch (urb->status) {
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNRESET:
+ DBG(1, "urb killed status %d", urb->status);
+ break;
+ default:
+ WARNING("urb status %d", urb->status);
+ if (d_out->busy == 0) {
+ st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, fifo_reseted, adapter);
+ }
+ break;
+ }
+ return; // Give up
+ }
+
+ FsmEvent(&adapter->d_out.fsm, EV_DOUT_COMPLETE, (void *) buf_nr);
+}
+
+/* ====================================================================== */
+
+static void dout_start_xmit(struct FsmInst *fsm, int event, void *arg)
+{
+ // FIXME unify?
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+ struct urb *urb;
+ int len, bytes_sent;
+ struct sk_buff *skb;
+ int buf_nr = 0;
+
+ skb = d_out->tx_skb;
+
+ DBG(2, "len=%d", skb->len);
+
+ isdnhdlc_out_init(&d_out->hdlc_state, HDLC_DCHANNEL | HDLC_BITREVERSE);
+
+ if (test_and_set_bit(buf_nr, &d_out->busy)) {
+ WARNING("ep %d urb %d busy %#lx", EP_D_OUT, buf_nr, d_out->busy);
+ return;
+ }
+ urb = d_out->urb[buf_nr];
+
+ DBG_SKB(0x10, skb);
+ len = isdnhdlc_encode(&d_out->hdlc_state,
+ skb->data, skb->len, &bytes_sent,
+ urb->transfer_buffer, 16);
+ skb_pull(skb, bytes_sent);
+
+ if (len < 16)
+ FsmChangeState(&d_out->fsm, ST_DOUT_SHORT_INIT);
+ else
+ FsmChangeState(&d_out->fsm, ST_DOUT_LONG_INIT);
+
+ if (skb->len == 0) {
+ d_out->tx_skb = NULL;
+ D_L1L2(adapter, PH_DATA | CONFIRM, NULL);
+ dev_kfree_skb_any(skb);
+ }
+
+// Prepare the URB
+ urb->transfer_buffer_length = len;
+
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = len;
+ urb->number_of_packets = 1;
+
+ // Prepare the URB
+ urb->dev = adapter->usb_dev;
+ urb->transfer_flags = URB_ISO_ASAP;
+
+ DBG_ISO_PACKET(0x20, urb);
+ SUBMIT_URB(urb, GFP_KERNEL);
+}
+
+static void dout_short_fifo(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ FsmChangeState(&d_out->fsm, ST_DOUT_SHORT_WAIT_DEN);
+ st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 16, NULL, NULL);
+}
+
+static void dout_end_short_frame(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_UNDERRUN);
+}
+
+static void dout_long_enable_fifo(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 16, NULL, NULL);
+ FsmChangeState(&d_out->fsm, ST_DOUT_LONG_WAIT_DEN);
+}
+
+static void dout_long_den(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ FsmChangeState(&d_out->fsm, ST_DOUT_NORMAL);
+ usb_d_out(adapter, 0);
+ usb_d_out(adapter, 1);
+}
+
+static void dout_reset(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_RESET);
+ st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, fifo_reseted, adapter);
+}
+
+static void dout_stop(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_STOP);
+ st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 0, dout_stop_event, adapter);
+}
+
+static void dout_underrun(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ if (test_bit(0, &d_out->busy) || test_bit(1, &d_out->busy)) {
+ FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_NOT_BUSY);
+ } else {
+ dout_stop(fsm, event, arg);
+ }
+}
+
+static void dout_check_busy(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ if (!test_bit(0, &d_out->busy) && !test_bit(1, &d_out->busy))
+ dout_stop(fsm, event, arg);
+}
+
+static void dout_reseted(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ FsmChangeState(&d_out->fsm, ST_DOUT_NONE);
+ // FIXME locking
+ if (d_out->tx_skb)
+ FsmEvent(&d_out->fsm, EV_DOUT_START_XMIT, NULL);
+}
+
+static void dout_complete(struct FsmInst *fsm, int event, void *arg)
+{
+ struct st5481_adapter *adapter = fsm->userdata;
+ long buf_nr = (long) arg;
+
+ usb_d_out(adapter, buf_nr);
+}
+
+static void dout_ignore(struct FsmInst *fsm, int event, void *arg)
+{
+}
+
+static struct FsmNode DoutFnList[] __initdata =
+{
+ {ST_DOUT_NONE, EV_DOUT_START_XMIT, dout_start_xmit},
+
+ {ST_DOUT_SHORT_INIT, EV_DOUT_COMPLETE, dout_short_fifo},
+
+ {ST_DOUT_SHORT_WAIT_DEN, EV_DOUT_DEN, dout_end_short_frame},
+ {ST_DOUT_SHORT_WAIT_DEN, EV_DOUT_UNDERRUN, dout_underrun},
+
+ {ST_DOUT_LONG_INIT, EV_DOUT_COMPLETE, dout_long_enable_fifo},
+
+ {ST_DOUT_LONG_WAIT_DEN, EV_DOUT_DEN, dout_long_den},
+ {ST_DOUT_LONG_WAIT_DEN, EV_DOUT_UNDERRUN, dout_underrun},
+
+ {ST_DOUT_NORMAL, EV_DOUT_UNDERRUN, dout_underrun},
+ {ST_DOUT_NORMAL, EV_DOUT_COMPLETE, dout_complete},
+
+ {ST_DOUT_WAIT_FOR_UNDERRUN, EV_DOUT_UNDERRUN, dout_underrun},
+ {ST_DOUT_WAIT_FOR_UNDERRUN, EV_DOUT_COMPLETE, dout_ignore},
+
+ {ST_DOUT_WAIT_FOR_NOT_BUSY, EV_DOUT_COMPLETE, dout_check_busy},
+
+ {ST_DOUT_WAIT_FOR_STOP, EV_DOUT_STOPPED, dout_reset},
+
+ {ST_DOUT_WAIT_FOR_RESET, EV_DOUT_RESETED, dout_reseted},
+};
+
+void st5481_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg)
+{
+ struct st5481_adapter *adapter = hisax_d_if->priv;
+ struct sk_buff *skb = arg;
+
+ switch (pr) {
+ case PH_ACTIVATE | REQUEST:
+ FsmEvent(&adapter->l1m, EV_PH_ACTIVATE_REQ, NULL);
+ break;
+ case PH_DEACTIVATE | REQUEST:
+ FsmEvent(&adapter->l1m, EV_PH_DEACTIVATE_REQ, NULL);
+ break;
+ case PH_DATA | REQUEST:
+ DBG(2, "PH_DATA REQUEST len %d", skb->len);
+ BUG_ON(adapter->d_out.tx_skb);
+ adapter->d_out.tx_skb = skb;
+ FsmEvent(&adapter->d_out.fsm, EV_DOUT_START_XMIT, NULL);
+ break;
+ default:
+ WARNING("pr %#x\n", pr);
+ break;
+ }
+}
+
+/* ======================================================================
+ */
+
+/*
+ * Start receiving on the D channel since entered state F7.
+ */
+static void ph_connect(struct st5481_adapter *adapter)
+{
+ struct st5481_d_out *d_out = &adapter->d_out;
+ struct st5481_in *d_in = &adapter->d_in;
+
+ DBG(8, "");
+
+ FsmChangeState(&d_out->fsm, ST_DOUT_NONE);
+
+ // st5481_usb_device_ctrl_msg(adapter, FFMSK_D, OUT_UNDERRUN, NULL, NULL);
+ st5481_usb_device_ctrl_msg(adapter, FFMSK_D, 0xfc, NULL, NULL);
+ st5481_in_mode(d_in, L1_MODE_HDLC);
+
+#ifdef LOOPBACK
+ // Turn loopback on (data sent on B and D looped back)
+ st5481_usb_device_ctrl_msg(cs, LBB, 0x04, NULL, NULL);
+#endif
+
+ st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, NULL, NULL);
+
+ // Turn on the green LED to tell that we are in state F7
+ adapter->leds |= GREEN_LED;
+ st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL);
+}
+
+/*
+ * Stop receiving on the D channel since not in state F7.
+ */
+static void ph_disconnect(struct st5481_adapter *adapter)
+{
+ DBG(8, "");
+
+ st5481_in_mode(&adapter->d_in, L1_MODE_NULL);
+
+ // Turn off the green LED to tell that we left state F7
+ adapter->leds &= ~GREEN_LED;
+ st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL);
+}
+
+static int st5481_setup_d_out(struct st5481_adapter *adapter)
+{
+ struct usb_device *dev = adapter->usb_dev;
+ struct usb_interface *intf;
+ struct usb_host_interface *altsetting = NULL;
+ struct usb_host_endpoint *endpoint;
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ DBG(2, "");
+
+ intf = usb_ifnum_to_if(dev, 0);
+ if (intf)
+ altsetting = usb_altnum_to_altsetting(intf, 3);
+ if (!altsetting)
+ return -ENXIO;
+
+ // Allocate URBs and buffers for the D channel out
+ endpoint = &altsetting->endpoint[EP_D_OUT-1];
+
+ DBG(2, "endpoint address=%02x,packet size=%d",
+ endpoint->desc.bEndpointAddress, le16_to_cpu(endpoint->desc.wMaxPacketSize));
+
+ return st5481_setup_isocpipes(d_out->urb, dev,
+ usb_sndisocpipe(dev, endpoint->desc.bEndpointAddress),
+ NUM_ISO_PACKETS_D, SIZE_ISO_PACKETS_D_OUT,
+ NUM_ISO_PACKETS_D * SIZE_ISO_PACKETS_D_OUT,
+ usb_d_out_complete, adapter);
+}
+
+static void st5481_release_d_out(struct st5481_adapter *adapter)
+{
+ struct st5481_d_out *d_out = &adapter->d_out;
+
+ DBG(2, "");
+
+ st5481_release_isocpipes(d_out->urb);
+}
+
+int st5481_setup_d(struct st5481_adapter *adapter)
+{
+ int retval;
+
+ DBG(2, "");
+
+ retval = st5481_setup_d_out(adapter);
+ if (retval)
+ goto err;
+ adapter->d_in.bufsize = MAX_DFRAME_LEN_L1;
+ adapter->d_in.num_packets = NUM_ISO_PACKETS_D;
+ adapter->d_in.packet_size = SIZE_ISO_PACKETS_D_IN;
+ adapter->d_in.ep = EP_D_IN | USB_DIR_IN;
+ adapter->d_in.counter = IN_D_COUNTER;
+ adapter->d_in.adapter = adapter;
+ adapter->d_in.hisax_if = &adapter->hisax_d_if.ifc;
+ retval = st5481_setup_in(&adapter->d_in);
+ if (retval)
+ goto err_d_out;
+
+ adapter->l1m.fsm = &l1fsm;
+ adapter->l1m.state = ST_L1_F3;
+ adapter->l1m.debug = st5481_debug & 0x100;
+ adapter->l1m.userdata = adapter;
+ adapter->l1m.printdebug = l1m_debug;
+ FsmInitTimer(&adapter->l1m, &adapter->timer);
+
+ adapter->d_out.fsm.fsm = &dout_fsm;
+ adapter->d_out.fsm.state = ST_DOUT_NONE;
+ adapter->d_out.fsm.debug = st5481_debug & 0x100;
+ adapter->d_out.fsm.userdata = adapter;
+ adapter->d_out.fsm.printdebug = dout_debug;
+
+ return 0;
+
+err_d_out:
+ st5481_release_d_out(adapter);
+err:
+ return retval;
+}
+
+void st5481_release_d(struct st5481_adapter *adapter)
+{
+ DBG(2, "");
+
+ st5481_release_in(&adapter->d_in);
+ st5481_release_d_out(adapter);
+}
+
+/* ======================================================================
+ * init / exit
+ */
+
+int __init st5481_d_init(void)
+{
+ int retval;
+
+ l1fsm.state_count = L1_STATE_COUNT;
+ l1fsm.event_count = L1_EVENT_COUNT;
+ l1fsm.strEvent = strL1Event;
+ l1fsm.strState = strL1State;
+ retval = FsmNew(&l1fsm, L1FnList, ARRAY_SIZE(L1FnList));
+ if (retval)
+ goto err;
+
+ dout_fsm.state_count = DOUT_STATE_COUNT;
+ dout_fsm.event_count = DOUT_EVENT_COUNT;
+ dout_fsm.strEvent = strDoutEvent;
+ dout_fsm.strState = strDoutState;
+ retval = FsmNew(&dout_fsm, DoutFnList, ARRAY_SIZE(DoutFnList));
+ if (retval)
+ goto err_l1;
+
+ return 0;
+
+err_l1:
+ FsmFree(&l1fsm);
+err:
+ return retval;
+}
+
+// can't be __exit
+void st5481_d_exit(void)
+{
+ FsmFree(&l1fsm);
+ FsmFree(&dout_fsm);
+}