HSI eDLP protcol delivery (20-24 February INTEG CAMP outcome)
authorFaouaz TENOUTIT <faouazx.tenoutit@intel.com>
Mon, 27 Feb 2012 18:55:46 +0000 (19:55 +0100)
committerbuildbot <buildbot@intel.com>
Thu, 8 Mar 2012 17:48:04 +0000 (09:48 -0800)
BZ: 24995

- Fix the boot crash/freeze (lock/unlock inconsistancy)
- Improve the modem reset mngt (Wait for modem reset)
- Remove the debug capability to the HSI controller
- Put the HSI Power management under DLP switch

Faouaz TENOUTIT <faouazx.tenoutit@intel.com>

Change-Id: I24e4fc288ce81d28e9b95f48b076f3ea6ae77a89
Signed-off-by: Faouaz TENOUTIT <faouazx.tenoutit@intel.com>
Reviewed-on: http://android.intel.com:8080/36786
Reviewed-by: Predon, Frederic <frederic.predon@intel.com>
Reviewed-by: Roulliere, Pierre <pierre.roulliere@intel.com>
Reviewed-by: Lebsir, SamiX <samix.lebsir@intel.com>
Tested-by: Lebsir, SamiX <samix.lebsir@intel.com>
Reviewed-by: buildbot <buildbot@intel.com>
Tested-by: buildbot <buildbot@intel.com>
13 files changed:
arch/x86/configs/i386_ctp_defconfig
arch/x86/platform/intel-mid/board-ctp.c
drivers/hsi/clients/Kconfig
drivers/hsi/clients/Makefile
drivers/hsi/clients/dlp_ctrl.c [new file with mode: 0644]
drivers/hsi/clients/dlp_debug.c [new file with mode: 0644]
drivers/hsi/clients/dlp_main.c [new file with mode: 0644]
drivers/hsi/clients/dlp_main.h [new file with mode: 0644]
drivers/hsi/clients/dlp_net.c [new file with mode: 0644]
drivers/hsi/clients/dlp_tty.c [new file with mode: 0644]
drivers/hsi/controllers/intel_mid_hsi.c
drivers/hsi/dlp_debug.h [new file with mode: 0644]
include/linux/hsi/hsi_dlp.h [new file with mode: 0644]

index 22a1cdd..919fb77 100644 (file)
@@ -1370,13 +1370,13 @@ CONFIG_HSI_ARASAN_CONFIG=y
 # HSI clients
 #
 # CONFIG_HSI_CHAR is not set
-CONFIG_HSI_FFL_TTY=y
-CONFIG_HSI_FFL_TTY_NAME="IFX"
-CONFIG_HSI_FFL_TTY_FRAME_LENGTH=4096
-CONFIG_HSI_FFL_TTY_HEADER_LENGTH=4
-CONFIG_HSI_FFL_ENSURE_LAST_WORD_NULL=y
-CONFIG_HSI_FFL_TTY_CHANNEL=1
-CONFIG_HSI_FFL_TTY_STATS=y
+# CONFIG_HSI_FFL_TTY is not set
+CONFIG_HSI_DLP=y
+CONFIG_HSI_DLP_TTY_NAME="IFX"
+CONFIG_HSI_DLP_TTY_STATS=y
+CONFIG_HSI_DLP_NET_NAME="rmnet"
+CONFIG_HSI_DLP_FRAME_LENGTH=4096
+CONFIG_HSI_DLP_HEADER_LENGTH=4
 
 
 #
index 93a195f..49700c6 100644 (file)
@@ -447,9 +447,21 @@ static void *hsi_modem_platform_data(void *data)
        int fcdp_rb = get_gpio_by_name("modem-gpio2");
 
        static const char hsi_char_name[]       = "hsi_char";
+#if defined(CONFIG_HSI_FFL_TTY)
        static const char hsi_ffl_name[]        = "hsi-ffl";
+#elif defined(CONFIG_HSI_DLP)
+       static const char hsi_dlp_name[]        = "hsi-dlp";
+#endif
 
-       static struct hsi_board_info hsi_info[2] = {
+#if ((defined CONFIG_HSI_FFL_TTY) && (defined CONFIG_HSI_DLP))
+#error "Only define one of HSI_FFL_TTY or HSI_DLP"
+#elif (defined(CONFIG_HSI_FFL_TTY) || defined(CONFIG_HSI_DLP))
+#define HSI_CLIENT_CNT 2
+#else
+#define HSI_CLIENT_CNT 1
+#endif
+
+       static struct hsi_board_info hsi_info[HSI_CLIENT_CNT] = {
                [0] = {
                        .name = hsi_char_name,
                        .hsi_id = 0,
@@ -463,6 +475,7 @@ static void *hsi_modem_platform_data(void *data)
                        .rx_cfg.mode = HSI_MODE_FRAME,
                        .rx_cfg.channels = 8
                },
+#if defined(CONFIG_HSI_FFL_TTY)
                [1] = {
                        .name = hsi_ffl_name,
                        .hsi_id = 0,
@@ -476,8 +489,24 @@ static void *hsi_modem_platform_data(void *data)
                        .rx_cfg.mode = HSI_MODE_FRAME,
                        .rx_cfg.channels = 8
                }
+#elif defined(CONFIG_HSI_DLP)
+               [1] = {
+                       .name = hsi_dlp_name,
+                       .hsi_id = 0,
+                       .port = 0,
+                       .archdata = NULL,
+                       .tx_cfg.speed = 100000, /* tx clock, kHz */
+                       .tx_cfg.channels = 8,
+                       .tx_cfg.mode = HSI_MODE_FRAME,
+                       .tx_cfg.arb_mode = HSI_ARB_RR,
+                       .rx_cfg.flow = HSI_FLOW_SYNC,
+                       .rx_cfg.mode = HSI_MODE_FRAME,
+                       .rx_cfg.channels = 8
+               }
+#endif
        };
 
+#if defined(CONFIG_HSI_FFL_TTY)
        static struct hsi_mid_platform_data mid_info = {
                .tx_dma_channels[0] = -1,
                .tx_dma_channels[1] = 5,
@@ -487,6 +516,14 @@ static void *hsi_modem_platform_data(void *data)
                .tx_dma_channels[5] = -1,
                .tx_dma_channels[6] = -1,
                .tx_dma_channels[7] = -1,
+               .tx_sg_entries[0] = 1,
+               .tx_sg_entries[1] = 1,
+               .tx_sg_entries[2] = 1,
+               .tx_sg_entries[3] = 1,
+               .tx_sg_entries[4] = 1,
+               .tx_sg_entries[5] = 1,
+               .tx_sg_entries[6] = 1,
+               .tx_sg_entries[7] = 1,
                .tx_fifo_sizes[0] = -1,
                .tx_fifo_sizes[1] = 1024,
                .tx_fifo_sizes[2] = -1,
@@ -503,6 +540,14 @@ static void *hsi_modem_platform_data(void *data)
                .rx_dma_channels[5] = -1,
                .rx_dma_channels[6] = -1,
                .rx_dma_channels[7] = -1,
+               .rx_sg_entries[0] = 1,
+               .rx_sg_entries[1] = 1,
+               .rx_sg_entries[2] = 1,
+               .rx_sg_entries[3] = 1,
+               .rx_sg_entries[4] = 1,
+               .rx_sg_entries[5] = 1,
+               .rx_sg_entries[6] = 1,
+               .rx_sg_entries[7] = 1,
                .rx_fifo_sizes[0] = -1,
                .rx_fifo_sizes[1] = 1024,
                .rx_fifo_sizes[2] = -1,
@@ -513,9 +558,61 @@ static void *hsi_modem_platform_data(void *data)
                .rx_fifo_sizes[7] = -1,
        };
 
-       printk(KERN_INFO "HSI platform data setup\n");
+#elif defined(CONFIG_HSI_DLP)
+       static struct hsi_mid_platform_data mid_info = {
+               .tx_dma_channels[0] = -1,
+               .tx_dma_channels[1] = 0,
+               .tx_dma_channels[2] = 1,
+               .tx_dma_channels[3] = 2,
+               .tx_dma_channels[4] = 3,
+               .tx_dma_channels[5] = -1,
+               .tx_dma_channels[6] = -1,
+               .tx_dma_channels[7] = -1,
+               .tx_sg_entries[0] = 1,
+               .tx_sg_entries[1] = 1,
+               .tx_sg_entries[2] = 64,
+               .tx_sg_entries[3] = 64,
+               .tx_sg_entries[4] = 64,
+               .tx_sg_entries[5] = 1,
+               .tx_sg_entries[6] = 1,
+               .tx_sg_entries[7] = 1,
+               .tx_fifo_sizes[0] = 128,
+               .tx_fifo_sizes[1] = 128,
+               .tx_fifo_sizes[2] = 256,
+               .tx_fifo_sizes[3] = 256,
+               .tx_fifo_sizes[4] = 256,
+               .tx_fifo_sizes[5] = -1,
+               .tx_fifo_sizes[6] = -1,
+               .tx_fifo_sizes[7] = -1,
+               .rx_dma_channels[0] = -1,
+               .rx_dma_channels[1] = 4,
+               .rx_dma_channels[2] = 5,
+               .rx_dma_channels[3] = 6,
+               .rx_dma_channels[4] = 7,
+               .rx_dma_channels[5] = -1,
+               .rx_dma_channels[6] = -1,
+               .rx_dma_channels[7] = -1,
+               .rx_sg_entries[0] = 1,
+               .rx_sg_entries[1] = 1,
+               .rx_sg_entries[2] = 1,
+               .rx_sg_entries[3] = 1,
+               .rx_sg_entries[4] = 1,
+               .rx_sg_entries[5] = 1,
+               .rx_sg_entries[6] = 1,
+               .rx_sg_entries[7] = 1,
+               .rx_fifo_sizes[0] = 128,
+               .rx_fifo_sizes[1] = 128,
+               .rx_fifo_sizes[2] = 256,
+               .rx_fifo_sizes[3] = 256,
+               .rx_fifo_sizes[4] = 256,
+               .rx_fifo_sizes[5] = -1,
+               .rx_fifo_sizes[6] = -1,
+               .rx_fifo_sizes[7] = -1,
+       };
+#endif
 
-       printk(KERN_INFO "HSI mdm GPIOs %d, %d, %d, %d\n",
+       printk(KERN_INFO "HSI mdm GPIOs rst_out:%d,"
+                       " pwr_on:%d, rst_bbn:%d, fcdp_rb:%d\n",
                rst_out, pwr_on, rst_pmu, fcdp_rb);
 
        mid_info.gpio_mdm_rst_out = rst_out;
index 95de9ed..dc36c87 100644 (file)
@@ -100,3 +100,65 @@ config HSI_FFL_TTY_STATS
 
 endif
 
+config HSI_DLP
+       tristate "Data Link Protocol protocol on HSI for LTE IMC modems"
+       default n
+       depends on HSI
+       help
+         If you say Y here, you will enable the DLP protocol for LTE IMC modem
+         over an HSI physical link.
+         This driver implements network interfaces and a TTY interface for
+         transferring data over HSI between an APE and an IMC modem.
+
+         If unsure, say N.
+
+if HSI_DLP
+
+config HSI_DLP_TTY_NAME
+       string "Base name for the TTY"
+       default "IFX"
+       help
+         Sets the base name for the TTY associated to this IMC modem protocol
+         for LTE.
+
+config HSI_DLP_TTY_STATS
+       bool "Statistics to assess the performance of the protocol (TTY)"
+       default n
+       help
+         If you say Y here, you will instanciate performance related counters
+         for measuring the number of sent and received frames as well as their
+         total actual length in bytes.
+
+         If not fine-tuning the HSI IMC LTE driver, say N.
+
+config HSI_DLP_NET_NAME
+       string "Base name for the network interfaces"
+       default "rmnet"
+       help
+         Sets the base name for the NETWORK interfaces associated to this IMC
+         modem protocol for LTE.
+
+config HSI_DLP_PDU_LENGTH
+       int "Fixed frame length"
+       default "4096"
+       range 4 131072
+       help
+         Sets the fixed frame length in bytes to be used in this protocol
+         driver. This frame length must be a multiple of 4 bytes, set between
+         4 bytes and 128 kiB (131072 bytes).
+
+         Set to 4096 bytes by default.
+
+config HSI_DLP_HEADER_LENGTH
+       int "Fixed frame header length"
+       default "4"
+       range 0 4
+       help
+         Sets the fixed frame header length in bytes to be used in this
+         protocol driver. This header length must be set to 4 in normal usage
+         or to 0 in raw protocol debug mode.
+
+         Set to 4 bytes by default.
+
+endif
+
index 6919101..a800e26 100644 (file)
@@ -3,9 +3,11 @@
 #
 
 CFLAGS_hsi_ffl_tty.o := -DDEBUG
+EXTRA_CFLAGS   += -DDEBUG
 
 obj-$(CONFIG_SSI_PROTOCOL)     += ssi_protocol.o
 obj-$(CONFIG_HSI_CHAR)         += hsi_char.o
 obj-$(CONFIG_HSI_CMT_SPEECH)   += cmt_speech.o
 obj-$(CONFIG_HSI_FFL_TTY)      += hsi_ffl_tty.o
-
+obj-$(CONFIG_HSI_DLP)          += hsi_dlp.o
+hsi_dlp-objs := dlp_main.o dlp_ctrl.o dlp_tty.o dlp_net.o dlp_debug.o
diff --git a/drivers/hsi/clients/dlp_ctrl.c b/drivers/hsi/clients/dlp_ctrl.c
new file mode 100644 (file)
index 0000000..0ba4137
--- /dev/null
@@ -0,0 +1,1255 @@
+/*
+ * dlp_ctrl.c
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI
+ * protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact: Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/jiffies.h>
+#include <linux/hsi/intel_mid_hsi.h>
+#include <linux/hsi/hsi_dlp.h>
+#include <linux/hsi/hsi.h>
+#include <linux/gpio.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include "dlp_main.h"
+
+#define DEBUG_TAG 0x2
+#define DEBUG_VAR dlp_drv.debug
+
+/*
+ *   23            16 15            8  7              0
+ *  |                |                |                |
+ *   ................ ................ ................
+ *
+ *        PARAM3            PARAM2           PARAM1
+ */
+
+#define PARAM1(w)  ((unsigned char) (w))
+#define PARAM2(w)  ((unsigned char)((w) >> 8))
+#define PARAM3(w)  ((unsigned char)((w) >> 16))
+
+#define LSB(b)    ((unsigned char) ((b) &  0xF))
+#define MSB(b)    ((unsigned char) ((b) >> 4))
+
+#define DLP_CMD_TX_TIMOUT               1000  /* 1 sec */
+#define DLP_CMD_RX_TIMOUT           5000  /* 5 sec */
+
+#define DLP_ECHO_CMD_CHECKSUM  0xACAFFE
+
+#define DLP_CTRL_CMD_TO_STR(id)                                                                \
+       (id == DLP_CMD_BREAK            ? "BREAK":                                      \
+       (id == DLP_CMD_ECHO                     ? "ECHO":                                       \
+       (id == DLP_CMD_NOP)                     ? "NOP":                                        \
+       (id == DLP_CMD_CONF_CH)         ? "CONF_CH":                            \
+       (id == DLP_CMD_OPEN_CONN)       ? "OPEN_CONN":                          \
+       (id == DLP_CMD_CANCEL_CONN) ? "CANCEL_CONN":                    \
+       (id == DLP_CMD_ACK)                     ? "ACK":                                        \
+       (id == DLP_CMD_NACK)            ? "NACK":                                       \
+       (id == DLP_CMD_OPEN_CONN_OCTET)  ? "OPEN_CONN_OCTET":   \
+       (id == DLP_CMD_CREDITS)         ? "CREDITS" : "UNKNOWN"))
+
+#define DLP_CTRL_CTX   dlp_drv.channels[DLP_CHANNEL_CTRL]->ch_data
+
+/* DLP commands list */
+#define DLP_CMD_BREAK                          0x0
+#define DLP_CMD_ECHO                           0x1
+#define DLP_CMD_NOP                                    0x4
+#define DLP_CMD_CONF_CH                                0x5
+#define DLP_CMD_OPEN_CONN                      0x7
+#define DLP_CMD_CANCEL_CONN                    0xA
+#define DLP_CMD_ACK                                    0xB
+#define DLP_CMD_NACK                           0xC
+#define DLP_CMD_OPEN_CONN_OCTET                0xE
+#define DLP_CMD_CREDITS                                0xF
+
+/* Flow control */
+enum {
+       DLP_FLOW_CTRL_NONE,
+       DLP_FLOW_CTRL_CREDITS
+};
+
+/* Data format */
+enum {
+       DLP_DATA_FORMAT_RAW,
+       DLP_DATA_FORMAT_PACKET
+};
+
+/* Direction */
+enum {
+       DLP_DIR_TRANSMIT,
+       DLP_DIR_RECEIVE,
+       DLP_DIR_TRANSMIT_AND_RECEIVE
+};
+
+/**
+ * struct dlp_ctrl_cmd_params - DLP modem comamnd/response
+ * @data1: Command data (byte1)
+ * @data2: Command data (byte2)
+ * @data3: Command data (byte3)
+ * @channel: the HSI channel number
+ * @id: The command id
+ */
+struct dlp_command_params {
+       unsigned char data1:8;
+       unsigned char data2:8;
+       unsigned char data3:8;
+
+       unsigned char channel:4;
+       unsigned char id:4;
+};
+
+/**
+ * struct dlp_ctrl_cmd - DLP comamnd
+ * @params: DLP modem comamnd/response
+ * @channel: the DLP channel context
+ * @status: Status of the transfer when completed
+ */
+struct dlp_command {
+       struct dlp_command_params params;
+
+       struct dlp_channel *channel;
+       int status;
+};
+
+/*
+ * struct dlp_ctrl_context - CTRL channel private data
+ *
+ * @response: Modem response
+ * @readiness_wq: Modem readiness worqueue
+ * @tx_done: Wait for the command TX (command request) to be sent
+ * @rx_done: Wait for the command RX (command response) to be received
+ * @start_rx_cb:
+ * @stop_rx_cb:
+ */
+struct dlp_ctrl_context {
+       /* Modem readiness work & worqueue */
+       struct workqueue_struct *readiness_wq;
+       struct work_struct      readiness_work;
+       struct completion reset_done;
+
+       /* Modem response */
+       struct dlp_command response;
+
+       /* Command RX/TX completion */
+       struct completion tx_done;
+       struct completion rx_done;
+
+       /* RX start/stop callbacks */
+       hsi_client_cb start_rx_cb;
+       hsi_client_cb stop_rx_cb;
+
+       /* GPIO */
+       unsigned int gpio_mdm_rst_out;
+       unsigned int gpio_mdm_pwr_on;
+       unsigned int gpio_mdm_rst_bbn;
+       unsigned int gpio_fcdp_rb;
+};
+
+/*
+ * dlp_ctrl_coredump_it        -       Modem has signaled a core dump
+ *
+ */
+static irqreturn_t dlp_ctrl_coredump_it(int irq, void *data)
+{
+       int i;
+       struct dlp_channel *ch_ctx = data;
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       CRITICAL("Modem CORE_DUMP 0x%x", gpio_get_value(ctrl_ctx->gpio_fcdp_rb));
+
+       /* Call registered channels */
+       for (i = 0; i < DLP_CHANNEL_COUNT; i++) {
+               ch_ctx = dlp_drv.channels[i];
+               if (ch_ctx && ch_ctx->modem_coredump_cb) {
+                       ch_ctx->modem_coredump_cb(ch_ctx);
+               }
+       }
+
+       EPILOG();
+       return IRQ_HANDLED;
+}
+
+/*
+ * dlp_ctrl_reset_it - Modem has changed reset state
+ *
+ */
+static irqreturn_t dlp_ctrl_reset_it(int irq, void *data)
+{
+       int i, value;
+       struct dlp_channel *ch_ctx = data;
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       value = gpio_get_value(ctrl_ctx->gpio_mdm_rst_out);
+
+       CRITICAL("Modem RESET_OUT 0x%x, reset_ignore: %d",
+                       value, dlp_drv.reset_ignore);
+       if (dlp_drv.reset_ignore) {
+               /* Rising EDGE (Reset done) ? */
+               if (value)
+                       complete(&ctrl_ctx->reset_done);
+
+               goto out;
+       }
+
+       /* Unexpected reset received */
+       dlp_drv.reset_ignore = 1;
+
+       /* Call registered channels */
+       for (i = 0; i < DLP_CHANNEL_COUNT; i++) {
+               ch_ctx = dlp_drv.channels[i];
+               if (ch_ctx) {
+                       /* Call any register callback */
+                       if (ch_ctx->modem_reset_cb) {
+                               ch_ctx->modem_reset_cb(ch_ctx);
+                       }
+
+                       /* Reset the credits value */
+                       ch_ctx->credits = 0;
+               }
+       }
+
+out:
+       EPILOG();
+       return IRQ_HANDLED;
+}
+
+/*
+ *
+ *
+ */
+static int dlp_ctrl_free_gpios(struct dlp_channel *ch_ctx,
+                                                               struct device *dev)
+{
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       gpio_free(ctrl_ctx->gpio_fcdp_rb);
+       gpio_free(ctrl_ctx->gpio_mdm_rst_out);
+       gpio_free(ctrl_ctx->gpio_mdm_pwr_on);
+       gpio_free(ctrl_ctx->gpio_mdm_rst_bbn);
+
+       if (dlp_drv.core_dump_irq)
+               free_irq(dlp_drv.core_dump_irq, dev);
+
+       if (dlp_drv.reset_irq)
+               free_irq(dlp_drv.reset_irq, dev);
+
+       EPILOG();
+       return 0;
+}
+
+static int dlp_ctrl_configure_gpios(struct dlp_channel *ch_ctx,
+                                                                   struct device *dev)
+{
+       int ret;
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       ret = gpio_request(ctrl_ctx->gpio_mdm_rst_bbn, "ifxHSIModem");
+       ret += gpio_direction_output(ctrl_ctx->gpio_mdm_rst_bbn, 1);
+       ret += gpio_export(ctrl_ctx->gpio_mdm_rst_bbn, 1);
+       if (ret) {
+               CRITICAL("Unable to configure GPIO%d (RESET)",
+                        ctrl_ctx->gpio_mdm_rst_bbn);
+               ret = -ENODEV;
+               goto free_ctx4;
+       }
+       pr_info("dlp: gpio rst_bbn %d\n", ctrl_ctx->gpio_mdm_rst_bbn);
+
+       ret = gpio_request(ctrl_ctx->gpio_mdm_pwr_on, "ifxHSIModem");
+       ret += gpio_direction_output(ctrl_ctx->gpio_mdm_pwr_on, 1);
+       ret += gpio_export(ctrl_ctx->gpio_mdm_pwr_on, 1);
+       if (ret) {
+               CRITICAL("Unable to configure GPIO%d (ON)",
+                        ctrl_ctx->gpio_mdm_pwr_on);
+               ret = -ENODEV;
+               goto free_ctx3;
+       }
+
+       pr_info("dlp: gpio pwr_on %d\n", ctrl_ctx->gpio_mdm_pwr_on);
+
+       /* set up irq for modem reset line */
+       ret = gpio_request(ctrl_ctx->gpio_mdm_rst_out, "ifxHSIModem");
+       ret += gpio_direction_input(ctrl_ctx->gpio_mdm_rst_out);
+       ret += gpio_export(ctrl_ctx->gpio_mdm_rst_out, 0);
+       if (ret) {
+               CRITICAL("Unable to configure GPIO%d (RST_OUT)",
+                        ctrl_ctx->gpio_mdm_rst_out);
+               ret = -ENODEV;
+               goto free_ctx2;
+       }
+
+       dlp_drv.reset_irq = gpio_to_irq(ctrl_ctx->gpio_mdm_rst_out);
+       if (dlp_drv.reset_irq < 0) {
+               ret = -ENODEV;
+               goto free_ctx2;
+       }
+
+       pr_info("dlp: gpio rst_out %d\n", ctrl_ctx->gpio_mdm_rst_out);
+
+       ret = request_irq(dlp_drv.reset_irq,
+                         dlp_ctrl_reset_it,
+                         IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DRVNAME,
+                         ch_ctx);
+       if (ret) {
+               CRITICAL("IRQ request failed for GPIO%d (RST_OUT)",
+                        dlp_drv.reset_irq);
+               ret = -ENODEV;
+               goto free_ctx2;
+       }
+
+       /* set up input for core dump interrupt line */
+       ret = gpio_request(ctrl_ctx->gpio_fcdp_rb, "ifxHSIModem");
+       ret += gpio_direction_input(ctrl_ctx->gpio_fcdp_rb);
+       ret += gpio_export(ctrl_ctx->gpio_fcdp_rb, 0);
+       if (ret) {
+               CRITICAL("Unable to configure GPIO%d (CORE DUMP)",
+                        ctrl_ctx->gpio_fcdp_rb);
+               ret = -ENODEV;
+               goto free_all;
+       }
+
+       dlp_drv.core_dump_irq = gpio_to_irq(ctrl_ctx->gpio_fcdp_rb);
+       if (dlp_drv.core_dump_irq < 0) {
+               ret = -ENODEV;
+               goto free_all;
+       }
+
+       ret = request_irq(dlp_drv.core_dump_irq,
+                         dlp_ctrl_coredump_it,
+                         IRQF_TRIGGER_RISING, DRVNAME,
+                         ch_ctx);
+       if (ret) {
+               CRITICAL("IRQ request failed for GPIO%d (CORE DUMP)",
+                        ctrl_ctx->gpio_fcdp_rb);
+               ret = -ENODEV;
+               goto free_all;
+       }
+
+       pr_info("dlp: gpio fcdp_rb %d\n", ctrl_ctx->gpio_fcdp_rb);
+       EPILOG();
+       return ret;
+
+free_all:
+       dlp_ctrl_free_gpios(ch_ctx, dev);
+       return ret;
+
+free_ctx2:
+       gpio_free(ctrl_ctx->gpio_mdm_rst_out);
+free_ctx3:
+       gpio_free(ctrl_ctx->gpio_mdm_pwr_on);
+free_ctx4:
+       gpio_free(ctrl_ctx->gpio_mdm_rst_bbn);
+
+       if (dlp_drv.core_dump_irq)
+               free_irq(dlp_drv.core_dump_irq, dev);
+
+       if (dlp_drv.reset_irq)
+               free_irq(dlp_drv.reset_irq, dev);
+
+       EPILOG();
+       return ret;
+}
+
+/****************************************************************************
+ *
+ * Control flow
+ *
+ *
+ ***************************************************************************/
+
+/*
+ *
+ */
+static void dlp_ctrl_save_rx_callbacks(struct dlp_ctrl_context *ctrl_ctx)
+{
+       ctrl_ctx->start_rx_cb = dlp_drv.client->hsi_start_rx;
+       ctrl_ctx->stop_rx_cb = dlp_drv.client->hsi_stop_rx;
+
+       dlp_drv.client->hsi_start_rx = NULL;
+       dlp_drv.client->hsi_stop_rx = NULL;
+}
+
+/*
+ *
+ */
+static void dlp_ctrl_restore_rx_callbacks(struct dlp_ctrl_context *ctrl_ctx)
+{
+       dlp_drv.client->hsi_start_rx = ctrl_ctx->start_rx_cb;
+       dlp_drv.client->hsi_stop_rx = ctrl_ctx->stop_rx_cb;
+
+       ctrl_ctx->start_rx_cb = NULL;
+       ctrl_ctx->stop_rx_cb = NULL;
+}
+
+/*
+ *
+ */
+static struct dlp_command *dlp_ctrl_cmd_alloc(struct dlp_channel *ch_ctx,
+                                             unsigned char id,
+                                             unsigned char param1,
+                                             unsigned char param2,
+                                             unsigned char param3)
+{
+       int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL;
+       struct dlp_command *dlp_cmd;
+
+       /* Allocate DLP command */
+       dlp_cmd = kmalloc(sizeof(struct dlp_command), flags);
+       if (!dlp_cmd) {
+               CRITICAL("Out of memory (dlp_cmd)");
+               goto out;
+       }
+
+       /* Set command params */
+       dlp_cmd->params.id = id;
+       dlp_cmd->params.channel = ch_ctx->hsi_channel;
+       dlp_cmd->params.data1 = param1;
+       dlp_cmd->params.data2 = param2;
+       dlp_cmd->params.data3 = param3;
+
+       dlp_cmd->channel = ch_ctx;
+
+out:
+       return dlp_cmd;
+}
+
+/*
+ *
+ */
+static inline void dlp_ctrl_cmd_free(struct dlp_command *dlp_cmd)
+{
+       if (dlp_cmd)
+               kfree(dlp_cmd);
+}
+
+/*
+ *
+ */
+static void dlp_ctrl_msg_destruct(struct hsi_msg *msg)
+{
+       struct dlp_command *dlp_cmd = msg->context;
+
+       PROLOG("hsi_ch:%d, cmd:%s, msg:0x%p",
+              dlp_cmd->params.channel,
+              DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), msg);
+
+       /* Delete the received msg */
+       dlp_pdu_free(msg, DLP_CTRL_PDU_SIZE);
+
+       /* Delete the command */
+       kfree(dlp_cmd);
+
+       EPILOG();
+}
+
+/**
+ * Synchronous TX message callback
+ *
+ * @msg: a reference to the HSI msg
+ *
+ * This function:
+ *             - Set the xfer status
+ *             - wakeup the caller
+ *             - delete the hsi message
+ */
+static void dlp_ctrl_complete_tx(struct hsi_msg *msg)
+{
+       struct dlp_command *dlp_cmd = msg->context;
+       struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX;
+
+       PROLOG("hsi_ch:%d, cmd:%s, msg:0x%p",
+              dlp_cmd->params.channel,
+              DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), msg);
+
+       dlp_cmd->status = (msg->status == HSI_STATUS_COMPLETED) ? 0 : -EIO;
+
+       /* Command done, notify the sender */
+       complete(&ctrl_ctx->tx_done);
+
+       /* Delete the received msg */
+       dlp_pdu_free(msg, DLP_CTRL_PDU_SIZE);
+
+       EPILOG();
+}
+
+/**
+ * Asynchronous TX message callback
+ *
+ * @msg: a reference to the HSI msg
+ *
+ * This function:
+ *             - delete the hsi message
+ */
+static void dlp_ctrl_complete_tx_async(struct hsi_msg *msg)
+{
+       struct dlp_command *dlp_cmd = msg->context;
+
+       PROLOG("cmd:%s, hsi_ch:%d, msg:0x%p",
+              DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id),
+              dlp_cmd->params.channel, msg);
+
+       /* Delete the received msg */
+       dlp_pdu_free(msg, DLP_CTRL_PDU_SIZE);
+
+       /* Delete the command */
+       kfree(dlp_cmd);
+
+       EPILOG();
+}
+
+/*
+ *
+ */
+static void dlp_ctrl_complete_rx(struct hsi_msg *msg)
+{
+       struct dlp_channel *ch_ctx;
+       struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX;
+       struct dlp_command_params params;
+       int hsi_channel, ret, response, msg_complete;
+
+       /* Copy the reponse */
+       memcpy(&params,
+              sg_virt(msg->sgt.sgl), sizeof(struct dlp_command_params));
+
+       response = -1;
+       msg_complete = (msg->status == HSI_STATUS_COMPLETED);
+
+       PROLOG("cmd:%s, hsi_ch:%d, params: 0x%02X%02X%02X, msg_status: %d",
+              DLP_CTRL_CMD_TO_STR(params.id),
+              params.channel,
+              params.data1, params.data2, params.data3, msg->status);
+
+       hsi_channel = params.channel;
+       if ((hsi_channel < 0) || (hsi_channel >= DLP_CHANNEL_COUNT)) {
+               CRITICAL("CREDITS: Invalid channel id (%d)", hsi_channel);
+               BUG_ON(1);      /* FIXME: We have BIGGGGGG pb (To be removed ?) */
+               goto out;
+       }
+
+       ch_ctx = dlp_drv.channels[hsi_channel];
+
+       switch (params.id) {
+       case DLP_CMD_CREDITS:
+               if (msg_complete) {
+                       unsigned long flags;
+
+                       /* Increase the CREDITS counter */
+                       spin_lock_irqsave(&ch_ctx->lock, flags);
+                       ch_ctx->credits += params.data3;
+                       ret = ch_ctx->credits;
+                       spin_unlock_irqrestore(&ch_ctx->lock, flags);
+
+                       /* Credits available ==> Notify the channel */
+                       if (ch_ctx->credits_available_cb) {
+                               ch_ctx->credits_available_cb(ch_ctx);
+                       }
+
+                       PTRACE_NO_FUNC("New CREDITS value: %d (+%d)\n", ret,
+                                      params.data3);
+                       response = DLP_CMD_ACK;
+               }
+               break;
+
+       case DLP_CMD_OPEN_CONN:
+               if (msg_complete) {
+                       ret = ((params.data2 << 8) | params.data1);
+                       response = DLP_CMD_ACK;
+
+                       /* Check the requested PDU size */
+                       if (ch_ctx->pdu_size != ret) {
+                               CRITICAL
+                                   ("Unexpected PDU size: %d => Expected: %d (ch: %d)",
+                                    ret, ch_ctx->pdu_size,
+                                    ch_ctx->hsi_channel);
+
+                               response = DLP_CMD_NACK;
+                       }
+               }
+               break;
+
+       default:
+               /* Save the modem response */
+               ctrl_ctx->response.status = (msg_complete ? 0 : -EIO);
+
+               memcpy(&ctrl_ctx->response.params,
+                      &params, sizeof(struct dlp_command_params));
+
+               /* Command done, notify the sender */
+               complete(&ctrl_ctx->rx_done);
+               break;
+       }
+
+       /* Send command response */
+       if (response != -1) {
+               struct dlp_command *dlp_cmd;
+               struct hsi_msg *tx_msg = NULL;
+
+               /* Allocate the DLP command */
+               dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx,
+                                            response,
+                                            params.data1,
+                                            params.data2, params.data3);
+               if (!dlp_cmd) {
+                       CRITICAL("Out of memory (dlp_cmd)");
+                       goto push_rx;
+               }
+
+               /* Allocate a new TX msg */
+               tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL,
+                                      HSI_MSG_WRITE,
+                                      DLP_CTRL_PDU_SIZE,
+                                      1,
+                                      dlp_cmd,
+                                      dlp_ctrl_complete_tx_async,
+                                      dlp_ctrl_msg_destruct);
+
+               if (!tx_msg) {
+                       CRITICAL("dlp_pdu_alloc(TX) failed");
+
+                       /* Delete the command */
+                       kfree(dlp_cmd);
+
+                       goto push_rx;
+               }
+
+               /* Copy the command data */
+               memcpy(sg_virt(tx_msg->sgt.sgl),
+                      &dlp_cmd->params, sizeof(struct dlp_command_params));
+
+               /* Send the TX HSI msg */
+               ret = hsi_async(tx_msg->cl, tx_msg);
+               if (ret) {
+                       CRITICAL("hsi_async(TX) failed ! (%s, ret:%d)",
+                                DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), ret);
+
+                       /* Free the TX msg */
+                       dlp_pdu_free(tx_msg, DLP_CTRL_PDU_SIZE);
+
+                       /* Delete the command */
+                       kfree(dlp_cmd);
+               }
+       }
+
+push_rx:
+       /* Push the RX msg again for futur response */
+       ret = hsi_async(msg->cl, msg);
+       if (ret) {
+               CRITICAL("hsi_async() failed, ret:%d", ret);
+
+               /* We have A BIG PROBLEM if the RX msg cant be  */
+               /* pushed again in the controller ==>                   */
+               /* No response could be received (FIFO empty)   */
+
+               /* Delete the received msg */
+               dlp_pdu_free(msg, DLP_CTRL_PDU_SIZE);
+       }
+
+out:
+       EPILOG();
+}
+
+/**
+ * Send an HSI msg AND wait for the status
+ *
+ * @ch_ctx: a reference to the channel context to use
+ * @id: the DLP command id
+ * @response_id: the expected response id
+ * @param1: the DLP command params
+ * @param2: the DLP command params
+ * @param3: the DLP command params
+ *
+ * This function blocks the caller until a response is received
+ * or the timeout expires
+ */
+static int dlp_ctrl_cmd_send(struct dlp_channel *ch_ctx,
+                            unsigned char id,
+                            unsigned char response_id,
+                            unsigned char param1,
+                            unsigned char param2, unsigned char param3)
+{
+       int ret = 0;
+       struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX;
+       struct dlp_command *dlp_cmd;
+       struct hsi_msg *tx_msg = NULL;
+
+       PROLOG("cmd:%s, hsi_ch:%d",
+              DLP_CTRL_CMD_TO_STR(id), ch_ctx->hsi_channel);
+
+       /* Backup RX callback */
+       dlp_ctrl_save_rx_callbacks(ctrl_ctx);
+
+       /* Allocate the DLP command */
+       dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, id, param1, param2, param3);
+       if (!dlp_cmd) {
+               CRITICAL("Out of memory (dlp_cmd)");
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /* Allocate a new TX msg */
+       tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL,
+                              HSI_MSG_WRITE,
+                              DLP_CTRL_PDU_SIZE,
+                              1,
+                              dlp_cmd,
+                              dlp_ctrl_complete_tx, dlp_ctrl_msg_destruct);
+
+       if (!tx_msg) {
+               CRITICAL("dlp_pdu_alloc(TX) failed");
+               ret = -ENOMEM;
+               goto free_cmd;
+       }
+
+       /* Copy the command data */
+       memcpy(sg_virt(tx_msg->sgt.sgl),
+              &dlp_cmd->params, sizeof(struct dlp_command_params));
+
+       /* Send the TX HSI msg */
+       ret = hsi_async(tx_msg->cl, tx_msg);
+       if (ret) {
+               CRITICAL("hsi_async(TX) failed ! (%s, ret:%d)",
+                        DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), ret);
+
+               goto free_tx;
+       }
+
+       /* Wait for TX msg to be sent */
+       ret = wait_for_completion_timeout(&ctrl_ctx->tx_done,
+                                         msecs_to_jiffies(DLP_CMD_TX_TIMOUT));
+       if (ret == 0) {
+               CRITICAL("TX Timeout => %s, hsi_ch: %d",
+                        DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id),
+                        dlp_cmd->params.channel);
+
+               ret = -EIO;
+               goto out;
+       }
+
+       /* TX msg sent, check the status */
+       if (dlp_cmd->status) {
+               CRITICAL("Failed to send  %s",
+                        DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id));
+
+               ret = -EIO;
+               goto out;
+       }
+
+       /* TX OK, Wait for the response */
+       ret = wait_for_completion_timeout(&ctrl_ctx->rx_done,
+                                         msecs_to_jiffies(DLP_CMD_RX_TIMOUT));
+       if (ret == 0) {
+               CRITICAL("RX Timeout => %s, hsi_ch: %d",
+                        DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id),
+                        dlp_cmd->params.channel);
+
+               ret = -EIO;
+               goto out;
+       }
+
+       /* Check the response */
+       ret = 0;
+       if ((ctrl_ctx->response.params.id != response_id) ||
+           (ctrl_ctx->response.params.data1 != param1) ||
+           (ctrl_ctx->response.params.data2 != param2) ||
+           (ctrl_ctx->response.params.data3 != param3)) {
+
+               CRITICAL("Unexpected response received: 0x%x%x [%02X%02X%02X]"
+                        " => expected:  0x%x%x [%02X%02X%02X]",
+                        ctrl_ctx->response.params.id,
+                        ctrl_ctx->response.params.channel,
+                        ctrl_ctx->response.params.data1,
+                        ctrl_ctx->response.params.data2,
+                        ctrl_ctx->response.params.data3,
+                        response_id,
+                        ch_ctx->hsi_channel, param1, param2, param3);
+
+               ret = -EIO;
+       }
+
+       /* Restore RX callback */
+       dlp_ctrl_restore_rx_callbacks(ctrl_ctx);
+
+       /* Everything is OK */
+       EPILOG("%d", ret);
+       return ret;
+
+free_tx:
+       /* Free the TX msg */
+       dlp_pdu_free(tx_msg, DLP_CTRL_PDU_SIZE);
+
+free_cmd:
+       /* Free the DLP command */
+       dlp_ctrl_cmd_free(dlp_cmd);
+
+out:
+       /* Restore RX callback */
+       dlp_ctrl_restore_rx_callbacks(ctrl_ctx);
+
+       EPILOG("%d", ret);
+       return ret;
+}
+
+/**
+ * Push RX pdu for any modem command
+ *
+ */
+static int dlp_ctrl_push_rx_pdu(struct dlp_channel *ch_ctx)
+{
+       int ret;
+       struct hsi_msg *rx_msg;
+       struct dlp_command *dlp_cmd;
+
+       PROLOG();
+
+       /* Allocate the DLP command */
+       dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, DLP_CMD_NOP, 0, 0, 0);
+       if (!dlp_cmd) {
+               CRITICAL("Out of memory (dlp_cmd)");
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /* Allocate a new RX msg */
+       rx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL,
+                              HSI_MSG_READ,
+                              DLP_CTRL_PDU_SIZE,
+                              1,
+                              dlp_cmd,
+                              dlp_ctrl_complete_rx, dlp_ctrl_msg_destruct);
+
+       if (!rx_msg) {
+               CRITICAL("dlp_pdu_alloc() failed");
+               ret = -ENOMEM;
+               goto free_cmd;
+       }
+
+       /* Send the RX HSI msg */
+       ret = hsi_async(rx_msg->cl, rx_msg);
+       if (ret) {
+               CRITICAL("hsi_async() failed, ret:%d", ret);
+               ret = -EIO;
+               goto free_msg;
+       }
+
+       EPILOG();
+       return 0;
+
+free_msg:
+       /* Free the msg */
+       dlp_pdu_free(rx_msg, DLP_CTRL_PDU_SIZE);
+
+free_cmd:
+       /* Delete the command */
+       kfree(dlp_cmd);
+
+out:
+       EPILOG();
+       return ret;
+}
+
+static inline void dlp_ctrl_set_modem_readiness(unsigned int value)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&dlp_drv.lock, flags);
+       dlp_drv.modem_ready = value;
+       spin_unlock_irqrestore(&dlp_drv.lock, flags);
+}
+
+
+/*
+* @brief
+*
+* @param work
+*/
+static void dlp_ctrl_ipc_readiness(struct work_struct *work)
+{
+       struct dlp_channel *ch_ctx = dlp_drv.channels[DLP_CHANNEL_CTRL];
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+       int ret;
+
+       PROLOG();
+
+       /* Set the modem readiness state */
+       dlp_ctrl_set_modem_readiness(0);
+
+       /* Wait for RESET_OUT */
+       wait_for_completion(&ctrl_ctx->reset_done);
+
+       /* Send ECHO continuously until the modem become ready */
+       do {
+               ret = 0;
+               // ret = dlp_ctrl_send_echo_cmd(ctrl_ctx);
+               if (ret == 0) {
+                       /* Set the modem state */
+                       dlp_ctrl_set_modem_readiness(1);
+               }
+       }
+       while (ret);
+
+       EPILOG();
+}
+
+
+/****************************************************************************
+ *
+ * Exported functions
+ *
+ ***************************************************************************/
+
+/**
+ * dlp_ctrl_modem_reset - activity required to bring up modem
+ * @ch_ctx: a reference to related channel context
+ *
+ * Toggle gpios required to bring up modem power and start modem.
+ * This can be called after the modem has been started to reset it.
+ */
+int dlp_ctrl_modem_reset(struct dlp_channel *ch_ctx)
+{
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+       int ret = 0;
+
+       PROLOG();
+
+       /* AP requested reset => just ignore */
+       dlp_drv.reset_ignore = 1;
+
+       gpio_set_value(ctrl_ctx->gpio_mdm_rst_bbn, 0);
+       mdelay(DLP_POWER_ON_INTERLINE_DELAY);
+
+       gpio_set_value(ctrl_ctx->gpio_mdm_rst_bbn, 1);
+       msleep(DLP_POWER_ON_POST_DELAY);
+
+       EPILOG();
+       return ret;
+}
+
+/*
+* @brief
+*
+* @param ch_ctx
+*
+* @return
+*/
+static int dlp_ctrl_modem_power(struct dlp_channel *ch_ctx)
+{
+       int ret = 0;
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       /* AP requested reset => just ignore */
+       dlp_drv.reset_ignore = 1;
+
+       gpio_set_value(ctrl_ctx->gpio_mdm_pwr_on, 1);
+       msleep(DLP_POWER_ON_INTERLINE_DELAY);
+
+       gpio_set_value(ctrl_ctx->gpio_mdm_pwr_on, 0);
+       msleep(DLP_POWER_ON_POST_DELAY);
+
+       EPILOG();
+       return ret;
+}
+
+
+/*
+* @brief
+*
+* @return
+*/
+inline unsigned int dlp_ctrl_modem_is_ready(void)
+{
+       unsigned long flags;
+       unsigned int value;
+
+       spin_lock_irqsave(&dlp_drv.lock, flags);
+       value = dlp_drv.modem_ready;
+       spin_unlock_irqrestore(&dlp_drv.lock, flags);
+
+       return value;
+}
+
+/*
+* @brief
+*
+* @param index
+* @param dev
+*
+* @return
+*/
+struct dlp_channel *dlp_ctrl_ctx_create(unsigned int index, struct device *dev)
+{
+       int ret, i;
+       struct hsi_client *client = to_hsi_client(dev);
+       struct hsi_mid_platform_data *pd = client->device.platform_data;
+       struct dlp_channel *ch_ctx;
+       struct dlp_ctrl_context *ctrl_ctx;
+
+       PROLOG();
+
+       ch_ctx = kzalloc(sizeof(struct dlp_channel), GFP_KERNEL);
+       if (!ch_ctx) {
+               CRITICAL("Unable to allocate memory (ch_ctx)");
+               goto out;
+       }
+
+       /* Allocate the context private data */
+       ctrl_ctx = kzalloc(sizeof(struct dlp_ctrl_context), GFP_KERNEL);
+       if (!ctrl_ctx) {
+               CRITICAL("Unable to allocate memory (ctrl_ctx)");
+               goto free_ch;
+       }
+
+       /* Create a workqueue to check the modem readiness */
+       ctrl_ctx->readiness_wq = create_singlethread_workqueue(DRVNAME "-mdmreadiness");
+       if (!ctrl_ctx->readiness_wq) {
+               CRITICAL("Unable to create modem readiness workqueue");
+               goto free_ctx;
+       }
+
+       /* Save params */
+       ch_ctx->ch_data = ctrl_ctx;
+       ch_ctx->hsi_channel = index;
+       ch_ctx->pdu_size = DLP_CTRL_PDU_SIZE;
+       ch_ctx->rx.config = client->rx_cfg;
+       ch_ctx->tx.config = client->tx_cfg;
+
+       spin_lock_init(&ch_ctx->lock);
+       init_completion(&ctrl_ctx->reset_done);
+       INIT_WORK(&ctrl_ctx->readiness_work, dlp_ctrl_ipc_readiness);
+
+       /* Configure GPIOs */
+       ctrl_ctx->gpio_mdm_rst_out = pd->gpio_mdm_rst_out;
+       ctrl_ctx->gpio_mdm_pwr_on  = pd->gpio_mdm_pwr_on;
+       ctrl_ctx->gpio_mdm_rst_bbn = pd->gpio_mdm_rst_bbn;
+       ctrl_ctx->gpio_fcdp_rb     = pd->gpio_fcdp_rb;
+
+       ret = dlp_ctrl_configure_gpios(ch_ctx, dev);
+       if (ret) {
+               goto free_ctx;
+       }
+
+       /* Power on the modem */
+       ret = dlp_ctrl_modem_power(ch_ctx);
+       if (ret) {
+               ret = -ENODEV;
+               dlp_ctrl_free_gpios(ch_ctx, dev);
+               goto free_ctx;
+       }
+
+       /* Reset the modem */
+       ret = dlp_ctrl_modem_reset(ch_ctx);
+       if (ret) {
+               ret = -ENODEV;
+               dlp_ctrl_free_gpios(ch_ctx, dev);
+               goto free_ctx;
+       }
+
+       /* Init the RX/TX completion */
+       init_completion(&ctrl_ctx->rx_done);
+       init_completion(&ctrl_ctx->tx_done);
+
+       /* Set ch_ctx, not yet done in the probe */
+       dlp_drv.channels[DLP_CHANNEL_CTRL] = ch_ctx;
+
+       /* Push RX pdus for CREDTIS/OPEN_CONN commands */
+       for (i = 0; i < DLP_CHANNEL_COUNT; i++)
+               dlp_ctrl_push_rx_pdu(ch_ctx);
+
+       /* Start the modem readiness worqeue */
+       queue_work(ctrl_ctx->readiness_wq, &ctrl_ctx->readiness_work);
+
+       EPILOG();
+       return ch_ctx;
+
+free_ctx:
+       kfree(ctrl_ctx);
+
+free_ch:
+       kfree(ch_ctx);
+
+out:
+       EPILOG("Failed");
+       return NULL;
+}
+
+/*
+* @brief
+*
+* @param ch_ctx
+*
+* @return
+*/
+int dlp_ctrl_ctx_delete(struct dlp_channel *ch_ctx)
+{
+       struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
+       int ret = 0;
+
+       PROLOG();
+
+       /* Delete the modem readiness worqueue */
+       destroy_workqueue(ctrl_ctx->readiness_wq);
+
+       /* Free GPIOs */
+
+       /* Free the CTRL context */
+       kfree(ctrl_ctx);
+
+       /* Free the ch_ctx */
+       kfree(ch_ctx);
+
+       EPILOG();
+       return ret;
+}
+
+/*
+ *
+ */
+int dlp_ctrl_send_echo_cmd(struct dlp_channel *ch_ctx)
+{
+       int ret = 0;
+       unsigned char param1 = PARAM1(DLP_ECHO_CMD_CHECKSUM);
+       unsigned char param2 = PARAM2(DLP_ECHO_CMD_CHECKSUM);
+       unsigned char param3 = PARAM3(DLP_ECHO_CMD_CHECKSUM);
+
+       PROLOG();
+
+       /* Send the command */
+       ret = dlp_ctrl_cmd_send(ch_ctx,
+                               DLP_CMD_ECHO,
+                               DLP_CMD_ECHO, param1, param2, param3);
+       EPILOG();
+       return ret;
+}
+
+/*
+ *
+ */
+int dlp_ctrl_send_open_conn_cmd(struct dlp_channel *ch_ctx)
+{
+       int ret = 0;
+       unsigned char param1 = PARAM1(ch_ctx->pdu_size);
+       unsigned char param2 = PARAM2(ch_ctx->pdu_size);
+
+       PROLOG();
+
+       /* Send the command */
+       ret = dlp_ctrl_cmd_send(ch_ctx,
+                               DLP_CMD_OPEN_CONN,
+                               DLP_CMD_ACK, param1, param2, 0);
+
+       EPILOG();
+       return ret;
+}
+
+/*
+ *
+ */
+int dlp_ctrl_send_cancel_conn_cmd(struct dlp_channel *ch_ctx)
+{
+       int ret = 0;
+       // FIXME: both or just xmit/receive ?
+       //unsigned char param3 = PARAM1(DLP_DIR_TRANSMIT_AND_RECEIVE);
+       unsigned char param3;
+
+       PROLOG();
+
+       // FIXME: TX & RX dont work ==> the modem response is TX only
+       param3 = PARAM1(DLP_DIR_TRANSMIT);
+
+       /* Send the command */
+       ret = dlp_ctrl_cmd_send(ch_ctx,
+                               DLP_CMD_CANCEL_CONN, DLP_CMD_ACK, 0, 0, param3);
+       EPILOG();
+       return ret;
+}
+
+
+/*
+ * Modem reset sysfs entries
+ */
+static int set_modem_reset(const char *val, struct kernel_param *kp)
+{
+       unsigned long reset_request;
+
+       if (strict_strtoul(val, 16, &reset_request) < 0)
+               return -EINVAL;
+
+       if (reset_request) {
+               struct dlp_channel *ch_ctx = dlp_drv.channels[DLP_CHANNEL_CTRL];
+
+               dlp_ctrl_modem_reset(ch_ctx);
+       }
+
+       return 0;
+}
+
+static int get_modem_reset(const char *val, struct kernel_param *kp)
+{
+       unsigned long reset_ongoing = 0;
+
+       return sprintf(val, "%d", reset_ongoing);
+}
+
+/*
+ * Modem hangup sysfs entries
+ */
+static int set_hangup_reasons(const char *val, struct kernel_param *kp)
+{
+       unsigned long reset_request;
+
+       if (strict_strtoul(val, 16, &reset_request) < 0)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int get_hangup_reasons(const char *val, struct kernel_param *kp)
+{
+       unsigned long hangup_reasons = 0;
+
+       return sprintf(val, "%d", hangup_reasons);
+}
+
+
+module_param_call(reset_modem, set_modem_reset, get_modem_reset, NULL, 0644);
+module_param_call(hangup_reasons, set_hangup_reasons, get_hangup_reasons,
+                 NULL, 0644);
+
diff --git a/drivers/hsi/clients/dlp_debug.c b/drivers/hsi/clients/dlp_debug.c
new file mode 100644 (file)
index 0000000..bc2f7ba
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * dlp_debug.c
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI
+ * protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact:  Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+
+#include "../dlp_debug.h"
+#include "dlp_main.h"
+
+#define DEBUG_TAG 0x1
+#define DEBUG_VAR dlp_drv.debug
+
+/*
+ *
+ *
+ */
+void dlp_dbg_dump_data_as_byte(unsigned char *data,
+                              int nb_bytes,
+                              int bytes_per_line, int should_dump, int is_rx)
+{
+       int i;
+
+       if (should_dump)
+               printk("-----\n");
+       else if (is_rx)
+               PRINT_RX("-----\n");
+       else
+               PRINT_TX("-----\n");
+
+       for (i = 0; i < nb_bytes; i++) {
+               if (should_dump)
+                       printk("%02X ", data[i]);
+               else if (is_rx)
+                       PRINT_RX("%02X ", data[i]);
+               else
+                       PRINT_TX("%02X ", data[i]);
+
+               /* Print requested items/line */
+               if (((i + 1) & (bytes_per_line - 1)) == 0) {
+                       if (should_dump)
+                               printk("\n");
+                       else if (is_rx)
+                               PRINT_RX("\n");
+                       else
+                               PRINT_TX("\n");
+               }
+       }
+
+       /* Previous line was less than "bytes_per_line" */
+       if ((i & (bytes_per_line - 1)) != 0) {
+               if (should_dump)
+                       printk("\n");
+               else if (is_rx)
+                       PRINT_RX("\n");
+               else
+                       PRINT_TX("\n");
+       }
+
+       if (should_dump)
+               printk("-----\n");
+       else if (is_rx)
+               PRINT_RX("-----\n");
+       else
+               PRINT_TX("-----\n");
+}
+
+void dlp_dbg_dump_data_as_word(unsigned int *data,
+                              int nb_words,
+                              int words_per_line, int should_dump, int is_rx)
+{
+       int i;
+
+       if (should_dump)
+               printk("-----\n");
+       else if (is_rx)
+               PRINT_RX("-----\n");
+       else
+               PRINT_TX("-----\n");
+
+       for (i = 0; i < nb_words; i++) {
+               if (should_dump)
+                       printk("%08X ", data[i]);
+               else if (is_rx)
+                       PRINT_RX("%08X ", data[i]);
+               else
+                       PRINT_TX("%08X ", data[i]);
+
+               /* Print requested items/line */
+               if (((i + 1) & (words_per_line - 1)) == 0) {
+                       if (should_dump)
+                               printk("\n");
+                       else if (is_rx)
+                               PRINT_RX("\n");
+                       else
+                               PRINT_TX("\n");
+               }
+       }
+
+       /* Previous line was less than "words_per_line" */
+       if ((i & (words_per_line - 1)) != 0) {
+               if (should_dump)
+                       printk("\n");
+               else if (is_rx)
+                       PRINT_RX("\n");
+               else
+                       PRINT_TX("\n");
+       }
+
+       if (should_dump)
+               printk("-----\n");
+       else if (is_rx)
+               PRINT_RX("-----\n");
+       else
+               PRINT_TX("-----\n");
+}
+
+/*
+* @brief
+*
+* @param pdu : The pdu to dump
+* @param items_per_line : Nb of items (bytes/words) to display/line
+* @param items_to_dump  : Nb of items (bytes/words) to dump
+* @param dump_as_words  : Dump data as sequence of WORDs instead of BYEs
+*/
+void dlp_dbg_dump_pdu(struct hsi_msg *pdu,
+                     int items_per_line, int items_to_dump, int dump_as_words)
+{
+       int i;
+       struct scatterlist *sg = pdu->sgt.sgl;
+       unsigned char *data;
+       int len, dumped, is_corrupted, is_rx;
+
+       PROLOG("0x%p [nents: %d, hsi_ch: %d, status: %d, "
+              "actual_len: %d, break_frame: %d",
+              pdu,
+              pdu->sgt.nents,
+              pdu->channel, pdu->status, pdu->actual_len, pdu->break_frame);
+
+       dumped = 0;
+       is_corrupted = 0;
+       is_rx = (pdu->ttype == HSI_MSG_READ);
+
+       if (is_rx)
+               PRINT_RX("RX PDU (0x%p): \n", pdu);
+       else
+               PRINT_TX("TX PDU (0x%p): \n", pdu);
+
+       /* Check the PDU signature */
+       data = sg_virt(sg);
+       if (!dlp_pdu_header_valid(pdu)) {
+               is_corrupted = 1;
+       }
+
+       for (i = 0; i < pdu->sgt.nents; i++) {
+               data = sg_virt(sg);
+               len = sg->length;
+
+               /* All requested items are dumped ? */
+               if (len < items_to_dump) {
+                       if (dumped >= items_to_dump) {
+                               break;
+                       } else {
+                               if ((len + dumped) > items_to_dump) {
+                                       len = items_to_dump - dumped;
+                               }
+                       }
+               } else {
+                       len = items_to_dump - dumped;
+               }
+
+               /* Dump current SGL entry */
+               if (dump_as_words)
+                       dlp_dbg_dump_data_as_word((u32 *) data,
+                                                 len,
+                                                 items_per_line,
+                                                 is_corrupted, is_rx);
+               else
+                       dlp_dbg_dump_data_as_byte(data,
+                                                 len,
+                                                 items_per_line,
+                                                 is_corrupted, is_rx);
+
+               /* Move to the next SGL entry */
+               sg = sg_next(sg);
+               dumped += len;
+       }
+
+       EPILOG();
+}
diff --git a/drivers/hsi/clients/dlp_main.c b/drivers/hsi/clients/dlp_main.c
new file mode 100644 (file)
index 0000000..deff70d
--- /dev/null
@@ -0,0 +1,1817 @@
+/*
+ * dlp_main.c
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (DLP)). This driver is implementing a 5-channel HSI
+ * protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact: Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/hsi/hsi.h>
+#include <linux/dma-mapping.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/hsi/hsi.h>
+#include <linux/proc_fs.h>
+
+#include "dlp_main.h"
+
+#define DEBUG_TAG 0x1
+#define DEBUG_VAR dlp_drv.debug
+
+/* Forward declarations */
+static void dlp_pdu_destructor(struct hsi_msg *pdu);
+
+static inline void dlp_ctx_update_state(struct dlp_xfer_ctx *xfer_ctx);
+
+static inline void dlp_fifo_recycled_push(struct dlp_xfer_ctx *xfer_ctx,
+                                         struct hsi_msg *pdu);
+
+/*
+ * Static protocol driver global variables
+ */
+struct dlp_driver dlp_drv;
+
+/* Module debug parameter */
+module_param_named(debug, dlp_drv.debug, int, S_IRUGO | S_IWUSR);
+
+/*
+ *
+ *
+ */
+static int dlp_proc_show(struct seq_file *m, void *v)
+{
+       int i;
+       unsigned long flags;
+       struct dlp_channel *ch_ctx;
+
+       for (i = 0; i < DLP_CHANNEL_COUNT; i++) {
+               ch_ctx = dlp_drv.channels[i];
+
+               seq_printf(m, "\nChannel: %d\n", ch_ctx->hsi_channel);
+               seq_printf(m, "-------------\n");
+               seq_printf(m, " pdu_size  : %d\n", ch_ctx->pdu_size);
+               seq_printf(m, " credits   : %d\n", ch_ctx->credits);
+
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               seq_printf(m, " RX ctx:\n");
+               seq_printf(m, "   wait_max: %d\n", ch_ctx->rx.wait_max);
+               seq_printf(m, "   ctrl_max: %d\n", ch_ctx->rx.ctrl_max);
+               seq_printf(m, "   all_len : %d\n", ch_ctx->rx.all_len);
+               seq_printf(m, "   ctrl_len: %d\n", ch_ctx->rx.ctrl_len);
+               seq_printf(m, "   wait_len: %d\n", ch_ctx->rx.wait_len);
+               seq_printf(m, "   seq_num : %d\n", ch_ctx->rx.seq_num);
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               seq_printf(m, " TX ctx:\n");
+               seq_printf(m, "   wait_max: %d\n", ch_ctx->tx.wait_max);
+               seq_printf(m, "   ctrl_max: %d\n", ch_ctx->tx.ctrl_max);
+               seq_printf(m, "   all_len : %d\n", ch_ctx->tx.all_len);
+               seq_printf(m, "   ctrl_len: %d\n", ch_ctx->tx.ctrl_len);
+               seq_printf(m, "   wait_len: %d\n", ch_ctx->tx.wait_len);
+               seq_printf(m, "   seq_num : %d\n", ch_ctx->tx.seq_num);
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+       }
+
+       return 0;
+}
+
+static int dlp_proc_seq_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, dlp_proc_show, PDE(inode)->data);
+}
+
+static const struct file_operations dlp_proc_ops = {
+       .open = dlp_proc_seq_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = seq_release
+};
+
+/**
+ * from_usecs - translating usecs to jiffies
+ * @delay: the dealy in usecs
+ *
+ * Returns the delay rounded up to the next jiffy and prevent it to be set
+ * to zero, as all delayed function calls shall occur to the next jiffy (at
+ * least).
+ */
+inline unsigned long from_usecs(const unsigned long delay)
+{
+       unsigned long j = usecs_to_jiffies(delay);
+
+       if (j == 0)
+               j = 1;
+
+       return j;
+}
+
+/****************************************************************************
+ *
+ * PDU creation and deletion
+ *
+ ***************************************************************************/
+
+/*
+ *
+ *
+ */
+void dlp_pdu_dump(struct hsi_msg *pdu, int as_string)
+{
+       u32 *addr = sg_virt(pdu->sgt.sgl);
+
+       PROLOG("0x%p [ttype: %s, nents: %d, status: %d, "
+              "channel: %d, actual_len: %d, break_frame: %d]",
+              pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX", pdu->sgt.nents,
+              pdu->status, pdu->channel, pdu->actual_len, pdu->break_frame);
+
+       /* Display as HEX */
+       if (pdu->sgt.sgl->length == DLP_CTRL_PDU_SIZE) {
+               PDEBUG("\n\t data[1-4]   : %08X", *(addr));
+       } else {
+               PDEBUG("\n\t data[1-4]   : %08X  %08X  %08X  %08X\n"
+                      "\t data[5-8]   : %08X  %08X  %08X  %08X\n"
+                      "\t data[9-12]  : %08X  %08X  %08X  %08X\n"
+                      "\t data[13-16] : %08X  %08X  %08X  %08X\n",
+                      *(addr), *(addr + 1), *(addr + 2), *(addr + 3),
+                      *(addr + 4), *(addr + 5), *(addr + 6), *(addr + 7),
+                      *(addr + 8), *(addr + 9), *(addr + 10), *(addr + 11),
+                      *(addr + 12), *(addr + 13), *(addr + 14), *(addr + 15));
+
+               /* Display as STRING */
+               if (as_string) {
+                       unsigned int len;
+                       unsigned char *str;
+
+                       len = *(addr + 2);
+                       addr += 4;
+                       str = (char *)addr;
+                       str[len] = '\0';
+
+                       PDEBUG("\n\t len: %d, data: \"%s\"", len, str);
+               }
+       }
+
+       EPILOG();
+}
+
+/**
+ * dlp_buffer_alloc - helper function to allocate memory
+ * @buff_size: buffer size
+ * @dma_addr: buffer dma address
+ *
+ * Returns
+ *     - a reference to the newly allocated buffer
+ *     - NULL if an error occured.
+ */
+void *dlp_buffer_alloc(unsigned int buff_size, dma_addr_t * dma_addr)
+{
+       void *buff;
+       int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL;
+
+       if (dlp_drv.is_dma_capable) {
+               buff = dma_alloc_coherent(dlp_drv.controller,
+                                         buff_size, dma_addr, flags);
+       } else {
+               if ((buff_size >= PAGE_SIZE)
+                   && ((buff_size & (PAGE_SIZE - 1)) == 0)) {
+                       buff =
+                           (void *)__get_free_pages(flags,
+                                                    get_order(buff_size));
+               } else {
+                       buff = kmalloc(buff_size, flags);
+               }
+       }
+
+       return buff;
+}
+
+/**
+ * dlp_buffer_free - Free memory alloccted by dlp_buffer_alloc
+ * @buff: buffer address
+ * @dma_addr: buffer dma address
+ * @buff_size: buffer size
+ *
+ */
+void dlp_buffer_free(void *buff, dma_addr_t dma_addr, unsigned int buff_size)
+{
+       if (dlp_drv.is_dma_capable) {
+               dma_free_coherent(dlp_drv.controller,
+                                 buff_size, buff, dma_addr);
+       } else {
+               if ((buff_size >= PAGE_SIZE)
+                   && ((buff_size & (PAGE_SIZE - 1)) == 0)) {
+                       free_pages((unsigned int)buff, get_order(buff_size));
+               } else {
+                       kfree(buff);
+               }
+       }
+}
+
+/**
+ * dlp_pdu_alloc - helper function to allocate and initialise a new pdu
+ * @hsi_channel: the HSI channel number
+ * @ttype: pdu transfer type READ/WRITE
+ * @buffer_size: pdu buffer size
+ * @nb_entries: number of entries in the Scatter Gather table
+ * @user_data: pdu context data (user data)
+ * @complete_cb: xfer complete callback
+ * @destruct_cb: pdu destruct callback
+ *
+ * Returns a reference to the newly created pdu or NULL if an error occured.
+ */
+struct hsi_msg *dlp_pdu_alloc(unsigned int hsi_channel,
+                             int ttype,
+                             int buffer_size,
+                             int nb_entries,
+                             void *user_data,
+                             xfer_complete_cb complete_cb,
+                             xfer_complete_cb destruct_cb)
+{
+       struct hsi_msg *new;
+       void *buffer;
+       int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL;
+
+       PROLOG("%d, %s, size:%d",
+              hsi_channel,
+              (ttype == HSI_MSG_WRITE) ? "TX" : "RX", buffer_size);
+
+       new = hsi_alloc_msg(nb_entries, flags);
+       if (!new) {
+               CRITICAL("No more memory to allocate hsi_msg struct");
+               goto out;
+       }
+
+       /* Allocate data buffer */
+       buffer = dlp_buffer_alloc(buffer_size, &sg_dma_address(new->sgt.sgl));
+       if (!buffer) {
+               CRITICAL("No more memory to allocate hsi_msg data buffer");
+               goto fail;
+       }
+
+       sg_set_buf(new->sgt.sgl, buffer, buffer_size);
+
+       new->cl = dlp_drv.client;
+       new->channel = hsi_channel;
+       new->ttype = ttype;
+       new->context = user_data;
+       new->complete = complete_cb;
+       new->destructor = destruct_cb;
+
+       EPILOG("0x%p", new);
+       return new;
+
+fail:
+       hsi_free_msg(new);
+
+out:
+       EPILOG("Failed");
+       return NULL;
+}
+
+/**
+ * dlp_pdu_free - helper function to delete and free an existing pdu
+ * @pdu: a reference to the pdu to delete
+ * @pdu_size: the real pdu size (allocated size)
+ *
+ * This function shall only be called by the pool of pdu management routines.
+ */
+void dlp_pdu_free(struct hsi_msg *pdu, unsigned int pdu_size)
+{
+       PROLOG("0x%p, %s, pdu_size: %d, sgl->length: %d",
+              pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX",
+              pdu_size, pdu->sgt.sgl->length);
+
+       /* Revert to the real allocated size */
+       pdu->sgt.sgl->length = pdu_size;
+
+       /* Free the data buffer */
+       dlp_buffer_free(sg_virt(pdu->sgt.sgl),
+                       sg_dma_address(pdu->sgt.sgl), pdu_size);
+
+       hsi_free_msg(pdu);
+
+       EPILOG();
+}
+
+/**
+ * dlp_pdu_delete - recycle or free a pdu
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ * @pdu: a reference to the pdu to delete
+ *
+ * This function is either recycling the pdu if there are not too many pdus
+ * in the system, otherwise destroy it and free its resource.
+ */
+void dlp_pdu_delete(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu)
+{
+       int full;
+
+       PROLOG("all_len: %d, wait_max: %d, ctrl_max: %d, pdu [0x%p, %s]",
+              xfer_ctx->all_len, xfer_ctx->wait_max, xfer_ctx->ctrl_max,
+              pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX");
+
+       full = (xfer_ctx->all_len > xfer_ctx->wait_max + xfer_ctx->ctrl_max);
+
+       if (full) {
+               dlp_pdu_free(pdu, xfer_ctx->channel->pdu_size);
+
+               xfer_ctx->all_len--;
+       } else {
+               pdu->status = HSI_STATUS_COMPLETED;
+               pdu->actual_len = 0;
+               pdu->break_frame = 0;
+
+               xfer_ctx->room += dlp_pdu_room_in(pdu);
+
+               /* Recycle the pdu */
+               dlp_fifo_recycled_push(xfer_ctx, pdu);
+       }
+
+       EPILOG("all_len: %d, wait_max: %d, ctrl_max: %d",
+              xfer_ctx->all_len, xfer_ctx->wait_max, xfer_ctx->ctrl_max);
+}
+
+/**
+ * dlp_pdu_recycle - pdu recycling helper function for the RX side
+ * @xfer_ctx: a reference to the RX context where recycled pdus FIFO sits
+ * @pdu: a reference to the pdu that shall be recycled
+ *
+ * This helper method is recycling the pdu and pushing a new pdu to the
+ * controller if there is room available in the controller FIFO, and finally
+ * updating the state of the RX state machine.
+ */
+void dlp_pdu_recycle(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu)
+{
+       unsigned long flags;
+       int have_space;
+
+       PROLOG("0x%p, ctrl_len: %d", pdu, xfer_ctx->ctrl_len);
+
+       /* Recycle or Free the pdu */
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       dlp_pdu_delete(xfer_ctx, pdu);
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       /* The CTRL still have space ? */
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       have_space = xfer_ctx->ctrl_len < xfer_ctx->ctrl_max;
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       if (have_space) {
+               struct hsi_msg *new;
+               int ret;
+
+               /* Get a recycled pdu */
+               new = dlp_fifo_recycled_pop(xfer_ctx);
+
+               /* Push the pdu */
+               ret = dlp_hsi_controller_push(xfer_ctx, new);
+               if (ret)
+                       dlp_fifo_recycled_push(xfer_ctx, new);
+       }
+
+       dlp_ctx_update_state(xfer_ctx);
+       EPILOG();
+}
+
+/**
+ * dlp_pdu_check_header - Check the DLP header (Signature)
+ * @pdu: a reference to the considered pdu
+ *
+ * Returns TRUE if the PDU signature is invalid
+ */
+inline int dlp_pdu_header_valid(struct hsi_msg *pdu)
+{
+       u32 *header = sg_virt(pdu->sgt.sgl);
+
+       return ((header[0] & 0xFFFF0000) == DLP_HEADER_SIGNATURE);
+}
+
+/**
+ * dlp_pdu_set_header - Write the DLP header (Signature+Sequence number)
+ * @pdu: a reference to the considered pdu
+ * @xfer_ctx: a reference to the xfer context
+ *
+ */
+static inline void dlp_pdu_set_header(struct dlp_xfer_ctx *xfer_ctx,
+                                     struct hsi_msg *pdu)
+{
+       u32 *header = sg_virt(pdu->sgt.sgl);
+
+       header[0] = (DLP_HEADER_SIGNATURE | (xfer_ctx->seq_num & 0xFFFF));
+}
+
+/**
+ * dlp_pdu_get_offset - Get the offset information from the eDLP header
+ * @pdu: a reference to the considered pdu
+ *
+ * Returns the offset information to encode in the header
+ */
+unsigned int dlp_pdu_get_offset(struct hsi_msg *pdu)
+{
+       u32 *header = (u32 *)(sg_virt(pdu->sgt.sgl));
+
+       return header[1];
+}
+
+/**
+ * dlp_pdu_get_length - Get the length information from the eDLP header
+ * @pdu: a reference to the considered pdu
+ *
+ * Returns the length information to encode in the header
+ */
+inline unsigned int dlp_pdu_get_length(struct hsi_msg *pdu)
+{
+       u32 *header = (u32 *)(sg_virt(pdu->sgt.sgl));
+
+       return (header[2] & 0x3FFFF);
+}
+
+
+/**
+ * dlp_pdu_update - initialise a pdu for entering the RX wait FIFO
+ * @ch_ctx: a reference to related channel context
+ * @pdu: a reference to the considered pdu
+ *
+ * This helper function is simply updating the scatterlist information.
+ */
+void dlp_pdu_update(struct dlp_channel *ch_ctx, struct hsi_msg *pdu)
+{
+       struct scatterlist *sg = pdu->sgt.sgl;
+
+       /* Use a non null pdu length when an error occur to forward it to
+        * the upper layers.
+        * Do not use the in-pdu length which can be broken */
+       if ((!pdu->break_frame) && (pdu->status == HSI_STATUS_COMPLETED)) {
+               pdu->actual_len = dlp_pdu_get_length(pdu);
+       } else {
+               CRITICAL
+                   ("Invalid RX pdu (0x%p) status [status: %d, break_frame: %d,"
+                    " actual_len: %d", pdu, pdu->status, pdu->break_frame,
+                    pdu->actual_len);
+
+               pdu->actual_len = 1;
+       }
+
+       /* If the decoded frame size is invalid, we have a big trouble */
+       if ((!pdu->actual_len) || (pdu->actual_len > ch_ctx->pdu_size)) {
+               CRITICAL("Invalid pdu (0x%p) size (0x%X Bytes)", pdu,
+                        pdu->actual_len);
+
+               pdu->status = HSI_STATUS_ERROR;
+               pdu->actual_len = 1;
+       }
+
+       sg->length = 0;
+}
+
+/**
+ * dlp_pdu_reset - revert a pdu to a working order
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ * @pdu: a reference to the considered pdu
+ * @length: the pdu length to set
+ *
+ * This helper function is simply updating the scatterlist information.
+ */
+inline void dlp_pdu_reset(struct dlp_xfer_ctx *xfer_ctx,
+                         struct hsi_msg *pdu, unsigned int length)
+{
+       struct scatterlist *sg = pdu->sgt.sgl;
+
+       PROLOG("0x%p", pdu);
+
+       sg->offset -= sg->length;
+       sg->length = length;
+       pdu->actual_len = 0;
+
+       EPILOG();
+};
+
+/**
+ * dlp_pdu_room_in - helper function for getting current room in the pdu
+ * @pdu: a reference to the considered pdu
+ *
+ * Returns the room in byte in the current pdu
+ */
+inline unsigned int dlp_pdu_room_in(struct hsi_msg *pdu)
+{
+       unsigned int used_len = pdu->actual_len + DLP_TTY_HEADER_LENGTH;
+
+       return (pdu->sgt.sgl->length - used_len);
+}
+
+/**
+ * dlp_pdu_destructor - delete or recycle an existing pdu
+ * @pdu: a reference to the pdu to delete
+ *
+ * This function shall only be called as an HSI destruct callback.
+ */
+static void dlp_pdu_destructor(struct hsi_msg *pdu)
+{
+       struct dlp_xfer_ctx *xfer_ctx = pdu->context;
+       unsigned long flags;
+
+       PROLOG("0x%p", pdu);
+
+       /* Decrease the CTRL fifo size */
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       dlp_hsi_controller_pop(xfer_ctx);
+
+       /* Recycle or Free the pdu */
+       dlp_pdu_delete(xfer_ctx, pdu);
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       if (xfer_ctx->ttype == HSI_MSG_READ)
+               dlp_ctx_update_state(xfer_ctx);
+       else if (dlp_ctx_is_empty(xfer_ctx))
+               wake_up(&xfer_ctx->channel->tx_empty_event);
+
+       EPILOG();
+}
+
+/****************************************************************************
+ *
+ * State handling helper
+ *
+ ***************************************************************************/
+
+/**
+ * _dlp_ctx_get_state - get the global state of a state machine
+ * @xfer_ctx: a reference to the state machine context
+ *
+ * Returns the current state of the requested TX or RX context.
+ */
+static inline __must_check
+    unsigned int _dlp_ctx_get_state(struct dlp_xfer_ctx *xfer_ctx)
+{
+       return (xfer_ctx->state & DLP_GLOBAL_STATE_MASK);
+}
+
+/**
+ * dlp_ctx_is_state - checks the global state of a state machine
+ * @xfer_ctx: a reference to the state machine context
+ * @state: the state to consider
+ *
+ * Returns a non-zero value if in the requested state.
+ */
+static inline __must_check int dlp_ctx_is_state(struct dlp_xfer_ctx *xfer_ctx,
+                                               unsigned int state)
+{
+       int is_state;
+       unsigned long flags;
+#ifdef DEBUG
+       BUG_ON(state & ~DLP_GLOBAL_STATE_MASK);
+#endif
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       is_state = (_dlp_ctx_get_state(xfer_ctx) == state);
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       return is_state;
+}
+
+/**
+ * dlp_ctx_get_state - get the global state of a state machine
+ * @xfer_ctx: a reference to the state machine context
+ *
+ * Returns the current state of the requested TX or RX context.
+ *
+ * This version adds the spinlock guarding
+ */
+inline __must_check
+    unsigned int dlp_ctx_get_state(struct dlp_xfer_ctx *xfer_ctx)
+{
+       unsigned int state;
+       unsigned long flags;
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       state = _dlp_ctx_get_state(xfer_ctx);
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       return state;
+}
+
+/**
+ * dlp_ctx_set_state - sets the global state of a state machine
+ * @xfer_ctx: a reference to the state machine context
+ * @state: the state to set
+ */
+inline void dlp_ctx_set_state(struct dlp_xfer_ctx *xfer_ctx, unsigned int state)
+{
+       unsigned long flags;
+
+#ifdef DEBUG
+       BUG_ON(state & ~DLP_GLOBAL_STATE_MASK);
+#endif
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       xfer_ctx->state = (xfer_ctx->state & ~DLP_GLOBAL_STATE_MASK) | state;
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+}
+
+/**
+ * dlp_ctx_has_flag - checks if a flag is present in the state
+ * @xfer_ctx: a reference to the state machine context
+ * @flag: the flag(s) to consider
+ *
+ * Returns a non-zero value if all requested flags are present.
+ */
+inline __must_check int dlp_ctx_has_flag(struct dlp_xfer_ctx *xfer_ctx,
+                                        unsigned int flag)
+{
+       unsigned long flags;
+       int has_flag;
+
+#ifdef DEBUG
+       BUG_ON(flag & DLP_GLOBAL_STATE_MASK);
+#endif
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       has_flag = ((xfer_ctx->state & flag) == flag);
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       return has_flag;
+}
+
+/**
+ * dlp_ctx_set_flag - flags some extra information in the state
+ * @xfer_ctx: a reference to the state machine context
+ * @flag: the flag(s) to set
+ */
+inline void dlp_ctx_set_flag(struct dlp_xfer_ctx *xfer_ctx, unsigned int flag)
+{
+       unsigned long flags;
+
+#ifdef DEBUG
+       BUG_ON(flag & DLP_GLOBAL_STATE_MASK);
+#endif
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       xfer_ctx->state |= flag;
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+}
+
+/**
+ * dlp_ctx_clear_flag - unflags some extra information in the state
+ * @xfer_ctx: a reference to the state machine context
+ * @flag: the flag(s) to clear
+ */
+inline void dlp_ctx_clear_flag(struct dlp_xfer_ctx *xfer_ctx, unsigned int flag)
+{
+       unsigned long flags;
+
+#ifdef DEBUG
+       BUG_ON(flag & DLP_GLOBAL_STATE_MASK);
+#endif
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       xfer_ctx->state &= ~flag;
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+}
+
+/**
+ * dlp_ctx_is_empty_safe - checks if a context is empty (all FIFO are empty)
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ *
+ * This helper function is returning a non-zero value if both the wait FIFO and
+ * the controller FIFO are empty.
+ */
+int dlp_ctx_is_empty(struct dlp_xfer_ctx *xfer_ctx)
+{
+       int ret;
+       unsigned long flags;
+
+       PROLOG();
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       ret = ((xfer_ctx->wait_len == 0) && (xfer_ctx->ctrl_len == 0));
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       EPILOG();
+       return ret;
+}
+
+/**
+ * dlp_ctx_have_credits - checks if a context have credits
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ * @ch_ctx: a reference to the channel context
+ *
+ * This helper function returns TRUE in these cases:
+ *             - RX context
+ *             - TX context with credits available.
+ *             - The driver is used for loopback test mode (for debug).
+ */
+int dlp_ctx_have_credits(struct dlp_xfer_ctx *xfer_ctx,
+                        struct dlp_channel *ch_ctx)
+{
+       int have_credits = 0;
+       unsigned long flags;
+
+       PROLOG();
+
+       spin_lock_irqsave(&ch_ctx->lock, flags);
+       if ((xfer_ctx->ttype == HSI_MSG_READ) ||
+               ((xfer_ctx->ttype == HSI_MSG_WRITE) && (ch_ctx->credits))) {
+               have_credits = 1;
+       }
+       spin_unlock_irqrestore(&ch_ctx->lock, flags);
+
+       EPILOG("%d", have_credits);
+       return have_credits;
+}
+
+/**
+ * dlp_ctx_update_status - updating the channel status further to config
+ *                         changes (channel, pdu length)
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ */
+void dlp_ctx_update_status(struct dlp_xfer_ctx *xfer_ctx)
+{
+       struct list_head *curr;
+       struct hsi_msg *pdu;
+       struct scatterlist *sg;
+       unsigned long flags;
+
+       PROLOG();
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+
+       list_for_each(curr, &xfer_ctx->recycled_pdus) {
+               pdu = list_entry(curr, struct hsi_msg, link);
+               pdu->channel = xfer_ctx->channel->hsi_channel;
+
+               sg = pdu->sgt.sgl;
+
+               // FIXME: To be removed !!!
+               sg->length = xfer_ctx->payload_len + DLP_TTY_HEADER_LENGTH;
+
+               read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               write_lock_irqsave(&xfer_ctx->lock, flags);
+               xfer_ctx->room += xfer_ctx->payload_len;
+               xfer_ctx->room -= (sg->length - DLP_TTY_HEADER_LENGTH);
+               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               read_lock_irqsave(&xfer_ctx->lock, flags);
+       }
+
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       if (xfer_ctx->ttype == HSI_MSG_WRITE)
+               dlp_drv.client->tx_cfg = xfer_ctx->config;
+       else
+               dlp_drv.client->rx_cfg = xfer_ctx->config;
+
+       EPILOG();
+}
+
+/**
+ * dlp_ctx_update_state_not_active - update the RX state machine upon reception of an
+ * @xfer_ctx: a reference to the RX context to consider
+ *
+ * This helper function updates the RX state in accordance with the status of
+ * the RX FIFO.
+ */
+static inline void dlp_ctx_update_state_not_active(struct dlp_xfer_ctx
+                                                  *xfer_ctx)
+{
+       PROLOG();
+
+       if (!dlp_ctx_is_empty(xfer_ctx))
+               dlp_ctx_set_state(xfer_ctx, TTY);
+       else
+               dlp_ctx_set_state(xfer_ctx, IDLE);
+
+       EPILOG();
+}
+
+/**
+ * dlp_ctx_update_state - update the RX state machine upon recycling of a
+ *                       RX pdu
+ * @xfer_ctx: a reference to the xfer RX context to consider
+ *
+ * This helper function updates the RX state in accordance with the status of
+ * the RX FIFO, unless the RX is required active.
+ */
+static inline void dlp_ctx_update_state(struct dlp_xfer_ctx *xfer_ctx)
+{
+       PROLOG();
+
+       if (!dlp_ctx_is_state(xfer_ctx, ACTIVE))
+               dlp_ctx_update_state_not_active(xfer_ctx);
+
+       EPILOG();
+}
+
+/****************************************************************************
+ *
+ * FIFO common functions
+ *
+ ***************************************************************************/
+
+/**
+ * dlp_fifo_tail - get a reference to the last pdu of a FIFO or NULL
+ *                      if the FIFO is empty
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ * @fifo: a reference of the FIFO to consider
+ *
+ * Returns a reference to the last pdu of this FIFO or NULL if the FIFO is
+ * empty.
+ */
+inline __must_check
+    struct hsi_msg *dlp_fifo_tail(struct dlp_xfer_ctx *xfer_ctx,
+                                 struct list_head *fifo)
+{
+       struct hsi_msg *pdu = NULL;
+
+       /* Empty ? */
+       if (! list_empty(fifo)) {
+               pdu = list_entry(fifo->prev, struct hsi_msg, link);
+       }
+
+       return pdu;
+}
+
+/**
+ * dlp_fifo_empty - deletes the whole content of a FIFO
+ * @fifo: a reference to the FIFO to empty
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ *
+ * This helper function is emptying a FIFO and deleting all its pdus.
+ */
+static void dlp_fifo_empty(struct list_head *fifo,
+                          struct dlp_xfer_ctx *xfer_ctx)
+{
+       struct hsi_msg *pdu;
+       unsigned long flags;
+
+       PROLOG();
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+
+       /* Empty ? */
+       if (list_empty(fifo))
+               goto out;
+
+       while ((pdu = list_entry(fifo->next, struct hsi_msg, link))) {
+               /* Remove the pdu from the list */
+               list_del_init(&pdu->link);
+
+               /* pdu free */
+               dlp_pdu_free(pdu, xfer_ctx->channel->pdu_size);
+       }
+
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+ out:
+       EPILOG();
+}
+
+/****************************************************************************
+ *
+ * Wait FIFO handling methods
+ *
+ ***************************************************************************/
+
+/**
+ * dlp_fifo_wait_pop - pop a pdu from the FIFO of waiting pdus
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ *
+ * This function is not only popping the pdu, but also updating the counters
+ * related to the FIFO of waiting pdus in the considered context.
+ */
+struct hsi_msg *dlp_fifo_wait_pop(struct dlp_xfer_ctx *xfer_ctx)
+{
+       struct list_head *fifo = &xfer_ctx->wait_pdus;
+       struct hsi_msg *pdu = NULL;
+
+       PROLOG("wait_len: %d, pdu: 0x%p", xfer_ctx->wait_len, pdu);
+
+       /* Check if the list was not flushed */
+       if (list_empty(fifo))
+               goto out;
+
+       /* Get the list head */
+       pdu = list_entry(fifo->next, struct hsi_msg, link);
+
+       /* Remove the pdu from the list */
+       list_del_init(&pdu->link);
+
+       xfer_ctx->wait_len --;
+       xfer_ctx->buffered -= pdu->actual_len;
+
+out:
+
+       EPILOG("wait_len: %d", xfer_ctx->wait_len);
+       return pdu;
+}
+
+/**
+ * dlp_fifo_wait_push - push a pdu to the FIFO of waiting pdus
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ * @pdu: a reference to the pdu to push
+ *
+ * This function is not only pushing the pdu, but also updating the counters
+ * related to the FIFO of waiting pdus in the considered context.
+ */
+inline void dlp_fifo_wait_push(struct dlp_xfer_ctx *xfer_ctx,
+                              struct hsi_msg *pdu)
+{
+       unsigned long flags;
+
+       PROLOG("wait_len: %d, pdu [0x%p, %s]", xfer_ctx->wait_len,
+              pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX");
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+
+       xfer_ctx->wait_len++;
+       xfer_ctx->buffered += pdu->actual_len;
+
+       /* at the end of the list */
+       list_add_tail(&pdu->link, &xfer_ctx->wait_pdus);
+
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       EPILOG("wait_len: %d", xfer_ctx->wait_len);
+}
+
+/**
+ * dlp_fifo_wait_push_back - push back a pdu in the FIFO of waiting pdus
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ * @pdu: a reference to the pdu to push back
+ *
+ * This function is not only pushing back the pdu, but also updating the
+ * counters related to the FIFO of waiting pdus in the considered context.
+ */
+inline void dlp_fifo_wait_push_back(struct dlp_xfer_ctx *xfer_ctx,
+                                   struct hsi_msg *pdu)
+{
+       unsigned long flags;
+
+       PROLOG("wait_len: %d, pdu: 0x%p", xfer_ctx->wait_len, pdu);
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+
+       xfer_ctx->wait_len++;
+       xfer_ctx->buffered += pdu->actual_len;
+
+       /* at the begining of the list */
+       list_add(&pdu->link, &xfer_ctx->wait_pdus);
+
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       EPILOG("wait_len: %d", xfer_ctx->wait_len);
+}
+
+/**
+ * dlp_pop_wait_push_ctrl - transfer the first TX pdu from the wait FIFO to
+ *                         the controller FIFO
+ * @xfer_ctx: a reference to the TX context to consider
+ * @check_pdu: to check if the pdu is being updated (marked as break pdu)
+ *
+ * This wrapper function is simply transferring the first pdu of the wait
+ * FIFO.
+ */
+void dlp_pop_wait_push_ctrl(struct dlp_xfer_ctx *xfer_ctx,
+                           unsigned int check_pdu)
+{
+       int ok = 1;
+       unsigned long flags;
+       struct hsi_msg *pdu;
+
+       PROLOG("check_pdu: %d", check_pdu);
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       pdu = dlp_fifo_wait_pop(xfer_ctx);
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       if (!pdu)
+               goto out;
+
+       if (check_pdu) {
+               ok = !pdu->break_frame;
+       }
+
+       if (ok) {
+               /* Note that no error is returned upon transfer failure,
+                * in such cases, the pdu is simply returned back to the
+                * wait FIFO, as nothing else can be done
+                */
+               int ret;
+               unsigned int actual_len = pdu->actual_len;
+
+               /* */
+               ret = dlp_hsi_controller_push(xfer_ctx, pdu);
+               if (ret) {
+                       if (ret == -EACCES) {   /* port released */
+
+                               write_lock_irqsave(&xfer_ctx->lock, flags);
+                               xfer_ctx->room -= dlp_pdu_room_in(pdu);
+
+                               /* Recycle or Free the pdu */
+                               dlp_pdu_delete(xfer_ctx, pdu);
+
+                               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+                       } else {
+                               /* Push back the pdu */
+                               dlp_fifo_wait_push_back(xfer_ctx, pdu);
+                       }
+               }
+#ifdef CONFIG_HSI_DLP_TTY_STATS
+               else {
+                       xfer_ctx->tty_stats.data_sz += actual_len;
+                       xfer_ctx->tty_stats.pdus_cnt++;
+               }
+#endif
+       }
+
+ out:
+       EPILOG();
+}
+
+/****************************************************************************
+ *
+ * Frame recycling helper functions
+ *
+ ***************************************************************************/
+
+static inline void dlp_fifo_recycled_push(struct dlp_xfer_ctx *xfer_ctx,
+                                         struct hsi_msg *pdu)
+{
+       PROLOG("pdu [0x%p, %s, sgl->len: %d]",
+              pdu,
+              (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX",
+              pdu->sgt.sgl->length);
+
+       /* at the end of the list */
+       list_add_tail(&pdu->link, &xfer_ctx->recycled_pdus);
+
+       EPILOG();
+}
+
+/**
+ * dlp_fifo_recycled_pop - creating a new empty file from the recycling FIFO
+ * @xfer_ctx: a reference to the xfer context (RX or TX) to consider
+ *
+ * Returns a reference to the new empty pdu or NULL if there are no recycled
+ * pdus left.
+ */
+struct hsi_msg *dlp_fifo_recycled_pop(struct dlp_xfer_ctx *xfer_ctx)
+{
+       struct hsi_msg *pdu = NULL;
+       struct list_head *first;
+       unsigned long flags;
+
+       PROLOG();
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       first = &xfer_ctx->recycled_pdus;
+
+       /* Empty ? */
+       if (first != first->next) {
+               /* Get the fist pdu */
+               pdu = list_entry(first->next, struct hsi_msg, link);
+
+               read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               /* Remove the pdu from the list */
+               write_lock_irqsave(&xfer_ctx->lock, flags);
+               list_del_init(&pdu->link);
+               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               EPILOG("pdu [0x%p, %s, %d]",
+                      pdu,
+                      (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX",
+                      pdu->sgt.sgl->length);
+       } else {
+               read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               EPILOG("pdu [NULL]");
+       }
+
+       return pdu;
+}
+
+/**
+ * dlp_pop_recycled_push_ctrl - Push as many recycled pdus as possible to the
+ *                          controller FIFO
+ * @xfer_ctx: a reference to the RX context where the FIFO of recycled pdus sits
+ *
+ * Returns 0 upon success or an error code.
+ *
+ * This helper method is returning 0 on success, or an error code.
+ */
+__must_check int dlp_pop_recycled_push_ctrl(struct dlp_xfer_ctx *xfer_ctx)
+{
+       int ret = 0;
+       struct hsi_msg *new;
+       unsigned long flags;
+
+       PROLOG();
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+
+       while (xfer_ctx->ctrl_len < xfer_ctx->ctrl_max) {
+               read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               new = dlp_fifo_recycled_pop(xfer_ctx);
+               if (!new) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+
+               ret = dlp_hsi_controller_push(xfer_ctx, new);
+               if (ret) {
+                       dlp_fifo_recycled_push(xfer_ctx, new);
+
+                       ret = -EAGAIN;
+                       goto out;
+               }
+
+               read_lock_irqsave(&xfer_ctx->lock, flags);
+       }
+
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+out:
+       EPILOG("ret: %d", ret);
+       return ret;
+}
+
+/****************************************************************************
+ *
+ * HSI Controller
+ *
+ ***************************************************************************/
+
+/**
+ * dlp_hsi_controller_push - push a pdu to the HSI controller FIFO
+ * @xfer_ctx: a reference to the xfer context (TX or RX) to consider
+ * @pdu: a reference to the pdu to push
+ *
+ * Returns 0 on success or an error code on failure.
+ *
+ * This function is not only pushing the pdu, but also updating the counters
+ * related to the FIFO of outstanding pdus in the considered context.
+ */
+int dlp_hsi_controller_push(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu)
+{
+       unsigned int lost_room = dlp_pdu_room_in(pdu);
+       struct dlp_channel *ch_ctx = xfer_ctx->channel;
+       unsigned long flags;
+       int err = 0;
+
+       PROLOG("ctrl_len: %d, credits: %d, pdu [0x%p, %s, %d]",
+              xfer_ctx->ctrl_len, xfer_ctx->channel->credits,
+              pdu,
+              (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX",
+              pdu->sgt.sgl->length);
+
+       /* Check credits */
+       if (!dlp_ctx_have_credits(xfer_ctx, ch_ctx)) {
+               CRITICAL("NO credits avaible");
+               goto out;
+       }
+
+       /* Decrease counters values */
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+
+       xfer_ctx->room -= lost_room;
+       xfer_ctx->ctrl_len++;
+
+       if (pdu->ttype == HSI_MSG_WRITE)
+               xfer_ctx->channel->credits--;
+
+       xfer_ctx->seq_num++;
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       /* Set the DLP signature + seq_num */
+       dlp_pdu_set_header(xfer_ctx, pdu);
+
+       /* Dump the PDU */
+       if (pdu->ttype == HSI_MSG_WRITE) {
+               dlp_pdu_dump(pdu, 0);
+               //dlp_dbg_dump_pdu(pdu, 16, 160, 0);
+       }
+
+       err = hsi_async(pdu->cl, pdu);
+       if (!err) {
+               if ((pdu->ttype == HSI_MSG_WRITE) && (xfer_ctx->ctrl_len)) {
+                       mod_timer(&ch_ctx->hangup_timer,
+                                 jiffies + ch_ctx->hangup_delay);
+               }
+       } else {
+               unsigned int ctrl_len;
+
+               CRITICAL("hsi_async() failed (%d)", err);
+
+               /* Failed to send pdu, set back counters values */
+               write_lock_irqsave(&xfer_ctx->lock, flags);
+
+               xfer_ctx->seq_num--;
+               xfer_ctx->room += lost_room;
+               xfer_ctx->ctrl_len--;
+
+               if (pdu->ttype == HSI_MSG_WRITE)
+                       xfer_ctx->channel->credits++;
+
+               ctrl_len = xfer_ctx->ctrl_len;
+
+               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               if (!ctrl_len) {
+                       struct dlp_channel *ch_ctx = xfer_ctx->channel;
+                       del_timer(&ch_ctx->hangup_timer);
+               }
+       }
+
+out:
+       EPILOG("ctrl_len: %d, credits: %d",
+              xfer_ctx->ctrl_len, xfer_ctx->channel->credits);
+       return err;
+}
+
+/**
+ * dlp_hsi_controller_pop - pop a pdu from the HSI controller FIFO
+ * @xfer_ctx: a reference to the xfer context (TX or RX) to consider
+ *
+ * This function is only updating the counters related to the FIFO of
+ * outstanding pdus in the considered context.
+ */
+inline void dlp_hsi_controller_pop(struct dlp_xfer_ctx *xfer_ctx)
+{
+       PROLOG("ctrl_len: %d", xfer_ctx->ctrl_len);
+
+       xfer_ctx->ctrl_len--;
+
+       EPILOG("ctrl_len: %d", xfer_ctx->ctrl_len);
+}
+
+/**
+ * dlp_hsi_start_tx - update the TX state machine on every new transfer
+ * @xfer_ctx: a reference to the TX context to consider
+ *
+ * This helper function updates the TX state if it is currently idle and
+ * inform the HSI pduwork and attached controller.
+ */
+void dlp_hsi_start_tx(struct dlp_xfer_ctx *xfer_ctx)
+{
+       PROLOG();
+
+       /* we found the case that we called hsi_port_shutdown and
+        * stop_tx, while the tty want to flush data, in this case
+        * just ignore the start_tx, and the code in ctl push already
+        * ignore the flush and discard the packet.
+        */
+       if (!hsi_port_claimed(dlp_drv.client))
+               return;
+
+       if (dlp_ctx_is_state(xfer_ctx, IDLE)) {
+               dlp_ctx_set_state(xfer_ctx, ACTIVE);
+
+               hsi_start_tx(dlp_drv.client);
+       } else {
+               del_timer(&xfer_ctx->timer);
+       }
+
+       EPILOG();
+}
+
+/**
+ * dlp_stop_tx - update the TX state machine after expiration of the TX active
+ *               timeout further to a no outstanding TX transaction status
+ * @xfer_ctx: a reference to the TX context to consider
+ *
+ * This helper function updates the TX state if it is currently active and
+ * inform the HSI pduwork and attached controller.
+ */
+void dlp_stop_tx(struct dlp_xfer_ctx *xfer_ctx)
+{
+       PROLOG();
+
+       if (dlp_ctx_is_state(xfer_ctx, ACTIVE)) {
+               dlp_ctx_set_state(xfer_ctx, IDLE);
+
+               hsi_stop_tx(dlp_drv.client);
+       }
+
+       EPILOG();
+}
+
+/**
+ * dlp_stop_rx - update the internal RX state machine
+ * @xfer_ctx: a reference to the RX context to consider
+ * @ch_ctx: a reference to related channel context
+ *
+ * This helper function updates the RX state and allows the HSI device to
+ * sleep.
+ */
+inline void dlp_stop_rx(struct dlp_xfer_ctx *xfer_ctx,
+                       struct dlp_channel *ch_ctx)
+{
+       PROLOG();
+
+       dlp_ctx_update_state_not_active(xfer_ctx);
+
+       EPILOG();
+}
+
+/**
+ * dlp_hsi_port_claim - Claim & setup the HSI port
+ * @ch_ctx: a reference to related channel context
+ */
+int dlp_hsi_port_claim(struct dlp_channel *ch_ctx)
+{
+       int ret = 0;
+
+       //PROLOG("hsi_ch: %d", ch_ctx->hsi_channel);
+       PROLOG();
+
+       /* Claim the HSI port (if not claimed already) */
+       if (atomic_inc_return(&dlp_drv.busy) != 1)
+               goto out;
+
+       /* Claim the HSI port */
+       ret = hsi_claim_port(dlp_drv.client, 1);
+       if (unlikely(ret)) {
+               CRITICAL("hsi_claim_port() failed (%d)", ret);
+               goto out;
+       }
+
+       /* Setup the HSI controller */
+       ret = hsi_setup(dlp_drv.client);
+       if (unlikely(ret)) {
+               CRITICAL("hsi_setup() failed (%d)", ret);
+               hsi_release_port(dlp_drv.client);
+               goto out;
+       }
+
+out:
+       EPILOG();
+       return ret;
+}
+
+/**
+ * dlp_hsi_port_unclaim - UnClaim (release) the HSI port
+ * @ch_ctx: a reference to related channel context
+ */
+inline void dlp_hsi_port_unclaim(struct dlp_channel *ch_ctx)
+{
+       //PROLOG("hsi_ch: %d", ch_ctx->hsi_channel);
+       PROLOG();
+
+       if (atomic_dec_and_test(&dlp_drv.busy))
+               hsi_release_port(dlp_drv.client);
+
+       EPILOG();
+}
+
+/**
+ * dlp_hsi_start_rx_cb - update the internal RX state machine
+ * @cl: a reference to HSI client to consider
+ *
+ * This helper function updates the RX state and wakes the device.
+ */
+static void dlp_hsi_start_rx_cb(struct hsi_client *cl)
+{
+       // FIXME: only ch_ctx[1]
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)hsi_client_drvdata(cl);
+       struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->rx;
+
+       PROLOG();
+
+       dlp_ctx_set_state(xfer_ctx, ACTIVE);
+
+       EPILOG();
+}
+
+/**
+ * dlp_hsi_stop_rx_cb - update the internal RX state machine
+ * @cl: a reference to HSI client to consider
+ *
+ * This helper function updates the RX state and allows the HSI device to
+ * sleep.
+ */
+static void dlp_hsi_stop_rx_cb(struct hsi_client *cl)
+{
+       // FIXME: only ch_ctx[1]
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)hsi_client_drvdata(cl);
+       struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->rx;
+
+       PROLOG();
+
+       dlp_stop_rx(xfer_ctx, ch_ctx);
+
+       EPILOG();
+}
+
+/****************************************************************************
+ *
+ * RX/TX xfer contexts
+ *
+ ***************************************************************************/
+
+/**
+ * dlp_increase_pdus_pool - background work aimed at creating new pdus
+ * @work: a reference to the work context
+ *
+ * This function is called as a background job (in the dlp_recycle_wq work
+ * queue) for performing the pdu resource allocation (which can then sleep).
+ *
+ * An error message is sent upon the failure of DLP_PDU_ALLOC_RETRY_MAX_CNT
+ * allocation requests.
+ */
+static void dlp_increase_pdus_pool(struct work_struct *work)
+{
+       struct dlp_xfer_ctx *xfer_ctx = container_of(work, struct dlp_xfer_ctx,
+                                                    increase_pool);
+       struct hsi_msg *new;
+       unsigned long flags;
+       int retry;
+
+       PROLOG();
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+
+       while (xfer_ctx->all_len < (xfer_ctx->wait_max + xfer_ctx->ctrl_max)) {
+               read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               retry = 0;
+               new = dlp_pdu_alloc(xfer_ctx->channel->hsi_channel,
+                                   xfer_ctx->ttype,
+                                   xfer_ctx->channel->pdu_size,
+                                   1,
+                                   xfer_ctx,
+                                   xfer_ctx->complete_cb, dlp_pdu_destructor);
+
+               while (!new) {
+                       ++retry;
+                       if (retry == DLP_PDU_ALLOC_RETRY_MAX_CNT) {
+                               CRITICAL("Cannot allocate a pdu after %d retries...", retry);
+                               retry = 0;
+                       }
+
+                       /* No memory available: do something more urgent ! */
+                       schedule();
+
+                       new = dlp_pdu_alloc(xfer_ctx->channel->hsi_channel,
+                                           xfer_ctx->ttype,
+                                           xfer_ctx->channel->pdu_size,
+                                           1,
+                                           xfer_ctx,
+                                           xfer_ctx->complete_cb,
+                                           dlp_pdu_destructor);
+               }
+
+               write_lock_irqsave(&xfer_ctx->lock, flags);
+               xfer_ctx->room += xfer_ctx->payload_len;
+               xfer_ctx->all_len++;
+
+               dlp_fifo_recycled_push(xfer_ctx, new);
+
+               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               read_lock_irqsave(&xfer_ctx->lock, flags);
+       }
+
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       EPILOG("%s pdu's pool created (hsi_channel: %d)",
+              (xfer_ctx->ttype == HSI_MSG_WRITE ? "TX" : "RX"),
+              xfer_ctx->channel->hsi_channel);
+}
+
+/**
+ * dlp_xfer_ctx_init - initialise a TX or RX context after its creation
+ * @ch_ctx: a reference to its related channel context
+ * @xfer_ctx: a reference to the considered TX or RX context
+ * @delay: the initial delay for the timer related to the TX or RX context
+ * @wait_max: the maximal size of the wait FIFO for this context
+ * @ctrl_max: the maximal size of the HSI controller FIFO for this context
+ * @complete_cb: HSI transfer complete callback
+ * @ttype: HSI transfer type
+ *
+ * This helper function is simply filling in the initial data into a newly
+ * created TX or RX context.
+ */
+void dlp_xfer_ctx_init(struct dlp_channel *ch_ctx,
+                      struct dlp_xfer_ctx *xfer_ctx,
+                      unsigned int delay,
+                      unsigned int wait_max,
+                      unsigned int ctrl_max,
+                      xfer_complete_cb complete_cb, unsigned int ttype)
+{
+       PROLOG("delay: %d, wait_max: %d, ctrl_max: %d",
+              delay, wait_max, ctrl_max);
+
+       INIT_LIST_HEAD(&xfer_ctx->wait_pdus);
+       INIT_LIST_HEAD(&xfer_ctx->recycled_pdus);
+
+       init_timer(&xfer_ctx->timer);
+       rwlock_init(&xfer_ctx->lock);
+
+       xfer_ctx->timer.data = (unsigned long)xfer_ctx;
+       xfer_ctx->delay = from_usecs(delay);
+       xfer_ctx->state = IDLE;
+       xfer_ctx->wait_max = wait_max;
+       xfer_ctx->ctrl_max = ctrl_max;
+       xfer_ctx->channel = ch_ctx;
+       xfer_ctx->payload_len = DLP_TTY_PAYLOAD_LENGTH;
+       xfer_ctx->ttype = ttype;
+       xfer_ctx->complete_cb = complete_cb;
+       INIT_WORK(&xfer_ctx->increase_pool, dlp_increase_pdus_pool);
+
+       EPILOG();
+}
+
+/**
+ * dlp_xfer_ctx_clear - clears a TX or RX context prior to its deletion
+ * @xfer_ctx: a reference to the considered TX or RX context
+ *
+ * This helper function is simply calling the relevant destructors
+ * and reseting the context information.
+ */
+void dlp_xfer_ctx_clear(struct dlp_xfer_ctx *xfer_ctx)
+{
+       unsigned long flags;
+
+       PROLOG();
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+
+       xfer_ctx->wait_max = 0;
+       xfer_ctx->ctrl_max = 0;
+       xfer_ctx->state = IDLE;
+       del_timer(&xfer_ctx->timer);
+
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       dlp_fifo_empty(&xfer_ctx->wait_pdus, xfer_ctx);
+       dlp_fifo_empty(&xfer_ctx->recycled_pdus, xfer_ctx);
+
+       flush_work(&xfer_ctx->increase_pool);
+
+       EPILOG();
+}
+
+/****************************************************************************
+ *
+ * Protocol driver functions
+ *
+ ***************************************************************************/
+
+/**
+ * dlp_driver_cleanup - Release driver allocated resources
+ *
+ * This helper function calls the "delete" function for each
+ *     channel context.
+ */
+static void dlp_driver_cleanup(void)
+{
+       int i = 0;
+       /* Contexts allocation functions */
+       /* NOTE : this array should be aligned with the context enum
+        *                defined in the .h file */
+       dlp_context_delete delete_funcs[DLP_CHANNEL_COUNT] = {
+               dlp_ctrl_ctx_delete,    /* 0 : CTRL */
+               dlp_tty_ctx_delete,             /* 1 : TTY  */
+               dlp_net_ctx_delete,             /* 2 : NET  */
+               dlp_net_ctx_delete,             /* 3 : NET  */
+               dlp_net_ctx_delete};    /* 4 : NET  */
+
+       PROLOG();
+
+       /* Free DLP contexts */
+       for (i = 0; i < DLP_CHANNEL_COUNT; i++) {
+               if (dlp_drv.channels[i]) {
+                       delete_funcs[i] (dlp_drv.channels[i]);
+                       dlp_drv.channels[i] = NULL;
+
+               }
+       }
+
+       EPILOG();
+}
+
+/**
+ * dlp_driver_probe - creates a new context in the DLP driver
+ * @dev: a reference to the HSI device requiring a context
+ *
+ * Returns 0 upon success or an error in case of an error
+ *
+ * This function is creating a new context per HSI controller requiring a
+ * DLP protocol driver, creates the related TTY port and TTY entry in the
+ * filesystem.
+ */
+static int __init dlp_driver_probe(struct device *dev)
+{
+       /* The parent of our device is the HSI port,
+        * the parent of the HSI port is the HSI controller device */
+       struct device *controller = dev->parent->parent;
+       struct hsi_client *client = to_hsi_client(dev);
+       int i, ret = 0;
+
+       /* Contexts allocation functions */
+       /* NOTE : this array should be aligned with the context enum
+        *                defined in the .h file */
+       dlp_context_create create_funcs[DLP_CHANNEL_COUNT] = {
+               dlp_ctrl_ctx_create,    /* 0 : CTRL */
+               dlp_tty_ctx_create,             /* 1 : TTY  */
+               dlp_net_ctx_create,             /* 2 : NET  */
+               dlp_net_ctx_create,             /* 3 : NET  */
+               dlp_net_ctx_create};    /* 4 : NET  */
+
+       PROLOG();
+
+       /* Save the controller & client */
+       dlp_drv.controller = controller;
+       dlp_drv.client = client;
+       dlp_drv.is_dma_capable = is_device_dma_capable(controller);
+       spin_lock_init(&dlp_drv.lock);
+
+       /* Warn if no DMA capability */
+       if (!dlp_drv.is_dma_capable) {
+               WARNING("HSI device is not DMA capable");
+       }
+
+       /* FIXME: Claim the HSI port */
+       ret = dlp_hsi_port_claim(NULL);
+       if (ret) {
+               goto out;
+       }
+
+       /* Create DLP contexts */
+       for (i = 0; i < DLP_CHANNEL_COUNT; i++) {
+               dlp_drv.channels[i] = create_funcs[i] (i, dev);
+               if (!dlp_drv.channels[i]) {
+                       goto cleanup;
+               }
+       }
+
+       /*  */
+       client->hsi_start_rx = dlp_hsi_start_rx_cb;
+       client->hsi_stop_rx = dlp_hsi_stop_rx_cb;
+
+       /* FIXME : other channels ? */
+       hsi_client_set_drvdata(client, dlp_drv.channels[DLP_CHANNEL_TTY]);
+
+       /* Create /proc/hsi-dlp */
+       // FIXME
+       //if (dlp_drv.debug)
+       {
+               proc_create_data(DRVNAME, S_IRUGO, NULL, &dlp_proc_ops, NULL);
+       }
+
+       EPILOG();
+       return 0;
+
+cleanup:
+       dlp_driver_cleanup();
+
+out:
+       EPILOG("%d", ret);
+       return ret;
+}
+
+/**
+ * dlp_driver_remove - removes a context from the DLP driver
+ * @dev: a reference to the device requiring the context
+ *
+ * Returns 0 on success or an error code
+ *
+ * This function is freeing all resources hold by the context attached to the
+ * requesting HSI device.
+ */
+static int __exit dlp_driver_remove(struct device *dev)
+{
+       struct hsi_client *client = to_hsi_client(dev);
+
+       PROLOG();
+
+       client->hsi_start_rx = NULL;
+       client->hsi_stop_rx = NULL;
+       hsi_client_set_drvdata(client, NULL);
+
+       dlp_driver_cleanup();
+
+       /* UnClaim the HSI port */
+       dlp_hsi_port_unclaim(NULL);
+
+       EPILOG();
+       return 0;
+}
+
+/*
+ * dlp_driver_setup - configuration of the DLP driver
+ */
+static struct hsi_client_driver dlp_driver_setup = {
+       .driver = {
+                  .name = DRVNAME,
+                  .owner = THIS_MODULE,
+                  .probe = dlp_driver_probe,
+                  .remove = dlp_driver_remove,
+                  },
+};
+
+/**
+ * dlp_module_init - initialises the DLP driver common parts
+ *
+ * Returns 0 on success or an error code
+ */
+static int __init dlp_module_init(void)
+{
+       int err;
+
+       /* Initialization */
+       memset(&dlp_drv, 0, sizeof(struct dlp_driver));
+
+       /* FIXME : to be removed */
+       dlp_drv.debug = 0x0;
+
+       PROLOG();
+
+       /* Create the workqueue for allocating pdus */
+       dlp_drv.recycle_wq = create_singlethread_workqueue(DRVNAME "-wq");
+       if (unlikely(!dlp_drv.recycle_wq)) {
+               CRITICAL("Unable to create pool-handling workqueue");
+               err = -EFAULT;
+               goto out;
+       }
+
+       /* Create the workqueue for tx hangup */
+       dlp_drv.tx_hangup_wq = create_singlethread_workqueue(DRVNAME "-hwq");
+       if (unlikely(!dlp_drv.tx_hangup_wq)) {
+               CRITICAL("Unable to create tx hangup workqueue");
+               err = -EFAULT;
+               goto del_wq;
+       }
+
+       /* Not used yet (claimed) */
+       atomic_set(&dlp_drv.busy, 0);
+
+       /* Now, register the client */
+       err = hsi_register_client_driver(&dlp_driver_setup);
+       if (unlikely(err)) {
+               CRITICAL("hsi_register_client_driver() failed (%d)", err);
+               goto del_2wq;
+       }
+
+       EPILOG("driver initialized");
+       return 0;
+
+del_2wq:
+       destroy_workqueue(dlp_drv.tx_hangup_wq);
+del_wq:
+       destroy_workqueue(dlp_drv.recycle_wq);
+
+out:
+       EPILOG("Failed");
+       return err;
+}
+
+/**
+ * dlp_driver_exit - frees the resources taken by the DLP driver common parts
+ */
+static void __exit dlp_module_exit(void)
+{
+       PROLOG();
+
+       destroy_workqueue(dlp_drv.recycle_wq);
+       destroy_workqueue(dlp_drv.tx_hangup_wq);
+
+       hsi_unregister_client_driver(&dlp_driver_setup);
+
+       EPILOG("driver removed");
+}
+
+module_init(dlp_module_init);
+module_exit(dlp_module_exit);
+
+MODULE_AUTHOR("Olivier Stoltz Douchet <olivierx.stoltz-douchet@intel.com>");
+MODULE_AUTHOR("Faouaz Tenoutit <faouazx.tenoutit@intel.com>");
+MODULE_DESCRIPTION("LTE protocol driver over HSI for IMC modems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0-HSI-LTE");
diff --git a/drivers/hsi/clients/dlp_main.h b/drivers/hsi/clients/dlp_main.h
new file mode 100644 (file)
index 0000000..9bb5aef
--- /dev/null
@@ -0,0 +1,505 @@
+/*
+ * dlp_main.h
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI
+ * protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact: Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef _DLP_MAIN_H_
+#define _DLP_MAIN_H_
+
+#include <linux/hsi/hsi.h>
+#include <linux/hsi/hsi_dlp.h>
+#include <linux/tty.h>
+#include <linux/netdevice.h>
+
+#include "../dlp_debug.h"
+
+#define DRVNAME                                "hsi-dlp"
+
+/* Defaut TX timeout delay (in microseconds) */
+#define DLP_HANGUP_DELAY       1000000
+
+/* Defaut HSI TX delay (in microseconds) */
+#define DLP_HSI_TX_DELAY               10000
+
+/* Defaut HSI RX delay (in microseconds) */
+#define DLP_HSI_RX_DELAY               10000
+
+/* Maximal number of pdu allocation failure prior firing an error message */
+#define DLP_PDU_ALLOC_RETRY_MAX_CNT    10
+
+/* Delays for powering up/resetting the modem (in milliseconds) */
+#define DLP_POWER_ON_INTERLINE_DELAY   1
+#define DLP_POWER_ON_POST_DELAY                        200
+
+/* Round-up the pdu and header length to a multiple of 4-bytes to align
+ * on the HSI 4-byte granularity*/
+#define DLP_PDU_LENGTH (((CONFIG_HSI_DLP_PDU_LENGTH+3)/4)*4)
+#define DLP_HEADER_LENGTH      (((CONFIG_HSI_DLP_HEADER_LENGTH+3)/4)*4)
+#define DLP_PAYLOAD_LENGTH     (DLP_PDU_LENGTH-DLP_HEADER_LENGTH)
+#define DLP_LENGTH_MASK                (roundup_pow_of_two(DLP_PAYLOAD_LENGTH)-1)
+
+/* Initial minimal buffering size (in bytes) */
+#define DLP_MIN_TX_BUFFERING   65536
+#define DLP_MIN_RX_BUFFERING   65536
+
+/* Compute the TX and RX, FIFO depth from the buffering requirements */
+/* For optimal performances the DLP_HSI_TX_CTRL_FIFO size shall be set to 2 at
+ * least to allow back-to-back transfers. */
+#define DLP_TX_MAX_LEN ((DLP_MIN_TX_BUFFERING+DLP_PAYLOAD_LENGTH-1)/DLP_PAYLOAD_LENGTH)
+#define DLP_RX_MAX_LEN ((DLP_MIN_RX_BUFFERING+DLP_PAYLOAD_LENGTH-1)/DLP_PAYLOAD_LENGTH)
+
+#define DLP_HSI_TX_CTRL_FIFO   2
+#define DLP_HSI_TX_WAIT_FIFO   max(DLP_TX_MAX_LEN-DLP_HSI_TX_CTRL_FIFO, 1)
+
+#define DLP_HSI_RX_WAIT_FIFO   max(DLP_RX_MAX_LEN/2, 1)
+#define DLP_HSI_RX_CTRL_FIFO   max(DLP_RX_MAX_LEN-DLP_HSI_RX_WAIT_FIFO, 1)
+
+/* Tag for detecting buggy pdu sizes (must be greater than the maximum pdu
+ * size */
+#define DLP_BUGGY_PDU_SIZE     0xFFFFFFFFUL
+
+/* PDU size for TTY channel */
+#define DLP_TTY_PDU_LENGTH             4096    /* 1500 Bytes */
+#define DLP_TTY_HEADER_LENGTH  16
+#define DLP_TTY_PAYLOAD_LENGTH (DLP_TTY_PDU_LENGTH - DLP_TTY_HEADER_LENGTH)
+
+/* PDU size for NET channels */
+#define DLP_NET_PDU_SIZE       4096    /* 15360: 15 KBytes */
+
+/* PDU size for CTRL channel */
+#define DLP_CTRL_PDU_SIZE      4       /* 4 Bytes */
+
+/* Alignment params */
+#define DLP_PACKET_ALIGN_AP            16
+#define DLP_PACKET_ALIGN_CP            16
+
+/* Header space params */
+#define DLP_HDR_SPACE_AP               16
+#define DLP_HDR_SPACE_CP               16
+
+/* Header signature */
+#define DLP_HEADER_SIGNATURE   0xF9A80000
+
+/* header fields */
+#define DLP_HDR_DATA_SIZE(sz)  ((sz) & 0x3FFFF)
+#define DLP_HDR_MORE_DESC              (0x1 << 31)
+#define DLP_HDR_NO_MORE_DESC   (0x0 << 31)
+
+#define DLP_HDR_MIDDLE_OF_PACKET (0x0 << 29)
+#define DLP_HDR_END_OF_PACKET   (0x1 << 29)
+#define DLP_HDR_START_OF_PACKET         (0x2 << 29)
+#define DLP_HDR_COMPLETE_PACKET         (0x3 << 29)
+
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+/* RX and TX state machine definitions */
+enum {
+       IDLE,
+       ACTIVE,
+       TTY,
+};
+
+/* DLP contexts */
+enum {
+       DLP_CHANNEL_CTRL,       /* HSI Channel 0 */
+       DLP_CHANNEL_TTY,        /* HSI Channel 1 */
+       DLP_CHANNEL_NET1,       /* HSI Channel 2 */
+       DLP_CHANNEL_NET2,       /* HSI Channel 3 */
+       DLP_CHANNEL_NET3,       /* HSI Channel 4 */
+
+       DLP_CHANNEL_COUNT
+};
+
+/*  */
+#define DLP_GLOBAL_STATE_SZ            2
+#define DLP_GLOBAL_STATE_MASK  ((1<<DLP_GLOBAL_STATE_SZ)-1)
+
+/*
+ * HSI transfer complete callback prototype
+ */
+typedef void (*xfer_complete_cb) (struct hsi_msg * pdu);
+
+/*
+ * HSI client start/stop RX callback prototype
+ */
+typedef void (*hsi_client_cb) (struct hsi_client * cl);
+
+/**
+ * struct dlp_xfer_ctx - TX/RX transfer context
+ * @wait_pdus: head of the FIFO of TX/RX waiting pdus
+ * @wait_len: current length of the TX/RX waiting pdus FIFO
+ * @wait_max: maximal length of the TX/RX waiting pdus FIFO
+ * @recycled_pdus: head of the FIFO of TX/RX recycled pdus
+ * @ctrl_max: maximal count of outstanding TX/RX pdus in the controller
+ * @ctrl_len: current count of TX/RX outstanding pdus in the controller
+ * @buffered: number of bytes currently buffered in the wait FIFO
+ * @room: room available in the wait FIFO with a byte granularity
+ * @timer: context of the TX active timeout/RX TTY insert retry timer
+ * @lock: spinlock to serialise access to the TX/RX context information
+ * @delay: nb of jiffies for the TX active timeout/RX TTY insert retry timer
+ * @state: current TX/RX state (global and internal one)
+ * @channel: reference to the channel context
+ * @ch_num: HSI channel number
+ * @payload_len: the fixed (maximal) size of a pdu payload in bytes
+ * @all_len: total count of TX/RX pdus (including recycled ones)
+ * @increase_pool: context of the increase pool work queue
+ * @config: current updated HSI configuration
+ * @complete_cb: xfer complete callback
+ * @ttype: xfer type (RX/TX)
+ * @tty_stats: TTY stats
+ */
+struct dlp_xfer_ctx {
+       struct list_head wait_pdus;
+       unsigned int wait_len;
+       unsigned int wait_max;
+
+       struct list_head recycled_pdus;
+
+       unsigned int ctrl_max;
+       unsigned int ctrl_len;
+
+       int buffered;
+       int room;
+
+       struct timer_list timer;
+       rwlock_t lock;
+       unsigned long delay;
+       unsigned int state;
+
+       struct dlp_channel *channel;
+       unsigned int payload_len;
+       unsigned int all_len;
+
+       struct work_struct increase_pool;
+       struct hsi_config config;
+
+       xfer_complete_cb complete_cb;
+       unsigned int ttype;
+
+       unsigned int seq_num;
+
+#ifdef CONFIG_HSI_DLP_TTY_STATS
+       struct hsi_dlp_stats tty_stats;
+#endif
+};
+
+/**
+ * struct dlp_channel - HSI channel context
+ * @client: reference to this HSI client
+ * @credits: credits value (nb of pdus that can be sent to the modem)
+ * @credits_lock: credits lock
+ * @pdu_size: the fixed pdu size
+ * @ready: the channel is ready (TTY opened, NET IF configure)
+ * @controller: reference to the controller bound to this context
+ * @hsi_channel: the HSI channel number
+ * @credits: the credits value
+ * @tx_empty_event: TX empty check event
+ * @tx: current TX context
+ * @rx: current RX context
+ */
+struct dlp_channel {
+       unsigned int hsi_channel;
+       unsigned int credits;
+       spinlock_t       lock;
+       unsigned int pdu_size;
+       unsigned int ready;
+
+       /* TX/RX contexts */
+       wait_queue_head_t tx_empty_event;
+       struct dlp_xfer_ctx tx;
+       struct dlp_xfer_ctx rx;
+
+       /* Hangup management */
+       struct timer_list hangup_timer;
+       struct work_struct hangup_queue;
+
+       int hangup_reason;
+       int hangup_delay;
+
+       /* Reset & Coredump callbacks */
+       void (*modem_coredump_cb) (struct dlp_channel * ch_ctx);
+       void (*modem_reset_cb) (struct dlp_channel * ch_ctx);
+
+       /* Credits callback */
+       void (*credits_available_cb) (struct dlp_channel * ch_ctx);
+
+       /* Channel sepecific data */
+       void *ch_data;
+};
+
+/**
+ * struct dlp_driver - fixed pdu length protocol on HSI driver data
+ * @channels: array of DLP Channel contex references
+ * @is_dma_capable: a flag to check if the ctrl supports the DMA
+ * @controller: a reference to the HSI controller
+ * @busy: port usage (claiming) counter
+ * @channels: a reference to the HSI client
+ * @recycle_wq: Workqueue for submitting pdu-recycling background tasks
+ * @tx_hangup_wq: Workqueue for submitting tx timeout hangup background tasks
+ * @modem_ready: The modem is up & running
+ * @core_dump_irq:
+ * @reset_irq:
+ * @reset_ignore:
+ *
+ * @debug: Debug variable
+ */
+struct dlp_driver {
+       struct dlp_channel *channels[DLP_CHANNEL_COUNT];
+
+       unsigned int is_dma_capable;
+       struct hsi_client *client;
+       struct device *controller;
+
+       /* Port claiming counter */
+       atomic_t busy;
+
+       struct workqueue_struct *recycle_wq;
+       struct workqueue_struct *tx_hangup_wq;
+
+       /* Modem readiness */
+       int modem_ready;
+       spinlock_t lock;
+       spinlock_t at_lock;
+
+       /* Modem coredump & reset */
+       int core_dump_irq;
+       int reset_irq;
+       int reset_ignore;
+
+       /* Debug variables */
+       int debug;
+};
+
+/*
+ * Context alloc/free function proptype
+ */
+typedef struct dlp_channel *(*dlp_context_create) (unsigned int index,
+                                                  struct device * dev);
+
+typedef int (*dlp_context_delete) (struct dlp_channel * ch_ctx);
+
+/****************************************************************************
+ *
+ * PDU handling
+ *
+ ***************************************************************************/
+void *dlp_buffer_alloc(unsigned int buff_size, dma_addr_t * dma_addr);
+
+void dlp_buffer_free(void *buff, dma_addr_t dma_addr, unsigned int buff_size);
+
+void dlp_pdu_dump(struct hsi_msg *pdu, int as_string);
+
+struct hsi_msg *dlp_pdu_alloc(unsigned int hsi_channel,
+                             int ttype,
+                             int buffer_size,
+                             int nb_entries,
+                             void *user_data,
+                             xfer_complete_cb complete_cb,
+                             xfer_complete_cb destruct_cb);
+
+void dlp_pdu_free(struct hsi_msg *pdu, unsigned int pdu_size);
+
+void dlp_pdu_delete(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu);
+
+void dlp_pdu_recycle(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu);
+
+void dlp_pdu_update(struct dlp_channel *ch_ctx, struct hsi_msg *pdu);
+
+inline void dlp_pdu_reset(struct dlp_xfer_ctx *xfer_ctx,
+                         struct hsi_msg *pdu, unsigned int length);
+
+int dlp_pdu_header_valid(struct hsi_msg *pdu);
+
+inline void dlp_pdu_set_length(struct hsi_msg *pdu, u32 sz);
+
+unsigned int dlp_pdu_get_offset(struct hsi_msg *pdu);
+
+inline unsigned int dlp_pdu_room_in(struct hsi_msg *pdu);
+
+inline __attribute_const__
+    unsigned char *dlp_pdu_data_ptr(struct hsi_msg *pdu, unsigned int offset);
+
+/****************************************************************************
+ *
+ * State handling
+ *
+ ***************************************************************************/
+inline __must_check
+    unsigned int dlp_ctx_get_state(struct dlp_xfer_ctx *xfer_ctx);
+
+inline void dlp_ctx_set_state(struct dlp_xfer_ctx *xfer_ctx,
+                             unsigned int state);
+
+inline __must_check int dlp_ctx_has_flag(struct dlp_xfer_ctx *xfer_ctx,
+                                        unsigned int flag);
+
+inline void dlp_ctx_set_flag(struct dlp_xfer_ctx *xfer_ctx, unsigned int flag);
+
+inline void dlp_ctx_clear_flag(struct dlp_xfer_ctx *xfer_ctx,
+                              unsigned int flag);
+
+inline int dlp_ctx_is_empty(struct dlp_xfer_ctx *xfer_ctx);
+
+int dlp_ctx_have_credits(struct dlp_xfer_ctx *xfer_ctx,
+                        struct dlp_channel *ch_ctx);
+
+int dlp_ctx_is_empty_safe(struct dlp_xfer_ctx *xfer_ctx);
+
+void dlp_ctx_update_status(struct dlp_xfer_ctx *xfer_ctx);
+
+/****************************************************************************
+ *
+ * Generic FIFO handling
+ *
+ ***************************************************************************/
+inline __must_check
+    struct hsi_msg *dlp_fifo_tail(struct dlp_xfer_ctx *xfer_ctx,
+                                 struct list_head *fifo);
+
+inline void _dlp_fifo_pdu_push(struct hsi_msg *pdu, struct list_head *fifo);
+
+inline void _dlp_fifo_pdu_push_back(struct hsi_msg *pdu,
+                                   struct list_head *fifo);
+
+/****************************************************************************
+ *
+ * Wait FIFO handling
+ *
+ ***************************************************************************/
+struct hsi_msg *dlp_fifo_wait_pop(struct dlp_xfer_ctx *xfer_ctx);
+
+inline void dlp_fifo_wait_push(struct dlp_xfer_ctx *xfer_ctx,
+                              struct hsi_msg *pdu);
+
+inline void dlp_fifo_wait_push_back(struct dlp_xfer_ctx *xfer_ctx,
+                                   struct hsi_msg *pdu);
+
+void dlp_pop_wait_push_ctrl(struct dlp_xfer_ctx *xfer_ctx,
+                           unsigned int check_pdu);
+
+/****************************************************************************
+ *
+ * Frame recycling handling
+ *
+ ***************************************************************************/
+inline struct hsi_msg *dlp_fifo_recycled_pop(struct dlp_xfer_ctx *xfer_ctx);
+
+__must_check int dlp_pop_recycled_push_ctrl(struct dlp_xfer_ctx *xfer_ctx);
+
+/****************************************************************************
+ *
+ * HSI Controller
+ *
+ ***************************************************************************/
+
+inline void dlp_hsi_controller_pop(struct dlp_xfer_ctx *xfer_ctx);
+
+int dlp_hsi_controller_push(struct dlp_xfer_ctx *xfer_ctx,
+               struct hsi_msg *pdu);
+
+void dlp_hsi_start_tx(struct dlp_xfer_ctx *xfer_ctx);
+
+void dlp_stop_tx(struct dlp_xfer_ctx *xfer_ctx);
+
+inline void dlp_stop_rx(struct dlp_xfer_ctx *xfer_ctx,
+                       struct dlp_channel *ch_ctx);
+
+int dlp_hsi_port_claim(struct dlp_channel *ch_ctx);
+
+inline void dlp_hsi_port_unclaim(struct dlp_channel *ch_ctx);
+
+/****************************************************************************
+ *
+ * RX/TX xfer contexts
+ *
+ ***************************************************************************/
+
+void dlp_xfer_ctx_init(struct dlp_channel *ch_ctx,
+                      struct dlp_xfer_ctx *xfer_ctx,
+                      unsigned int delay,
+                      unsigned int wait_max,
+                      unsigned int ctrl_max,
+                      xfer_complete_cb complete_cb, unsigned int ttype);
+
+void dlp_xfer_ctx_clear(struct dlp_xfer_ctx *xfer_ctx);
+
+/****************************************************************************
+ *
+ * Time handling
+ *
+ ***************************************************************************/
+inline unsigned long from_usecs(const unsigned long delay);
+
+/****************************************************************************
+ *
+ * CONTROL channel exported functions
+ *
+ ***************************************************************************/
+struct dlp_channel *dlp_ctrl_ctx_create(unsigned int index,
+               struct device *dev);
+
+int dlp_ctrl_ctx_delete(struct dlp_channel *ch_ctx);
+
+int dlp_ctrl_modem_reset(struct dlp_channel *ch_ctx);
+
+inline unsigned int dlp_ctrl_modem_is_ready(void);
+
+int dlp_ctrl_send_echo_cmd(struct dlp_channel *ch_ctx);
+
+int dlp_ctrl_send_open_conn_cmd(struct dlp_channel *ch_ctx);
+
+int dlp_ctrl_send_cancel_conn_cmd(struct dlp_channel *ch_ctx);
+
+/****************************************************************************
+ *
+ * TTY channel exported functions
+ *
+ ***************************************************************************/
+struct dlp_channel *dlp_tty_ctx_create(unsigned int index, struct device *dev);
+
+int dlp_tty_ctx_delete(struct dlp_channel *ch_ctx);
+
+/****************************************************************************
+ *
+ * NETWORK channels exported functions
+ *
+ ***************************************************************************/
+struct dlp_channel *dlp_net_ctx_create(unsigned int index, struct device *dev);
+
+int dlp_net_ctx_delete(struct dlp_channel *ch_ctx);
+
+/****************************************************************************
+ *
+ * Global variables
+ *
+ ***************************************************************************/
+extern struct dlp_driver dlp_drv;
+
+#endif /* _DLP_MAIN_H_ */
diff --git a/drivers/hsi/clients/dlp_net.c b/drivers/hsi/clients/dlp_net.c
new file mode 100644 (file)
index 0000000..59a938d
--- /dev/null
@@ -0,0 +1,928 @@
+/*
+ * dlp_net.c
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI
+ * protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact: Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/moduleparam.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/dma-mapping.h>
+#include <net/arp.h>
+
+#include "dlp_main.h"
+
+#define DEBUG_TAG 0x8
+#define DEBUG_VAR dlp_drv.debug
+
+/* Defaut HSI TX timeout delay (in microseconds) */
+#define NET_HANGUP_DELAY               1000000 /* 1 sec */
+
+/* Defaut NET stack TX timeout delay (in milliseconds) */
+#define DLP_NET_TX_DELAY               20000   /* 20 sec */
+
+/*
+ * struct dlp_net_context - NET channel private data
+ *
+ * @ndev: Registred network device
+ * @net_padd: Padding buffer
+ * @net_padd_dma: Padding buffer dma address
+ */
+struct dlp_net_context {
+       struct net_device *ndev;
+
+       /* Padding buffer */
+       void *net_padd;
+       dma_addr_t net_padd_dma;
+};
+
+/*
+ *
+ *
+ */
+struct dlp_net_tx_params {
+       struct dlp_channel *ch_ctx;
+       struct sk_buff *skb;
+};
+
+/*
+ *
+ * LOCAL functions
+ *
+ **/
+
+/*
+ *
+ *
+ */
+static void dlp_net_modem_hangup(struct dlp_channel *ch_ctx, int reason)
+{
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       ch_ctx->hangup_reason |= reason;
+
+       /* Stop the NET IF */
+       if (!netif_queue_stopped(net_ctx->ndev))
+               netif_stop_queue(net_ctx->ndev);
+
+       EPILOG();
+}
+
+/**
+ *     dlp_net_mdm_coredump_cb -       Modem has signaled a core dump
+ *     @irq: interrupt number
+ *     @dev: our device pointer
+ *
+ *     The modem has indicated a core dump.
+ */
+static void dlp_net_mdm_coredump_cb(struct dlp_channel *ch_ctx)
+{
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+
+       PROLOG("%s", net_ctx->ndev->name);
+
+       dlp_net_modem_hangup(ch_ctx, DLP_MODEM_HU_COREDUMP);
+
+       EPILOG();
+}
+
+/**
+ *     dlp_net_mdm_reset_cb    -       Modem has changed reset state
+ *     @data: channel pointer
+ *
+ *     The modem has either entered or left reset state. Check the GPIO
+ *     line to see which.
+ */
+static void dlp_net_mdm_reset_cb(struct dlp_channel *ch_ctx)
+{
+       PROLOG();
+
+       dlp_net_modem_hangup(ch_ctx, DLP_MODEM_HU_RESET);
+
+       EPILOG();
+}
+
+/**
+ *     dlp_net_credits_available_cb -  TX credits are available
+ *     @data: channel pointer
+ */
+static void dlp_net_credits_available_cb(struct dlp_channel *ch_ctx)
+{
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       /* Restart the NET stack if it was stopped */
+       if (netif_queue_stopped(net_ctx->ndev))
+               netif_wake_queue(net_ctx->ndev);
+
+       EPILOG();
+}
+
+/**
+ *     dlp_net_type_trans
+ *
+ */
+static __be16 dlp_net_type_trans(const char *buffer)
+{
+       if (!buffer)
+               return htons(0);
+
+       /* Look at IP version field */
+       switch ((*buffer) >> 4) {
+       case 4:
+               return htons(ETH_P_IP);
+       case 6:
+               return htons(ETH_P_IPV6);
+       default:
+               CRITICAL("Invalid IP frame header");
+       }
+
+       return htons(0);
+}
+
+/**
+ * dlp_net_complete_tx - bottom-up flow for the TX side
+ * @pdu: a reference to the completed pdu
+ *
+ * A TX transfer has completed: recycle the completed pdu and kick a new
+ * delayed request or enter the IDLE state if nothing else is expected.
+ */
+static void dlp_net_complete_tx(struct hsi_msg *pdu)
+{
+       unsigned long flags;
+       struct dlp_net_tx_params *msg_param = pdu->context;
+       struct dlp_channel *ch_ctx = msg_param->ch_ctx;
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+       struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->tx;
+
+       PROLOG("%s", net_ctx->ndev->name);
+
+       /* TX done, free the skb */
+       dev_kfree_skb(msg_param->skb);
+
+       /* Dump the PDU */
+       // dlp_pdu_dump(pdu, 1);
+
+       /* Update statistics */
+       net_ctx->ndev->stats.tx_bytes += pdu->actual_len;
+       net_ctx->ndev->stats.tx_packets++;
+
+       /* Free the pdu */
+       dlp_pdu_free(pdu, pdu->sgt.sgl->length);
+
+       /* Decrease the CTRL fifo size */
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       dlp_hsi_controller_pop(xfer_ctx);
+
+       /* Decrease the pdus counter */
+       xfer_ctx->all_len--;
+
+       /* Still have queued TX pdu ? */
+       if (xfer_ctx->ctrl_len) {
+               mod_timer(&ch_ctx->hangup_timer,
+                         jiffies + ch_ctx->hangup_delay);
+       } else {
+               del_timer(&ch_ctx->hangup_timer);
+       }
+
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+       EPILOG();
+}
+
+/*
+ * Receive a packet: retrieve, encapsulate and pass over to upper levels
+ */
+static void dlp_net_complete_rx(struct hsi_msg *pdu)
+{
+       struct sk_buff *skb;
+       struct dlp_xfer_ctx *xfer_ctx = pdu->context;
+       struct dlp_channel *ch_ctx = xfer_ctx->channel;
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+       unsigned int more_packets, data_size, ret, offset;
+       unsigned char *skb_data, *data_addr, *start_addr;
+       unsigned int *ptr;
+       unsigned long flags;
+
+       PROLOG("%s, pdu [0x%p, actual_len: %d, sgl->len: %d]",
+              net_ctx->ndev->name, pdu, pdu->actual_len, pdu->sgt.sgl->length);
+
+       ret = 0;
+
+       /* Dump the first 160 bytes */
+       dlp_dbg_dump_pdu(pdu, 16, 160, 0);
+
+       /* Pop the CTRL queue */
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       dlp_hsi_controller_pop(xfer_ctx);
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       /* Get the data pointer */
+       ptr = sg_virt(pdu->sgt.sgl);
+
+       /* Read the header    */
+       /*--------------------*/
+       if (!dlp_pdu_header_valid(pdu)) {
+               CRITICAL("Invalid PDU signature (0x%x)", (*ptr));
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* Read packets desc  */
+       /*---------------------*/
+       start_addr = (unsigned char *)ptr;      /* Skip the header */
+
+       do {
+               /* Get the start offset */
+               ptr++;
+               offset = (*ptr);
+
+               /* Get the size & address */
+               ptr++;
+               more_packets = (*ptr) & DLP_HDR_MORE_DESC;
+               data_size = DLP_HDR_DATA_SIZE((*ptr)) - DLP_HDR_SPACE_AP;
+               data_addr = start_addr + offset + DLP_HDR_SPACE_AP;
+
+               PRINT_RX
+                   ("RX: DESC => data_addr: 0x%p, offset: 0x%x, size: %d\n",
+                    data_addr, offset, data_size);
+
+               /* Dump the first 160 bytes */
+               // dlp_dbg_dump_data_as_byte(data_addr, MIN(data_size, 160), 16, 0);
+
+               /*
+                * The packet has been retrieved from the transmission
+                * medium. Build an skb around it, so upper layers can handle it
+                */
+               skb = netdev_alloc_skb_ip_align(net_ctx->ndev, data_size);
+               if (!skb) {
+                       CRITICAL
+                           ("No more memory (data_size: %d) - packet dropped\n",
+                            data_size);
+
+                       net_ctx->ndev->stats.rx_dropped++;
+                       goto out;
+               }
+
+               skb_data = skb_put(skb, data_size);
+               memcpy(skb_data, data_addr, data_size);
+
+               skb->dev = net_ctx->ndev;
+               skb_reset_mac_header(skb);
+               skb->protocol = dlp_net_type_trans(skb_data);
+               skb->ip_summed = CHECKSUM_UNNECESSARY;  /* don't check it */
+
+               /* Dump the first 160 bytes */
+               //dlp_dbg_dump_data_as_byte(skb->data, MIN(skb->len, 160), 16, 0);
+
+               /* Push received packet up to the IP networking stack */
+               ret = netif_rx(skb);
+
+               /* Update statistics */
+               if (ret) {
+                       WARNING("Packet dropped");
+                       net_ctx->ndev->stats.rx_dropped++;
+
+                       /* Free the allocated skb */
+                       /* FIXME : to be freed ???? */
+               } else {
+                       net_ctx->ndev->stats.rx_bytes += data_size;
+                       net_ctx->ndev->stats.rx_packets++;
+               }
+       }
+       while (more_packets);
+
+       ret = 0;
+
+out:
+       /* Recycle the RX pdu */
+       dlp_pdu_recycle(xfer_ctx, pdu);
+
+       EPILOG("%d", ret);
+}
+
+/**
+ * dlp_net_hangup_timer_cb - timer function for tx timeout hangup request
+ * @param: a reference to the channel to consider
+ */
+static void dlp_net_hangup_timer_cb(unsigned long int param)
+{
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)param;
+
+       PROLOG();
+
+       CRITICAL("Hangup (Timeout) !");
+
+       ch_ctx->hangup_reason |= DLP_MODEM_HU_TIMEOUT;
+       queue_work(dlp_drv.tx_hangup_wq, &ch_ctx->hangup_queue);
+
+       EPILOG();
+}
+
+/**
+ * dlp_net_hsi_tx_timeout - work queue function called from hangup timer
+ * @work: a reference to work queue element
+ *
+ */
+static void dlp_net_hsi_tx_timeout(struct work_struct *work)
+{
+       //struct dlp_channel    *ch_ctx = container_of(work, struct dlp_channel,
+       //                                          hangup_queue);
+
+       /* FIXME : TBD */
+
+       CRITICAL("HSI TX timeout");
+}
+
+/*
+ *
+ * NETWORK INTERFACE functions
+ *
+ **/
+int dlp_net_open(struct net_device *dev)
+{
+       int ret;
+       struct dlp_channel *ch_ctx = netdev_priv(dev);
+
+       PROLOG("%s, hsi_ch:%d", dev->name, ch_ctx->hsi_channel);
+
+       /* Check the modem readiness */
+       if (! dlp_ctrl_modem_is_ready()) {
+               CRITICAL("Unale to open NETWORK IF (Modem NOT ready) !");
+               ret = -EBUSY;
+               goto out;
+       }
+
+       /* Claim the HSI port */
+       ret = dlp_hsi_port_claim(ch_ctx);
+       if (ret) {
+               goto out;
+       }
+
+       // FIXME: To be removed (done in dlp_net_ctx_create)
+       ret = dlp_ctrl_send_open_conn_cmd(ch_ctx);
+       if (ret) {
+               CRITICAL("dlp_ctrl_send_open_conn_cmd() failed !");
+               ret = -EIO;
+               goto unclaim;
+       }
+
+       /* Push all RX pdus */
+       ret = dlp_pop_recycled_push_ctrl(&ch_ctx->rx);
+
+       /* Start the netif */
+       netif_wake_queue(dev);
+
+       EPILOG();
+       return ret;
+
+unclaim:
+       dlp_hsi_port_unclaim(ch_ctx);
+
+out:
+       EPILOG();
+       return ret;
+}
+
+int dlp_net_stop(struct net_device *dev)
+{
+       int ret;
+       struct dlp_channel *ch_ctx = netdev_priv(dev);
+       struct dlp_xfer_ctx *tx_ctx;
+       struct dlp_xfer_ctx *rx_ctx;
+
+       PROLOG("%s, hsi_ch:%d", dev->name, ch_ctx->hsi_channel);
+
+       tx_ctx = &ch_ctx->tx;
+       rx_ctx = &ch_ctx->rx;
+
+       del_timer_sync(&ch_ctx->hangup_timer);
+
+       /* Stop the NET IF */
+       if (!netif_queue_stopped(dev))
+               netif_stop_queue(dev);
+
+       // FIXME: To be removed (should be done in dlp_net_ctx_delete)
+       ret = dlp_ctrl_send_cancel_conn_cmd(ch_ctx);
+       if (ret) {
+               CRITICAL("dlp_ctrl_send_cancel_conn_cmd() failed !");
+       }
+
+       del_timer_sync(&ch_ctx->hangup_timer);
+       hsi_flush(dlp_drv.client);
+
+       /* RX */
+       del_timer_sync(&rx_ctx->timer);
+       dlp_stop_rx(rx_ctx, ch_ctx);
+
+       /* TX */
+       del_timer_sync(&tx_ctx->timer);
+       dlp_stop_tx(tx_ctx);
+
+       dlp_ctx_set_state(tx_ctx, IDLE);
+
+       /* Release the HSI port */
+       dlp_hsi_port_unclaim(ch_ctx);
+
+       EPILOG();
+       return 0;
+}
+
+static void dlp_net_pdu_destructor(struct hsi_msg *pdu)
+{
+       struct dlp_xfer_ctx *xfer_ctx = pdu->context;
+       struct dlp_channel *ch_ctx = xfer_ctx->channel;
+
+       PROLOG();
+
+       dlp_pdu_free(pdu, ch_ctx->pdu_size);
+
+       EPILOG();
+}
+
+/*
+ * Transmit a packet
+ */
+static int dlp_net_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+       struct dlp_channel *ch_ctx = netdev_priv(dev);
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+       struct dlp_net_tx_params *msg_param;
+       int i, ret, nb_padding, nb_entries, nb_packets;
+       unsigned char *skb_data;
+       unsigned int *ptr;
+       unsigned int skb_len, padding_len, offset, desc_size, align_size;
+       struct hsi_msg *new;
+       struct scatterlist *sg;
+       skb_frag_t *frag;
+
+       PROLOG("%s, len:%d, nb_frag: %d",
+              dev->name, skb->len, skb_shinfo(skb)->nr_frags);
+
+       if (!dlp_ctx_have_credits(&ch_ctx->tx, ch_ctx)) {
+               /* Stop the NET if */
+               netif_stop_queue(net_ctx->ndev);
+
+               CRITICAL("No credits available (%d)", ch_ctx->tx.seq_num);
+               ret = NETDEV_TX_BUSY;
+               goto out;
+       }
+
+       /* Dump the first 160 bytes */
+       // dlp_dbg_dump_data_as_byte(skb->data, MIN(skb->len, 160), 16);
+
+       if (skb->len < ETH_ZLEN) {
+               // WARNING("Padding received packet (too small size: %d)", skb->len);
+
+               if (skb_padto(skb, ETH_ZLEN)) {
+                       return NETDEV_TX_OK;
+               }
+       }
+
+       /* Set msg params */
+       msg_param = (struct dlp_net_tx_params *)skb->cb;
+       msg_param->ch_ctx = ch_ctx;
+       msg_param->skb = skb;
+
+       /* Save the timestamp */
+       dev->trans_start = jiffies;
+
+       /* Set the IP header */
+#if 0                          // FIXME : Check if it is V4/6
+       struct iphdr *iph;
+
+       iph = ip_hdr(skb);
+       iph->check = 0;
+
+       {
+               unsigned int saddr, daddr;
+               char *s, *d;
+
+#define        BYTE(b) (((int)b)&0xff)
+
+               saddr = htonl(iph->saddr);
+               daddr = htonl(iph->daddr);
+
+               s = (char *)&saddr;
+               d = (char *)&daddr;
+
+               PDEBUG("\n\tver: 0x%x, ihl: 0x%x, tos: 0x%x, tot_len: %d\n"
+                      "\tid : 0x%x, frag_off: 0x%x\n"
+                      "\tttl: 0x%x, protocol: 0x%x, check: 0x%x\n"
+                      "\tsaddr: [%u.%u.%u.%u]\n"
+                      "\tdaddr: [%u.%u.%u.%u]\n",
+                      iph->version, iph->ihl, iph->tos, ntohs(iph->tot_len),
+                      ntohs(iph->id), ntohs(iph->frag_off),
+                      iph->ttl, iph->protocol, iph->check,
+                      BYTE(s[3]), BYTE(s[2]), BYTE(s[1]), BYTE(s[0]),
+                      BYTE(d[3]), BYTE(d[2]), BYTE(d[1]), BYTE(d[0]));
+       }
+
+       iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+#endif
+
+       /* Compute the number of needed padding entries */
+       nb_padding = 1;
+       if (skb_has_frag_list(skb)) {
+               for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+                       /* Size aligned ? */
+                       nb_padding += (IS_ALIGNED(skb_shinfo(skb)->frags[i].size,DLP_PACKET_ALIGN_CP) ? 0 : 1);
+               }
+       }
+
+       /* Compute the number of needed SGT entries */
+       nb_packets = skb_shinfo(skb)->nr_frags + 1;
+       nb_entries = 1 +        /* Header */
+           nb_packets +        /* Packets */
+           nb_padding;         /* Padding */
+
+       /* Allocate the HSI msg */
+       new = hsi_alloc_msg(nb_entries, GFP_ATOMIC);
+       if (!new) {
+               CRITICAL("No more memory to allocate hsi_msg struct");
+               ret = NETDEV_TX_BUSY;
+               goto out;
+       }
+
+       new->cl = dlp_drv.client;
+       new->channel = ch_ctx->hsi_channel;
+       new->ttype = HSI_MSG_WRITE;
+       new->context = msg_param;
+       new->complete = dlp_net_complete_tx;
+       new->destructor = dlp_net_pdu_destructor;
+
+       /* Allocate the header buffer */
+       sg = new->sgt.sgl;
+
+       desc_size = 1 * 4 +     /* Signature + Seq_num */
+           nb_packets * 8;     /* Packets Offset+Size */
+
+       desc_size = ALIGN(desc_size, DLP_PACKET_ALIGN_CP);
+       desc_size += DLP_HDR_SPACE_CP;
+
+       ptr = dlp_buffer_alloc(desc_size, &sg_dma_address(sg));
+       if (!ptr) {
+               CRITICAL("No more memory to allocate msg descriptors");
+               goto free_msg;
+       }
+
+       /* Set the header buffer */
+       /*-----------------------*/
+       sg_set_buf(sg, ptr, desc_size);
+
+       /* Write packets desc  */
+       /*---------------------*/
+       i = 0;
+       offset = desc_size - DLP_HDR_SPACE_CP;
+
+       PRINT_TX("TX: desc_size: 0x%x, nb_entries:%d, nb_packets:%d\n",
+                desc_size, nb_entries, nb_packets);
+
+       do {
+               if (nb_packets == 1) {
+                       skb_data = skb->data;
+                       skb_len = skb->len;
+               } else {
+                       frag = &skb_shinfo(skb)->frags[i];
+
+                       skb_len = frag->size;
+                       skb_data = (void *)frag->page;
+
+                       CRITICAL("FRAGGGGGGGGGGGGGGGGGGGGG");
+               }
+
+               /* Set the start offset */
+               ptr++;
+               (*ptr) = offset;
+
+               /* Set the size */
+               ptr++;
+               (*ptr) = (DLP_HDR_NO_MORE_DESC | DLP_HDR_COMPLETE_PACKET | skb_len);
+               (*ptr) += DLP_HDR_SPACE_CP;
+
+               /* Set the packet SG entry */
+               sg = sg_next(sg);
+               sg_set_buf(sg, skb_data, skb_len);
+               sg->dma_address = dma_map_single(dlp_drv.controller,
+                                                skb_data,
+                                                skb_len, DMA_TO_DEVICE);
+
+               PRINT_TX("TX: Entry %d: offset: 0x%x, size:0x%x\n", i, offset,
+                        (*ptr));
+
+               /* Still have packets ? */
+               i++;
+               if (i < nb_packets) {
+                       /* Need padding ? */
+                       align_size = ALIGN(skb_len, DLP_PACKET_ALIGN_CP);
+                       if (align_size != skb_len) {
+                               skb_data = net_ctx->net_padd;
+                               skb_len = align_size - skb_len;
+
+                               sg = sg_next(sg);
+                               sg_set_buf(sg, skb_data, skb_len);
+
+                               sg->dma_address =
+                                   dma_map_single(dlp_drv.controller, skb_data,
+                                                  skb_len, DMA_TO_DEVICE);
+                       }
+
+                       /* Update the offset value */
+                       offset += align_size + DLP_HDR_SPACE_CP;
+               } else {
+                       /* Update the offset value */
+                       offset += skb_len + DLP_HDR_SPACE_CP;
+               }
+       }
+       while (i < nb_packets);
+
+       /* Write the padding entry (Check 4 bytes alignment) */
+       /*---------------------------------------------------*/
+       padding_len = ch_ctx->pdu_size - offset;
+       padding_len = (padding_len / 4) * 4;
+       if (padding_len) {
+               sg = sg_next(sg);
+               sg_set_buf(sg, net_ctx->net_padd, padding_len);
+               sg->dma_address = net_ctx->net_padd_dma;
+       }
+
+       PRINT_TX("TX: Entry %d (Padding): offset: 0x%x, size:0x%x\n", i, offset,
+                padding_len);
+
+#if  0
+       /* */
+       u32 first_len, first_mapping;
+       int frag, first_entry = entry;
+
+       BUG_ON(1);
+
+       /* give this initial chunk to the */
+       first_len = skb_headlen(skb);
+       first_mapping = dma_map_single(hp->dma_dev, skb->data, first_len,
+                                      DMA_TO_DEVICE);
+       entry = NEXT_TX(entry);
+
+       for (frag = 0; frag < skb_shinfo(skb)->nr_frags; frag++) {
+               skb_frag_t *this_frag = &skb_shinfo(skb)->frags[frag];
+               u32 len, mapping, this_txflags;
+
+               len = this_frag->size;
+               mapping = dma_map_page(hp->dma_dev, this_frag->page,
+                                      this_frag->page_offset, len,
+                                      DMA_TO_DEVICE);
+               // Process
+
+               entry = NEXT_TX(entry);
+       }
+#endif
+
+       ret = dlp_hsi_controller_push(&ch_ctx->tx, new);
+       if (ret) {
+               ret = NETDEV_TX_BUSY;
+               goto free_msg;
+       }
+
+       ret = NETDEV_TX_OK;
+       EPILOG("%d", ret);
+       return ret;
+
+free_msg:
+       hsi_free_msg(new);
+
+out:
+       EPILOG("%d", ret);
+       return ret;
+}
+
+/*
+ * Deal with a transmit timeout.
+ */
+void dlp_net_tx_timeout(struct net_device *dev)
+{
+       struct dlp_channel *ch_ctx = netdev_priv(dev);
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+
+       PROLOG("%s", dev->name);
+
+       PDEBUG("NET TX timeout at %d ms (latency: %d ms)",
+              jiffies_to_msecs(jiffies),
+              jiffies_to_msecs(jiffies - dev->trans_start));
+
+       /* Update statistics */
+       net_ctx->ndev->stats.tx_errors++;
+
+       EPILOG();
+}
+
+/*
+ *
+ */
+int dlp_net_change_mtu(struct net_device *dev, int new_mtu)
+{
+       int ret = -EPERM;
+
+       PROLOG("%s", dev->name);
+
+       EPILOG();
+       return ret;
+}
+
+static const struct net_device_ops dlp_net_netdev_ops = {
+       .ndo_open = dlp_net_open,
+       .ndo_stop = dlp_net_stop,
+       .ndo_start_xmit = dlp_net_start_xmit,
+       .ndo_change_mtu = dlp_net_change_mtu,
+       .ndo_tx_timeout = dlp_net_tx_timeout,
+};
+
+/*
+ *
+ * INIT function
+ *
+ **/
+void dlp_net_dev_setup(struct net_device *dev)
+{
+       PROLOG();
+
+       dev->netdev_ops = &dlp_net_netdev_ops;
+       dev->watchdog_timeo = DLP_NET_TX_DELAY;
+
+       /* fill in the other fields */
+//  dev->features = NETIF_F_SG | NETIF_F_NO_CSUM; // FIXME: wget is KO
+
+       dev->type = ARPHRD_NONE;
+       dev->mtu = DLP_NET_PDU_SIZE;    // FIXME: check wget crash
+       dev->tx_queue_len = 10;
+       dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+
+       EPILOG();
+}
+
+/****************************************************************************
+ *
+ * Exported functions
+ *
+ ***************************************************************************/
+
+struct dlp_channel *dlp_net_ctx_create(unsigned int index, struct device *dev)
+{
+       struct hsi_client *client = to_hsi_client(dev);
+       struct dlp_channel *ch_ctx;
+       struct net_device *ndev;
+       struct dlp_net_context *net_ctx;
+       int ret;
+
+       PROLOG("%d", index);
+
+       /* Allocate the net device */
+       ndev = alloc_netdev(sizeof(struct dlp_channel),
+                           CONFIG_HSI_DLP_NET_NAME "%d", dlp_net_dev_setup);
+
+       if (!ndev) {
+               CRITICAL("alloc_netdev() failed !");
+               goto out;
+       }
+
+       /* Allocate the context private data */
+       net_ctx = kzalloc(sizeof(struct dlp_net_context), GFP_KERNEL);
+       if (!net_ctx) {
+               CRITICAL("Unable to allocate memory (net_ctx)");
+               goto free_dev;
+       }
+
+       /* Allocate the padding buffer */
+       net_ctx->net_padd = dlp_buffer_alloc(DLP_NET_PDU_SIZE,
+                                            &net_ctx->net_padd_dma);
+
+       if (!net_ctx->net_padd) {
+               CRITICAL("No more memory to allocate padding buffer");
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /* Register the net device */
+       ret = register_netdev(ndev);
+       if (ret) {
+               CRITICAL("register_netdev() for %s failed, error %d\n",
+                        ndev->name, ret);
+               goto free_dev;
+       }
+
+       net_ctx->ndev = ndev;
+       ch_ctx = netdev_priv(ndev);
+
+       /* Save params */
+       ch_ctx->ch_data = net_ctx;
+       ch_ctx->hsi_channel = index;
+       ch_ctx->credits = 0;
+       ch_ctx->pdu_size = DLP_NET_PDU_SIZE;
+       ch_ctx->rx.config = client->rx_cfg;
+       ch_ctx->tx.config = client->tx_cfg;
+
+       spin_lock_init(&ch_ctx->lock);
+       init_timer(&ch_ctx->hangup_timer);
+       init_waitqueue_head(&ch_ctx->tx_empty_event);
+       INIT_WORK(&ch_ctx->hangup_queue, dlp_net_hsi_tx_timeout);
+
+       ch_ctx->hangup_delay = from_usecs(NET_HANGUP_DELAY);
+       ch_ctx->hangup_timer.function = dlp_net_hangup_timer_cb;
+       ch_ctx->hangup_timer.data = (unsigned long int)ch_ctx;
+
+       ch_ctx->modem_coredump_cb = dlp_net_mdm_coredump_cb;
+       ch_ctx->modem_reset_cb = dlp_net_mdm_reset_cb;
+       ch_ctx->credits_available_cb = dlp_net_credits_available_cb;
+
+       dlp_xfer_ctx_init(ch_ctx, &ch_ctx->tx,
+                         DLP_HSI_TX_DELAY,
+                         DLP_HSI_TX_WAIT_FIFO, DLP_HSI_TX_CTRL_FIFO,
+                         dlp_net_complete_tx, HSI_MSG_WRITE);
+
+       dlp_xfer_ctx_init(ch_ctx, &ch_ctx->rx,
+                         DLP_HSI_RX_DELAY,
+                         DLP_HSI_RX_WAIT_FIFO, DLP_HSI_RX_CTRL_FIFO,
+                         dlp_net_complete_rx, HSI_MSG_READ);
+
+       /* FIXME : to be activated */
+       /* Open the HSI channel */
+       //ret = dlp_ctrl_send_open_conn_cmd(ch_ctx);
+       //if (ret) {
+       //      CRITICAL("dlp_ctrl_send_open_conn_cmd() failed !");
+       //      goto free_dev;
+       //}
+
+       /* Allocate RX FIFOs in background */
+       queue_work(dlp_drv.recycle_wq, &ch_ctx->rx.increase_pool);
+
+       EPILOG();
+       return ch_ctx;
+
+free_dev:
+       free_netdev(ndev);
+
+out:
+       EPILOG("Failed");
+       return NULL;
+}
+
+int dlp_net_ctx_delete(struct dlp_channel *ch_ctx)
+{
+       int ret = 0;
+       struct dlp_net_context *net_ctx = ch_ctx->ch_data;
+
+       del_timer_sync(&ch_ctx->hangup_timer);
+
+       /* Unregister the net device */
+       unregister_netdev(net_ctx->ndev);
+
+       /* Delete the xfers context */
+       dlp_xfer_ctx_clear(&ch_ctx->rx);
+       dlp_xfer_ctx_clear(&ch_ctx->tx);
+
+       /* FIXME : to be activated */
+       /* Release the HSI channel */
+       // ret = dlp_ctrl_send_cancel_conn_cmd(ch_ctx);
+       //if (ret) {
+       //      CRITICAL("dlp_ctrl_send_cancel_conn_cmd() failed !");
+       //}
+
+       /* Free the padding buffer */
+       dlp_buffer_free(net_ctx->net_padd,
+                       net_ctx->net_padd_dma, DLP_NET_PDU_SIZE);
+
+       /* Free the ch_ctx */
+       free_netdev(net_ctx->ndev);
+
+       EPILOG();
+       return ret;
+}
diff --git a/drivers/hsi/clients/dlp_tty.c b/drivers/hsi/clients/dlp_tty.c
new file mode 100644 (file)
index 0000000..816c333
--- /dev/null
@@ -0,0 +1,1621 @@
+/*
+ * dlp_tty.c
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI
+ * protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact: Olivier Stoltz Douchet <olivierx.stoltz-douchet@intel.com>
+ *          Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/log2.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/jiffies.h>
+#include <linux/hsi/intel_mid_hsi.h>
+#include <linux/hsi/hsi_dlp.h>
+#include <linux/hsi/hsi.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include "dlp_main.h"
+
+#define DEBUG_TAG 0x4
+#define DEBUG_VAR dlp_drv.debug
+
+#define TTYNAME                                "tty"CONFIG_HSI_DLP_TTY_NAME
+
+#define RX_TTY_FORWARDING_BIT          (1<<DLP_GLOBAL_STATE_SZ)
+#define RX_TTY_REFORWARD_BIT           (2<<DLP_GLOBAL_STATE_SZ)
+
+#define TX_TTY_WRITE_PENDING_BIT       (1<<DLP_GLOBAL_STATE_SZ)
+
+/*
+ * struct dlp_tty_context - TTY channel private data
+ *
+ * @tty_drv: TTY driver struct
+ * @tty_prt: TTY port struct
+ * @buffer_wq: Workqueue for tty buffer flush
+ * @ch_ctx : Channel context ref
+ */
+struct dlp_tty_context {
+       struct tty_port tty_prt;
+       struct tty_driver *tty_drv;
+
+       struct dlp_channel *ch_ctx;
+};
+
+/*
+ *
+ *
+ */
+static void dlp_tty_modem_hangup(struct dlp_channel *ch_ctx, int reason)
+{
+       struct tty_struct *tty;
+       struct dlp_tty_context *tty_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       ch_ctx->hangup_reason |= reason;
+       tty = tty_port_tty_get(&tty_ctx->tty_prt);
+       if (tty) {
+               tty_hangup(tty);
+               tty_kref_put(tty);
+       }
+
+       EPILOG();
+}
+
+/**
+ *     dlp_tty_mdm_coredump_cb -       Modem has signaled a core dump
+ *     @irq: interrupt number
+ *     @dev: our device pointer
+ *
+ *     The modem has indicated a core dump.
+ */
+static void dlp_tty_mdm_coredump_cb(struct dlp_channel *ch_ctx)
+{
+       PROLOG();
+
+       dlp_tty_modem_hangup(ch_ctx, DLP_MODEM_HU_COREDUMP);
+
+       EPILOG();
+}
+
+/**
+ *     dlp_tty_mdm_reset_cb    -       Modem has changed reset state
+ *     @data: channel pointer
+ *
+ *     The modem has either entered or left reset state. Check the GPIO
+ *     line to see which.
+ */
+static void dlp_tty_mdm_reset_cb(struct dlp_channel *ch_ctx)
+{
+       PROLOG();
+
+       dlp_tty_modem_hangup(ch_ctx, DLP_MODEM_HU_RESET);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_pdu_data_ptr - helper function for getting the actual virtual address of
+ *                                     a pdu data, taking into account the header offset
+ * @pdu: a reference to the considered pdu
+ * @offset: an offset to add to the current virtual address of the pdu data
+ *
+ * Returns the virtual base address of the actual pdu data
+ */
+inline __attribute_const__
+    unsigned char *dlp_tty_pdu_data_ptr(struct hsi_msg *pdu, unsigned int offset)
+{
+       u32 *addr = sg_virt(pdu->sgt.sgl);
+
+       /* Skip the Signature (+2) & Start address (+2) */
+       addr += 4;
+
+       return (((unsigned char *)addr) + offset);
+}
+
+/**
+ * dlp_tty_pdu_set_length - write down the length information to the frame header
+ * @frame_ptr: a pointer to the virtual base address of a frame
+ * @sz: the length information to encode in the header
+ */
+inline void dlp_tty_pdu_set_length(struct hsi_msg *pdu, u32 sz)
+{
+       u32 *header = (u32 *) (sg_virt(pdu->sgt.sgl));
+       header[1] = DLP_TTY_HEADER_LENGTH;
+       header[2] = DLP_HDR_NO_MORE_DESC |
+               DLP_HDR_COMPLETE_PACKET | DLP_HDR_DATA_SIZE(sz);
+}
+
+/**
+ * dlp_tty_pdu_skip - skip a chunk of data at the beginning of a pdu
+ * @pdu: a reference to the considered pdu
+ * @copied: the length of the chunk to skip
+ *
+ * This helper function is simply updating the scatterlist information.
+ */
+inline void dlp_tty_pdu_skip(struct hsi_msg *pdu, unsigned int copied)
+{
+       struct scatterlist *sg = pdu->sgt.sgl;
+
+       sg->offset += copied;
+       sg->length += copied;
+       pdu->actual_len -= copied;
+};
+
+/**
+ * dlp_tty_wakeup - wakeup an asleep TTY write function call
+ * @ch_ctx: a reference to the context related to this TTY
+ *
+ * This helper function awakes any asleep TTY write callback function.
+ */
+static void dlp_tty_wakeup(struct dlp_channel *ch_ctx)
+{
+       struct tty_struct *tty;
+       struct dlp_tty_context *tty_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       tty = tty_port_tty_get(&tty_ctx->tty_prt);
+       if (likely(tty)) {
+               tty_wakeup(tty);
+               tty_kref_put(tty);
+       }
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_forward - RX data TTY forwarding helper function
+ * @tty: a reference to the TTY where the data shall be forwarded
+ * @xfer_ctx: a reference to the RX context where the FIFO of waiting pdus sits
+ *
+ * Data contained in the waiting pdu FIFO shall be forwarded to the TTY.
+ * This function is :
+ *     - Pushing as much data as possible to the TTY interface
+ *  - Recycling pdus that have been fully forwarded
+ *  - Kicking a TTY insert
+ *  - Restart delayed job if some data is remaining in the waiting FIFO or if the
+ *    controller FIFO is not full yet.
+ */
+static void dlp_tty_forward(struct tty_struct *tty,
+                           struct dlp_xfer_ctx *xfer_ctx)
+{
+       struct hsi_msg *pdu;
+       unsigned long flags;
+       unsigned char *data_ptr;
+       unsigned int copied;
+       int do_push, ret;
+       char tty_flag;
+
+       PROLOG();
+
+       if (dlp_ctx_has_flag(xfer_ctx, RX_TTY_FORWARDING_BIT)) {
+               dlp_ctx_set_flag(xfer_ctx, RX_TTY_REFORWARD_BIT);
+               return;
+       }
+
+       /* Initialised to 1 to prevent unexpected TTY forwarding resume
+        * function when there is no TTY or when it is throttled */
+       dlp_ctx_set_flag(xfer_ctx, RX_TTY_FORWARDING_BIT);
+
+       copied = 1;
+       do_push = 0;
+
+       del_timer(&xfer_ctx->timer);
+
+shoot_again_now:
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+
+       while (xfer_ctx->wait_len > 0) {
+               read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               write_lock_irqsave(&xfer_ctx->lock, flags);
+               pdu = dlp_fifo_wait_pop(xfer_ctx);
+               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+               if (!pdu)
+                       goto no_more_tty_insert;
+
+               if (pdu->status == HSI_STATUS_COMPLETED)
+                       tty_flag = (likely(!pdu->break_frame)) ?
+                           TTY_NORMAL : TTY_BREAK;
+               else
+                       tty_flag = TTY_FRAME;
+
+               if (unlikely(!tty))
+                       goto recycle_pdu;
+
+               while (pdu->actual_len > 0) {
+
+                       if (test_bit(TTY_THROTTLED, &tty->flags)) {
+                               /* Initialised to 1 to prevent unexpected TTY
+                                * forwarding resume function schedule */
+                               copied = 1;
+                               dlp_fifo_wait_push_back(xfer_ctx, pdu);
+
+                               goto no_more_tty_insert;
+                       }
+
+                       /* Copy the data to the flip buffers */
+                       data_ptr = dlp_tty_pdu_data_ptr(pdu, 0);
+                       copied = (unsigned int)
+                           tty_insert_flip_string_fixed_flag(tty,
+                                                             data_ptr,
+                                                             tty_flag,
+                                                             pdu->actual_len);
+                       dlp_tty_pdu_skip(pdu, copied);
+
+                       /* We'll push the flip buffers each time something has
+                        * been written to them to allow low latency */
+                       do_push |= (copied > 0);
+
+                       if (copied == 0) {
+                               dlp_fifo_wait_push_back(xfer_ctx, pdu);
+
+                               goto no_more_tty_insert;
+                       }
+               }
+
+recycle_pdu:
+               /* Reset the pdu offset & length */
+               dlp_pdu_reset(xfer_ctx,
+                             pdu,
+                             xfer_ctx->payload_len + DLP_TTY_HEADER_LENGTH);
+
+               /* Recycle or free the pdu */
+               dlp_pdu_recycle(xfer_ctx, pdu);
+
+               read_lock_irqsave(&xfer_ctx->lock, flags);
+       }
+
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+no_more_tty_insert:
+       /* Schedule a flip since called from complete_rx() in an interrupt
+        * context instead of tty_flip_buffer_push() */
+       if (do_push)
+               tty_schedule_flip(tty);
+
+       /* If some reforwarding request occur in the meantime, do this now */
+       if (dlp_ctx_has_flag(xfer_ctx, RX_TTY_REFORWARD_BIT)) {
+               dlp_ctx_clear_flag(xfer_ctx, RX_TTY_REFORWARD_BIT);
+               goto shoot_again_now;
+       }
+
+       dlp_ctx_clear_flag(xfer_ctx, RX_TTY_FORWARDING_BIT);
+
+       /* Push any available pud to the CTRL */
+       ret = dlp_pop_recycled_push_ctrl(xfer_ctx);
+
+       /* Shoot again later if there is still pending data to serve or if
+        * the RX controller FIFO is not full yet */
+       if ((!copied) || (unlikely(ret == -EAGAIN)))
+               mod_timer(&xfer_ctx->timer, jiffies + xfer_ctx->delay);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_rx_forward_retry - TTY forwarding retry job
+ * @param: a casted reference to the to the RX context where the FIFO of
+ *        waiting pdus sits
+ *
+ * This simply calls the TTY forwarding function in a tasklet shell.
+ */
+static void dlp_tty_rx_forward_retry(unsigned long param)
+{
+       struct dlp_xfer_ctx *xfer_ctx = (struct dlp_xfer_ctx *)param;
+       struct dlp_tty_context *tty_ctx = xfer_ctx->channel->ch_data;
+       struct tty_struct *tty;
+
+       PROLOG();
+
+       tty = tty_port_tty_get(&tty_ctx->tty_prt);
+
+       dlp_tty_forward(tty, xfer_ctx);
+
+       if (tty)
+               tty_kref_put(tty);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_rx_forward_resume - TTY forwarding resume callback
+ * @tty: a reference to the TTY requesting the resume
+ *
+ * This simply calls the TTY forwarding function as a response to a TTY
+ * unthrottle event.
+ */
+static void dlp_tty_rx_forward_resume(struct tty_struct *tty)
+{
+       struct dlp_channel *ch_ctx;
+
+       PROLOG();
+
+       /* Get the context reference from the driver data if already opened */
+       ch_ctx = (struct dlp_channel *)tty->driver_data;
+
+       if (!ch_ctx)
+               return;
+
+       dlp_tty_forward(tty, &ch_ctx->rx);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_complete_tx - bottom-up flow for the TX side
+ * @pdu: a reference to the completed pdu
+ *
+ * A TX transfer has completed: recycle the completed pdu and kick a new
+ * delayed request to enter the IDLE state if nothing else is expected.
+ */
+static void dlp_tty_complete_tx(struct hsi_msg *pdu)
+{
+       struct dlp_xfer_ctx *xfer_ctx = pdu->context;
+       struct dlp_channel *ch_ctx = xfer_ctx->channel;
+       int pdu_available, wakeup = 0;
+       unsigned long flags;
+
+       PROLOG();
+
+       dlp_drv.reset_ignore = 0;
+
+       /* Dump the PDU */
+       // dlp_pdu_dump(pdu, 1);
+
+       /* Recycle or Free the pdu */
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       dlp_pdu_delete(xfer_ctx, pdu);
+
+       /* Decrease the CTRL fifo size */
+       dlp_hsi_controller_pop(xfer_ctx);
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+
+       /* Still have queued TX pdu ? */
+       if (xfer_ctx->ctrl_len) {
+               mod_timer(&ch_ctx->hangup_timer,
+                         jiffies + ch_ctx->hangup_delay);
+       } else {
+               del_timer(&ch_ctx->hangup_timer);
+       }
+
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       if (dlp_ctx_is_empty(xfer_ctx)) {
+               wake_up(&ch_ctx->tx_empty_event);
+               mod_timer(&xfer_ctx->timer, jiffies + xfer_ctx->delay);
+       } else {
+               del_timer(&xfer_ctx->timer);
+       }
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       pdu_available = (xfer_ctx->wait_len > 0);
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       if (pdu_available)
+               dlp_pop_wait_push_ctrl(xfer_ctx, 1);
+
+       /* Wake-up the TTY write whenever the TX wait FIFO is half empty, and
+        * not before, to prevent too many wakeups */
+       wakeup = ((dlp_ctx_has_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT)) &&
+                 (xfer_ctx->wait_len <= xfer_ctx->wait_max / 2));
+       if (wakeup)
+               dlp_ctx_clear_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT);
+
+       if (wakeup)
+               dlp_tty_wakeup(ch_ctx);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_complete_rx - bottom-up flow for the RX side
+ * @pdu: a reference to the completed pdu
+ *
+ * A RX transfer has completed: push the data conveyed in the pdu to the TTY
+ * interface and signal any existing error.
+ */
+static void dlp_tty_complete_rx(struct hsi_msg *pdu)
+{
+       struct dlp_xfer_ctx *xfer_ctx = pdu->context;
+       struct dlp_tty_context *tty_ctx = xfer_ctx->channel->ch_data;
+       struct tty_struct *tty;
+       unsigned long flags;
+
+       PROLOG();
+
+       tty = tty_port_tty_get(&tty_ctx->tty_prt);
+
+       /* Dump the PDU : Do it before or after the actual_len update ? */
+       dlp_pdu_dump(pdu, 1);
+
+       /* Check and update the PDU len & status */
+       dlp_pdu_update(tty_ctx->ch_ctx, pdu);
+
+       /* Decrease the CTRL fifo size */
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+       dlp_hsi_controller_pop(xfer_ctx);
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+#ifdef CONFIG_HSI_DLP_TTY_STATS
+       xfer_ctx->tty_stats.data_sz += pdu->actual_len;
+       xfer_ctx->tty_stats.pdus_cnt++;
+       if (!xfer_ctx->ctrl_len)
+               xfer_ctx->tty_stats.overflow_cnt++;
+#endif
+
+       dlp_fifo_wait_push(xfer_ctx, pdu);
+
+       dlp_tty_forward(tty, xfer_ctx);
+
+       if (tty)
+               tty_kref_put(tty);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_tx_fifo_wait_recycle - recycle the whole content of the TX waiting FIFO
+ * @xfer_ctx: a reference to the TX context to consider
+ *
+ * This helper function is emptying a waiting TX FIFO and recycling all its
+ * pdus.
+ */
+static void dlp_tty_tx_fifo_wait_recycle(struct dlp_xfer_ctx *xfer_ctx)
+{
+       struct hsi_msg *pdu;
+       unsigned long flags;
+
+       PROLOG();
+
+       dlp_ctx_clear_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT);
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+
+       while ((pdu = dlp_fifo_wait_pop(xfer_ctx))) {
+               xfer_ctx->room -= dlp_pdu_room_in(pdu);
+
+               /* check if pdu is active in dlp_tty_do_write */
+               if (pdu->status != HSI_STATUS_PENDING)
+                       dlp_pdu_delete(xfer_ctx, pdu);
+               else
+                       pdu->break_frame = 0;
+       }
+
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_rx_fifo_wait_recycle - recycle the whole content of the RX waiting FIFO
+ * @xfer_ctx: a reference to the RX context to consider
+ *
+ * This helper function is emptying a waiting RX FIFO and recycling all its
+ * pdus.
+ */
+static void dlp_tty_rx_fifo_wait_recycle(struct dlp_xfer_ctx *xfer_ctx)
+{
+       struct hsi_msg *pdu;
+       unsigned long flags;
+       unsigned int length = xfer_ctx->payload_len + DLP_TTY_HEADER_LENGTH;
+
+       PROLOG();
+
+       dlp_ctx_clear_flag(xfer_ctx,
+                          RX_TTY_FORWARDING_BIT | RX_TTY_REFORWARD_BIT);
+
+       write_lock_irqsave(&xfer_ctx->lock, flags);
+
+       while ((pdu = dlp_fifo_wait_pop(xfer_ctx))) {
+               /* Reset offset & length */
+               dlp_pdu_reset(xfer_ctx, pdu, length);
+
+               /* Recycle or Free the pdu */
+               dlp_pdu_delete(xfer_ctx, pdu);
+       }
+
+       write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       EPILOG();
+}
+
+/*
+ * TTY handling methods
+ */
+
+/**
+ * dlp_tty_wait_until_ctx_sent - waits for all the TX FIFO to be empty
+ * @ch_ctx: a reference to the considered context
+ * @timeout: a timeout value expressed in jiffies
+ */
+inline void dlp_tty_wait_until_ctx_sent(struct dlp_channel *ch_ctx, int timeout)
+{
+       PROLOG();
+
+       wait_event_interruptible_timeout(ch_ctx->tx_empty_event,
+                                        dlp_ctx_is_empty(&ch_ctx->tx),
+                                        timeout);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_port_activate - callback to the TTY port activate function
+ * @port: a reference to the calling TTY port
+ * @tty: a reference to the calling TTY
+ *
+ * Return 0 on success or a negative error code on error.
+ *
+ * The TTY port activate is only called on the first port open.
+ */
+static int dlp_tty_port_activate(struct tty_port *port, struct tty_struct *tty)
+{
+       struct dlp_channel *ch_ctx;
+       struct dlp_xfer_ctx *tx_ctx;
+       struct dlp_xfer_ctx *rx_ctx;
+       int ret;
+
+       PROLOG();
+
+       /* Get the context reference stored in the TTY open() */
+       ch_ctx = (struct dlp_channel *)tty->driver_data;
+       tx_ctx = &ch_ctx->tx;
+       rx_ctx = &ch_ctx->rx;
+
+       /* Update the TX and RX HSI configuration */
+       dlp_ctx_update_status(tx_ctx);
+       dlp_ctx_update_status(rx_ctx);
+
+       /* Configure the DLP channel */
+       // FIXME: To be removed (should be done in dlp_tty_ctx_create)
+       if (tty->count == 1) {
+               ret = dlp_ctrl_send_open_conn_cmd(ch_ctx);
+               if (ret) {
+                       CRITICAL("dlp_ctrl_send_open_conn_cmd() failed !");
+                       goto out;
+               }
+       }
+
+       /* Push all RX pdus */
+       ret = dlp_pop_recycled_push_ctrl(rx_ctx);
+       if (ret == -EAGAIN)
+               mod_timer(&rx_ctx->timer, jiffies + rx_ctx->delay);
+
+out:
+       PDEBUG("tty port activated (ret: %d)", ret);
+
+       EPILOG();
+       return ret;
+}
+
+/**
+ * dlp_tty_port_shutdown - callback to the TTY port shutdown function
+ * @port: a reference to the calling TTY port
+ *
+ * The TTY port shutdown is only called on the last port close.
+ */
+static void dlp_tty_port_shutdown(struct tty_port *port)
+{
+       struct dlp_channel *ch_ctx;
+       struct dlp_tty_context *tty_ctx;
+       struct dlp_xfer_ctx *tx_ctx;
+       struct dlp_xfer_ctx *rx_ctx;
+
+       PROLOG();
+
+       tty_ctx = container_of(port, struct dlp_tty_context, tty_prt);
+       ch_ctx = tty_ctx->ch_ctx;
+       tx_ctx = &ch_ctx->tx;
+       rx_ctx = &ch_ctx->rx;
+
+       /* we need hang_up timer alive to avoid long wait here */
+       if (!(ch_ctx->hangup_reason & DLP_MODEM_HU_TIMEOUT))
+               dlp_tty_wait_until_ctx_sent(ch_ctx, 0);
+
+       del_timer_sync(&ch_ctx->hangup_timer);
+
+       // FIXME: FTE
+       // hsi_flush(dlp_drv.client);
+
+       /* RX */
+       del_timer(&rx_ctx->timer);
+       dlp_tty_rx_fifo_wait_recycle(rx_ctx);
+       dlp_stop_rx(rx_ctx, ch_ctx);
+
+       /* TX */
+       del_timer(&tx_ctx->timer);
+       dlp_stop_tx(tx_ctx);
+       dlp_tty_tx_fifo_wait_recycle(tx_ctx);
+
+       dlp_ctx_set_state(tx_ctx, IDLE);
+
+       /* Close the HSI channel */
+#if 0 /* FIXME : Not supported by the modem */ 
+       if (port->tty->count == 1) {
+               int ret;
+               ret = dlp_ctrl_send_cancel_conn_cmd(ch_ctx);
+               if (ret) {
+                       CRITICAL("dlp_ctrl_send_cancel_conn_cmd() failed !");
+               }
+       }
+#endif
+
+       PDEBUG("tty port shut down");
+       EPILOG();
+}
+
+/**
+ * dlp_tty_open - callback to the TTY open function
+ * @tty: a reference to the calling TTY
+ * @filp: a reference to the calling file
+ *
+ * Return 0 on success or a negative error code on error.
+ *
+ * The HSI layer is only initialised during the first opening.
+ */
+static int dlp_tty_open(struct tty_struct *tty, struct file *filp)
+{
+       struct dlp_channel *ch_ctx;
+       struct dlp_tty_context *tty_ctx;
+       int ret;
+
+       PROLOG();
+
+       /* Check the modem readiness */
+       if (! dlp_ctrl_modem_is_ready()) {
+               CRITICAL("Unale to open TTY (Modem NOT ready) !");
+               ret = -EBUSY;
+               goto out;
+       }
+
+       /* Get the context reference from the driver data if already opened */
+       ch_ctx = (struct dlp_channel *)tty->driver_data;
+
+       /* First open ? */
+       if (!ch_ctx) {
+               ch_ctx = dlp_drv.channels[DLP_CHANNEL_TTY];
+               tty->driver_data = ch_ctx;
+       }
+
+       if (unlikely(!ch_ctx)) {
+               ret = -ENODEV;
+               CRITICAL("Cannot find TTY context (%d)", ret);
+               goto out;
+       }
+
+       /* Open the TTY port (calls port->activate on first opening) */
+       tty_ctx = ch_ctx->ch_data;
+       ret = tty_port_open(&tty_ctx->tty_prt, tty, filp);
+       if (ret)
+               CRITICAL("TTY open failed (%d)", ret);
+
+       /* Set the TTY_NO_WRITE_SPLIT to transfer as much data as possible on
+        * the first write request. This shall not introduce denial of service
+        * as this flag will later adapt to the available TX buffer size. */
+       tty->flags |= (1 << TTY_NO_WRITE_SPLIT);
+
+out:
+       EPILOG();
+       return ret;
+}
+
+/**
+ * dlp_tty_flush_tx_buffer - flushes the TX waiting FIFO
+ * @tty: a reference to the requesting TTY
+ */
+static void dlp_tty_flush_tx_buffer(struct tty_struct *tty)
+{
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data;
+       struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->tx;
+
+       PROLOG();
+
+       dlp_tty_tx_fifo_wait_recycle(xfer_ctx);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_tx_stop - update the TX state machine after expiration of the TX active
+ *              timeout further to a no outstanding TX transaction status
+ * @param: a hidden reference to the TX context to consider
+ *
+ * This helper function updates the TX state if it is currently active and
+ * inform the HSI pduwork and attached controller.
+ */
+void dlp_tty_tx_stop(unsigned long param)
+{
+       struct dlp_xfer_ctx *xfer_ctx = (struct dlp_xfer_ctx *)param;
+
+       PROLOG();
+
+       dlp_stop_tx(xfer_ctx);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_hangup_timer_cb - timer function for tx timeout hangup request
+ * @param: a reference to the channel to consider
+ */
+static void dlp_tty_hangup_timer_cb(unsigned long int param)
+{
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)param;
+
+       PROLOG();
+
+       CRITICAL("Tx timeout");
+
+       ch_ctx->hangup_reason |= DLP_MODEM_HU_TIMEOUT;
+       queue_work(dlp_drv.tx_hangup_wq, &ch_ctx->hangup_queue);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_handle_tx_timeout - work queue function called from hangup timer
+ * @work: a reference to work queue element
+ *
+ * Required since port shutdown calls a mutex that might sleep
+ */
+static void dlp_tty_handle_tx_timeout(struct work_struct *work)
+{
+       struct dlp_channel *ch_ctx = container_of(work, struct dlp_channel,
+                                                 hangup_queue);
+       struct dlp_tty_context *tty_ctx = ch_ctx->ch_data;
+       struct tty_struct *tty;
+
+       PROLOG();
+
+       wake_up(&ch_ctx->tx_empty_event);
+       tty = tty_port_tty_get(&tty_ctx->tty_prt);
+       if (tty) {
+               tty_hangup(tty);
+               tty_kref_put(tty);
+       }
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_hangup - callback to a TTY hangup request
+ * @tty: a reference to the requesting TTY
+ */
+static void dlp_tty_hangup(struct tty_struct *tty)
+{
+       struct dlp_tty_context *tty_ctx =
+           (((struct dlp_channel *)tty->driver_data))->ch_data;
+
+       CRITICAL("tty hangup");
+
+       tty_port_hangup(&tty_ctx->tty_prt);
+}
+
+/**
+ * dlp_tty_wait_until_sent - callback to a TTY wait until sent request
+ * @tty: a reference to the requesting TTY
+ * @timeout: a timeout value expressed in jiffies
+ */
+static void dlp_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data;
+
+       PROLOG();
+
+       dlp_tty_wait_until_ctx_sent(ch_ctx, timeout);
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_close - callback to the TTY close function
+ * @tty: a reference to the calling TTY
+ * @filp: a reference to the calling file
+ *
+ * The HSI layer is only released during the last closing.
+ */
+static void dlp_tty_close(struct tty_struct *tty, struct file *filp)
+{
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data;
+
+       PROLOG();
+
+       if (filp && ch_ctx) {
+               struct dlp_tty_context *tty_ctx = ch_ctx->ch_data;
+               tty_port_close(&tty_ctx->tty_prt, tty, filp);
+       }
+
+       EPILOG();
+}
+
+/**
+ * dlp_tty_do_write - writes data coming from the TTY to the TX FIFO
+ * @xfer_ctx: a reference to the considered TX context
+ * @buf: the virtual address of the current input buffer (from TTY)
+ * @len: the remaining buffer size
+ *
+ * Returns the total size of what has been transferred.
+ *
+ * This is a recursive function, the core of the TTY write callback function.
+ */
+int dlp_tty_do_write(struct dlp_xfer_ctx *xfer_ctx, unsigned char *buf,
+                           int len)
+{
+       struct hsi_msg *pdu;
+       int offset, avail, copied;
+       unsigned int updated_actual_len;
+       unsigned long flags;
+
+       PROLOG();
+
+       offset = 0;
+       avail = 0;
+       copied = 0;
+
+       if (!dlp_ctx_have_credits(xfer_ctx, xfer_ctx->channel)) {
+               CRITICAL("No credits available (%d)", xfer_ctx->seq_num);
+               goto out;
+       }
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       pdu = dlp_fifo_tail(xfer_ctx, &xfer_ctx->wait_pdus);
+       if (pdu) {
+               offset = pdu->actual_len;
+               avail = xfer_ctx->payload_len - offset;
+       }
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       if (avail == 0) {
+               pdu = dlp_fifo_recycled_pop(xfer_ctx);
+               if (pdu) {
+                       offset = 0;
+
+                       read_lock_irqsave(&xfer_ctx->lock, flags);
+                       avail = xfer_ctx->payload_len;
+                       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+                       dlp_fifo_wait_push(xfer_ctx, pdu);
+               }
+       }
+
+       if (!pdu) {
+               goto out;
+       }
+
+       pdu->status = HSI_STATUS_PENDING;
+       /* Do a start TX on new frames only and after having marked
+        * the current frame as pending, e.g. don't touch ! */
+       if (offset == 0) {
+               dlp_hsi_start_tx(xfer_ctx);
+       }
+       else {
+               dlp_ctx_set_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT);
+       }
+
+       copied = min(avail, len);
+       updated_actual_len = pdu->actual_len + copied;
+       dlp_tty_pdu_set_length(pdu, updated_actual_len);
+       (void)memcpy(dlp_tty_pdu_data_ptr(pdu, offset), buf, copied);
+
+       if (pdu->status != HSI_STATUS_ERROR) {  /* still valid ? */
+               pdu->actual_len = updated_actual_len;
+               pdu->status = HSI_STATUS_COMPLETED;
+
+               write_lock_irqsave(&xfer_ctx->lock, flags);
+
+               xfer_ctx->buffered += copied;
+               xfer_ctx->room -= copied;
+
+               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               read_lock_irqsave(&xfer_ctx->lock, flags);
+               avail = (xfer_ctx->ctrl_len < xfer_ctx->ctrl_max);
+               read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+               if (avail)
+                       dlp_pop_wait_push_ctrl(xfer_ctx, 0);
+       } else {
+               /* ERROR frames have already been popped from the wait FIFO */
+               write_lock_irqsave(&xfer_ctx->lock, flags);
+               dlp_pdu_delete(xfer_ctx, pdu);
+               write_unlock_irqrestore(&xfer_ctx->lock, flags);
+               copied = 0;
+       }
+
+out:
+       EPILOG("%d", copied);
+       return copied;
+}
+
+/**
+ * dlp_tty_write - writes data coming from the TTY to the TX FIFO
+ * @tty: a reference to the calling TTY
+ * @buf: the virtual address of the current input buffer (from TTY)
+ * @len: the TTY buffer size
+ *
+ * Returns the total size of what has been transferred in the TX FIFO
+ *
+ * This is the TTY write callback function.
+ */
+static int dlp_tty_write(struct tty_struct *tty, const unsigned char *buf,
+                        int len)
+{
+       struct dlp_xfer_ctx *xfer_ctx =
+           &((struct dlp_channel *)tty->driver_data)->tx;
+       int already_copied, copied;
+       unsigned char *ptr;
+       unsigned long flags;
+
+       PROLOG("seq_num: %d, len: %d", xfer_ctx->seq_num, len);
+
+       read_lock_irqsave(&xfer_ctx->lock, flags);
+       if (xfer_ctx->room >= len)
+               tty->flags |= (1 << TTY_NO_WRITE_SPLIT);
+       else
+               tty->flags &= ~(1 << TTY_NO_WRITE_SPLIT);
+       read_unlock_irqrestore(&xfer_ctx->lock, flags);
+
+       already_copied = 0;
+       while (len > 0) {
+               ptr = (unsigned char *)(buf + already_copied);
+               copied = dlp_tty_do_write(xfer_ctx, ptr, len);
+               if (copied == 0)
+                       break;
+               already_copied += copied;
+               len -= copied;
+       }
+
+       EPILOG("%d", already_copied);
+       return already_copied;
+}
+
+/**
+ * dlp_tty_write_room - returns the available buffer size on the TX FIFO
+ * @tty: a reference to the calling TTY
+ *
+ * Returns the total available size in the TX wait FIFO.
+ */
+static int dlp_tty_write_room(struct tty_struct *tty)
+{
+       struct dlp_xfer_ctx *ch_ctx =
+           &((struct dlp_channel *)tty->driver_data)->tx;
+       unsigned int room;
+       unsigned long flags;
+
+       PROLOG();
+
+       read_lock_irqsave(&ch_ctx->lock, flags);
+       room = ch_ctx->room;
+       read_unlock_irqrestore(&ch_ctx->lock, flags);
+
+       EPILOG("%d", room);
+       return room;
+}
+
+/**
+ * dlp_tty_chars_in_buffer - returns the size of the data hold in the TX FIFO
+ * @tty: a reference to the calling TTY
+ *
+ * Returns the total size of data hold in the TX wait FIFO. It does not take
+ * into account the data which has already been passed to the HSI controller
+ * in both in software and hardware FIFO.
+ */
+static int dlp_tty_chars_in_buffer(struct tty_struct *tty)
+{
+       struct dlp_xfer_ctx *ch_ctx =
+           &((struct dlp_channel *)tty->driver_data)->tx;
+       unsigned int buffered;
+       unsigned long flags;
+
+       PROLOG();
+
+       read_lock_irqsave(&ch_ctx->lock, flags);
+       buffered = ch_ctx->buffered;
+       read_unlock_irqrestore(&ch_ctx->lock, flags);
+
+       EPILOG("%d", buffered);
+       return buffered;
+}
+
+/**
+ * dlp_tty_ioctl - manages the IOCTL read and write requests
+ * @tty: a reference to the calling TTY
+ * @cmd: the IOCTL command
+ * @arg: the I/O argument to pass or retrieve data
+ *
+ * Returns 0 upon normal completion or the error code in case of an error.
+ */
+static int dlp_tty_ioctl(struct tty_struct *tty,
+                        unsigned int cmd, unsigned long arg)
+{
+       struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data;
+       struct work_struct *increase_pool = NULL;
+       unsigned int data;
+#ifdef CONFIG_HSI_DLP_TTY_STATS
+       struct hsi_dlp_stats stats;
+#endif
+       unsigned long flags;
+       int ret;
+
+       PROLOG();
+
+       switch (cmd) {
+       case HSI_DLP_RESET_TX:
+               dlp_tty_tx_fifo_wait_recycle(&ch_ctx->tx);
+               break;
+
+       case HSI_DLP_RESET_RX:
+               dlp_tty_rx_fifo_wait_recycle(&ch_ctx->rx);
+               break;
+
+       case HSI_DLP_GET_TX_STATE:
+               data = dlp_ctx_get_state(&ch_ctx->tx);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_GET_RX_STATE:
+               data = dlp_ctx_get_state(&ch_ctx->rx);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_WAIT_MAX:
+               if (arg > 0) {
+                       write_lock_irqsave(&ch_ctx->tx.lock, flags);
+                       if (arg > ch_ctx->tx.wait_max)
+                               increase_pool = &ch_ctx->tx.increase_pool;
+                       ch_ctx->tx.wait_max = arg;
+                       write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               } else {
+                       dev_dbg(&dlp_drv.client->device,
+                               "Invalid TX wait FIFO size %li\n", arg);
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_TX_WAIT_MAX:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = ch_ctx->tx.wait_max;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_RX_WAIT_MAX:
+               if (arg > 0) {
+                       write_lock_irqsave(&ch_ctx->rx.lock, flags);
+                       if (arg > ch_ctx->rx.ctrl_max)
+                               increase_pool = &ch_ctx->rx.increase_pool;
+                       ch_ctx->rx.wait_max = arg;
+                       write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               } else {
+                       dev_dbg(&dlp_drv.client->device,
+                               "Invalid RX wait FIFO size %li\n", arg);
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_RX_WAIT_MAX:
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               data = ch_ctx->rx.wait_max;
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_CTRL_MAX:
+               if (arg > 0) {
+                       write_lock_irqsave(&ch_ctx->tx.lock, flags);
+                       if (arg > ch_ctx->tx.ctrl_max)
+                               increase_pool = &ch_ctx->tx.increase_pool;
+                       ch_ctx->tx.ctrl_max = arg;
+                       write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               } else {
+                       dev_dbg(&dlp_drv.client->device,
+                               "Invalid TX controller FIFO size %li\n", arg);
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_TX_CTRL_MAX:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = ch_ctx->tx.ctrl_max;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_RX_CTRL_MAX:
+               if (arg > 0) {
+                       write_lock_irqsave(&ch_ctx->rx.lock, flags);
+                       if (arg > ch_ctx->rx.ctrl_max)
+                               increase_pool = &ch_ctx->rx.increase_pool;
+                       ch_ctx->rx.ctrl_max = arg;
+                       write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               } else {
+                       dev_dbg(&dlp_drv.client->device,
+                               "Invalid RX controller FIFO size %li\n", arg);
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_RX_CTRL_MAX:
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               data = ch_ctx->rx.ctrl_max;
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_DELAY:
+               write_lock_irqsave(&ch_ctx->tx.lock, flags);
+               ch_ctx->tx.delay = from_usecs(arg);
+               write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               break;
+
+       case HSI_DLP_GET_TX_DELAY:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = jiffies_to_usecs(ch_ctx->tx.delay);
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_RX_DELAY:
+               write_lock_irqsave(&ch_ctx->rx.lock, flags);
+               ch_ctx->rx.delay = from_usecs(arg);
+               write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               break;
+
+       case HSI_DLP_GET_RX_DELAY:
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               data = jiffies_to_usecs(ch_ctx->rx.delay);
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_FLOW:
+               switch (arg) {
+               case HSI_FLOW_SYNC:
+               case HSI_FLOW_PIPE:
+                       write_lock_irqsave(&ch_ctx->tx.lock, flags);
+                       ch_ctx->tx.config.flow = arg;
+                       write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_TX_FLOW:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = ch_ctx->tx.config.flow;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_RX_FLOW:
+               switch (arg) {
+               case HSI_FLOW_SYNC:
+               case HSI_FLOW_PIPE:
+                       write_lock_irqsave(&ch_ctx->rx.lock, flags);
+                       dlp_drv.client->rx_cfg.flow = arg;
+                       write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_RX_FLOW:
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               data = ch_ctx->rx.config.flow;
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_MODE:
+               switch (arg) {
+               case HSI_MODE_STREAM:
+               case HSI_MODE_FRAME:
+                       write_lock_irqsave(&ch_ctx->tx.lock, flags);
+                       ch_ctx->tx.config.mode = arg;
+                       write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_TX_MODE:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = ch_ctx->tx.config.mode;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_RX_MODE:
+               switch (arg) {
+               case HSI_MODE_STREAM:
+               case HSI_MODE_FRAME:
+                       write_lock_irqsave(&ch_ctx->rx.lock, flags);
+                       ch_ctx->rx.config.mode = arg;
+                       write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_RX_MODE:
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               data = ch_ctx->rx.config.mode;
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_PDU_LEN:
+               if ((arg <= DLP_TTY_HEADER_LENGTH) || (arg > ch_ctx->pdu_size))
+                       return -EINVAL;
+
+               write_lock_irqsave(&ch_ctx->tx.lock, flags);
+               ch_ctx->tx.payload_len =
+                   ((arg + 3) / 4) * 4 - DLP_TTY_HEADER_LENGTH;
+               write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               break;
+
+       case HSI_DLP_GET_TX_PDU_LEN:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = ch_ctx->tx.payload_len + DLP_TTY_HEADER_LENGTH;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_RX_PDU_LEN:
+               if ((arg <= DLP_TTY_HEADER_LENGTH) || (arg > ch_ctx->pdu_size))
+                       return -EINVAL;
+               write_lock_irqsave(&ch_ctx->rx.lock, flags);
+               ch_ctx->rx.payload_len =
+                   ((arg + 3) / 4) * 4 - DLP_TTY_HEADER_LENGTH;
+               write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               break;
+
+       case HSI_DLP_GET_RX_PDU_LEN:
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               data = ch_ctx->rx.payload_len + DLP_TTY_HEADER_LENGTH;
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_ARB_MODE:
+               switch (arg) {
+               case HSI_ARB_RR:
+               case HSI_ARB_PRIO:
+                       write_lock_irqsave(&ch_ctx->tx.lock, flags);
+                       ch_ctx->tx.config.arb_mode = arg;
+                       write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       case HSI_DLP_GET_TX_ARB_MODE:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = ch_ctx->tx.config.arb_mode;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_SET_TX_FREQUENCY:
+               if (arg == 0)
+                       return -EINVAL;
+               write_lock_irqsave(&ch_ctx->tx.lock, flags);
+               ch_ctx->tx.config.speed = arg;
+               write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               break;
+
+       case HSI_DLP_GET_TX_FREQUENCY:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               data = ch_ctx->tx.config.speed;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+#ifdef CONFIG_HSI_DLP_TTY_STATS
+       case HSI_DLP_RESET_TX_STATS:
+               write_lock_irqsave(&ch_ctx->tx.lock, flags);
+               ch_ctx->tx.tty_stats.data_sz = 0;
+               ch_ctx->tx.tty_stats.pdus_cnt = 0;
+               ch_ctx->tx.tty_stats.overflow_cnt = 0;
+               write_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               break;
+
+       case HSI_DLP_GET_TX_STATS:
+               read_lock_irqsave(&ch_ctx->tx.lock, flags);
+               stats.data_sz = ch_ctx->tx.tty_stats.data_sz;
+               stats.pdus_cnt = ch_ctx->tx.tty_stats.pdus_cnt;
+               stats.overflow_cnt = ch_ctx->tx.tty_stats.overflow_cnt;
+               read_unlock_irqrestore(&ch_ctx->tx.lock, flags);
+               return copy_to_user((void __user *)arg, &stats, sizeof(stats));
+               break;
+
+       case HSI_DLP_RESET_RX_STATS:
+               write_lock_irqsave(&ch_ctx->rx.lock, flags);
+               ch_ctx->rx.tty_stats.data_sz = 0;
+               ch_ctx->rx.tty_stats.pdus_cnt = 0;
+               ch_ctx->rx.tty_stats.overflow_cnt = 0;
+               write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               break;
+
+       case HSI_DLP_GET_RX_STATS:
+               read_lock_irqsave(&ch_ctx->rx.lock, flags);
+               stats.data_sz = ch_ctx->rx.tty_stats.data_sz;
+               stats.pdus_cnt = ch_ctx->rx.tty_stats.pdus_cnt;
+               stats.overflow_cnt = ch_ctx->rx.tty_stats.overflow_cnt;
+               read_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return copy_to_user((void __user *)arg, &stats, sizeof(stats));
+               break;
+#endif
+
+       case HSI_DLP_MODEM_RESET:
+               WARNING("Modem reset requested");
+               dlp_ctrl_modem_reset(ch_ctx);
+               break;
+
+       case HSI_DLP_MODEM_STATE:
+               data = !(dlp_drv.reset_ignore);
+               return put_user(data, (unsigned int __user *)arg);
+               break;
+
+       case HSI_DLP_GET_HANGUP_REASON:
+               ret = put_user(ch_ctx->hangup_reason,
+                              (unsigned int __user *)arg);
+               write_lock_irqsave(&ch_ctx->rx.lock, flags);
+               ch_ctx->hangup_reason = 0;
+               write_unlock_irqrestore(&ch_ctx->rx.lock, flags);
+               return ret;
+               break;
+
+       default:
+               return -ENOIOCTLCMD;
+       }
+
+       if (increase_pool)
+               (void)queue_work(dlp_drv.recycle_wq, increase_pool);
+
+       EPILOG();
+       return 0;
+}
+
+/*
+ * Protocol driver handling routines
+ */
+
+/*
+ * dlp_termios_init - default termios initialisation
+ */
+static const struct ktermios dlp_termios_init = {
+       .c_iflag = 0,
+       .c_oflag = 0,
+       .c_cflag = B115200 | CS8,
+       .c_lflag = 0,
+       .c_cc = INIT_C_CC,
+       .c_ispeed = 0,
+       .c_ospeed = 0
+};
+
+/*
+ * dlp_driver_tty_ops - table of supported TTY operations
+ */
+static const struct tty_operations dlp_driver_tty_ops = {
+       .open = dlp_tty_open,
+       .close = dlp_tty_close,
+       .write = dlp_tty_write,
+       .write_room = dlp_tty_write_room,
+       .chars_in_buffer = dlp_tty_chars_in_buffer,
+       .ioctl = dlp_tty_ioctl,
+       .hangup = dlp_tty_hangup,
+       .wait_until_sent = dlp_tty_wait_until_sent,
+       .unthrottle = dlp_tty_rx_forward_resume,
+       .flush_buffer = dlp_tty_flush_tx_buffer,
+};
+
+/*
+ * dlp_port_tty_ops - table of supported TTY port operations
+ */
+static const struct tty_port_operations dlp_port_tty_ops = {
+       .activate = dlp_tty_port_activate,
+       .shutdown = dlp_tty_port_shutdown,
+};
+
+/****************************************************************************
+ *
+ * Exported functions
+ *
+ ***************************************************************************/
+
+struct dlp_channel *dlp_tty_ctx_create(unsigned int index, struct device *dev)
+{
+       struct hsi_client *client = to_hsi_client(dev);
+       struct tty_driver *new_drv;
+       struct dlp_channel *ch_ctx;
+       struct dlp_tty_context *tty_ctx;
+       int ret;
+
+       PROLOG();
+
+       ch_ctx = kzalloc(sizeof(struct dlp_channel), GFP_KERNEL);
+       if (!ch_ctx) {
+               CRITICAL("Unable to allocate memory (ch_ctx)");
+               goto out;
+       }
+
+       /* Allocate the context private data */
+       tty_ctx = kzalloc(sizeof(struct dlp_tty_context), GFP_KERNEL);
+       if (!tty_ctx) {
+               CRITICAL("Unable to allocate memory (tty_ctx)");
+               goto free_ch;
+       }
+
+       /* Allocate & configure the TTY driver */
+       new_drv = alloc_tty_driver(1);
+       if (unlikely(!new_drv)) {
+               CRITICAL("alloc_tty_driver() failed !");
+               goto free_ctx;
+       }
+
+       /* Configure the TTY */
+       new_drv->magic = TTY_DRIVER_MAGIC;
+       new_drv->owner = THIS_MODULE;
+       new_drv->driver_name = DRVNAME;
+       new_drv->name = TTYNAME;
+       new_drv->minor_start = 0;
+       new_drv->num = 1;
+       new_drv->type = TTY_DRIVER_TYPE_SERIAL;
+       new_drv->subtype = SERIAL_TYPE_NORMAL;
+       new_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+       new_drv->init_termios = dlp_termios_init;
+
+       tty_set_operations(new_drv, &dlp_driver_tty_ops);
+
+       /* Register the TTY driver */
+       ret = tty_register_driver(new_drv);
+       if (unlikely(ret)) {
+               CRITICAL("tty_register_driver() failed (%d)", ret);
+               goto free_drv;
+       }
+
+       /* Save params */
+       ch_ctx->ch_data = tty_ctx;
+       ch_ctx->hsi_channel = index;
+       ch_ctx->credits = 0;
+       ch_ctx->pdu_size = DLP_TTY_PDU_LENGTH;
+       ch_ctx->rx.config = client->rx_cfg;
+       ch_ctx->tx.config = client->tx_cfg;
+
+       dlp_drv.reset_ignore = 1;
+
+       spin_lock_init(&ch_ctx->lock);
+       init_timer(&ch_ctx->hangup_timer);
+       init_waitqueue_head(&ch_ctx->tx_empty_event);
+       INIT_WORK(&ch_ctx->hangup_queue, dlp_tty_handle_tx_timeout);
+
+       ch_ctx->hangup_delay = from_usecs(DLP_HANGUP_DELAY);
+       ch_ctx->hangup_timer.function = dlp_tty_hangup_timer_cb;
+       ch_ctx->hangup_timer.data = (unsigned long int)ch_ctx;
+
+       ch_ctx->modem_coredump_cb = dlp_tty_mdm_coredump_cb;
+       ch_ctx->modem_reset_cb = dlp_tty_mdm_reset_cb;
+
+       /* TX & RX contexts */
+       dlp_xfer_ctx_init(ch_ctx, &ch_ctx->tx,
+                         DLP_HSI_TX_DELAY,
+                         DLP_HSI_TX_WAIT_FIFO, DLP_HSI_TX_CTRL_FIFO,
+                         dlp_tty_complete_tx, HSI_MSG_WRITE);
+
+       dlp_xfer_ctx_init(ch_ctx, &ch_ctx->rx,
+                         DLP_HSI_RX_DELAY,
+                         DLP_HSI_RX_WAIT_FIFO, DLP_HSI_RX_CTRL_FIFO,
+                         dlp_tty_complete_rx, HSI_MSG_READ);
+
+       ch_ctx->tx.timer.function = dlp_tty_tx_stop;
+       ch_ctx->rx.timer.function = dlp_tty_rx_forward_retry;
+
+       /* Register the TTY device (port) */
+       tty_port_init(&(tty_ctx->tty_prt));
+       tty_ctx->tty_prt.ops = &dlp_port_tty_ops;
+
+       if (unlikely(!tty_register_device(new_drv, 0, dev))) {
+               CRITICAL("tty_register_device() failed (%d)!", ret);
+               goto unreg_drv;
+       }
+
+       tty_ctx->ch_ctx = ch_ctx;
+       tty_ctx->tty_drv = new_drv;
+
+       /* Allocate RX & TX FIFOs in background */
+       queue_work(dlp_drv.recycle_wq, &ch_ctx->tx.increase_pool);
+       queue_work(dlp_drv.recycle_wq, &ch_ctx->rx.increase_pool);
+
+       EPILOG();
+       return ch_ctx;
+
+unreg_drv:
+       tty_unregister_driver(new_drv);
+
+free_drv:
+       put_tty_driver(new_drv);
+
+free_ctx:
+       kfree(tty_ctx);
+
+free_ch:
+       kfree(ch_ctx);
+
+out:
+       EPILOG("Failed");
+       return NULL;
+}
+
+int dlp_tty_ctx_delete(struct dlp_channel *ch_ctx)
+{
+       int ret = 0;
+       struct dlp_tty_context *tty_ctx = ch_ctx->ch_data;
+
+       PROLOG();
+
+       del_timer(&ch_ctx->hangup_timer);
+
+       /* Unregister device */
+       tty_unregister_device(tty_ctx->tty_drv, 0);
+
+       /* Unregister driver */
+       tty_unregister_driver(tty_ctx->tty_drv);
+
+       /* Free */
+       put_tty_driver(tty_ctx->tty_drv);
+       tty_ctx->tty_drv = NULL;
+
+       /* Delete the xfers context */
+       dlp_xfer_ctx_clear(&ch_ctx->tx);
+       dlp_xfer_ctx_clear(&ch_ctx->rx);
+
+       /* Free the tty_ctx */
+       kfree(tty_ctx);
+
+       /* Free the ch_ctx */
+       kfree(ch_ctx);
+
+       EPILOG();
+       return ret;
+}
index fe14e69..42fe0af 100644 (file)
 
 /* Set the following to prevent ACWAKE toggling (for debugging). This also
  * disbales power management */
+#ifdef CONFIG_HSI_DLP
+#define PREVENT_ACWAKE_TOGGLING
+#else
 #undef PREVENT_ACWAKE_TOGGLING
+#endif
 
 /* Set the following to allow software workaround of the DMA link listing */
 #define USE_SOFWARE_WORKAROUND_FOR_DMA_LLI
@@ -3466,3 +3470,4 @@ MODULE_ALIAS("pci:intel_hsi");
 MODULE_AUTHOR("Olivier Stoltz Douchet <olivierx.stoltz-douchet@intel.com>");
 MODULE_DESCRIPTION("Intel mid HSI Controller Driver");
 MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/hsi/dlp_debug.h b/drivers/hsi/dlp_debug.h
new file mode 100644 (file)
index 0000000..ea1e700
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * hsi_dlp_debug.h
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI
+ * protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact: Olivier Stoltz Douchet <olivierx.stoltz-douchet@intel.com>
+ *          Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef _HSI_DLP_DEBUG_H_
+#define _HSI_DLP_DEBUG_H_
+
+/******************************************************************************
+ * To enable debug:
+ *
+ *     - define the CONFIG_DEBUG debug macro (kernel compile)
+ *
+ *     - define a DEBUG_VAR : Variable to dynamically filter tag/level
+ *                                                      (typically a module parameter)
+ *
+ *     - define a DEBUG_TAG : Tag to debug (typically 1 by .c file)
+ *
+ *  - Activate the debug by:
+ *             echo 0xTAGLEVEL > /sys/module/dlp_lte/parameters/debug
+ *
+ * LEVEL is defined on 12 bits:
+ *             0x000  => nothing, except CRITICAL, WARNING which are always displayed
+ *             0x001  => usefull light traces (PRINT, PTRACE)
+ *             0x002  => prolog & epilog (functions in/out)
+ *             0x004  => Dump TX PDUs
+ *             0x008  => Dump RX PDUs
+ *
+ *             0xFFF => deep debug (everything)
+ *
+ * LEVEL can be mixed together, for example:
+ *             0xFFF => Debug everything (All traces)
+ *             0x3   => prolog/epilog + traces
+ *
+ * TAG:
+ *             0x1 => COMM (to debug the common related part)
+ *             0x2 => CTRL (to debug the ctrl related part)
+ *             0x4 => TTY  (to debug the tty related part)
+ *             0x8 => NET  (to debug the netif related part)
+ *
+ *     Exp: to debug everything (all TAGs & all LEVELs) :
+ *             echo 0xFFFF > /sys/module/dlp_lte/parameters/debug
+ *
+ *  Exp: to debug the TTY functions entry/exit:
+ *             echo 0x4002 > /sys/module/dlp_lte/parameters/debug
+ *
+ *     Exp: to deactivate the debug :
+ *             echo 0x0 > /sys/module/dlp_lte/parameters/debug
+ *
+ *****************************************************************************/
+#include <linux/hsi/hsi.h>
+
+#define DEBUG_NONE             0x000
+#define DEBUG_TRACES   0x001
+#define DEBUG_PROLOG   0x002
+#define DEBUG_TX_PDU   0x004
+#define DEBUG_RX_PDU   0x008
+#define DEBUG_ALL              0xFFF
+
+#define DEBUG_LEVEL_SZ 12
+#define DEBUG_CURR_TAG (DEBUG_TAG << DEBUG_LEVEL_SZ)
+
+#ifdef DEBUG
+       #define PRINT(fmt, args...)     printk (fmt, ## args)
+#else
+       #define PRINT(fmt, args...) /* nothing, its a placeholder */
+#endif
+
+#define CRITICAL(fmt, args...) printk("! %s(" fmt ")\n", __func__, ## args)
+#define WARNING(fmt, args...)  printk("? %s(" fmt ")\n", __func__, ## args)
+
+#define PTRACE(fmt, args...)                                                                   \
+    do                                                                                                                 \
+        if (DEBUG_VAR & (DEBUG_TRACES | DEBUG_CURR_TAG))               \
+            PRINT (" %s(" fmt ")\n", __func__, ## args);               \
+    while (0)
+
+#define PTRACE_NO_FUNC(fmt, args...)                                                   \
+    do                                                                                                                 \
+        if (DEBUG_VAR & (DEBUG_TRACES | DEBUG_CURR_TAG))               \
+            PRINT (fmt, ## args);                                                              \
+    while (0)
+
+#define PRINT_TX(fmt, args...)                                                                 \
+    do                                                                                                                 \
+        if (DEBUG_VAR & (DEBUG_TX_PDU | DEBUG_CURR_TAG))               \
+            PRINT (fmt, ## args);                                                              \
+    while (0)
+
+#define PRINT_RX(fmt, args...)                                                                 \
+    do                                                                                                                 \
+        if (DEBUG_VAR & (DEBUG_RX_PDU | DEBUG_CURR_TAG))               \
+            PRINT (fmt, ## args);                                                              \
+    while (0)
+
+#define PROLOG(fmt, args...)                                                                   \
+    do                                                                                                                 \
+        if (DEBUG_VAR & (DEBUG_PROLOG | DEBUG_CURR_TAG))               \
+            PRINT ("> %s(" fmt ")\n", __func__, ## args);              \
+    while (0)
+
+#define EPILOG(fmt, args...)                                                                   \
+    do                                                                                                                 \
+        if (DEBUG_VAR & (DEBUG_PROLOG | DEBUG_CURR_TAG))               \
+            PRINT ("< %s(" fmt ")\n", __func__, ## args);              \
+    while (0)
+
+#define PDEBUG(fmt, args...)                                                                   \
+    do                                                                                                                 \
+        if (DEBUG_VAR & (DEBUG_ALL | DEBUG_CURR_TAG))                  \
+            PRINT (" %s(" fmt ")\n", __func__, ## args);               \
+    while (0)
+
+
+
+#define HSI_MSG_STATUS_TO_STR(status)                                                                  \
+       (status == HSI_STATUS_COMPLETED         ? "COMPLETED ":                                 \
+       (status == HSI_STATUS_PENDING           ? "PENDING   ":                                 \
+       (status == HSI_STATUS_PROCEEDING)       ? "PROCEEDING":                                 \
+       (status == HSI_STATUS_QUEUED)           ? "QUEUED    ":                                 \
+       (status == HSI_STATUS_ERROR)            ? "ERROR     " : "UNKNOWN   "))
+
+
+/*
+ *
+ *
+ */
+void dlp_dbg_dump_data_as_word(unsigned int *data,
+                                                               int nb_words,
+                                                               int words_per_line,
+                                                               int is_corrupted,
+                                                               int is_rx);
+
+void dlp_dbg_dump_data_as_byte(unsigned char *data,
+                                                               int nb_bytes,
+                                                               int bytes_per_line,
+                                                               int is_corrupted,
+                                                               int is_rx);
+
+void dlp_dbg_dump_pdu(struct hsi_msg *pdu,
+                                               int items_per_line,
+                                               int items_to_dump,
+                                               int dump_as_words);
+
+#endif /* _HSI_DLP_DEBUG_H_ */
+
diff --git a/include/linux/hsi/hsi_dlp.h b/include/linux/hsi/hsi_dlp.h
new file mode 100644 (file)
index 0000000..48ca169
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * hsi_dlp.h
+ *
+ * Intel Mobile Communication modem protocol driver for DLP
+ * (Data Link Protocl (LTE)).
+ * This driver is implementing a 5-channel HSI protocol consisting of:
+ * - An internal communication control channel;
+ * - A multiplexed channel exporting a TTY interface;
+ * - Three dedicated high speed channels exporting each a network interface.
+ * All channels are using fixed-length pdus, although of different sizes.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
+ *
+ * Contact: Olivier Stoltz Douchet <olivierx.stoltz-douchet@intel.com>
+ *          Faouaz Tenoutit <faouazx.tenoutit@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#ifndef _HSI_DLP_H
+#define _HSI_DLP_H
+
+#include <linux/ioctl.h>
+
+/* reasons for hanging up */
+enum {
+       DLP_MODEM_HU_TIMEOUT    = 1,
+       DLP_MODEM_HU_RESET              = 2,
+       DLP_MODEM_HU_COREDUMP   = 4,
+};
+
+/**
+ * struct hsi_dlp_stats - statistics related to the TX and RX side
+ * @data_sz: total size of actual transferred data
+ * @pdus_cnt: total number of transferred puds
+ * @overflow_cnt: total number of transfer stalls due to FIFO full
+ */
+struct hsi_dlp_stats {
+       unsigned long long      data_sz;
+       unsigned int            pdus_cnt;
+       unsigned int            overflow_cnt;
+};
+
+#define HSI_DLP_MAGIC  0x77
+
+/*
+ * HSI_DLP_RESET_TX    -        reset the TX state machine (flushes it)
+ */
+#define HSI_DLP_RESET_TX               _IO(HSI_DLP_MAGIC, 0)
+
+/*
+ * HSI_DLP_RESET_RX    -        reset the RX state machine (flushes it)
+ */
+#define HSI_DLP_RESET_RX               _IO(HSI_DLP_MAGIC, 1)
+
+/*
+ * HSI_DLP_GET_TX_STATE        -        get the current state of the TX state machine
+ */
+#define HSI_DLP_GET_TX_STATE           _IOR(HSI_DLP_MAGIC, 2, unsigned int)
+
+/*
+ * HSI_DLP_GET_RX_STATE        -        get the current state of the RX state machine
+ */
+#define HSI_DLP_GET_RX_STATE           _IOR(HSI_DLP_MAGIC, 3, unsigned int)
+
+/*
+ * HSI_DLP_MODEM_RESET         - reset the modem (solicited reset)
+ * Shared with SPI
+ */
+#define HSI_DLP_MODEM_RESET            _IO(HSI_DLP_MAGIC, 4)
+
+/*
+ * HSI_DLP_MODEM_STATE         - return 1 if first transmission completed
+ * Shared with SPI
+ */
+#define HSI_DLP_MODEM_STATE            _IOR(HSI_DLP_MAGIC, 5, int)
+
+/*
+ * HSI_DLP_GET_HANGUP_REASON   - return reason for latest hangup
+ * Shared with SPI
+ */
+#define HSI_DLP_GET_HANGUP_REASON      _IOR(HSI_DLP_MAGIC, 6, int)
+
+/*
+ * HSI_DLP_SET_TX_WAIT_MAX     - set the maximal size of the TX waiting FIFO
+ */
+#define HSI_DLP_SET_TX_WAIT_MAX                _IOW(HSI_DLP_MAGIC, 8, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_WAIT_MAX     - get the maximal size of the TX waiting FIFO
+ */
+#define HSI_DLP_GET_TX_WAIT_MAX                _IOR(HSI_DLP_MAGIC, 8, unsigned int)
+
+/*
+ * HSI_DLP_SET_RX_WAIT_MAX     - set the maximal size of the RX waiting FIFO
+ */
+#define HSI_DLP_SET_RX_WAIT_MAX                _IOW(HSI_DLP_MAGIC, 9, unsigned int)
+
+/*
+ * HSI_DLP_GET_RX_WAIT_MAX     - get the maximal size of the RX waiting FIFO
+ */
+#define HSI_DLP_GET_RX_WAIT_MAX                _IOR(HSI_DLP_MAGIC, 9, unsigned int)
+
+/*
+ * HSI_DLP_SET_TX_CTRL_MAX     - set the maximal size of the TX controller FIFO
+ */
+#define HSI_DLP_SET_TX_CTRL_MAX                _IOW(HSI_DLP_MAGIC, 10, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_CTRL_MAX     - get the maximal size of the TX controller FIFO
+ */
+#define HSI_DLP_GET_TX_CTRL_MAX                _IOR(HSI_DLP_MAGIC, 10, unsigned int)
+
+/*
+ * HSI_DLP_SET_RX_CTRL_MAX     - set the maximal size of the RX controller FIFO
+ */
+#define HSI_DLP_SET_RX_CTRL_MAX                _IOW(HSI_DLP_MAGIC, 11, unsigned int)
+
+/*
+ * HSI_DLP_GET_RX_CTRL_MAX     - get the maximal size of the RX controller FIFO
+ */
+#define HSI_DLP_GET_RX_CTRL_MAX                _IOR(HSI_DLP_MAGIC, 11, unsigned int)
+
+/*
+ * HSI_DLP_SET_TX_DELAY                - set the TX delay in us
+ */
+#define HSI_DLP_SET_TX_DELAY           _IOW(HSI_DLP_MAGIC, 12, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_DELAY                - get the TX delay in us
+ */
+#define HSI_DLP_GET_TX_DELAY           _IOR(HSI_DLP_MAGIC, 12, unsigned int)
+
+/*
+ * HSI_DLP_SET_RX_DELAY                - set the RX delay in us
+ */
+#define HSI_DLP_SET_RX_DELAY           _IOW(HSI_DLP_MAGIC, 13, unsigned int)
+
+/*
+ * HSI_DLP_GET_RX_DELAY                - get the RX delay in us
+ */
+#define HSI_DLP_GET_RX_DELAY           _IOR(HSI_DLP_MAGIC, 13, unsigned int)
+
+/*
+ * HSI_DLP_SET_TX_FLOW         - set the TX flow type (PIPE, SYNC)
+ */
+#define HSI_DLP_SET_TX_FLOW            _IOW(HSI_DLP_MAGIC, 16, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_FLOW         - get the TX flow type (PIPE, SYNC)
+ */
+#define HSI_DLP_GET_TX_FLOW            _IOR(HSI_DLP_MAGIC, 16, unsigned int)
+
+/*
+ * HSI_DLP_SET_RX_FLOW         - set the RX flow type (PIPE, SYNC)
+ */
+#define HSI_DLP_SET_RX_FLOW            _IOW(HSI_DLP_MAGIC, 17, unsigned int)
+
+/*
+ * HSI_DLP_GET_RX_FLOW         - get the RX flow type (PIPE, SYNC)
+ */
+#define HSI_DLP_GET_RX_FLOW            _IOR(HSI_DLP_MAGIC, 17, unsigned int)
+
+/*
+ * HSI_DLP_SET_TX_MODE         - set the TX mode type (FRAME, STREAM)
+ */
+#define HSI_DLP_SET_TX_MODE            _IOW(HSI_DLP_MAGIC, 18, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_MODE         - get the TX mode type (FRAME, STREAM)
+ */
+#define HSI_DLP_GET_TX_MODE            _IOR(HSI_DLP_MAGIC, 18, unsigned int)
+
+/*
+ * HSI_DLP_SET_RX_MODE         - set the RX mode type (FRAME, STREAM)
+ */
+#define HSI_DLP_SET_RX_MODE            _IOW(HSI_DLP_MAGIC, 19, unsigned int)
+
+/*
+ * HSI_DLP_GET_RX_MODE         - get the RX mode type (FRAME, STREAM)
+ */
+#define HSI_DLP_GET_RX_MODE            _IOR(HSI_DLP_MAGIC, 19, unsigned int)
+
+/*
+ * HSI_DLP_SET_TX_PDU_LEN      - set the FFL TX frame length
+ */
+#define HSI_DLP_SET_TX_PDU_LEN _IOW(HSI_DLP_MAGIC, 24, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_PDU_LEN      - get the FFL TX frame length
+ */
+#define HSI_DLP_GET_TX_PDU_LEN _IOR(HSI_DLP_MAGIC, 24, unsigned int)
+
+/*
+ * HSI_DLP_SET_RX_PDU_LEN      - set the FFL RX frame length
+ */
+#define HSI_DLP_SET_RX_PDU_LEN _IOW(HSI_DLP_MAGIC, 25, unsigned int)
+
+/*
+ * HSI_DLP_GET_RX_PDU_LEN      - get the FFL RX frame length
+ */
+#define HSI_DLP_GET_RX_PDU_LEN _IOR(HSI_DLP_MAGIC, 25, unsigned int)
+
+/*
+ * HSI_DLP_SET_TX_ARB_MODE     - set the FFL TX arbitration (RR ou priority)
+ */
+#define HSI_DLP_SET_TX_ARB_MODE                _IOW(HSI_DLP_MAGIC, 28, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_ARB_MODE     - get the FFL TX arbitration (RR or priority)
+ */
+#define HSI_DLP_GET_TX_ARB_MODE                _IOR(HSI_DLP_MAGIC, 28, unsigned int)
+
+/*
+ * HSI_DLP_SET_TX_FREQUENCY    - set the maximum FFL TX frequency (in kbit/s)
+ */
+#define HSI_DLP_SET_TX_FREQUENCY       _IOW(HSI_DLP_MAGIC, 30, unsigned int)
+
+/*
+ * HSI_DLP_GET_TX_FREQUENCY    - get the maximum FFL TX frequency (in kbit/s)
+ */
+#define HSI_DLP_GET_TX_FREQUENCY       _IOR(HSI_DLP_MAGIC, 30, unsigned int)
+
+/*
+ * HSI_DLP_RESET_TX_STATS      - reset the TX statistics
+ */
+#define HSI_DLP_RESET_TX_STATS         _IO(HSI_DLP_MAGIC, 32)
+
+/*
+ * HSI_DLP_GET_TX_STATS                - get the TX statistics
+ */
+#define HSI_DLP_GET_TX_STATS           _IOR(HSI_DLP_MAGIC, 32, \
+                                            struct hsi_dlp_stats)
+
+/*
+ * HSI_DLP_RESET_RX_STATS      - reset the RX statistics
+ */
+#define HSI_DLP_RESET_RX_STATS         _IO(HSI_DLP_MAGIC, 33)
+
+/*
+ * HSI_DLP_GET_RX_STATS                - get the RX statistics
+ */
+#define HSI_DLP_GET_RX_STATS           _IOR(HSI_DLP_MAGIC, 33, \
+                                            struct hsi_dlp_stats)
+
+/*
+ * HSI_DLP_NET_RESET_RX_STATS  - reset the network interface RX statistics
+ */
+#define HSI_DLP_NET_RESET_RX_STATS             _IOW(HSI_DLP_MAGIC, 40, unsigned int)
+
+/*
+ * HSI_DLP_NET_RESET_TX_STATS  - reset the network interface TX statistics
+ */
+#define HSI_DLP_NET_RESET_TX_STATS             _IOW(HSI_DLP_MAGIC, 41, unsigned int)
+
+
+
+#endif /* _HSI_DLP_H */
+