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"); |
