# 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
#
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,
.rx_cfg.mode = HSI_MODE_FRAME,
.rx_cfg.channels = 8
},
+#if defined(CONFIG_HSI_FFL_TTY)
[1] = {
.name = hsi_ffl_name,
.hsi_id = 0,
.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,
.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,
.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,
.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;
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
+
#
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
--- /dev/null
+/*
+ * 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(¶ms,
+ 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,
+ ¶ms, 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);
+
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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_ */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
/* 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
MODULE_AUTHOR("Olivier Stoltz Douchet <olivierx.stoltz-douchet@intel.com>");
MODULE_DESCRIPTION("Intel mid HSI Controller Driver");
MODULE_LICENSE("GPL v2");
+
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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 */
+