1228 lines
30 KiB
C
1228 lines
30 KiB
C
/*
|
|
* 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 "dlp_main.h"
|
|
|
|
/*
|
|
* 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 CMD_ID(b, id) (LSB(b) | (id << 4))
|
|
#define CMD_ID_ERR(p, err) (CMD_ID(p->data3, p->id) | err)
|
|
|
|
#define DLP_CMD_TX_TIMOUT 1000 /* 1 sec */
|
|
#define DLP_CMD_RX_TIMOUT 1000 /* 1 sec */
|
|
|
|
#define DLP_ECHO_CMD_CHECKSUM 0xACAFFE
|
|
#define DLP_NOP_CMD_CHECKSUM 0xE7E7EC
|
|
|
|
#define DLP_CTRL_CTX (dlp_drv.channels[DLP_CHANNEL_CTRL]->ch_data)
|
|
|
|
/* DLP commands list */
|
|
#define DLP_CMD_BREAK 0x00
|
|
#define DLP_CMD_ECHO 0x01
|
|
#define DLP_CMD_NOP 0x04
|
|
#define DLP_CMD_CONF_CH 0x05
|
|
#define DLP_CMD_OPEN_CONN 0x07
|
|
#define DLP_CMD_CLOSE_CONN 0x0A
|
|
#define DLP_CMD_ACK 0x0B
|
|
#define DLP_CMD_NACK 0x0C
|
|
#define DLP_CMD_OPEN_CONN_OCTET 0x0E
|
|
#define DLP_CMD_CREDITS 0x0F
|
|
#define DLP_CMD_NONE 0xFF
|
|
|
|
/* 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_command_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_command - 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
|
|
*
|
|
* @wq: Modem readiness/TX timeout worqueue
|
|
* @tx_timeout_work: Modem TX timeout work
|
|
* @response: Received response from the modem
|
|
* @ehandler: HSI client events CB
|
|
* @open_lock: Lock to prevent concurrent open issues
|
|
*/
|
|
struct dlp_ctrl_context {
|
|
/* Modem readiness/TX timeout work & worqueue */
|
|
struct workqueue_struct *wq;
|
|
struct work_struct tx_timeout_work;
|
|
|
|
/* Modem response */
|
|
struct dlp_command response;
|
|
|
|
/* HSI events callback */
|
|
hsi_client_cb ehandler;
|
|
|
|
/* Lock for concurrent open operations */
|
|
spinlock_t open_lock;
|
|
};
|
|
|
|
/**
|
|
* dlp_ctrl_hsi_tx_timout_cb - timer function for tx timeout
|
|
* @param: a reference to the channel to consider
|
|
*
|
|
*/
|
|
static void dlp_ctrl_hsi_tx_timout_cb(unsigned long int param)
|
|
{
|
|
struct dlp_channel *ch_ctx = (struct dlp_channel *)param;
|
|
struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX;
|
|
|
|
pr_debug(DRVNAME ": HSI TX Timeout (CH%d, HSI CH%d)\n",
|
|
ch_ctx->ch_id, ch_ctx->hsi_channel);
|
|
|
|
/* Set the reason & launch the work to handle the hangup */
|
|
dlp_drv.tx_timeout = 1;
|
|
queue_work(ctrl_ctx->wq, &ctrl_ctx->tx_timeout_work);
|
|
}
|
|
|
|
/**
|
|
* dlp_ctrl_handle_tx_timeout - Manage the HSI TX timeout
|
|
* @work: a reference to work queue element
|
|
*
|
|
* Required since hsi_port->flush calls might sleep
|
|
*/
|
|
static void dlp_ctrl_handle_tx_timeout(struct work_struct *work)
|
|
{
|
|
struct dlp_channel *ch_ctx;
|
|
int i;
|
|
|
|
pr_err(DRVNAME ": Processing HSI TX Timeout\n");
|
|
|
|
/* Call any register TX timeout CB */
|
|
for (i = 0; i < DLP_CHANNEL_COUNT; i++) {
|
|
ch_ctx = DLP_CHANNEL_CTX(i);
|
|
/* Call the channel callback function */
|
|
if ((ch_ctx) && (ch_ctx->modem_tx_timeout_cb))
|
|
ch_ctx->modem_tx_timeout_cb(ch_ctx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function will check if the CTRL channel ctx is created
|
|
* (In some special cases (PnP tests for exp), the eDLP protocol will
|
|
* not be registered and the CTRL channel is not initialized at all,
|
|
* but the module_param are created
|
|
*/
|
|
static inline int dlp_ctrl_have_control_context(void)
|
|
{
|
|
int have_ctrl = 0;
|
|
struct dlp_channel *ctrl_ch = DLP_CHANNEL_CTX(DLP_CHANNEL_CTRL);
|
|
|
|
if ((ctrl_ch) && (ctrl_ch->ch_data))
|
|
have_ctrl = 1;
|
|
|
|
return have_ctrl;
|
|
}
|
|
|
|
/*
|
|
* Check if the provided PDU size is compatible with the
|
|
* channel PDU size
|
|
*
|
|
* Return the response to send ACK/NACK
|
|
*/
|
|
static int dlp_ctrl_check_pdu_size(struct dlp_channel *ch_ctx,
|
|
struct dlp_command_params *params,
|
|
struct dlp_command_params *tx_params)
|
|
{
|
|
int size, response;
|
|
|
|
memcpy(tx_params, params, sizeof(struct dlp_command_params));
|
|
|
|
size = ((params->data2 << 8) | params->data1);
|
|
|
|
/* OPEN_CONN => ACK by default */
|
|
response = DLP_CMD_ACK;
|
|
tx_params->data3 = CMD_ID(params->data3, params->id);
|
|
|
|
/* Check the requested PDU size */
|
|
if (ch_ctx->rx.pdu_size != size) {
|
|
pr_err(DRVNAME ": ch%d wrong pdu size %d (expected %d)\n",
|
|
ch_ctx->hsi_channel,
|
|
size,
|
|
ch_ctx->rx.pdu_size);
|
|
|
|
/* OPEN_CONN => NACK (Unexpected PDU size) */
|
|
response = DLP_CMD_NACK;
|
|
|
|
/* Set the response params */
|
|
tx_params->data1 = 0;
|
|
tx_params->data2 = 0;
|
|
tx_params->data3 = CMD_ID_ERR(params,
|
|
EDLP_ERR_WRONG_PDU_SIZE);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* Control flow
|
|
*
|
|
*
|
|
***************************************************************************/
|
|
|
|
/*
|
|
*
|
|
*/
|
|
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)
|
|
{
|
|
struct dlp_command *dlp_cmd;
|
|
int flags = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
|
|
|
|
/* Allocate DLP command */
|
|
dlp_cmd = kmalloc(sizeof(struct dlp_command), flags);
|
|
if (!dlp_cmd) {
|
|
pr_err(DRVNAME ": Out of memory (dlp_cmd: 0x%X)\n", id);
|
|
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)
|
|
{
|
|
kfree(dlp_cmd);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void dlp_ctrl_msg_destruct(struct hsi_msg *msg)
|
|
{
|
|
struct dlp_command *dlp_cmd = msg->context;
|
|
|
|
/* Delete the received msg */
|
|
dlp_pdu_free(msg, msg->channel);
|
|
|
|
/* Delete the command */
|
|
kfree(dlp_cmd);
|
|
}
|
|
|
|
/*
|
|
* Send eDLP response
|
|
*/
|
|
|
|
/* Forward declaration */
|
|
static void dlp_ctrl_complete_tx_async(struct hsi_msg *msg);
|
|
|
|
static int dlp_ctrl_send_response(struct dlp_channel *ch_ctx,
|
|
struct dlp_command_params *tx_params,
|
|
int response)
|
|
{
|
|
int ret, state;
|
|
struct hsi_msg *tx_msg;
|
|
struct dlp_command *dlp_cmd;
|
|
unsigned long flags;
|
|
|
|
/* Allocate the eDLP response */
|
|
dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx,
|
|
response,
|
|
tx_params->data1,
|
|
tx_params->data2, tx_params->data3);
|
|
if (!dlp_cmd) {
|
|
pr_err(DRVNAME ": Out of memory (dlp_cmd: 0x%X)\n", response);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Allocate a new TX msg */
|
|
tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL,
|
|
HSI_MSG_WRITE,
|
|
DLP_CTRL_TX_PDU_SIZE,
|
|
1,
|
|
dlp_cmd,
|
|
dlp_ctrl_complete_tx_async,
|
|
dlp_ctrl_msg_destruct);
|
|
|
|
if (!tx_msg) {
|
|
pr_err(DRVNAME ": TX alloc failed\n");
|
|
|
|
/* Delete the command */
|
|
kfree(dlp_cmd);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Copy the command data */
|
|
memcpy(sg_virt(tx_msg->sgt.sgl),
|
|
&dlp_cmd->params, sizeof(struct dlp_command_params));
|
|
|
|
spin_lock_irqsave(&ch_ctx->lock, flags);
|
|
state = dlp_ctrl_get_channel_state(ch_ctx->ch_id);
|
|
if ((state != DLP_CH_STATE_OPENING) && (state != DLP_CH_STATE_OPENED) &&
|
|
(ch_ctx->ch_id != DLP_CHANNEL_TRACE))
|
|
spin_unlock_irqrestore(&ch_ctx->lock, flags);
|
|
|
|
else {
|
|
spin_unlock_irqrestore(&ch_ctx->lock, flags);
|
|
/* Send the TX HSI msg */
|
|
ret = hsi_async(tx_msg->cl, tx_msg);
|
|
if (ret)
|
|
pr_err(DRVNAME ": TX xfer failed ! (cmd:0x%X, ret:%d)\n",
|
|
dlp_cmd->params.id, ret);
|
|
|
|
else {
|
|
/* Dump the TX command */
|
|
if (EDLP_CTRL_TX_DATA_REPORT)
|
|
pr_debug(DRVNAME ": CTRL_TX (0x%X)\n",
|
|
*((u32 *)&dlp_cmd->params));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Free the TX msg */
|
|
dlp_pdu_free(tx_msg, tx_msg->channel);
|
|
|
|
/* Delete the command */
|
|
kfree(dlp_cmd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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_channel *ch_ctx = dlp_cmd->channel;
|
|
|
|
dlp_cmd->status = (msg->status == HSI_STATUS_COMPLETED) ? 0 : -EIO;
|
|
|
|
/* Command done, notify the sender */
|
|
complete(&ch_ctx->tx.cmd_xfer_done);
|
|
|
|
/* Delete the received msg */
|
|
dlp_pdu_free(msg, msg->channel);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/* Delete the received msg */
|
|
dlp_pdu_free(msg, msg->channel);
|
|
|
|
/* Delete the command */
|
|
kfree(dlp_cmd);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void dlp_ctrl_complete_rx(struct hsi_msg *msg)
|
|
{
|
|
struct dlp_channel *ch_ctx;
|
|
struct dlp_ctrl_context *ctrl_ctx;
|
|
struct dlp_command *dlp_cmd = msg->context;
|
|
struct dlp_command_params params, tx_params;
|
|
unsigned long flags;
|
|
int hsi_channel, elp_channel, ret, response, msg_complete, state;
|
|
|
|
params.channel = 0; /* To please KlocWork */
|
|
|
|
/* Check the link readiness (TTY still opened) */
|
|
if (!dlp_tty_is_link_valid()) {
|
|
if (EDLP_CTRL_RX_DATA_REPORT)
|
|
pr_debug(DRVNAME ": CTRL: CH%d RX PDU ignored (close:%d, Time out: %d)\n",
|
|
params.channel,
|
|
dlp_drv.tty_closed, dlp_drv.tx_timeout);
|
|
/* Delete the received msg */
|
|
dlp_pdu_free(msg, msg->channel);
|
|
|
|
/* Delete the command */
|
|
dlp_ctrl_cmd_free(dlp_cmd);
|
|
return;
|
|
}
|
|
|
|
/* Copy the reponse */
|
|
memcpy(¶ms,
|
|
sg_virt(msg->sgt.sgl),
|
|
sizeof(struct dlp_command_params));
|
|
|
|
ctrl_ctx = DLP_CTRL_CTX;
|
|
response = -1;
|
|
memcpy(&tx_params, ¶ms, sizeof(struct dlp_command_params));
|
|
msg_complete = (msg->status == HSI_STATUS_COMPLETED);
|
|
|
|
/* Dump the RX command */
|
|
if (EDLP_CTRL_RX_DATA_REPORT)
|
|
pr_debug(DRVNAME ": CTRL_RX (0x%X)\n", *((u32 *)¶ms));
|
|
|
|
hsi_channel = params.channel;
|
|
if ((hsi_channel < 0) || (hsi_channel >= DLP_CHANNEL_COUNT)) {
|
|
pr_err(DRVNAME ": Invalid HSI channel id (%d)\n", hsi_channel);
|
|
goto push_rx;
|
|
}
|
|
|
|
elp_channel = dlp_drv.channels_hsi[hsi_channel].edlp_channel;
|
|
ch_ctx = DLP_CHANNEL_CTX(elp_channel);
|
|
switch (params.id) {
|
|
case DLP_CMD_NOP:
|
|
break;
|
|
|
|
case DLP_CMD_CREDITS:
|
|
if (!msg_complete)
|
|
break;
|
|
|
|
/* 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);
|
|
|
|
/* CREDITS => ACK */
|
|
response = DLP_CMD_ACK;
|
|
|
|
/* Set the response params */
|
|
tx_params.data1 = params.data3;
|
|
tx_params.data2 = 0;
|
|
tx_params.data3 = 0;
|
|
break;
|
|
|
|
case DLP_CMD_OPEN_CONN:
|
|
if (!msg_complete)
|
|
break;
|
|
|
|
/* Ready ? if not, just save the OPEN_CONN request params */
|
|
spin_lock_irqsave(&ctrl_ctx->open_lock, flags);
|
|
state = dlp_ctrl_get_channel_state(hsi_channel);
|
|
if ((state != DLP_CH_STATE_OPENING) && (state != DLP_CH_STATE_OPENED)) {
|
|
pr_debug(DRVNAME ": HSI CH%d OPEN_CONN received (postponed) when state is %d\n",
|
|
params.channel, state);
|
|
/* Only CHANEL_TRACE can support this */
|
|
if (ch_ctx->ch_id == DLP_CHANNEL_TRACE) {
|
|
struct dlp_hsi_channel *hsi_ch;
|
|
|
|
hsi_ch = &dlp_drv.channels_hsi[hsi_channel];
|
|
memcpy(&hsi_ch->open_conn, ¶ms, sizeof(params));
|
|
spin_unlock_irqrestore(&ctrl_ctx->open_lock, flags);
|
|
|
|
response = -1;
|
|
goto push_rx;
|
|
} else {
|
|
/* OPEN_CONN => NACK (Unexpected open when closed) */
|
|
spin_unlock_irqrestore(&ctrl_ctx->open_lock, flags);
|
|
|
|
pr_debug(DRVNAME ": Not allowed for this channel => answer NAK\n");
|
|
response = DLP_CMD_NACK;
|
|
/* Set the response params */
|
|
tx_params.data1 = params.id;
|
|
tx_params.data2 = 0;
|
|
struct dlp_command_params *params_pt = ¶ms;
|
|
tx_params.data3 = CMD_ID_ERR(params_pt, EDLP_ERR_CH_ALREADY_CLOSED);
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&ctrl_ctx->open_lock, flags);
|
|
|
|
pr_debug(DRVNAME ": HSI CH%d OPEN_CONN received (size: %d)\n",
|
|
params.channel,
|
|
(params.data2 << 8) | params.data1);
|
|
|
|
/* Check the PDU size */
|
|
response = dlp_ctrl_check_pdu_size(ch_ctx, ¶ms, &tx_params);
|
|
break;
|
|
|
|
case DLP_CMD_CLOSE_CONN:
|
|
/* CLOSE_CONN => ACK */
|
|
response = DLP_CMD_ACK;
|
|
|
|
/* Set the response params */
|
|
tx_params.data1 = params.data3;
|
|
tx_params.data2 = 0;
|
|
tx_params.data3 = CMD_ID(params.data3, params.id);
|
|
pr_debug(DRVNAME ": ch%d close_conn received\n",
|
|
params.channel);
|
|
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(&ch_ctx->rx.cmd_xfer_done);
|
|
break;
|
|
}
|
|
|
|
/* Any response to send ? */
|
|
if (response == -1)
|
|
goto push_rx;
|
|
|
|
dlp_ctrl_send_response(ch_ctx, &tx_params, response);
|
|
|
|
push_rx:
|
|
/* Push the RX msg again for futur response */
|
|
ret = hsi_async(msg->cl, msg);
|
|
if (ret) {
|
|
pr_err(DRVNAME ": RX push failed, ret:%d\n", 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, msg->channel);
|
|
|
|
/* Delete the command */
|
|
dlp_ctrl_cmd_free(dlp_cmd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @interm_state: the intermidiate state (to be set when TX is OK)
|
|
* @final_state: the final state (to be set when RX (response) is OK)
|
|
* @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 interm_state,
|
|
unsigned char final_state,
|
|
unsigned char param1,
|
|
unsigned char param2, unsigned char param3)
|
|
{
|
|
int ret = 0, old_state;
|
|
struct dlp_ctrl_context *ctrl_ctx;
|
|
struct dlp_command *dlp_cmd;
|
|
struct dlp_command_params expected_resp;
|
|
struct hsi_msg *tx_msg = NULL;
|
|
|
|
/* Check the link readiness (TTY still opened) */
|
|
if (!dlp_tty_is_link_valid()) {
|
|
if (EDLP_CTRL_TX_DATA_REPORT)
|
|
pr_debug(DRVNAME ": CH%d (HSI CH%d) cmd 0x%X ignored (close:%d, Time out: %d)\n",
|
|
ch_ctx->ch_id, ch_ctx->hsi_channel,
|
|
id, dlp_drv.tty_closed,
|
|
dlp_drv.tx_timeout);
|
|
|
|
return ret;
|
|
}
|
|
|
|
ctrl_ctx = DLP_CTRL_CTX;
|
|
|
|
/* Save the current channel state */
|
|
old_state = dlp_ctrl_get_channel_state(ch_ctx->hsi_channel);
|
|
|
|
/* Backup RX callback */
|
|
dlp_save_rx_callbacks(&ctrl_ctx->ehandler);
|
|
|
|
/* Allocate the DLP command */
|
|
dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, id, param1, param2, param3);
|
|
if (!dlp_cmd) {
|
|
pr_err(DRVNAME ": Out of memory (dlp_cmd: 0x%X)\n", id);
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Allocate a new TX msg */
|
|
tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL,
|
|
HSI_MSG_WRITE,
|
|
DLP_CTRL_TX_PDU_SIZE,
|
|
1,
|
|
dlp_cmd,
|
|
dlp_ctrl_complete_tx, dlp_ctrl_msg_destruct);
|
|
|
|
if (!tx_msg) {
|
|
pr_err(DRVNAME ": dlp_pdu_alloc(TX) failed\n");
|
|
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) {
|
|
pr_err(DRVNAME ": Unable to send 0x%X cmd (ret:%d)\n",
|
|
dlp_cmd->params.id, ret);
|
|
|
|
/* Free the TX msg */
|
|
dlp_pdu_free(tx_msg, tx_msg->channel);
|
|
|
|
goto free_cmd;
|
|
}
|
|
|
|
/* Dump the TX command */
|
|
if (EDLP_CTRL_TX_DATA_REPORT)
|
|
pr_debug(DRVNAME ": CTRL_TX (0x%X)\n",
|
|
*((u32 *)&dlp_cmd->params));
|
|
|
|
/* Wait for TX msg to be sent */
|
|
ret = wait_for_completion_timeout(&ch_ctx->tx.cmd_xfer_done,
|
|
msecs_to_jiffies(DLP_CMD_TX_TIMOUT));
|
|
if (ret == 0) {
|
|
pr_err(DRVNAME ": hsi_ch:%d, cmd:0x%X => TX timeout\n",
|
|
dlp_cmd->params.channel, dlp_cmd->params.id);
|
|
|
|
/* No need to call the complete sending call back,
|
|
* because of failure */
|
|
tx_msg->complete = NULL;
|
|
ret = -EIO;
|
|
/* free only the cmd, because
|
|
* the message is already in the controller fifo.
|
|
* It will be freed when the controller fifo will be
|
|
* flushed/freed
|
|
*/
|
|
goto free_cmd;
|
|
}
|
|
|
|
/* TX msg sent, check the status */
|
|
if (dlp_cmd->status) {
|
|
pr_err(DRVNAME ": Failed to send cmd:0x%X\n",
|
|
dlp_cmd->params.id);
|
|
|
|
ret = -EIO;
|
|
/* free only the command because
|
|
* the message has been already freed by the complete_tx
|
|
* callback
|
|
*/
|
|
goto free_cmd;
|
|
}
|
|
|
|
/* TX OK */
|
|
/* 1. Set the intermidiate channel state */
|
|
if (interm_state != DLP_CH_STATE_NONE)
|
|
dlp_ctrl_set_channel_state(ch_ctx->hsi_channel, interm_state);
|
|
|
|
/* Wait for response ? */
|
|
if (response_id == DLP_CMD_NONE) {
|
|
ret = 0;
|
|
goto no_resp;
|
|
}
|
|
|
|
/* 2. Wait for the response */
|
|
ret = wait_for_completion_timeout(&ch_ctx->rx.cmd_xfer_done,
|
|
msecs_to_jiffies(DLP_CMD_RX_TIMOUT));
|
|
if (ret == 0) {
|
|
pr_err(DRVNAME ": hsi_ch:%d, cmd:0x%X => RX timeout\n",
|
|
dlp_cmd->params.channel, dlp_cmd->params.id);
|
|
|
|
ret = -EIO;
|
|
goto free_cmd;
|
|
}
|
|
|
|
/* Set the expected response params */
|
|
expected_resp.id = response_id;
|
|
expected_resp.channel = ch_ctx->hsi_channel;
|
|
|
|
switch (id) {
|
|
case DLP_CMD_CLOSE_CONN:
|
|
expected_resp.data1 = param3;
|
|
expected_resp.data2 = param2;
|
|
expected_resp.data3 = CMD_ID(param1, id);
|
|
break;
|
|
|
|
case DLP_CMD_OPEN_CONN:
|
|
default:
|
|
expected_resp.data1 = param1;
|
|
expected_resp.data2 = param2;
|
|
expected_resp.data3 = CMD_ID(param3, id);
|
|
}
|
|
|
|
/* Check the received response params */
|
|
ret = 0;
|
|
if (memcmp(&ctrl_ctx->response.params,
|
|
&expected_resp,
|
|
sizeof(expected_resp))) {
|
|
pr_err(DRVNAME": cmd 0x%X unexpected response 0x%X (expected 0x%X)",
|
|
id,
|
|
(unsigned int)(*(u32 *)&ctrl_ctx->response.params),
|
|
(unsigned int)(*(u32 *)(&expected_resp)));
|
|
|
|
tx_msg->context = NULL;
|
|
ret = -EIO;
|
|
goto free_cmd;
|
|
}
|
|
|
|
no_resp:
|
|
/* Response received & OK => set the new channel state */
|
|
if (final_state != DLP_CH_STATE_NONE)
|
|
dlp_ctrl_set_channel_state(ch_ctx->hsi_channel, final_state);
|
|
|
|
free_cmd:
|
|
/* Free the DLP command */
|
|
dlp_ctrl_cmd_free(dlp_cmd);
|
|
|
|
out:
|
|
/* Restore RX callback */
|
|
dlp_restore_rx_callbacks(&ctrl_ctx->ehandler);
|
|
|
|
/* Restore the channel state in error case */
|
|
if (ret)
|
|
dlp_ctrl_set_channel_state(ch_ctx->hsi_channel, old_state);
|
|
|
|
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;
|
|
|
|
/* Allocate the DLP command */
|
|
dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, DLP_CMD_NOP, 0, 0, 0);
|
|
if (!dlp_cmd) {
|
|
pr_err(DRVNAME ": Out of memory (rx_pdu)\n");
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Allocate a new RX msg */
|
|
rx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL,
|
|
HSI_MSG_READ,
|
|
DLP_CTRL_RX_PDU_SIZE,
|
|
1,
|
|
dlp_cmd,
|
|
dlp_ctrl_complete_rx, dlp_ctrl_msg_destruct);
|
|
|
|
if (!rx_msg) {
|
|
pr_err(DRVNAME ": dlp_pdu_alloc() failed\n");
|
|
ret = -ENOMEM;
|
|
goto free_cmd;
|
|
}
|
|
|
|
/* Send the RX HSI msg */
|
|
ret = hsi_async(rx_msg->cl, rx_msg);
|
|
if (ret) {
|
|
pr_err(DRVNAME ": RX push failed, ret:%d\n", ret);
|
|
ret = -EIO;
|
|
goto free_msg;
|
|
}
|
|
|
|
return 0;
|
|
|
|
free_msg:
|
|
/* Free the msg */
|
|
dlp_pdu_free(rx_msg, rx_msg->channel);
|
|
|
|
free_cmd:
|
|
/* Delete the command */
|
|
kfree(dlp_cmd);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Push RX PDUs in the controller FIFO for modem requests
|
|
*
|
|
* @ch_ctx: a reference to related channel context
|
|
*
|
|
* @return 0 when OK, an error otherwise
|
|
*/
|
|
static int dlp_ctrl_push_rx_pdus(struct dlp_channel *ch_ctx)
|
|
{
|
|
int i, ret = 0;
|
|
|
|
/* Push RX pdus for CREDTIS/OPEN_CONN/NOP commands */
|
|
for (i = 0; i < DLP_CHANNEL_COUNT; i++)
|
|
dlp_ctrl_push_rx_pdu(ch_ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* Exported functions
|
|
*
|
|
***************************************************************************/
|
|
|
|
/*
|
|
* @brief
|
|
*
|
|
* @param ch_id
|
|
* @param hsi_channel
|
|
* @param dev
|
|
*
|
|
* @return
|
|
*/
|
|
static int dlp_ctrl_ctx_cleanup(struct dlp_channel *ch_ctx);
|
|
|
|
struct dlp_channel *dlp_ctrl_ctx_create(unsigned int ch_id,
|
|
unsigned int hsi_channel,
|
|
struct device *dev)
|
|
{
|
|
struct hsi_client *client = to_hsi_client(dev);
|
|
struct dlp_channel *ch_ctx;
|
|
struct dlp_ctrl_context *ctrl_ctx;
|
|
|
|
ch_ctx = kzalloc(sizeof(struct dlp_channel), GFP_KERNEL);
|
|
if (!ch_ctx) {
|
|
pr_err(DRVNAME ": Out of memory (ctrl_ch_ctx)\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate the context private data */
|
|
ctrl_ctx = kzalloc(sizeof(struct dlp_ctrl_context), GFP_KERNEL);
|
|
if (!ctrl_ctx) {
|
|
pr_err(DRVNAME ": Out of memory (ctrl_ctx)\n");
|
|
goto free_ch;
|
|
}
|
|
|
|
/* Create a workqueue to to manage:
|
|
* - Modem readiness
|
|
* - HSI TX timeout
|
|
*/
|
|
ctrl_ctx->wq = create_singlethread_workqueue(DRVNAME "-ctrl_wq");
|
|
if (!ctrl_ctx->wq) {
|
|
pr_err(DRVNAME ": Unable to create CTRL workqueue\n");
|
|
goto free_ctx;
|
|
}
|
|
|
|
/* Save params */
|
|
ch_ctx->ch_data = ctrl_ctx;
|
|
ch_ctx->ch_id = ch_id;
|
|
ch_ctx->hsi_channel = hsi_channel;
|
|
ch_ctx->rx.config = client->rx_cfg;
|
|
ch_ctx->tx.config = client->tx_cfg;
|
|
|
|
spin_lock_init(&ch_ctx->lock);
|
|
INIT_WORK(&ctrl_ctx->tx_timeout_work, dlp_ctrl_handle_tx_timeout);
|
|
spin_lock_init(&ctrl_ctx->open_lock);
|
|
|
|
/* Register PDUs push, cleanup CBs */
|
|
ch_ctx->push_rx_pdus = dlp_ctrl_push_rx_pdus;
|
|
ch_ctx->cleanup = dlp_ctrl_ctx_cleanup;
|
|
|
|
dlp_xfer_ctx_init(ch_ctx,
|
|
DLP_CTRL_TX_PDU_SIZE, 0, 0, 0, NULL, HSI_MSG_WRITE);
|
|
|
|
dlp_xfer_ctx_init(ch_ctx,
|
|
DLP_CTRL_RX_PDU_SIZE, 0, 0, 0, NULL, HSI_MSG_READ);
|
|
|
|
/* Set ch_ctx, not yet done in the probe */
|
|
DLP_CHANNEL_CTX(DLP_CHANNEL_CTRL) = ch_ctx;
|
|
|
|
return ch_ctx;
|
|
|
|
free_ctx:
|
|
kfree(ctrl_ctx);
|
|
|
|
free_ch:
|
|
kfree(ch_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* @brief Delete any resources allocated by dlp_ctrl_ctx_create
|
|
*
|
|
* @param ch_ctx : The channel context to consider
|
|
*
|
|
* @return 0 when OK, error value otherwise
|
|
*/
|
|
static int dlp_ctrl_ctx_cleanup(struct dlp_channel *ch_ctx)
|
|
{
|
|
struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
|
|
int ret = 0;
|
|
|
|
/* Delete the modem readiness/tx timeout worqueue */
|
|
destroy_workqueue(ctrl_ctx->wq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int dlp_ctrl_ctx_delete(struct dlp_channel *ch_ctx)
|
|
{
|
|
struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data;
|
|
|
|
/* Free the CTRL context */
|
|
kfree(ctrl_ctx);
|
|
|
|
/* Free the ch_ctx */
|
|
kfree(ch_ctx);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* @brief Open the specified channel for communication
|
|
* - Send the OPEN_CONN command to the modem
|
|
* - Return the operation status
|
|
*
|
|
* @param ch_ctx : The channel context to consider
|
|
*
|
|
* @return 0 when OK, error value otherwise
|
|
*/
|
|
int dlp_ctrl_open_channel(struct dlp_channel *ch_ctx)
|
|
{
|
|
int ret = 0;
|
|
unsigned char param1 = PARAM1(ch_ctx->tx.pdu_size);
|
|
unsigned char param2 = PARAM2(ch_ctx->tx.pdu_size);
|
|
struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX;
|
|
|
|
/* Send the OPEN_CONN command */
|
|
ret = dlp_ctrl_cmd_send(ch_ctx,
|
|
DLP_CMD_OPEN_CONN, DLP_CMD_ACK,
|
|
DLP_CH_STATE_OPENING, DLP_CH_STATE_NONE,
|
|
param1, param2, 0);
|
|
|
|
/* Channel correctly opened ? */
|
|
if (ret == 0) {
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ctrl_ctx->open_lock, flags);
|
|
dlp_ctrl_set_channel_state(ch_ctx->hsi_channel,
|
|
DLP_CH_STATE_OPENED);
|
|
spin_unlock_irqrestore(&ctrl_ctx->open_lock, flags);
|
|
|
|
/* Check if we have any waiting OPEN_CONN */
|
|
ret = dlp_ctrl_send_ack_nack(ch_ctx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* @brief Close the specified channel
|
|
* - Send the CLOSE_CONN command to the modem
|
|
* - Return the operation status
|
|
*
|
|
* @param ch_ctx : The channel context to consider
|
|
*
|
|
* @return 0 when OK, error value otherwise
|
|
*/
|
|
int dlp_ctrl_close_channel(struct dlp_channel *ch_ctx)
|
|
{
|
|
int state, ret = 0;
|
|
unsigned char param3 = PARAM1(DLP_DIR_TRANSMIT_AND_RECEIVE);
|
|
|
|
/* Reset the credits counter */
|
|
ch_ctx->credits = 0;
|
|
|
|
/* Reset the RX/TX seq_num */
|
|
ch_ctx->rx.seq_num = 0;
|
|
ch_ctx->tx.seq_num = 0;
|
|
|
|
/* Check if the channel was correctly opened */
|
|
state = dlp_ctrl_get_channel_state(ch_ctx->hsi_channel);
|
|
if (state == DLP_CH_STATE_OPENED) {
|
|
/* Send the command */
|
|
ret = dlp_ctrl_cmd_send(ch_ctx,
|
|
DLP_CMD_CLOSE_CONN, DLP_CMD_ACK,
|
|
DLP_CH_STATE_CLOSING, DLP_CH_STATE_CLOSED,
|
|
0, 0, param3);
|
|
} else {
|
|
pr_warn(DRVNAME ": Can't close CH%d (HSI CH%d) => invalid state: %d\n",
|
|
ch_ctx->ch_id, ch_ctx->hsi_channel, state);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* @brief Send the NOP command
|
|
*
|
|
* @param ch_ctx : The channel context to consider
|
|
*
|
|
* @return 0 when OK, error value otherwise
|
|
*/
|
|
int dlp_ctrl_send_nop(struct dlp_channel *ch_ctx)
|
|
{
|
|
int ret;
|
|
unsigned char param1, param2, param3;
|
|
|
|
param1 = PARAM1(DLP_NOP_CMD_CHECKSUM);
|
|
param2 = PARAM2(DLP_NOP_CMD_CHECKSUM);
|
|
param3 = PARAM3(DLP_NOP_CMD_CHECKSUM);
|
|
|
|
/* Send the NOP command */
|
|
ret = dlp_ctrl_cmd_send(ch_ctx,
|
|
DLP_CMD_NOP, DLP_CMD_NONE,
|
|
DLP_CH_STATE_NONE, DLP_CH_STATE_NONE,
|
|
param1, param2, param3);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* @brief Reply to any waiting OPEN_CONN command
|
|
*
|
|
* @param ch_ctx : The channel context to consider
|
|
*
|
|
* @return 0 when OK, error value otherwise
|
|
*/
|
|
int dlp_ctrl_send_ack_nack(struct dlp_channel *ch_ctx)
|
|
{
|
|
int ret = 0;
|
|
struct dlp_hsi_channel *hsi_ch;
|
|
|
|
/* Get any saved OPEN_CONN params */
|
|
hsi_ch = &dlp_drv.channels_hsi[ch_ctx->hsi_channel];
|
|
|
|
/* Check if we have any waiting OPEN_CONN */
|
|
if (hsi_ch->open_conn) {
|
|
int response;
|
|
struct dlp_command_params *params;
|
|
struct dlp_command_params tx_params;
|
|
|
|
/* Check the PDU size */
|
|
params = (struct dlp_command_params *)&hsi_ch->open_conn;
|
|
response = dlp_ctrl_check_pdu_size(ch_ctx, params, &tx_params);
|
|
|
|
/* Send the response (ACK/NACK) */
|
|
ret = dlp_ctrl_send_response(ch_ctx, &tx_params, response);
|
|
if (ret)
|
|
pr_err(DRVNAME ": ch%d will not opened\n",
|
|
ch_ctx->hsi_channel);
|
|
else {
|
|
pr_debug(DRVNAME ": ch%d open_conn response (0x%X) sent\n",
|
|
params->channel, response);
|
|
|
|
/* Respnse sent => clear the saved command */
|
|
hsi_ch->open_conn = 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* @brief Clean the stored open_conn command from channel context
|
|
*
|
|
* @param none
|
|
*
|
|
* @return none
|
|
*/
|
|
void dlp_ctrl_clean_stored_cmd(void)
|
|
{
|
|
int i;
|
|
struct dlp_hsi_channel *hsi_ch;
|
|
|
|
/* Get any saved OPEN_CONN params */
|
|
for (i = 0; i < DLP_CHANNEL_COUNT; i++) {
|
|
hsi_ch = &dlp_drv.channels_hsi[i];
|
|
hsi_ch->open_conn = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Get the current channel state
|
|
*
|
|
* @param hsi_channel : The HSI channel ID to consider
|
|
*
|
|
* @return the current channel state
|
|
*/
|
|
inline unsigned char dlp_ctrl_get_channel_state(unsigned int hsi_channel)
|
|
{
|
|
unsigned long flags;
|
|
unsigned char state;
|
|
|
|
spin_lock_irqsave(&dlp_drv.lock, flags);
|
|
state = dlp_drv.channels_hsi[hsi_channel].state;
|
|
spin_unlock_irqrestore(&dlp_drv.lock, flags);
|
|
|
|
return state;
|
|
}
|
|
|
|
/*
|
|
* @brief Set the given channel state
|
|
*
|
|
* @param hsi_channel : The HSI channel ID to consider
|
|
* @param state : The new channel state to set
|
|
*
|
|
*/
|
|
inline void dlp_ctrl_set_channel_state(unsigned int hsi_channel,
|
|
unsigned char state)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dlp_drv.lock, flags);
|
|
dlp_drv.channels_hsi[hsi_channel].state = state;
|
|
spin_unlock_irqrestore(&dlp_drv.lock, flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* Hangup/Reset management
|
|
*
|
|
***************************************************************************/
|
|
/*
|
|
* dlp_ctrl_hangup_ctx_init - Initialises the given hangup context
|
|
*
|
|
* @param ch_ctx : Channel context to consider
|
|
*/
|
|
void dlp_ctrl_hangup_ctx_init(struct dlp_channel *ch_ctx,
|
|
void (*timeout_func)(struct dlp_channel *ch_ctx))
|
|
{
|
|
/* Init values */
|
|
ch_ctx->modem_tx_timeout_cb = timeout_func;
|
|
|
|
/* Register the timer CB (Use always the CTRL context) */
|
|
init_timer(&dlp_drv.timer[ch_ctx->ch_id]);
|
|
dlp_drv.timer[ch_ctx->ch_id].function = dlp_ctrl_hsi_tx_timout_cb;
|
|
dlp_drv.timer[ch_ctx->ch_id].data = (unsigned long int)ch_ctx;
|
|
}
|
|
|
|
/**
|
|
* dlp_ctrl_hangup_ctx_deinit - Clears a hangup context
|
|
* @hangup_ctx: a reference to the considered hangup context
|
|
*/
|
|
void dlp_ctrl_hangup_ctx_deinit(struct dlp_channel *ch_ctx)
|
|
{
|
|
unsigned long flags;
|
|
int is_hunging_up;
|
|
|
|
spin_lock_irqsave(&dlp_drv.lock, flags);
|
|
is_hunging_up = dlp_drv.tx_timeout;
|
|
spin_unlock_irqrestore(&dlp_drv.lock, flags);
|
|
|
|
/* No need to wait for the end of the calling work! */
|
|
if (!is_hunging_up) {
|
|
struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX;
|
|
|
|
if (del_timer_sync(&dlp_drv.timer[ch_ctx->ch_id]))
|
|
cancel_work_sync(&ctrl_ctx->tx_timeout_work);
|
|
else
|
|
flush_work(&ctrl_ctx->tx_timeout_work);
|
|
}
|
|
}
|
|
|