diff options
Diffstat (limited to 'drivers/hsi/clients')
| -rw-r--r-- | drivers/hsi/clients/Kconfig | 30 | ||||
| -rw-r--r-- | drivers/hsi/clients/Makefile | 7 | ||||
| -rw-r--r-- | drivers/hsi/clients/hsi_char.c | 802 | ||||
| -rw-r--r-- | drivers/hsi/clients/nokia-modem.c | 285 | ||||
| -rw-r--r-- | drivers/hsi/clients/ssi_protocol.c | 1191 | 
5 files changed, 2315 insertions, 0 deletions
diff --git a/drivers/hsi/clients/Kconfig b/drivers/hsi/clients/Kconfig new file mode 100644 index 00000000000..bc60dec3f58 --- /dev/null +++ b/drivers/hsi/clients/Kconfig @@ -0,0 +1,30 @@ +# +# HSI clients configuration +# + +comment "HSI clients" + +config NOKIA_MODEM +	tristate "Nokia Modem" +	depends on HSI && SSI_PROTOCOL +	help +	Say Y here if you want to add support for the modem on Nokia +	N900 (Nokia RX-51) hardware. + +	If unsure, say N. + +config SSI_PROTOCOL +	tristate "SSI protocol" +	depends on HSI && PHONET && OMAP_SSI +	help +	If you say Y here, you will enable the SSI protocol aka McSAAB. + +	If unsure, say N. + +config HSI_CHAR +	tristate "HSI/SSI character driver" +	depends on HSI +	---help--- +	  If you say Y here, you will enable the HSI/SSI character driver. +	  This driver provides a simple character device interface for +	  serial communication with the cellular modem over HSI/SSI bus. diff --git a/drivers/hsi/clients/Makefile b/drivers/hsi/clients/Makefile new file mode 100644 index 00000000000..4d5bc0e0b27 --- /dev/null +++ b/drivers/hsi/clients/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for HSI clients +# + +obj-$(CONFIG_NOKIA_MODEM)	+= nokia-modem.o +obj-$(CONFIG_SSI_PROTOCOL)	+= ssi_protocol.o +obj-$(CONFIG_HSI_CHAR)		+= hsi_char.o diff --git a/drivers/hsi/clients/hsi_char.c b/drivers/hsi/clients/hsi_char.c new file mode 100644 index 00000000000..57f70c28fa3 --- /dev/null +++ b/drivers/hsi/clients/hsi_char.c @@ -0,0 +1,802 @@ +/* + * HSI character device driver, implements the character device + * interface. + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * + * Contact: Andras Domokos <andras.domokos@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/errno.h> +#include <linux/types.h> +#include <linux/atomic.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/kmemleak.h> +#include <linux/ioctl.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/uaccess.h> +#include <linux/scatterlist.h> +#include <linux/stat.h> +#include <linux/hsi/hsi.h> +#include <linux/hsi/hsi_char.h> + +#define HSC_DEVS		16 /* Num of channels */ +#define HSC_MSGS		4 + +#define HSC_RXBREAK		0 + +#define HSC_ID_BITS		6 +#define HSC_PORT_ID_BITS	4 +#define HSC_ID_MASK		3 +#define HSC_PORT_ID_MASK	3 +#define HSC_CH_MASK		0xf + +/* + * We support up to 4 controllers that can have up to 4 + * ports, which should currently be more than enough. + */ +#define HSC_BASEMINOR(id, port_id) \ +		((((id) & HSC_ID_MASK) << HSC_ID_BITS) | \ +		(((port_id) & HSC_PORT_ID_MASK) << HSC_PORT_ID_BITS)) + +enum { +	HSC_CH_OPEN, +	HSC_CH_READ, +	HSC_CH_WRITE, +	HSC_CH_WLINE, +}; + +enum { +	HSC_RX, +	HSC_TX, +}; + +struct hsc_client_data; +/** + * struct hsc_channel - hsi_char internal channel data + * @ch: channel number + * @flags: Keeps state of the channel (open/close, reading, writing) + * @free_msgs_list: List of free HSI messages/requests + * @rx_msgs_queue: List of pending RX requests + * @tx_msgs_queue: List of pending TX requests + * @lock: Serialize access to the lists + * @cl: reference to the associated hsi_client + * @cl_data: reference to the client data that this channels belongs to + * @rx_wait: RX requests wait queue + * @tx_wait: TX requests wait queue + */ +struct hsc_channel { +	unsigned int		ch; +	unsigned long		flags; +	struct list_head	free_msgs_list; +	struct list_head	rx_msgs_queue; +	struct list_head	tx_msgs_queue; +	spinlock_t		lock; +	struct hsi_client	*cl; +	struct hsc_client_data *cl_data; +	wait_queue_head_t	rx_wait; +	wait_queue_head_t	tx_wait; +}; + +/** + * struct hsc_client_data - hsi_char internal client data + * @cdev: Characther device associated to the hsi_client + * @lock: Lock to serialize open/close access + * @flags: Keeps track of port state (rx hwbreak armed) + * @usecnt: Use count for claiming the HSI port (mutex protected) + * @cl: Referece to the HSI client + * @channels: Array of channels accessible by the client + */ +struct hsc_client_data { +	struct cdev		cdev; +	struct mutex		lock; +	unsigned long		flags; +	unsigned int		usecnt; +	struct hsi_client	*cl; +	struct hsc_channel	channels[HSC_DEVS]; +}; + +/* Stores the major number dynamically allocated for hsi_char */ +static unsigned int hsc_major; +/* Maximum buffer size that hsi_char will accept from userspace */ +static unsigned int max_data_size = 0x1000; +module_param(max_data_size, uint, 0); +MODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)"); + +static void hsc_add_tail(struct hsc_channel *channel, struct hsi_msg *msg, +							struct list_head *queue) +{ +	unsigned long flags; + +	spin_lock_irqsave(&channel->lock, flags); +	list_add_tail(&msg->link, queue); +	spin_unlock_irqrestore(&channel->lock, flags); +} + +static struct hsi_msg *hsc_get_first_msg(struct hsc_channel *channel, +							struct list_head *queue) +{ +	struct hsi_msg *msg = NULL; +	unsigned long flags; + +	spin_lock_irqsave(&channel->lock, flags); + +	if (list_empty(queue)) +		goto out; + +	msg = list_first_entry(queue, struct hsi_msg, link); +	list_del(&msg->link); +out: +	spin_unlock_irqrestore(&channel->lock, flags); + +	return msg; +} + +static inline void hsc_msg_free(struct hsi_msg *msg) +{ +	kfree(sg_virt(msg->sgt.sgl)); +	hsi_free_msg(msg); +} + +static void hsc_free_list(struct list_head *list) +{ +	struct hsi_msg *msg, *tmp; + +	list_for_each_entry_safe(msg, tmp, list, link) { +		list_del(&msg->link); +		hsc_msg_free(msg); +	} +} + +static void hsc_reset_list(struct hsc_channel *channel, struct list_head *l) +{ +	unsigned long flags; +	LIST_HEAD(list); + +	spin_lock_irqsave(&channel->lock, flags); +	list_splice_init(l, &list); +	spin_unlock_irqrestore(&channel->lock, flags); + +	hsc_free_list(&list); +} + +static inline struct hsi_msg *hsc_msg_alloc(unsigned int alloc_size) +{ +	struct hsi_msg *msg; +	void *buf; + +	msg = hsi_alloc_msg(1, GFP_KERNEL); +	if (!msg) +		goto out; +	buf = kmalloc(alloc_size, GFP_KERNEL); +	if (!buf) { +		hsi_free_msg(msg); +		goto out; +	} +	sg_init_one(msg->sgt.sgl, buf, alloc_size); +	/* Ignore false positive, due to sg pointer handling */ +	kmemleak_ignore(buf); + +	return msg; +out: +	return NULL; +} + +static inline int hsc_msgs_alloc(struct hsc_channel *channel) +{ +	struct hsi_msg *msg; +	int i; + +	for (i = 0; i < HSC_MSGS; i++) { +		msg = hsc_msg_alloc(max_data_size); +		if (!msg) +			goto out; +		msg->channel = channel->ch; +		list_add_tail(&msg->link, &channel->free_msgs_list); +	} + +	return 0; +out: +	hsc_free_list(&channel->free_msgs_list); + +	return -ENOMEM; +} + +static inline unsigned int hsc_msg_len_get(struct hsi_msg *msg) +{ +	return msg->sgt.sgl->length; +} + +static inline void hsc_msg_len_set(struct hsi_msg *msg, unsigned int len) +{ +	msg->sgt.sgl->length = len; +} + +static void hsc_rx_completed(struct hsi_msg *msg) +{ +	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl); +	struct hsc_channel *channel = cl_data->channels + msg->channel; + +	if (test_bit(HSC_CH_READ, &channel->flags)) { +		hsc_add_tail(channel, msg, &channel->rx_msgs_queue); +		wake_up(&channel->rx_wait); +	} else { +		hsc_add_tail(channel, msg, &channel->free_msgs_list); +	} +} + +static void hsc_rx_msg_destructor(struct hsi_msg *msg) +{ +	msg->status = HSI_STATUS_ERROR; +	hsc_msg_len_set(msg, 0); +	hsc_rx_completed(msg); +} + +static void hsc_tx_completed(struct hsi_msg *msg) +{ +	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl); +	struct hsc_channel *channel = cl_data->channels + msg->channel; + +	if (test_bit(HSC_CH_WRITE, &channel->flags)) { +		hsc_add_tail(channel, msg, &channel->tx_msgs_queue); +		wake_up(&channel->tx_wait); +	} else { +		hsc_add_tail(channel, msg, &channel->free_msgs_list); +	} +} + +static void hsc_tx_msg_destructor(struct hsi_msg *msg) +{ +	msg->status = HSI_STATUS_ERROR; +	hsc_msg_len_set(msg, 0); +	hsc_tx_completed(msg); +} + +static void hsc_break_req_destructor(struct hsi_msg *msg) +{ +	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl); + +	hsi_free_msg(msg); +	clear_bit(HSC_RXBREAK, &cl_data->flags); +} + +static void hsc_break_received(struct hsi_msg *msg) +{ +	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl); +	struct hsc_channel *channel = cl_data->channels; +	int i, ret; + +	/* Broadcast HWBREAK on all channels */ +	for (i = 0; i < HSC_DEVS; i++, channel++) { +		struct hsi_msg *msg2; + +		if (!test_bit(HSC_CH_READ, &channel->flags)) +			continue; +		msg2 = hsc_get_first_msg(channel, &channel->free_msgs_list); +		if (!msg2) +			continue; +		clear_bit(HSC_CH_READ, &channel->flags); +		hsc_msg_len_set(msg2, 0); +		msg2->status = HSI_STATUS_COMPLETED; +		hsc_add_tail(channel, msg2, &channel->rx_msgs_queue); +		wake_up(&channel->rx_wait); +	} +	hsi_flush(msg->cl); +	ret = hsi_async_read(msg->cl, msg); +	if (ret < 0) +		hsc_break_req_destructor(msg); +} + +static int hsc_break_request(struct hsi_client *cl) +{ +	struct hsc_client_data *cl_data = hsi_client_drvdata(cl); +	struct hsi_msg *msg; +	int ret; + +	if (test_and_set_bit(HSC_RXBREAK, &cl_data->flags)) +		return -EBUSY; + +	msg = hsi_alloc_msg(0, GFP_KERNEL); +	if (!msg) { +		clear_bit(HSC_RXBREAK, &cl_data->flags); +		return -ENOMEM; +	} +	msg->break_frame = 1; +	msg->complete = hsc_break_received; +	msg->destructor = hsc_break_req_destructor; +	ret = hsi_async_read(cl, msg); +	if (ret < 0) +		hsc_break_req_destructor(msg); + +	return ret; +} + +static int hsc_break_send(struct hsi_client *cl) +{ +	struct hsi_msg *msg; +	int ret; + +	msg = hsi_alloc_msg(0, GFP_ATOMIC); +	if (!msg) +		return -ENOMEM; +	msg->break_frame = 1; +	msg->complete = hsi_free_msg; +	msg->destructor = hsi_free_msg; +	ret = hsi_async_write(cl, msg); +	if (ret < 0) +		hsi_free_msg(msg); + +	return ret; +} + +static int hsc_rx_set(struct hsi_client *cl, struct hsc_rx_config *rxc) +{ +	struct hsi_config tmp; +	int ret; + +	if ((rxc->mode != HSI_MODE_STREAM) && (rxc->mode != HSI_MODE_FRAME)) +		return -EINVAL; +	if ((rxc->channels == 0) || (rxc->channels > HSC_DEVS)) +		return -EINVAL; +	if (rxc->channels & (rxc->channels - 1)) +		return -EINVAL; +	if ((rxc->flow != HSI_FLOW_SYNC) && (rxc->flow != HSI_FLOW_PIPE)) +		return -EINVAL; +	tmp = cl->rx_cfg; +	cl->rx_cfg.mode = rxc->mode; +	cl->rx_cfg.num_hw_channels = rxc->channels; +	cl->rx_cfg.flow = rxc->flow; +	ret = hsi_setup(cl); +	if (ret < 0) { +		cl->rx_cfg = tmp; +		return ret; +	} +	if (rxc->mode == HSI_MODE_FRAME) +		hsc_break_request(cl); + +	return ret; +} + +static inline void hsc_rx_get(struct hsi_client *cl, struct hsc_rx_config *rxc) +{ +	rxc->mode = cl->rx_cfg.mode; +	rxc->channels = cl->rx_cfg.num_hw_channels; +	rxc->flow = cl->rx_cfg.flow; +} + +static int hsc_tx_set(struct hsi_client *cl, struct hsc_tx_config *txc) +{ +	struct hsi_config tmp; +	int ret; + +	if ((txc->mode != HSI_MODE_STREAM) && (txc->mode != HSI_MODE_FRAME)) +		return -EINVAL; +	if ((txc->channels == 0) || (txc->channels > HSC_DEVS)) +		return -EINVAL; +	if (txc->channels & (txc->channels - 1)) +		return -EINVAL; +	if ((txc->arb_mode != HSI_ARB_RR) && (txc->arb_mode != HSI_ARB_PRIO)) +		return -EINVAL; +	tmp = cl->tx_cfg; +	cl->tx_cfg.mode = txc->mode; +	cl->tx_cfg.num_hw_channels = txc->channels; +	cl->tx_cfg.speed = txc->speed; +	cl->tx_cfg.arb_mode = txc->arb_mode; +	ret = hsi_setup(cl); +	if (ret < 0) { +		cl->tx_cfg = tmp; +		return ret; +	} + +	return ret; +} + +static inline void hsc_tx_get(struct hsi_client *cl, struct hsc_tx_config *txc) +{ +	txc->mode = cl->tx_cfg.mode; +	txc->channels = cl->tx_cfg.num_hw_channels; +	txc->speed = cl->tx_cfg.speed; +	txc->arb_mode = cl->tx_cfg.arb_mode; +} + +static ssize_t hsc_read(struct file *file, char __user *buf, size_t len, +						loff_t *ppos __maybe_unused) +{ +	struct hsc_channel *channel = file->private_data; +	struct hsi_msg *msg; +	ssize_t ret; + +	if (len == 0) +		return 0; +	if (!IS_ALIGNED(len, sizeof(u32))) +		return -EINVAL; +	if (len > max_data_size) +		len = max_data_size; +	if (channel->ch >= channel->cl->rx_cfg.num_hw_channels) +		return -ECHRNG; +	if (test_and_set_bit(HSC_CH_READ, &channel->flags)) +		return -EBUSY; +	msg = hsc_get_first_msg(channel, &channel->free_msgs_list); +	if (!msg) { +		ret = -ENOSPC; +		goto out; +	} +	hsc_msg_len_set(msg, len); +	msg->complete = hsc_rx_completed; +	msg->destructor = hsc_rx_msg_destructor; +	ret = hsi_async_read(channel->cl, msg); +	if (ret < 0) { +		hsc_add_tail(channel, msg, &channel->free_msgs_list); +		goto out; +	} + +	ret = wait_event_interruptible(channel->rx_wait, +					!list_empty(&channel->rx_msgs_queue)); +	if (ret < 0) { +		clear_bit(HSC_CH_READ, &channel->flags); +		hsi_flush(channel->cl); +		return -EINTR; +	} + +	msg = hsc_get_first_msg(channel, &channel->rx_msgs_queue); +	if (msg) { +		if (msg->status != HSI_STATUS_ERROR) { +			ret = copy_to_user((void __user *)buf, +			sg_virt(msg->sgt.sgl), hsc_msg_len_get(msg)); +			if (ret) +				ret = -EFAULT; +			else +				ret = hsc_msg_len_get(msg); +		} else { +			ret = -EIO; +		} +		hsc_add_tail(channel, msg, &channel->free_msgs_list); +	} +out: +	clear_bit(HSC_CH_READ, &channel->flags); + +	return ret; +} + +static ssize_t hsc_write(struct file *file, const char __user *buf, size_t len, +						loff_t *ppos __maybe_unused) +{ +	struct hsc_channel *channel = file->private_data; +	struct hsi_msg *msg; +	ssize_t ret; + +	if ((len == 0) || !IS_ALIGNED(len, sizeof(u32))) +		return -EINVAL; +	if (len > max_data_size) +		len = max_data_size; +	if (channel->ch >= channel->cl->tx_cfg.num_hw_channels) +		return -ECHRNG; +	if (test_and_set_bit(HSC_CH_WRITE, &channel->flags)) +		return -EBUSY; +	msg = hsc_get_first_msg(channel, &channel->free_msgs_list); +	if (!msg) { +		clear_bit(HSC_CH_WRITE, &channel->flags); +		return -ENOSPC; +	} +	if (copy_from_user(sg_virt(msg->sgt.sgl), (void __user *)buf, len)) { +		ret = -EFAULT; +		goto out; +	} +	hsc_msg_len_set(msg, len); +	msg->complete = hsc_tx_completed; +	msg->destructor = hsc_tx_msg_destructor; +	ret = hsi_async_write(channel->cl, msg); +	if (ret < 0) +		goto out; + +	ret = wait_event_interruptible(channel->tx_wait, +					!list_empty(&channel->tx_msgs_queue)); +	if (ret < 0) { +		clear_bit(HSC_CH_WRITE, &channel->flags); +		hsi_flush(channel->cl); +		return -EINTR; +	} + +	msg = hsc_get_first_msg(channel, &channel->tx_msgs_queue); +	if (msg) { +		if (msg->status == HSI_STATUS_ERROR) +			ret = -EIO; +		else +			ret = hsc_msg_len_get(msg); + +		hsc_add_tail(channel, msg, &channel->free_msgs_list); +	} +out: +	clear_bit(HSC_CH_WRITE, &channel->flags); + +	return ret; +} + +static long hsc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ +	struct hsc_channel *channel = file->private_data; +	unsigned int state; +	struct hsc_rx_config rxc; +	struct hsc_tx_config txc; +	long ret = 0; + +	switch (cmd) { +	case HSC_RESET: +		hsi_flush(channel->cl); +		break; +	case HSC_SET_PM: +		if (copy_from_user(&state, (void __user *)arg, sizeof(state))) +			return -EFAULT; +		if (state == HSC_PM_DISABLE) { +			if (test_and_set_bit(HSC_CH_WLINE, &channel->flags)) +				return -EINVAL; +			ret = hsi_start_tx(channel->cl); +		} else if (state == HSC_PM_ENABLE) { +			if (!test_and_clear_bit(HSC_CH_WLINE, &channel->flags)) +				return -EINVAL; +			ret = hsi_stop_tx(channel->cl); +		} else { +			ret = -EINVAL; +		} +		break; +	case HSC_SEND_BREAK: +		return hsc_break_send(channel->cl); +	case HSC_SET_RX: +		if (copy_from_user(&rxc, (void __user *)arg, sizeof(rxc))) +			return -EFAULT; +		return hsc_rx_set(channel->cl, &rxc); +	case HSC_GET_RX: +		hsc_rx_get(channel->cl, &rxc); +		if (copy_to_user((void __user *)arg, &rxc, sizeof(rxc))) +			return -EFAULT; +		break; +	case HSC_SET_TX: +		if (copy_from_user(&txc, (void __user *)arg, sizeof(txc))) +			return -EFAULT; +		return hsc_tx_set(channel->cl, &txc); +	case HSC_GET_TX: +		hsc_tx_get(channel->cl, &txc); +		if (copy_to_user((void __user *)arg, &txc, sizeof(txc))) +			return -EFAULT; +		break; +	default: +		return -ENOIOCTLCMD; +	} + +	return ret; +} + +static inline void __hsc_port_release(struct hsc_client_data *cl_data) +{ +	BUG_ON(cl_data->usecnt == 0); + +	if (--cl_data->usecnt == 0) { +		hsi_flush(cl_data->cl); +		hsi_release_port(cl_data->cl); +	} +} + +static int hsc_open(struct inode *inode, struct file *file) +{ +	struct hsc_client_data *cl_data; +	struct hsc_channel *channel; +	int ret = 0; + +	pr_debug("open, minor = %d\n", iminor(inode)); + +	cl_data = container_of(inode->i_cdev, struct hsc_client_data, cdev); +	mutex_lock(&cl_data->lock); +	channel = cl_data->channels + (iminor(inode) & HSC_CH_MASK); + +	if (test_and_set_bit(HSC_CH_OPEN, &channel->flags)) { +		ret = -EBUSY; +		goto out; +	} +	/* +	 * Check if we have already claimed the port associated to the HSI +	 * client. If not then try to claim it, else increase its refcount +	 */ +	if (cl_data->usecnt == 0) { +		ret = hsi_claim_port(cl_data->cl, 0); +		if (ret < 0) +			goto out; +		hsi_setup(cl_data->cl); +	} +	cl_data->usecnt++; + +	ret = hsc_msgs_alloc(channel); +	if (ret < 0) { +		__hsc_port_release(cl_data); +		goto out; +	} + +	file->private_data = channel; +	mutex_unlock(&cl_data->lock); + +	return ret; +out: +	mutex_unlock(&cl_data->lock); + +	return ret; +} + +static int hsc_release(struct inode *inode __maybe_unused, struct file *file) +{ +	struct hsc_channel *channel = file->private_data; +	struct hsc_client_data *cl_data = channel->cl_data; + +	mutex_lock(&cl_data->lock); +	file->private_data = NULL; +	if (test_and_clear_bit(HSC_CH_WLINE, &channel->flags)) +		hsi_stop_tx(channel->cl); +	__hsc_port_release(cl_data); +	hsc_reset_list(channel, &channel->rx_msgs_queue); +	hsc_reset_list(channel, &channel->tx_msgs_queue); +	hsc_reset_list(channel, &channel->free_msgs_list); +	clear_bit(HSC_CH_READ, &channel->flags); +	clear_bit(HSC_CH_WRITE, &channel->flags); +	clear_bit(HSC_CH_OPEN, &channel->flags); +	wake_up(&channel->rx_wait); +	wake_up(&channel->tx_wait); +	mutex_unlock(&cl_data->lock); + +	return 0; +} + +static const struct file_operations hsc_fops = { +	.owner		= THIS_MODULE, +	.read		= hsc_read, +	.write		= hsc_write, +	.unlocked_ioctl	= hsc_ioctl, +	.open		= hsc_open, +	.release	= hsc_release, +}; + +static void hsc_channel_init(struct hsc_channel *channel) +{ +	init_waitqueue_head(&channel->rx_wait); +	init_waitqueue_head(&channel->tx_wait); +	spin_lock_init(&channel->lock); +	INIT_LIST_HEAD(&channel->free_msgs_list); +	INIT_LIST_HEAD(&channel->rx_msgs_queue); +	INIT_LIST_HEAD(&channel->tx_msgs_queue); +} + +static int hsc_probe(struct device *dev) +{ +	const char devname[] = "hsi_char"; +	struct hsc_client_data *cl_data; +	struct hsc_channel *channel; +	struct hsi_client *cl = to_hsi_client(dev); +	unsigned int hsc_baseminor; +	dev_t hsc_dev; +	int ret; +	int i; + +	cl_data = kzalloc(sizeof(*cl_data), GFP_KERNEL); +	if (!cl_data) { +		dev_err(dev, "Could not allocate hsc_client_data\n"); +		return -ENOMEM; +	} +	hsc_baseminor = HSC_BASEMINOR(hsi_id(cl), hsi_port_id(cl)); +	if (!hsc_major) { +		ret = alloc_chrdev_region(&hsc_dev, hsc_baseminor, +						HSC_DEVS, devname); +		if (ret == 0) +			hsc_major = MAJOR(hsc_dev); +	} else { +		hsc_dev = MKDEV(hsc_major, hsc_baseminor); +		ret = register_chrdev_region(hsc_dev, HSC_DEVS, devname); +	} +	if (ret < 0) { +		dev_err(dev, "Device %s allocation failed %d\n", +					hsc_major ? "minor" : "major", ret); +		goto out1; +	} +	mutex_init(&cl_data->lock); +	hsi_client_set_drvdata(cl, cl_data); +	cdev_init(&cl_data->cdev, &hsc_fops); +	cl_data->cdev.owner = THIS_MODULE; +	cl_data->cl = cl; +	for (i = 0, channel = cl_data->channels; i < HSC_DEVS; i++, channel++) { +		hsc_channel_init(channel); +		channel->ch = i; +		channel->cl = cl; +		channel->cl_data = cl_data; +	} + +	/* 1 hsi client -> N char devices (one for each channel) */ +	ret = cdev_add(&cl_data->cdev, hsc_dev, HSC_DEVS); +	if (ret) { +		dev_err(dev, "Could not add char device %d\n", ret); +		goto out2; +	} + +	return 0; +out2: +	unregister_chrdev_region(hsc_dev, HSC_DEVS); +out1: +	kfree(cl_data); + +	return ret; +} + +static int hsc_remove(struct device *dev) +{ +	struct hsi_client *cl = to_hsi_client(dev); +	struct hsc_client_data *cl_data = hsi_client_drvdata(cl); +	dev_t hsc_dev = cl_data->cdev.dev; + +	cdev_del(&cl_data->cdev); +	unregister_chrdev_region(hsc_dev, HSC_DEVS); +	hsi_client_set_drvdata(cl, NULL); +	kfree(cl_data); + +	return 0; +} + +static struct hsi_client_driver hsc_driver = { +	.driver = { +		.name	= "hsi_char", +		.owner	= THIS_MODULE, +		.probe	= hsc_probe, +		.remove	= hsc_remove, +	}, +}; + +static int __init hsc_init(void) +{ +	int ret; + +	if ((max_data_size < 4) || (max_data_size > 0x10000) || +		(max_data_size & (max_data_size - 1))) { +		pr_err("Invalid max read/write data size"); +		return -EINVAL; +	} + +	ret = hsi_register_client_driver(&hsc_driver); +	if (ret) { +		pr_err("Error while registering HSI/SSI driver %d", ret); +		return ret; +	} + +	pr_info("HSI/SSI char device loaded\n"); + +	return 0; +} +module_init(hsc_init); + +static void __exit hsc_exit(void) +{ +	hsi_unregister_client_driver(&hsc_driver); +	pr_info("HSI char device removed\n"); +} +module_exit(hsc_exit); + +MODULE_AUTHOR("Andras Domokos <andras.domokos@nokia.com>"); +MODULE_ALIAS("hsi:hsi_char"); +MODULE_DESCRIPTION("HSI character device"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hsi/clients/nokia-modem.c b/drivers/hsi/clients/nokia-modem.c new file mode 100644 index 00000000000..363b780dace --- /dev/null +++ b/drivers/hsi/clients/nokia-modem.c @@ -0,0 +1,285 @@ +/* + * nokia-modem.c + * + * HSI client driver for Nokia N900 modem. + * + * Copyright (C) 2014 Sebastian Reichel <sre@kernel.org> + * + * 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/gpio/consumer.h> +#include <linux/hsi/hsi.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/hsi/ssi_protocol.h> + +static unsigned int pm; +module_param(pm, int, 0400); +MODULE_PARM_DESC(pm, +	"Enable power management (0=disabled, 1=userland based [default])"); + +struct nokia_modem_gpio { +	struct gpio_desc	*gpio; +	const char		*name; +}; + +struct nokia_modem_device { +	struct tasklet_struct	nokia_modem_rst_ind_tasklet; +	int			nokia_modem_rst_ind_irq; +	struct device		*device; +	struct nokia_modem_gpio	*gpios; +	int			gpio_amount; +	struct hsi_client	*ssi_protocol; +}; + +static void do_nokia_modem_rst_ind_tasklet(unsigned long data) +{ +	struct nokia_modem_device *modem = (struct nokia_modem_device *)data; + +	if (!modem) +		return; + +	dev_info(modem->device, "CMT rst line change detected\n"); + +	if (modem->ssi_protocol) +		ssip_reset_event(modem->ssi_protocol); +} + +static irqreturn_t nokia_modem_rst_ind_isr(int irq, void *data) +{ +	struct nokia_modem_device *modem = (struct nokia_modem_device *)data; + +	tasklet_schedule(&modem->nokia_modem_rst_ind_tasklet); + +	return IRQ_HANDLED; +} + +static void nokia_modem_gpio_unexport(struct device *dev) +{ +	struct nokia_modem_device *modem = dev_get_drvdata(dev); +	int i; + +	for (i = 0; i < modem->gpio_amount; i++) { +		sysfs_remove_link(&dev->kobj, modem->gpios[i].name); +		gpiod_unexport(modem->gpios[i].gpio); +	} +} + +static int nokia_modem_gpio_probe(struct device *dev) +{ +	struct device_node *np = dev->of_node; +	struct nokia_modem_device *modem = dev_get_drvdata(dev); +	int gpio_count, gpio_name_count, i, err; + +	gpio_count = of_gpio_count(np); + +	if (gpio_count < 0) { +		dev_err(dev, "missing gpios: %d\n", gpio_count); +		return gpio_count; +	} + +	gpio_name_count = of_property_count_strings(np, "gpio-names"); + +	if (gpio_count != gpio_name_count) { +		dev_err(dev, "number of gpios does not equal number of gpio names\n"); +		return -EINVAL; +	} + +	modem->gpios = devm_kzalloc(dev, gpio_count * +				sizeof(struct nokia_modem_gpio), GFP_KERNEL); +	if (!modem->gpios) { +		dev_err(dev, "Could not allocate memory for gpios\n"); +		return -ENOMEM; +	} + +	modem->gpio_amount = gpio_count; + +	for (i = 0; i < gpio_count; i++) { +		modem->gpios[i].gpio = devm_gpiod_get_index(dev, NULL, i); +		if (IS_ERR(modem->gpios[i].gpio)) { +			dev_err(dev, "Could not get gpio %d\n", i); +			return PTR_ERR(modem->gpios[i].gpio); +		} + +		err = of_property_read_string_index(np, "gpio-names", i, +						&(modem->gpios[i].name)); +		if (err) { +			dev_err(dev, "Could not get gpio name %d\n", i); +			return err; +		} + +		err = gpiod_direction_output(modem->gpios[i].gpio, 0); +		if (err) +			return err; + +		err = gpiod_export(modem->gpios[i].gpio, 0); +		if (err) +			return err; + +		err = gpiod_export_link(dev, modem->gpios[i].name, +							modem->gpios[i].gpio); +		if (err) +			return err; +	} + +	return 0; +} + +static int nokia_modem_probe(struct device *dev) +{ +	struct device_node *np; +	struct nokia_modem_device *modem; +	struct hsi_client *cl = to_hsi_client(dev); +	struct hsi_port *port = hsi_get_port(cl); +	int irq, pflags, err; +	struct hsi_board_info ssip; + +	np = dev->of_node; +	if (!np) { +		dev_err(dev, "device tree node not found\n"); +		return -ENXIO; +	} + +	modem = devm_kzalloc(dev, sizeof(*modem), GFP_KERNEL); +	if (!modem) { +		dev_err(dev, "Could not allocate memory for nokia_modem_device\n"); +		return -ENOMEM; +	} +	dev_set_drvdata(dev, modem); + +	irq = irq_of_parse_and_map(np, 0); +	if (irq < 0) { +		dev_err(dev, "Invalid rst_ind interrupt (%d)\n", irq); +		return irq; +	} +	modem->nokia_modem_rst_ind_irq = irq; +	pflags = irq_get_trigger_type(irq); + +	tasklet_init(&modem->nokia_modem_rst_ind_tasklet, +			do_nokia_modem_rst_ind_tasklet, (unsigned long)modem); +	err = devm_request_irq(dev, irq, nokia_modem_rst_ind_isr, +				IRQF_DISABLED | pflags, "modem_rst_ind", modem); +	if (err < 0) { +		dev_err(dev, "Request rst_ind irq(%d) failed (flags %d)\n", +								irq, pflags); +		return err; +	} +	enable_irq_wake(irq); + +	if(pm) { +		err = nokia_modem_gpio_probe(dev); +		if (err < 0) { +			dev_err(dev, "Could not probe GPIOs\n"); +			goto error1; +		} +	} + +	ssip.name = "ssi-protocol"; +	ssip.tx_cfg = cl->tx_cfg; +	ssip.rx_cfg = cl->rx_cfg; +	ssip.platform_data = NULL; +	ssip.archdata = NULL; + +	modem->ssi_protocol = hsi_new_client(port, &ssip); +	if (!modem->ssi_protocol) { +		dev_err(dev, "Could not register ssi-protocol device\n"); +		goto error2; +	} + +	err = device_attach(&modem->ssi_protocol->device); +	if (err == 0) { +		dev_err(dev, "Missing ssi-protocol driver\n"); +		err = -EPROBE_DEFER; +		goto error3; +	} else if (err < 0) { +		dev_err(dev, "Could not load ssi-protocol driver (%d)\n", err); +		goto error3; +	} + +	/* TODO: register cmt-speech hsi client */ + +	dev_info(dev, "Registered Nokia HSI modem\n"); + +	return 0; + +error3: +	hsi_remove_client(&modem->ssi_protocol->device, NULL); +error2: +	nokia_modem_gpio_unexport(dev); +error1: +	disable_irq_wake(modem->nokia_modem_rst_ind_irq); +	tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); + +	return err; +} + +static int nokia_modem_remove(struct device *dev) +{ +	struct nokia_modem_device *modem = dev_get_drvdata(dev); + +	if (!modem) +		return 0; + +	if (modem->ssi_protocol) { +		hsi_remove_client(&modem->ssi_protocol->device, NULL); +		modem->ssi_protocol = NULL; +	} + +	nokia_modem_gpio_unexport(dev); +	dev_set_drvdata(dev, NULL); +	disable_irq_wake(modem->nokia_modem_rst_ind_irq); +	tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); + +	return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id nokia_modem_of_match[] = { +	{ .compatible = "nokia,n900-modem", }, +	{}, +}; +MODULE_DEVICE_TABLE(of, nokia_modem_of_match); +#endif + +static struct hsi_client_driver nokia_modem_driver = { +	.driver = { +		.name	= "nokia-modem", +		.owner	= THIS_MODULE, +		.probe	= nokia_modem_probe, +		.remove	= nokia_modem_remove, +		.of_match_table = of_match_ptr(nokia_modem_of_match), +	}, +}; + +static int __init nokia_modem_init(void) +{ +	return hsi_register_client_driver(&nokia_modem_driver); +} +module_init(nokia_modem_init); + +static void __exit nokia_modem_exit(void) +{ +	hsi_unregister_client_driver(&nokia_modem_driver); +} +module_exit(nokia_modem_exit); + +MODULE_ALIAS("hsi:nokia-modem"); +MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); +MODULE_DESCRIPTION("HSI driver module for Nokia N900 Modem"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hsi/clients/ssi_protocol.c b/drivers/hsi/clients/ssi_protocol.c new file mode 100644 index 00000000000..ce4be3738d4 --- /dev/null +++ b/drivers/hsi/clients/ssi_protocol.c @@ -0,0 +1,1191 @@ +/* + * 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)) { +		if (ssi->main_state == INIT) { +			ssi->main_state = HANDSHAKE; +			spin_unlock(&ssi->lock); +			ssip_send_bootinfo_req_cmd(cl); +		} else { +			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: +		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; +	case HANDSHAKE: +		/* Ignore */ +		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 expeceted %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); +} + +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 = INIT; +	spin_unlock_bh(&ssi->lock); + +	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, 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");  | 
