forked from Minki/linux
87d99063be
When using the ssi-protocol driver with the Nokia N950, the following error is thrown during modem powered up sequence. [13852.274993] port0: SSI error: 0x01 [13852.279205] ssi-protocol ssi-protocol: RX error detected [13852.284820] ssi-protocol ssi-protocol: Main state: 1 [13852.290069] ssi-protocol ssi-protocol: Recv state: 0 [13852.295288] ssi-protocol ssi-protocol: Send state: 0 [13852.300537] ssi-protocol ssi-protocol: CMT Offline [13852.305603] ssi-protocol ssi-protocol: Wake test 1 [13852.310638] ssi-protocol ssi-protocol: Data RX id: 0 [13852.315887] ssi-protocol ssi-protocol: Data TX id: 0 [13856.001464] ssi-protocol ssi-protocol: Watchdog trigerred [13856.007293] ssi-protocol ssi-protocol: Main state: 1 [13856.012542] ssi-protocol ssi-protocol: Recv state: 0 [13856.017761] ssi-protocol ssi-protocol: Send state: 0 [13856.023010] ssi-protocol ssi-protocol: CMT Offline [13856.028045] ssi-protocol ssi-protocol: Wake test 0 [13856.033111] ssi-protocol ssi-protocol: Data RX id: 0 [13856.038330] ssi-protocol ssi-protocol: Data TX id: 0 This patch fixes the issue by using the handshake setup from the Nokia N950 kernel. The new handshake sequence also works with the N900, so there is no need to differentiate between both modems. Tested-by: Aaro Koskinen <aaro.koskinen@iki.fi> Signed-off-by: Sebastian Reichel <sre@kernel.org>
1186 lines
29 KiB
C
1186 lines
29 KiB
C
/*
|
|
* ssi_protocol.c
|
|
*
|
|
* Implementation of the SSI McSAAB improved protocol.
|
|
*
|
|
* Copyright (C) 2010 Nokia Corporation. All rights reserved.
|
|
* Copyright (C) 2013 Sebastian Reichel <sre@kernel.org>
|
|
*
|
|
* Contact: Carlos Chinea <carlos.chinea@nokia.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/atomic.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/if_phonet.h>
|
|
#include <linux/init.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/hsi/hsi.h>
|
|
#include <linux/hsi/ssi_protocol.h>
|
|
|
|
void ssi_waketest(struct hsi_client *cl, unsigned int enable);
|
|
|
|
#define SSIP_TXQUEUE_LEN 100
|
|
#define SSIP_MAX_MTU 65535
|
|
#define SSIP_DEFAULT_MTU 4000
|
|
#define PN_MEDIA_SOS 21
|
|
#define SSIP_MIN_PN_HDR 6 /* FIXME: Revisit */
|
|
#define SSIP_WDTOUT 2000 /* FIXME: has to be 500 msecs */
|
|
#define SSIP_KATOUT 15 /* 15 msecs */
|
|
#define SSIP_MAX_CMDS 5 /* Number of pre-allocated commands buffers */
|
|
#define SSIP_BYTES_TO_FRAMES(x) ((((x) - 1) >> 2) + 1)
|
|
#define SSIP_CMT_LOADER_SYNC 0x11223344
|
|
/*
|
|
* SSI protocol command definitions
|
|
*/
|
|
#define SSIP_COMMAND(data) ((data) >> 28)
|
|
#define SSIP_PAYLOAD(data) ((data) & 0xfffffff)
|
|
/* Commands */
|
|
#define SSIP_SW_BREAK 0
|
|
#define SSIP_BOOTINFO_REQ 1
|
|
#define SSIP_BOOTINFO_RESP 2
|
|
#define SSIP_WAKETEST_RESULT 3
|
|
#define SSIP_START_TRANS 4
|
|
#define SSIP_READY 5
|
|
/* Payloads */
|
|
#define SSIP_DATA_VERSION(data) ((data) & 0xff)
|
|
#define SSIP_LOCAL_VERID 1
|
|
#define SSIP_WAKETEST_OK 0
|
|
#define SSIP_WAKETEST_FAILED 1
|
|
#define SSIP_PDU_LENGTH(data) (((data) >> 8) & 0xffff)
|
|
#define SSIP_MSG_ID(data) ((data) & 0xff)
|
|
/* Generic Command */
|
|
#define SSIP_CMD(cmd, payload) (((cmd) << 28) | ((payload) & 0xfffffff))
|
|
/* Commands for the control channel */
|
|
#define SSIP_BOOTINFO_REQ_CMD(ver) \
|
|
SSIP_CMD(SSIP_BOOTINFO_REQ, SSIP_DATA_VERSION(ver))
|
|
#define SSIP_BOOTINFO_RESP_CMD(ver) \
|
|
SSIP_CMD(SSIP_BOOTINFO_RESP, SSIP_DATA_VERSION(ver))
|
|
#define SSIP_START_TRANS_CMD(pdulen, id) \
|
|
SSIP_CMD(SSIP_START_TRANS, (((pdulen) << 8) | SSIP_MSG_ID(id)))
|
|
#define SSIP_READY_CMD SSIP_CMD(SSIP_READY, 0)
|
|
#define SSIP_SWBREAK_CMD SSIP_CMD(SSIP_SW_BREAK, 0)
|
|
|
|
/* Main state machine states */
|
|
enum {
|
|
INIT,
|
|
HANDSHAKE,
|
|
ACTIVE,
|
|
};
|
|
|
|
/* Send state machine states */
|
|
enum {
|
|
SEND_IDLE,
|
|
WAIT4READY,
|
|
SEND_READY,
|
|
SENDING,
|
|
SENDING_SWBREAK,
|
|
};
|
|
|
|
/* Receive state machine states */
|
|
enum {
|
|
RECV_IDLE,
|
|
RECV_READY,
|
|
RECEIVING,
|
|
};
|
|
|
|
/**
|
|
* struct ssi_protocol - SSI protocol (McSAAB) data
|
|
* @main_state: Main state machine
|
|
* @send_state: TX state machine
|
|
* @recv_state: RX state machine
|
|
* @waketest: Flag to follow wake line test
|
|
* @rxid: RX data id
|
|
* @txid: TX data id
|
|
* @txqueue_len: TX queue length
|
|
* @tx_wd: TX watchdog
|
|
* @rx_wd: RX watchdog
|
|
* @keep_alive: Workaround for SSI HW bug
|
|
* @lock: To serialize access to this struct
|
|
* @netdev: Phonet network device
|
|
* @txqueue: TX data queue
|
|
* @cmdqueue: Queue of free commands
|
|
* @cl: HSI client own reference
|
|
* @link: Link for ssip_list
|
|
* @tx_usecount: Refcount to keep track the slaves that use the wake line
|
|
* @channel_id_cmd: HSI channel id for command stream
|
|
* @channel_id_data: HSI channel id for data stream
|
|
*/
|
|
struct ssi_protocol {
|
|
unsigned int main_state;
|
|
unsigned int send_state;
|
|
unsigned int recv_state;
|
|
unsigned int waketest:1;
|
|
u8 rxid;
|
|
u8 txid;
|
|
unsigned int txqueue_len;
|
|
struct timer_list tx_wd;
|
|
struct timer_list rx_wd;
|
|
struct timer_list keep_alive; /* wake-up workaround */
|
|
spinlock_t lock;
|
|
struct net_device *netdev;
|
|
struct list_head txqueue;
|
|
struct list_head cmdqueue;
|
|
struct hsi_client *cl;
|
|
struct list_head link;
|
|
atomic_t tx_usecnt;
|
|
int channel_id_cmd;
|
|
int channel_id_data;
|
|
};
|
|
|
|
/* List of ssi protocol instances */
|
|
static LIST_HEAD(ssip_list);
|
|
|
|
static void ssip_rxcmd_complete(struct hsi_msg *msg);
|
|
|
|
static inline void ssip_set_cmd(struct hsi_msg *msg, u32 cmd)
|
|
{
|
|
u32 *data;
|
|
|
|
data = sg_virt(msg->sgt.sgl);
|
|
*data = cmd;
|
|
}
|
|
|
|
static inline u32 ssip_get_cmd(struct hsi_msg *msg)
|
|
{
|
|
u32 *data;
|
|
|
|
data = sg_virt(msg->sgt.sgl);
|
|
|
|
return *data;
|
|
}
|
|
|
|
static void ssip_skb_to_msg(struct sk_buff *skb, struct hsi_msg *msg)
|
|
{
|
|
skb_frag_t *frag;
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
BUG_ON(msg->sgt.nents != (unsigned int)(skb_shinfo(skb)->nr_frags + 1));
|
|
|
|
sg = msg->sgt.sgl;
|
|
sg_set_buf(sg, skb->data, skb_headlen(skb));
|
|
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
sg = sg_next(sg);
|
|
BUG_ON(!sg);
|
|
frag = &skb_shinfo(skb)->frags[i];
|
|
sg_set_page(sg, frag->page.p, frag->size, frag->page_offset);
|
|
}
|
|
}
|
|
|
|
static void ssip_free_data(struct hsi_msg *msg)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
skb = msg->context;
|
|
pr_debug("free data: msg %p context %p skb %p\n", msg, msg->context,
|
|
skb);
|
|
msg->destructor = NULL;
|
|
dev_kfree_skb(skb);
|
|
hsi_free_msg(msg);
|
|
}
|
|
|
|
static struct hsi_msg *ssip_alloc_data(struct ssi_protocol *ssi,
|
|
struct sk_buff *skb, gfp_t flags)
|
|
{
|
|
struct hsi_msg *msg;
|
|
|
|
msg = hsi_alloc_msg(skb_shinfo(skb)->nr_frags + 1, flags);
|
|
if (!msg)
|
|
return NULL;
|
|
ssip_skb_to_msg(skb, msg);
|
|
msg->destructor = ssip_free_data;
|
|
msg->channel = ssi->channel_id_data;
|
|
msg->context = skb;
|
|
|
|
return msg;
|
|
}
|
|
|
|
static inline void ssip_release_cmd(struct hsi_msg *msg)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(msg->cl);
|
|
|
|
dev_dbg(&msg->cl->device, "Release cmd 0x%08x\n", ssip_get_cmd(msg));
|
|
spin_lock_bh(&ssi->lock);
|
|
list_add_tail(&msg->link, &ssi->cmdqueue);
|
|
spin_unlock_bh(&ssi->lock);
|
|
}
|
|
|
|
static struct hsi_msg *ssip_claim_cmd(struct ssi_protocol *ssi)
|
|
{
|
|
struct hsi_msg *msg;
|
|
|
|
BUG_ON(list_empty(&ssi->cmdqueue));
|
|
|
|
spin_lock_bh(&ssi->lock);
|
|
msg = list_first_entry(&ssi->cmdqueue, struct hsi_msg, link);
|
|
list_del(&msg->link);
|
|
spin_unlock_bh(&ssi->lock);
|
|
msg->destructor = ssip_release_cmd;
|
|
|
|
return msg;
|
|
}
|
|
|
|
static void ssip_free_cmds(struct ssi_protocol *ssi)
|
|
{
|
|
struct hsi_msg *msg, *tmp;
|
|
|
|
list_for_each_entry_safe(msg, tmp, &ssi->cmdqueue, link) {
|
|
list_del(&msg->link);
|
|
msg->destructor = NULL;
|
|
kfree(sg_virt(msg->sgt.sgl));
|
|
hsi_free_msg(msg);
|
|
}
|
|
}
|
|
|
|
static int ssip_alloc_cmds(struct ssi_protocol *ssi)
|
|
{
|
|
struct hsi_msg *msg;
|
|
u32 *buf;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < SSIP_MAX_CMDS; i++) {
|
|
msg = hsi_alloc_msg(1, GFP_KERNEL);
|
|
if (!msg)
|
|
goto out;
|
|
buf = kmalloc(sizeof(*buf), GFP_KERNEL);
|
|
if (!buf) {
|
|
hsi_free_msg(msg);
|
|
goto out;
|
|
}
|
|
sg_init_one(msg->sgt.sgl, buf, sizeof(*buf));
|
|
msg->channel = ssi->channel_id_cmd;
|
|
list_add_tail(&msg->link, &ssi->cmdqueue);
|
|
}
|
|
|
|
return 0;
|
|
out:
|
|
ssip_free_cmds(ssi);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void ssip_set_rxstate(struct ssi_protocol *ssi, unsigned int state)
|
|
{
|
|
ssi->recv_state = state;
|
|
switch (state) {
|
|
case RECV_IDLE:
|
|
del_timer(&ssi->rx_wd);
|
|
if (ssi->send_state == SEND_IDLE)
|
|
del_timer(&ssi->keep_alive);
|
|
break;
|
|
case RECV_READY:
|
|
/* CMT speech workaround */
|
|
if (atomic_read(&ssi->tx_usecnt))
|
|
break;
|
|
/* Otherwise fall through */
|
|
case RECEIVING:
|
|
mod_timer(&ssi->keep_alive, jiffies +
|
|
msecs_to_jiffies(SSIP_KATOUT));
|
|
mod_timer(&ssi->rx_wd, jiffies + msecs_to_jiffies(SSIP_WDTOUT));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ssip_set_txstate(struct ssi_protocol *ssi, unsigned int state)
|
|
{
|
|
ssi->send_state = state;
|
|
switch (state) {
|
|
case SEND_IDLE:
|
|
case SEND_READY:
|
|
del_timer(&ssi->tx_wd);
|
|
if (ssi->recv_state == RECV_IDLE)
|
|
del_timer(&ssi->keep_alive);
|
|
break;
|
|
case WAIT4READY:
|
|
case SENDING:
|
|
case SENDING_SWBREAK:
|
|
mod_timer(&ssi->keep_alive,
|
|
jiffies + msecs_to_jiffies(SSIP_KATOUT));
|
|
mod_timer(&ssi->tx_wd, jiffies + msecs_to_jiffies(SSIP_WDTOUT));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct hsi_client *ssip_slave_get_master(struct hsi_client *slave)
|
|
{
|
|
struct hsi_client *master = ERR_PTR(-ENODEV);
|
|
struct ssi_protocol *ssi;
|
|
|
|
list_for_each_entry(ssi, &ssip_list, link)
|
|
if (slave->device.parent == ssi->cl->device.parent) {
|
|
master = ssi->cl;
|
|
break;
|
|
}
|
|
|
|
return master;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ssip_slave_get_master);
|
|
|
|
int ssip_slave_start_tx(struct hsi_client *master)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(master);
|
|
|
|
dev_dbg(&master->device, "start TX %d\n", atomic_read(&ssi->tx_usecnt));
|
|
spin_lock_bh(&ssi->lock);
|
|
if (ssi->send_state == SEND_IDLE) {
|
|
ssip_set_txstate(ssi, WAIT4READY);
|
|
hsi_start_tx(master);
|
|
}
|
|
spin_unlock_bh(&ssi->lock);
|
|
atomic_inc(&ssi->tx_usecnt);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ssip_slave_start_tx);
|
|
|
|
int ssip_slave_stop_tx(struct hsi_client *master)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(master);
|
|
|
|
WARN_ON_ONCE(atomic_read(&ssi->tx_usecnt) == 0);
|
|
|
|
if (atomic_dec_and_test(&ssi->tx_usecnt)) {
|
|
spin_lock_bh(&ssi->lock);
|
|
if ((ssi->send_state == SEND_READY) ||
|
|
(ssi->send_state == WAIT4READY)) {
|
|
ssip_set_txstate(ssi, SEND_IDLE);
|
|
hsi_stop_tx(master);
|
|
}
|
|
spin_unlock_bh(&ssi->lock);
|
|
}
|
|
dev_dbg(&master->device, "stop TX %d\n", atomic_read(&ssi->tx_usecnt));
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ssip_slave_stop_tx);
|
|
|
|
int ssip_slave_running(struct hsi_client *master)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(master);
|
|
return netif_running(ssi->netdev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(ssip_slave_running);
|
|
|
|
static void ssip_reset(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct list_head *head, *tmp;
|
|
struct hsi_msg *msg;
|
|
|
|
if (netif_running(ssi->netdev))
|
|
netif_carrier_off(ssi->netdev);
|
|
hsi_flush(cl);
|
|
spin_lock_bh(&ssi->lock);
|
|
if (ssi->send_state != SEND_IDLE)
|
|
hsi_stop_tx(cl);
|
|
if (ssi->waketest)
|
|
ssi_waketest(cl, 0);
|
|
del_timer(&ssi->rx_wd);
|
|
del_timer(&ssi->tx_wd);
|
|
del_timer(&ssi->keep_alive);
|
|
ssi->main_state = 0;
|
|
ssi->send_state = 0;
|
|
ssi->recv_state = 0;
|
|
ssi->waketest = 0;
|
|
ssi->rxid = 0;
|
|
ssi->txid = 0;
|
|
list_for_each_safe(head, tmp, &ssi->txqueue) {
|
|
msg = list_entry(head, struct hsi_msg, link);
|
|
dev_dbg(&cl->device, "Pending TX data\n");
|
|
list_del(head);
|
|
ssip_free_data(msg);
|
|
}
|
|
ssi->txqueue_len = 0;
|
|
spin_unlock_bh(&ssi->lock);
|
|
}
|
|
|
|
static void ssip_dump_state(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *msg;
|
|
|
|
spin_lock_bh(&ssi->lock);
|
|
dev_err(&cl->device, "Main state: %d\n", ssi->main_state);
|
|
dev_err(&cl->device, "Recv state: %d\n", ssi->recv_state);
|
|
dev_err(&cl->device, "Send state: %d\n", ssi->send_state);
|
|
dev_err(&cl->device, "CMT %s\n", (ssi->main_state == ACTIVE) ?
|
|
"Online" : "Offline");
|
|
dev_err(&cl->device, "Wake test %d\n", ssi->waketest);
|
|
dev_err(&cl->device, "Data RX id: %d\n", ssi->rxid);
|
|
dev_err(&cl->device, "Data TX id: %d\n", ssi->txid);
|
|
|
|
list_for_each_entry(msg, &ssi->txqueue, link)
|
|
dev_err(&cl->device, "pending TX data (%p)\n", msg);
|
|
spin_unlock_bh(&ssi->lock);
|
|
}
|
|
|
|
static void ssip_error(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *msg;
|
|
|
|
ssip_dump_state(cl);
|
|
ssip_reset(cl);
|
|
msg = ssip_claim_cmd(ssi);
|
|
msg->complete = ssip_rxcmd_complete;
|
|
hsi_async_read(cl, msg);
|
|
}
|
|
|
|
static void ssip_keep_alive(unsigned long data)
|
|
{
|
|
struct hsi_client *cl = (struct hsi_client *)data;
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
|
|
dev_dbg(&cl->device, "Keep alive kick in: m(%d) r(%d) s(%d)\n",
|
|
ssi->main_state, ssi->recv_state, ssi->send_state);
|
|
|
|
spin_lock(&ssi->lock);
|
|
if (ssi->recv_state == RECV_IDLE)
|
|
switch (ssi->send_state) {
|
|
case SEND_READY:
|
|
if (atomic_read(&ssi->tx_usecnt) == 0)
|
|
break;
|
|
/*
|
|
* Fall through. Workaround for cmt-speech
|
|
* in that case we relay on audio timers.
|
|
*/
|
|
case SEND_IDLE:
|
|
spin_unlock(&ssi->lock);
|
|
return;
|
|
}
|
|
mod_timer(&ssi->keep_alive, jiffies + msecs_to_jiffies(SSIP_KATOUT));
|
|
spin_unlock(&ssi->lock);
|
|
}
|
|
|
|
static void ssip_wd(unsigned long data)
|
|
{
|
|
struct hsi_client *cl = (struct hsi_client *)data;
|
|
|
|
dev_err(&cl->device, "Watchdog trigerred\n");
|
|
ssip_error(cl);
|
|
}
|
|
|
|
static void ssip_send_bootinfo_req_cmd(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *msg;
|
|
|
|
dev_dbg(&cl->device, "Issuing BOOT INFO REQ command\n");
|
|
msg = ssip_claim_cmd(ssi);
|
|
ssip_set_cmd(msg, SSIP_BOOTINFO_REQ_CMD(SSIP_LOCAL_VERID));
|
|
msg->complete = ssip_release_cmd;
|
|
hsi_async_write(cl, msg);
|
|
dev_dbg(&cl->device, "Issuing RX command\n");
|
|
msg = ssip_claim_cmd(ssi);
|
|
msg->complete = ssip_rxcmd_complete;
|
|
hsi_async_read(cl, msg);
|
|
}
|
|
|
|
static void ssip_start_rx(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *msg;
|
|
|
|
dev_dbg(&cl->device, "RX start M(%d) R(%d)\n", ssi->main_state,
|
|
ssi->recv_state);
|
|
spin_lock(&ssi->lock);
|
|
/*
|
|
* We can have two UP events in a row due to a short low
|
|
* high transition. Therefore we need to ignore the sencond UP event.
|
|
*/
|
|
if ((ssi->main_state != ACTIVE) || (ssi->recv_state == RECV_READY)) {
|
|
spin_unlock(&ssi->lock);
|
|
return;
|
|
}
|
|
ssip_set_rxstate(ssi, RECV_READY);
|
|
spin_unlock(&ssi->lock);
|
|
|
|
msg = ssip_claim_cmd(ssi);
|
|
ssip_set_cmd(msg, SSIP_READY_CMD);
|
|
msg->complete = ssip_release_cmd;
|
|
dev_dbg(&cl->device, "Send READY\n");
|
|
hsi_async_write(cl, msg);
|
|
}
|
|
|
|
static void ssip_stop_rx(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
|
|
dev_dbg(&cl->device, "RX stop M(%d)\n", ssi->main_state);
|
|
spin_lock(&ssi->lock);
|
|
if (likely(ssi->main_state == ACTIVE))
|
|
ssip_set_rxstate(ssi, RECV_IDLE);
|
|
spin_unlock(&ssi->lock);
|
|
}
|
|
|
|
static void ssip_free_strans(struct hsi_msg *msg)
|
|
{
|
|
ssip_free_data(msg->context);
|
|
ssip_release_cmd(msg);
|
|
}
|
|
|
|
static void ssip_strans_complete(struct hsi_msg *msg)
|
|
{
|
|
struct hsi_client *cl = msg->cl;
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *data;
|
|
|
|
data = msg->context;
|
|
ssip_release_cmd(msg);
|
|
spin_lock(&ssi->lock);
|
|
ssip_set_txstate(ssi, SENDING);
|
|
spin_unlock(&ssi->lock);
|
|
hsi_async_write(cl, data);
|
|
}
|
|
|
|
static int ssip_xmit(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *msg, *dmsg;
|
|
struct sk_buff *skb;
|
|
|
|
spin_lock_bh(&ssi->lock);
|
|
if (list_empty(&ssi->txqueue)) {
|
|
spin_unlock_bh(&ssi->lock);
|
|
return 0;
|
|
}
|
|
dmsg = list_first_entry(&ssi->txqueue, struct hsi_msg, link);
|
|
list_del(&dmsg->link);
|
|
ssi->txqueue_len--;
|
|
spin_unlock_bh(&ssi->lock);
|
|
|
|
msg = ssip_claim_cmd(ssi);
|
|
skb = dmsg->context;
|
|
msg->context = dmsg;
|
|
msg->complete = ssip_strans_complete;
|
|
msg->destructor = ssip_free_strans;
|
|
|
|
spin_lock_bh(&ssi->lock);
|
|
ssip_set_cmd(msg, SSIP_START_TRANS_CMD(SSIP_BYTES_TO_FRAMES(skb->len),
|
|
ssi->txid));
|
|
ssi->txid++;
|
|
ssip_set_txstate(ssi, SENDING);
|
|
spin_unlock_bh(&ssi->lock);
|
|
|
|
dev_dbg(&cl->device, "Send STRANS (%d frames)\n",
|
|
SSIP_BYTES_TO_FRAMES(skb->len));
|
|
|
|
return hsi_async_write(cl, msg);
|
|
}
|
|
|
|
/* In soft IRQ context */
|
|
static void ssip_pn_rx(struct sk_buff *skb)
|
|
{
|
|
struct net_device *dev = skb->dev;
|
|
|
|
if (unlikely(!netif_running(dev))) {
|
|
dev_dbg(&dev->dev, "Drop RX packet\n");
|
|
dev->stats.rx_dropped++;
|
|
dev_kfree_skb(skb);
|
|
return;
|
|
}
|
|
if (unlikely(!pskb_may_pull(skb, SSIP_MIN_PN_HDR))) {
|
|
dev_dbg(&dev->dev, "Error drop RX packet\n");
|
|
dev->stats.rx_errors++;
|
|
dev->stats.rx_length_errors++;
|
|
dev_kfree_skb(skb);
|
|
return;
|
|
}
|
|
dev->stats.rx_packets++;
|
|
dev->stats.rx_bytes += skb->len;
|
|
|
|
/* length field is exchanged in network byte order */
|
|
((u16 *)skb->data)[2] = ntohs(((u16 *)skb->data)[2]);
|
|
dev_dbg(&dev->dev, "RX length fixed (%04x -> %u)\n",
|
|
((u16 *)skb->data)[2], ntohs(((u16 *)skb->data)[2]));
|
|
|
|
skb->protocol = htons(ETH_P_PHONET);
|
|
skb_reset_mac_header(skb);
|
|
__skb_pull(skb, 1);
|
|
netif_rx(skb);
|
|
}
|
|
|
|
static void ssip_rx_data_complete(struct hsi_msg *msg)
|
|
{
|
|
struct hsi_client *cl = msg->cl;
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct sk_buff *skb;
|
|
|
|
if (msg->status == HSI_STATUS_ERROR) {
|
|
dev_err(&cl->device, "RX data error\n");
|
|
ssip_free_data(msg);
|
|
ssip_error(cl);
|
|
return;
|
|
}
|
|
del_timer(&ssi->rx_wd); /* FIXME: Revisit */
|
|
skb = msg->context;
|
|
ssip_pn_rx(skb);
|
|
hsi_free_msg(msg);
|
|
}
|
|
|
|
static void ssip_rx_bootinforeq(struct hsi_client *cl, u32 cmd)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *msg;
|
|
|
|
/* Workaroud: Ignore CMT Loader message leftover */
|
|
if (cmd == SSIP_CMT_LOADER_SYNC)
|
|
return;
|
|
|
|
switch (ssi->main_state) {
|
|
case ACTIVE:
|
|
dev_err(&cl->device, "Boot info req on active state\n");
|
|
ssip_error(cl);
|
|
/* Fall through */
|
|
case INIT:
|
|
case HANDSHAKE:
|
|
spin_lock(&ssi->lock);
|
|
ssi->main_state = HANDSHAKE;
|
|
if (!ssi->waketest) {
|
|
ssi->waketest = 1;
|
|
ssi_waketest(cl, 1); /* FIXME: To be removed */
|
|
}
|
|
/* Start boot handshake watchdog */
|
|
mod_timer(&ssi->tx_wd, jiffies + msecs_to_jiffies(SSIP_WDTOUT));
|
|
spin_unlock(&ssi->lock);
|
|
dev_dbg(&cl->device, "Send BOOTINFO_RESP\n");
|
|
if (SSIP_DATA_VERSION(cmd) != SSIP_LOCAL_VERID)
|
|
dev_warn(&cl->device, "boot info req verid mismatch\n");
|
|
msg = ssip_claim_cmd(ssi);
|
|
ssip_set_cmd(msg, SSIP_BOOTINFO_RESP_CMD(SSIP_LOCAL_VERID));
|
|
msg->complete = ssip_release_cmd;
|
|
hsi_async_write(cl, msg);
|
|
break;
|
|
default:
|
|
dev_dbg(&cl->device, "Wrong state M(%d)\n", ssi->main_state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ssip_rx_bootinforesp(struct hsi_client *cl, u32 cmd)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
|
|
if (SSIP_DATA_VERSION(cmd) != SSIP_LOCAL_VERID)
|
|
dev_warn(&cl->device, "boot info resp verid mismatch\n");
|
|
|
|
spin_lock(&ssi->lock);
|
|
if (ssi->main_state != ACTIVE)
|
|
/* Use tx_wd as a boot watchdog in non ACTIVE state */
|
|
mod_timer(&ssi->tx_wd, jiffies + msecs_to_jiffies(SSIP_WDTOUT));
|
|
else
|
|
dev_dbg(&cl->device, "boot info resp ignored M(%d)\n",
|
|
ssi->main_state);
|
|
spin_unlock(&ssi->lock);
|
|
}
|
|
|
|
static void ssip_rx_waketest(struct hsi_client *cl, u32 cmd)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
unsigned int wkres = SSIP_PAYLOAD(cmd);
|
|
|
|
spin_lock(&ssi->lock);
|
|
if (ssi->main_state != HANDSHAKE) {
|
|
dev_dbg(&cl->device, "wake lines test ignored M(%d)\n",
|
|
ssi->main_state);
|
|
spin_unlock(&ssi->lock);
|
|
return;
|
|
}
|
|
if (ssi->waketest) {
|
|
ssi->waketest = 0;
|
|
ssi_waketest(cl, 0); /* FIXME: To be removed */
|
|
}
|
|
ssi->main_state = ACTIVE;
|
|
del_timer(&ssi->tx_wd); /* Stop boot handshake timer */
|
|
spin_unlock(&ssi->lock);
|
|
|
|
dev_notice(&cl->device, "WAKELINES TEST %s\n",
|
|
wkres & SSIP_WAKETEST_FAILED ? "FAILED" : "OK");
|
|
if (wkres & SSIP_WAKETEST_FAILED) {
|
|
ssip_error(cl);
|
|
return;
|
|
}
|
|
dev_dbg(&cl->device, "CMT is ONLINE\n");
|
|
netif_wake_queue(ssi->netdev);
|
|
netif_carrier_on(ssi->netdev);
|
|
}
|
|
|
|
static void ssip_rx_ready(struct hsi_client *cl)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
|
|
spin_lock(&ssi->lock);
|
|
if (unlikely(ssi->main_state != ACTIVE)) {
|
|
dev_dbg(&cl->device, "READY on wrong state: S(%d) M(%d)\n",
|
|
ssi->send_state, ssi->main_state);
|
|
spin_unlock(&ssi->lock);
|
|
return;
|
|
}
|
|
if (ssi->send_state != WAIT4READY) {
|
|
dev_dbg(&cl->device, "Ignore spurious READY command\n");
|
|
spin_unlock(&ssi->lock);
|
|
return;
|
|
}
|
|
ssip_set_txstate(ssi, SEND_READY);
|
|
spin_unlock(&ssi->lock);
|
|
ssip_xmit(cl);
|
|
}
|
|
|
|
static void ssip_rx_strans(struct hsi_client *cl, u32 cmd)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct sk_buff *skb;
|
|
struct hsi_msg *msg;
|
|
int len = SSIP_PDU_LENGTH(cmd);
|
|
|
|
dev_dbg(&cl->device, "RX strans: %d frames\n", len);
|
|
spin_lock(&ssi->lock);
|
|
if (unlikely(ssi->main_state != ACTIVE)) {
|
|
dev_err(&cl->device, "START TRANS wrong state: S(%d) M(%d)\n",
|
|
ssi->send_state, ssi->main_state);
|
|
spin_unlock(&ssi->lock);
|
|
return;
|
|
}
|
|
ssip_set_rxstate(ssi, RECEIVING);
|
|
if (unlikely(SSIP_MSG_ID(cmd) != ssi->rxid)) {
|
|
dev_err(&cl->device, "START TRANS id %d expected %d\n",
|
|
SSIP_MSG_ID(cmd), ssi->rxid);
|
|
spin_unlock(&ssi->lock);
|
|
goto out1;
|
|
}
|
|
ssi->rxid++;
|
|
spin_unlock(&ssi->lock);
|
|
skb = netdev_alloc_skb(ssi->netdev, len * 4);
|
|
if (unlikely(!skb)) {
|
|
dev_err(&cl->device, "No memory for rx skb\n");
|
|
goto out1;
|
|
}
|
|
skb->dev = ssi->netdev;
|
|
skb_put(skb, len * 4);
|
|
msg = ssip_alloc_data(ssi, skb, GFP_ATOMIC);
|
|
if (unlikely(!msg)) {
|
|
dev_err(&cl->device, "No memory for RX data msg\n");
|
|
goto out2;
|
|
}
|
|
msg->complete = ssip_rx_data_complete;
|
|
hsi_async_read(cl, msg);
|
|
|
|
return;
|
|
out2:
|
|
dev_kfree_skb(skb);
|
|
out1:
|
|
ssip_error(cl);
|
|
}
|
|
|
|
static void ssip_rxcmd_complete(struct hsi_msg *msg)
|
|
{
|
|
struct hsi_client *cl = msg->cl;
|
|
u32 cmd = ssip_get_cmd(msg);
|
|
unsigned int cmdid = SSIP_COMMAND(cmd);
|
|
|
|
if (msg->status == HSI_STATUS_ERROR) {
|
|
dev_err(&cl->device, "RX error detected\n");
|
|
ssip_release_cmd(msg);
|
|
ssip_error(cl);
|
|
return;
|
|
}
|
|
hsi_async_read(cl, msg);
|
|
dev_dbg(&cl->device, "RX cmd: 0x%08x\n", cmd);
|
|
switch (cmdid) {
|
|
case SSIP_SW_BREAK:
|
|
/* Ignored */
|
|
break;
|
|
case SSIP_BOOTINFO_REQ:
|
|
ssip_rx_bootinforeq(cl, cmd);
|
|
break;
|
|
case SSIP_BOOTINFO_RESP:
|
|
ssip_rx_bootinforesp(cl, cmd);
|
|
break;
|
|
case SSIP_WAKETEST_RESULT:
|
|
ssip_rx_waketest(cl, cmd);
|
|
break;
|
|
case SSIP_START_TRANS:
|
|
ssip_rx_strans(cl, cmd);
|
|
break;
|
|
case SSIP_READY:
|
|
ssip_rx_ready(cl);
|
|
break;
|
|
default:
|
|
dev_warn(&cl->device, "command 0x%08x not supported\n", cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ssip_swbreak_complete(struct hsi_msg *msg)
|
|
{
|
|
struct hsi_client *cl = msg->cl;
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
|
|
ssip_release_cmd(msg);
|
|
spin_lock(&ssi->lock);
|
|
if (list_empty(&ssi->txqueue)) {
|
|
if (atomic_read(&ssi->tx_usecnt)) {
|
|
ssip_set_txstate(ssi, SEND_READY);
|
|
} else {
|
|
ssip_set_txstate(ssi, SEND_IDLE);
|
|
hsi_stop_tx(cl);
|
|
}
|
|
spin_unlock(&ssi->lock);
|
|
} else {
|
|
spin_unlock(&ssi->lock);
|
|
ssip_xmit(cl);
|
|
}
|
|
netif_wake_queue(ssi->netdev);
|
|
}
|
|
|
|
static void ssip_tx_data_complete(struct hsi_msg *msg)
|
|
{
|
|
struct hsi_client *cl = msg->cl;
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *cmsg;
|
|
|
|
if (msg->status == HSI_STATUS_ERROR) {
|
|
dev_err(&cl->device, "TX data error\n");
|
|
ssip_error(cl);
|
|
goto out;
|
|
}
|
|
spin_lock(&ssi->lock);
|
|
if (list_empty(&ssi->txqueue)) {
|
|
ssip_set_txstate(ssi, SENDING_SWBREAK);
|
|
spin_unlock(&ssi->lock);
|
|
cmsg = ssip_claim_cmd(ssi);
|
|
ssip_set_cmd(cmsg, SSIP_SWBREAK_CMD);
|
|
cmsg->complete = ssip_swbreak_complete;
|
|
dev_dbg(&cl->device, "Send SWBREAK\n");
|
|
hsi_async_write(cl, cmsg);
|
|
} else {
|
|
spin_unlock(&ssi->lock);
|
|
ssip_xmit(cl);
|
|
}
|
|
out:
|
|
ssip_free_data(msg);
|
|
}
|
|
|
|
static void ssip_port_event(struct hsi_client *cl, unsigned long event)
|
|
{
|
|
switch (event) {
|
|
case HSI_EVENT_START_RX:
|
|
ssip_start_rx(cl);
|
|
break;
|
|
case HSI_EVENT_STOP_RX:
|
|
ssip_stop_rx(cl);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int ssip_pn_open(struct net_device *dev)
|
|
{
|
|
struct hsi_client *cl = to_hsi_client(dev->dev.parent);
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
int err;
|
|
|
|
err = hsi_claim_port(cl, 1);
|
|
if (err < 0) {
|
|
dev_err(&cl->device, "SSI port already claimed\n");
|
|
return err;
|
|
}
|
|
err = hsi_register_port_event(cl, ssip_port_event);
|
|
if (err < 0) {
|
|
dev_err(&cl->device, "Register HSI port event failed (%d)\n",
|
|
err);
|
|
return err;
|
|
}
|
|
dev_dbg(&cl->device, "Configuring SSI port\n");
|
|
hsi_setup(cl);
|
|
spin_lock_bh(&ssi->lock);
|
|
if (!ssi->waketest) {
|
|
ssi->waketest = 1;
|
|
ssi_waketest(cl, 1); /* FIXME: To be removed */
|
|
}
|
|
ssi->main_state = HANDSHAKE;
|
|
spin_unlock_bh(&ssi->lock);
|
|
|
|
ssip_send_bootinfo_req_cmd(cl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssip_pn_stop(struct net_device *dev)
|
|
{
|
|
struct hsi_client *cl = to_hsi_client(dev->dev.parent);
|
|
|
|
ssip_reset(cl);
|
|
hsi_unregister_port_event(cl);
|
|
hsi_release_port(cl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssip_pn_set_mtu(struct net_device *dev, int new_mtu)
|
|
{
|
|
if (new_mtu > SSIP_MAX_MTU || new_mtu < PHONET_MIN_MTU)
|
|
return -EINVAL;
|
|
dev->mtu = new_mtu;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssip_pn_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
struct hsi_client *cl = to_hsi_client(dev->dev.parent);
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
struct hsi_msg *msg;
|
|
|
|
if ((skb->protocol != htons(ETH_P_PHONET)) ||
|
|
(skb->len < SSIP_MIN_PN_HDR))
|
|
goto drop;
|
|
/* Pad to 32-bits - FIXME: Revisit*/
|
|
if ((skb->len & 3) && skb_pad(skb, 4 - (skb->len & 3)))
|
|
goto drop;
|
|
|
|
/*
|
|
* Modem sends Phonet messages over SSI with its own endianess...
|
|
* Assume that modem has the same endianess as we do.
|
|
*/
|
|
if (skb_cow_head(skb, 0))
|
|
goto drop;
|
|
|
|
/* length field is exchanged in network byte order */
|
|
((u16 *)skb->data)[2] = htons(((u16 *)skb->data)[2]);
|
|
|
|
msg = ssip_alloc_data(ssi, skb, GFP_ATOMIC);
|
|
if (!msg) {
|
|
dev_dbg(&cl->device, "Dropping tx data: No memory\n");
|
|
goto drop;
|
|
}
|
|
msg->complete = ssip_tx_data_complete;
|
|
|
|
spin_lock_bh(&ssi->lock);
|
|
if (unlikely(ssi->main_state != ACTIVE)) {
|
|
spin_unlock_bh(&ssi->lock);
|
|
dev_dbg(&cl->device, "Dropping tx data: CMT is OFFLINE\n");
|
|
goto drop2;
|
|
}
|
|
list_add_tail(&msg->link, &ssi->txqueue);
|
|
ssi->txqueue_len++;
|
|
if (dev->tx_queue_len < ssi->txqueue_len) {
|
|
dev_info(&cl->device, "TX queue full %d\n", ssi->txqueue_len);
|
|
netif_stop_queue(dev);
|
|
}
|
|
if (ssi->send_state == SEND_IDLE) {
|
|
ssip_set_txstate(ssi, WAIT4READY);
|
|
spin_unlock_bh(&ssi->lock);
|
|
dev_dbg(&cl->device, "Start TX qlen %d\n", ssi->txqueue_len);
|
|
hsi_start_tx(cl);
|
|
} else if (ssi->send_state == SEND_READY) {
|
|
/* Needed for cmt-speech workaround */
|
|
dev_dbg(&cl->device, "Start TX on SEND READY qlen %d\n",
|
|
ssi->txqueue_len);
|
|
spin_unlock_bh(&ssi->lock);
|
|
ssip_xmit(cl);
|
|
} else {
|
|
spin_unlock_bh(&ssi->lock);
|
|
}
|
|
dev->stats.tx_packets++;
|
|
dev->stats.tx_bytes += skb->len;
|
|
|
|
return 0;
|
|
drop2:
|
|
hsi_free_msg(msg);
|
|
drop:
|
|
dev->stats.tx_dropped++;
|
|
dev_kfree_skb(skb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* CMT reset event handler */
|
|
void ssip_reset_event(struct hsi_client *master)
|
|
{
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(master);
|
|
dev_err(&ssi->cl->device, "CMT reset detected!\n");
|
|
ssip_error(ssi->cl);
|
|
}
|
|
EXPORT_SYMBOL_GPL(ssip_reset_event);
|
|
|
|
static const struct net_device_ops ssip_pn_ops = {
|
|
.ndo_open = ssip_pn_open,
|
|
.ndo_stop = ssip_pn_stop,
|
|
.ndo_start_xmit = ssip_pn_xmit,
|
|
.ndo_change_mtu = ssip_pn_set_mtu,
|
|
};
|
|
|
|
static void ssip_pn_setup(struct net_device *dev)
|
|
{
|
|
dev->features = 0;
|
|
dev->netdev_ops = &ssip_pn_ops;
|
|
dev->type = ARPHRD_PHONET;
|
|
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
|
dev->mtu = SSIP_DEFAULT_MTU;
|
|
dev->hard_header_len = 1;
|
|
dev->dev_addr[0] = PN_MEDIA_SOS;
|
|
dev->addr_len = 1;
|
|
dev->tx_queue_len = SSIP_TXQUEUE_LEN;
|
|
|
|
dev->destructor = free_netdev;
|
|
dev->header_ops = &phonet_header_ops;
|
|
}
|
|
|
|
static int ssi_protocol_probe(struct device *dev)
|
|
{
|
|
static const char ifname[] = "phonet%d";
|
|
struct hsi_client *cl = to_hsi_client(dev);
|
|
struct ssi_protocol *ssi;
|
|
int err;
|
|
|
|
ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
|
|
if (!ssi) {
|
|
dev_err(dev, "No memory for ssi protocol\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spin_lock_init(&ssi->lock);
|
|
init_timer_deferrable(&ssi->rx_wd);
|
|
init_timer_deferrable(&ssi->tx_wd);
|
|
init_timer(&ssi->keep_alive);
|
|
ssi->rx_wd.data = (unsigned long)cl;
|
|
ssi->rx_wd.function = ssip_wd;
|
|
ssi->tx_wd.data = (unsigned long)cl;
|
|
ssi->tx_wd.function = ssip_wd;
|
|
ssi->keep_alive.data = (unsigned long)cl;
|
|
ssi->keep_alive.function = ssip_keep_alive;
|
|
INIT_LIST_HEAD(&ssi->txqueue);
|
|
INIT_LIST_HEAD(&ssi->cmdqueue);
|
|
atomic_set(&ssi->tx_usecnt, 0);
|
|
hsi_client_set_drvdata(cl, ssi);
|
|
ssi->cl = cl;
|
|
|
|
ssi->channel_id_cmd = hsi_get_channel_id_by_name(cl, "mcsaab-control");
|
|
if (ssi->channel_id_cmd < 0) {
|
|
err = ssi->channel_id_cmd;
|
|
dev_err(dev, "Could not get cmd channel (%d)\n", err);
|
|
goto out;
|
|
}
|
|
|
|
ssi->channel_id_data = hsi_get_channel_id_by_name(cl, "mcsaab-data");
|
|
if (ssi->channel_id_data < 0) {
|
|
err = ssi->channel_id_data;
|
|
dev_err(dev, "Could not get data channel (%d)\n", err);
|
|
goto out;
|
|
}
|
|
|
|
err = ssip_alloc_cmds(ssi);
|
|
if (err < 0) {
|
|
dev_err(dev, "No memory for commands\n");
|
|
goto out;
|
|
}
|
|
|
|
ssi->netdev = alloc_netdev(0, ifname, NET_NAME_UNKNOWN, ssip_pn_setup);
|
|
if (!ssi->netdev) {
|
|
dev_err(dev, "No memory for netdev\n");
|
|
err = -ENOMEM;
|
|
goto out1;
|
|
}
|
|
|
|
SET_NETDEV_DEV(ssi->netdev, dev);
|
|
netif_carrier_off(ssi->netdev);
|
|
err = register_netdev(ssi->netdev);
|
|
if (err < 0) {
|
|
dev_err(dev, "Register netdev failed (%d)\n", err);
|
|
goto out2;
|
|
}
|
|
|
|
list_add(&ssi->link, &ssip_list);
|
|
|
|
dev_dbg(dev, "channel configuration: cmd=%d, data=%d\n",
|
|
ssi->channel_id_cmd, ssi->channel_id_data);
|
|
|
|
return 0;
|
|
out2:
|
|
free_netdev(ssi->netdev);
|
|
out1:
|
|
ssip_free_cmds(ssi);
|
|
out:
|
|
kfree(ssi);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ssi_protocol_remove(struct device *dev)
|
|
{
|
|
struct hsi_client *cl = to_hsi_client(dev);
|
|
struct ssi_protocol *ssi = hsi_client_drvdata(cl);
|
|
|
|
list_del(&ssi->link);
|
|
unregister_netdev(ssi->netdev);
|
|
ssip_free_cmds(ssi);
|
|
hsi_client_set_drvdata(cl, NULL);
|
|
kfree(ssi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct hsi_client_driver ssip_driver = {
|
|
.driver = {
|
|
.name = "ssi-protocol",
|
|
.owner = THIS_MODULE,
|
|
.probe = ssi_protocol_probe,
|
|
.remove = ssi_protocol_remove,
|
|
},
|
|
};
|
|
|
|
static int __init ssip_init(void)
|
|
{
|
|
pr_info("SSI protocol aka McSAAB added\n");
|
|
|
|
return hsi_register_client_driver(&ssip_driver);
|
|
}
|
|
module_init(ssip_init);
|
|
|
|
static void __exit ssip_exit(void)
|
|
{
|
|
hsi_unregister_client_driver(&ssip_driver);
|
|
pr_info("SSI protocol driver removed\n");
|
|
}
|
|
module_exit(ssip_exit);
|
|
|
|
MODULE_ALIAS("hsi:ssi-protocol");
|
|
MODULE_AUTHOR("Carlos Chinea <carlos.chinea@nokia.com>");
|
|
MODULE_AUTHOR("Remi Denis-Courmont <remi.denis-courmont@nokia.com>");
|
|
MODULE_DESCRIPTION("SSI protocol improved aka McSAAB");
|
|
MODULE_LICENSE("GPL");
|