summaryrefslogtreecommitdiff
path: root/drivers/net
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/Makefile5
-rw-r--r--drivers/net/caif/caif_serial.c1
-rw-r--r--drivers/net/caif/caif_shmcore.c52
-rw-r--r--drivers/net/ethernet/smsc/smsc911x.c51
-rw-r--r--drivers/net/m6718_modem_net.c333
-rw-r--r--drivers/net/u8500_shrm.c318
6 files changed, 745 insertions, 15 deletions
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index a6b8ce11a22..a0f2484368b 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -70,3 +70,8 @@ obj-$(CONFIG_USB_IPHETH) += usb/
obj-$(CONFIG_USB_CDC_PHONET) += usb/
obj-$(CONFIG_HYPERV_NET) += hyperv/
+
+ifdef CONFIG_PHONET
+obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o
+obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_net.o
+endif
diff --git a/drivers/net/caif/caif_serial.c b/drivers/net/caif/caif_serial.c
index 8a3054b8481..957363ceae4 100644
--- a/drivers/net/caif/caif_serial.c
+++ b/drivers/net/caif/caif_serial.c
@@ -182,6 +182,7 @@ static void ldisc_receive(struct tty_struct *tty, const u8 *data,
* This is not yet handled.
*/
+ BUG_ON(ser->dev == NULL);
/*
* Workaround for garbage at start of transmission,
diff --git a/drivers/net/caif/caif_shmcore.c b/drivers/net/caif/caif_shmcore.c
index 5b2041319a3..fc55bf65f1c 100644
--- a/drivers/net/caif/caif_shmcore.c
+++ b/drivers/net/caif/caif_shmcore.c
@@ -13,6 +13,7 @@
#include <linux/list.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
+#include <linux/kthread.h>
#include <net/caif/caif_device.h>
#include <net/caif/caif_shm.h>
@@ -107,8 +108,13 @@ struct shmdrv_layer {
struct workqueue_struct *pshm_tx_workqueue;
struct workqueue_struct *pshm_rx_workqueue;
+ struct kthread_worker pshm_flow_ctrl_kw;
+ struct task_struct *pshm_flow_ctrl_kw_task;
+
struct work_struct shm_tx_work;
struct work_struct shm_rx_work;
+ struct kthread_work shm_flow_on_work;
+ struct kthread_work shm_flow_off_work;
struct sk_buff_head sk_qhead;
struct shmdev_layer *pshm_dev;
@@ -126,6 +132,24 @@ static int shm_netdev_close(struct net_device *shm_netdev)
return 0;
}
+static void shm_flow_on_work_func(struct kthread_work *work)
+{
+ struct shmdrv_layer *pshm_drv = container_of(work, struct shmdrv_layer, shm_flow_on_work);
+
+ pshm_drv->cfdev.flowctrl
+ (pshm_drv->pshm_dev->pshm_netdev,
+ CAIF_FLOW_ON);
+}
+
+static void shm_flow_off_work_func(struct kthread_work *work)
+{
+ struct shmdrv_layer *pshm_drv = container_of(work, struct shmdrv_layer, shm_flow_off_work);
+
+ pshm_drv->cfdev.flowctrl
+ (pshm_drv->pshm_dev->pshm_netdev,
+ CAIF_FLOW_OFF);
+}
+
int caif_shmdrv_rx_cb(u32 mbx_msg, void *priv)
{
struct buf_list *pbuf;
@@ -238,11 +262,9 @@ int caif_shmdrv_rx_cb(u32 mbx_msg, void *priv)
if ((avail_emptybuff > HIGH_WATERMARK) &&
(!pshm_drv->tx_empty_available)) {
pshm_drv->tx_empty_available = 1;
+ queue_kthread_work(&pshm_drv->pshm_flow_ctrl_kw,
+ &pshm_drv->shm_flow_on_work);
spin_unlock_irqrestore(&pshm_drv->lock, flags);
- pshm_drv->cfdev.flowctrl
- (pshm_drv->pshm_dev->pshm_netdev,
- CAIF_FLOW_ON);
-
/* Schedule the work queue. if required */
if (!work_pending(&pshm_drv->shm_tx_work))
@@ -426,11 +448,8 @@ static void shm_tx_work_func(struct work_struct *tx_work)
pshm_drv->tx_empty_available) {
/* Update blocking condition. */
pshm_drv->tx_empty_available = 0;
- spin_unlock_irqrestore(&pshm_drv->lock, flags);
- pshm_drv->cfdev.flowctrl
- (pshm_drv->pshm_dev->pshm_netdev,
- CAIF_FLOW_OFF);
- spin_lock_irqsave(&pshm_drv->lock, flags);
+ queue_kthread_work(&pshm_drv->pshm_flow_ctrl_kw,
+ &pshm_drv->shm_flow_off_work);
}
/*
* We simply return back to the caller if we do not have space
@@ -503,7 +522,8 @@ static void shm_tx_work_func(struct work_struct *tx_work)
pbuf->frames++;
pbuf->frm_ofs += frmlen + (frmlen % 32);
- } while (pbuf->frames < SHM_MAX_FRMS_PER_BUF);
+ } while (pbuf->frames < SHM_MAX_FRMS_PER_BUF &&
+ pbuf->frm_ofs < pbuf->len);
/* Assign buffer as full. */
list_add_tail(&pbuf->list, &pshm_drv->tx_full_list);
@@ -562,6 +582,7 @@ int caif_shmcore_probe(struct shmdev_layer *pshm_dev)
{
int result, j;
struct shmdrv_layer *pshm_drv = NULL;
+ struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };
pshm_dev->pshm_netdev = alloc_netdev(sizeof(struct shmdrv_layer),
"cfshm%d", shm_netdev_setup);
@@ -622,11 +643,20 @@ int caif_shmcore_probe(struct shmdev_layer *pshm_dev)
INIT_WORK(&pshm_drv->shm_tx_work, shm_tx_work_func);
INIT_WORK(&pshm_drv->shm_rx_work, shm_rx_work_func);
+ init_kthread_work(&pshm_drv->shm_flow_on_work, shm_flow_on_work_func);
+ init_kthread_work(&pshm_drv->shm_flow_off_work, shm_flow_off_work_func);
+
pshm_drv->pshm_tx_workqueue =
create_singlethread_workqueue("shm_tx_work");
pshm_drv->pshm_rx_workqueue =
create_singlethread_workqueue("shm_rx_work");
+ init_kthread_worker(&pshm_drv->pshm_flow_ctrl_kw);
+ pshm_drv->pshm_flow_ctrl_kw_task = kthread_run(kthread_worker_fn,
+ &pshm_drv->pshm_flow_ctrl_kw, "pshm_caif_flow_ctrl");
+ /* must use the FIFO scheduler as it is realtime sensitive */
+ sched_setscheduler(pshm_drv->pshm_flow_ctrl_kw_task, SCHED_FIFO, &param);
+
for (j = 0; j < NR_TX_BUF; j++) {
struct buf_list *tx_buf =
kmalloc(sizeof(struct buf_list), GFP_KERNEL);
@@ -744,6 +774,8 @@ void caif_shmcore_remove(struct net_device *pshm_netdev)
/* Destroy work queues. */
destroy_workqueue(pshm_drv->pshm_tx_workqueue);
destroy_workqueue(pshm_drv->pshm_rx_workqueue);
+ flush_kthread_worker(&pshm_drv->pshm_flow_ctrl_kw);
+ kthread_stop(pshm_drv->pshm_flow_ctrl_kw_task);
unregister_netdev(pshm_netdev);
}
diff --git a/drivers/net/ethernet/smsc/smsc911x.c b/drivers/net/ethernet/smsc/smsc911x.c
index 24d2df068d7..3b2a64ab1d6 100644
--- a/drivers/net/ethernet/smsc/smsc911x.c
+++ b/drivers/net/ethernet/smsc/smsc911x.c
@@ -33,6 +33,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/crc32.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
@@ -144,6 +145,9 @@ struct smsc911x_data {
/* regulators */
struct regulator_bulk_data supplies[SMSC911X_NUM_SUPPLIES];
+
+ /* clock */
+ struct clk *fsmc_clk;
};
/* Easy access to information */
@@ -369,7 +373,7 @@ out:
}
/*
- * enable resources, currently just regulators.
+ * enable resources, regulators & clocks.
*/
static int smsc911x_enable_resources(struct platform_device *pdev)
{
@@ -379,9 +383,17 @@ static int smsc911x_enable_resources(struct platform_device *pdev)
ret = regulator_bulk_enable(ARRAY_SIZE(pdata->supplies),
pdata->supplies);
- if (ret)
+ if (ret) {
netdev_err(ndev, "failed to enable regulators %d\n",
ret);
+ return ret;
+ }
+
+ if (pdata->fsmc_clk) {
+ ret = clk_enable(pdata->fsmc_clk);
+ if (ret < 0)
+ netdev_err(ndev, "failed to enable clock %d\n", ret);
+ }
return ret;
}
@@ -396,6 +408,8 @@ static int smsc911x_disable_resources(struct platform_device *pdev)
ret = regulator_bulk_disable(ARRAY_SIZE(pdata->supplies),
pdata->supplies);
+ if (pdata->fsmc_clk)
+ clk_disable(pdata->fsmc_clk);
return ret;
}
@@ -418,9 +432,17 @@ static int smsc911x_request_resources(struct platform_device *pdev)
ret = regulator_bulk_get(&pdev->dev,
ARRAY_SIZE(pdata->supplies),
pdata->supplies);
- if (ret)
- netdev_err(ndev, "couldn't get regulators %d\n",
- ret);
+ if (ret) {
+ netdev_err(ndev, "couldn't get regulators %d\n", ret);
+ return ret;
+ }
+
+ /* Request clock, ignore if not here */
+ pdata->fsmc_clk = clk_get(NULL, "fsmc");
+ if (IS_ERR(pdata->fsmc_clk)) {
+ netdev_warn(ndev, "couldn't get clock %d\n", ret);
+ pdata->fsmc_clk = NULL;
+ }
return ret;
}
@@ -436,6 +458,12 @@ static void smsc911x_free_resources(struct platform_device *pdev)
/* Free regulators */
regulator_bulk_free(ARRAY_SIZE(pdata->supplies),
pdata->supplies);
+
+ /* Free clock */
+ if (pdata->fsmc_clk) {
+ clk_put(pdata->fsmc_clk);
+ pdata->fsmc_clk = NULL;
+ }
}
/* waits for MAC not busy, with timeout. Only called by smsc911x_mac_read
@@ -2346,6 +2374,7 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
unsigned int intcfg = 0;
int res_size, irq_flags;
int retval;
+ int to = 100;
pr_info("Driver version %s\n", SMSC_DRV_VERSION);
@@ -2424,6 +2453,18 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
if (pdata->config.shift)
pdata->ops = &shifted_smsc911x_ops;
+ /* poll the READY bit in PMT_CTRL. Any other access to the device is
+ * forbidden while this bit isn't set. Try for 100ms
+ */
+ while (!(smsc911x_reg_read(pdata, PMT_CTRL) & PMT_CTRL_READY_) && --to)
+ udelay(1000);
+
+ if (to == 0) {
+ pr_err("Device not READY in 100ms aborting\n");
+ goto out_0;
+ }
+
+
retval = smsc911x_init(dev);
if (retval < 0)
goto out_disable_resources;
diff --git a/drivers/net/m6718_modem_net.c b/drivers/net/m6718_modem_net.c
new file mode 100644
index 00000000000..f64a775560b
--- /dev/null
+++ b/drivers/net/m6718_modem_net.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ * based on u8500_shrm.c
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * M6718 modem net device interface.
+ */
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/phonet.h>
+#include <linux/if_phonet.h>
+#include <linux/if_arp.h>
+#include <net/sock.h>
+#include <net/phonet/phonet.h>
+#include <net/phonet/pep.h>
+#include <linux/modem/m6718_spi/modem_net.h>
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include <linux/modem/m6718_spi/modem_char.h>
+#include <linux/ratelimit.h>
+
+
+/**
+ * modem_net_receive() - receive data and copy to user space buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copy data from ISI queue to the user space buffer.
+ */
+int modem_net_receive(struct net_device *dev)
+{
+ struct sk_buff *skb;
+ struct isa_device_context *isadev;
+ struct message_queue *q;
+ u32 msgsize;
+ u32 size = 0;
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ isadev = &modem_spi_dev->isa_context->isadev[MODEM_M6718_SPI_CHN_ISI];
+ q = &isadev->dl_queue;
+
+ spin_lock_bh(&q->update_lock);
+ if (list_empty(&q->msg_list)) {
+ spin_unlock_bh(&q->update_lock);
+ dev_dbg(modem_spi_dev->dev, "empty queue!\n");
+ return 0;
+ }
+ spin_unlock_bh(&q->update_lock);
+
+ msgsize = modem_isa_msg_size(q);
+ if (msgsize <= 0)
+ return msgsize;
+
+ /*
+ * The packet has been retrieved from the transmission
+ * medium. Build an skb around it, so upper layers can handle it
+ */
+ skb = dev_alloc_skb(msgsize);
+ if (!skb) {
+ pr_notice_ratelimited("isa rx: low on mem - packet dropped\n");
+ dev->stats.rx_dropped++;
+ return -ENOMEM;
+ }
+
+ if ((q->readptr + msgsize) >= q->size) {
+ size = (q->size - q->readptr);
+ /* copy first part of msg */
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base + q->readptr), size);
+ skb_put(skb, size);
+
+ /* copy second part of msg at the top of fifo */
+ skb_copy_to_linear_data_offset(skb, size,
+ (u8 *)(q->fifo_base), (msgsize - size));
+ skb_put(skb, msgsize - size);
+
+ } else {
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base + q->readptr), msgsize);
+ skb_put(skb, msgsize);
+ }
+
+ spin_lock_bh(&q->update_lock);
+ modem_isa_unqueue_msg(q);
+ spin_unlock_bh(&q->update_lock);
+
+ skb_reset_mac_header(skb);
+ __skb_pull(skb, dev->hard_header_len);
+ /* write metadata and then pass to the receive level */
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_PHONET);
+ skb->priority = 0;
+ skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
+ if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) {
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += msgsize;
+ } else {
+ dev->stats.rx_dropped++;
+ }
+
+ return msgsize;
+}
+EXPORT_SYMBOL_GPL(modem_net_receive);
+
+static int netdev_isa_open(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ modem_spi_dev->netdev_flag_up = 1;
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_wake_queue(dev);
+ return 0;
+}
+
+static int netdev_isa_close(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ modem_spi_dev->netdev_flag_up = 0;
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+ return 0;
+}
+
+static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct if_phonet_req *req = (struct if_phonet_req *)ifr;
+
+ switch (cmd) {
+ case SIOCPNGAUTOCONF:
+ req->ifr_phonet_autoconf.device = PN_DEV_HOST;
+ return 0;
+ }
+ return -ENOIOCTLCMD;
+}
+
+static struct net_device_stats *netdev_isa_stats(struct net_device *dev)
+{
+ return &dev->stats;
+}
+
+/**
+ * netdev_isa_write() - write through the net interface
+ * @skb: pointer to the socket buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copies data(ISI message) from the user buffer to the kernel buffer and
+ * schedule transfer thread to transmit the message to the modem via FIFO.
+ */
+static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev)
+{
+ int err;
+ int retval = 0;
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ /*
+ * FIXME:
+ * U8500 modem requires that Pipe created/enabled Indication should
+ * be sent from the port corresponding to GPRS socket.
+ * Also, the U8500 modem does not implement Pipe controller
+ * which takes care of port manipulations for GPRS traffic.
+ *
+ * Now, APE has GPRS socket and the socket for sending
+ * Indication msgs bound to different ports.
+ * Phonet stack does not allow an indication msg to be sent
+ * from GPRS socket, since Phonet stack assumes the presence
+ * of Pipe controller in modem.
+ *
+ * So, due to lack of Pipe controller implementation in the
+ * U8500 modem, carry out the port manipulation related to
+ * GPRS traffic here.
+ * Ideally, it should be done either by Pipe controller in
+ * modem OR some implementation of Pipe controller on APE side
+ */
+ if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) {
+ if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND))
+ skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX];
+ }
+
+ spin_lock_bh(&modem_spi_dev->isa_context->common_tx_lock);
+ err = modem_m6718_spi_send(modem_spi_dev, MODEM_M6718_SPI_CHN_ISI,
+ skb->len, skb->data);
+ if (!err) {
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+ retval = NETDEV_TX_OK;
+ dev_kfree_skb(skb);
+ } else {
+ dev->stats.tx_dropped++;
+ retval = NETDEV_TX_BUSY;
+ }
+ spin_unlock_bh(&modem_spi_dev->isa_context->common_tx_lock);
+
+ return retval;
+}
+
+static const struct net_device_ops modem_netdev_ops = {
+ .ndo_open = netdev_isa_open,
+ .ndo_stop = netdev_isa_close,
+ .ndo_do_ioctl = netdev_isa_ioctl,
+ .ndo_start_xmit = netdev_isa_write,
+ .ndo_get_stats = netdev_isa_stats,
+};
+
+static void net_device_init(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv;
+
+ dev->netdev_ops = &modem_netdev_ops;
+ dev->header_ops = &phonet_header_ops;
+ dev->type = ARPHRD_PHONET;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->mtu = PHONET_MAX_MTU;
+ dev->hard_header_len = MODEM_HLEN;
+ dev->addr_len = PHONET_ALEN;
+ dev->tx_queue_len = PN_TX_QUEUE_LEN;
+ dev->destructor = free_netdev;
+ dev->dev_addr[0] = PN_LINK_ADDR;
+ net_iface_priv = netdev_priv(dev);
+ memset(net_iface_priv, 0 , sizeof(struct modem_spi_net_dev));
+}
+
+int modem_net_init(struct modem_spi_dev *modem_spi_dev)
+{
+ struct net_device *nw_device;
+ struct modem_spi_net_dev *net_iface_priv;
+ int err;
+ /*
+ * keep the same net device name as U8500 to allow userspace clients
+ * to remain unchanged and use the same interfaces
+ */
+ char *devname = "shrm%d";
+
+ /* allocate the net device */
+ nw_device = modem_spi_dev->ndev =
+ alloc_netdev(sizeof(struct modem_spi_net_dev),
+ devname, net_device_init);
+ if (nw_device == NULL) {
+ dev_err(modem_spi_dev->dev,
+ "failed to allocate modem net device\n");
+ return -ENOMEM;
+ }
+ err = register_netdev(modem_spi_dev->ndev);
+ if (err) {
+ dev_err(modem_spi_dev->dev,
+ "failed to register modem net device: error %d\n", err);
+ free_netdev(modem_spi_dev->ndev);
+ return -ENODEV;
+ }
+ dev_dbg(modem_spi_dev->dev, "registered modem net device\n");
+
+ net_iface_priv = (struct modem_spi_net_dev *)netdev_priv(nw_device);
+ net_iface_priv->modem_spi_dev = modem_spi_dev;
+ net_iface_priv->iface_num = 0;
+ return err;
+}
+EXPORT_SYMBOL_GPL(modem_net_init);
+
+int modem_net_stop(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+int modem_net_restart(struct net_device *dev)
+{
+ if (netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_restart);
+
+int modem_net_start(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_start_queue(dev);
+ modem_spi_dev->netdev_flag_up = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_start);
+
+int modem_net_suspend(struct net_device *dev)
+{
+ if (netif_running(dev)) {
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+ }
+ netif_device_detach(dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_suspend);
+
+int modem_net_resume(struct net_device *dev)
+{
+ netif_device_attach(dev);
+ if (netif_running(dev)) {
+ netif_carrier_on(dev);
+ netif_wake_queue(dev);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_resume);
+
+void modem_net_exit(struct modem_spi_dev *modem_spi_dev)
+{
+ if (modem_spi_dev && modem_spi_dev->ndev) {
+ unregister_netdev(modem_spi_dev->ndev);
+ modem_spi_dev->ndev = NULL;
+ dev_dbg(modem_spi_dev->dev, "removed modem net device\n");
+ }
+}
+EXPORT_SYMBOL_GPL(modem_net_exit);
+
+MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>");
+MODULE_DESCRIPTION("M6718 modem IPC net device interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/u8500_shrm.c b/drivers/net/u8500_shrm.c
new file mode 100644
index 00000000000..0e813bbb3cc
--- /dev/null
+++ b/drivers/net/u8500_shrm.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2009
+ *
+ * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson
+ * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson
+ * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/phonet.h>
+#include <linux/if_phonet.h>
+#include <linux/if_arp.h>
+#include <linux/modem/shrm/shrm_driver.h>
+#include <linux/modem/shrm/shrm_private.h>
+#include <linux/modem/shrm/shrm_config.h>
+#include <linux/modem/shrm/shrm_net.h>
+#include <linux/modem/shrm/shrm.h>
+#include <net/sock.h>
+#include <net/phonet/phonet.h>
+#include <net/phonet/pep.h>
+
+
+/**
+ * shrm_net_receive() - receive data and copy to user space buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copy data from ISI queue to the user space buffer.
+ */
+int shrm_net_receive(struct net_device *dev)
+{
+ struct sk_buff *skb;
+ struct isadev_context *isadev;
+ struct message_queue *q;
+ u32 msgsize;
+ u32 size = 0;
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ isadev = &shrm->isa_context->isadev[ISI_MESSAGING];
+ q = &isadev->dl_queue;
+
+ spin_lock_bh(&q->update_lock);
+ if (list_empty(&q->msg_list)) {
+ spin_unlock_bh(&q->update_lock);
+ dev_dbg(shrm->dev, "Empty Shrm queue\n");
+ return 0;
+ }
+ spin_unlock_bh(&q->update_lock);
+
+ msgsize = get_size_of_new_msg(q);
+ if (msgsize <= 0)
+ return msgsize;
+
+ /*
+ * The packet has been retrieved from the transmission
+ * medium. Build an skb around it, so upper layers can handle it
+ */
+ skb = dev_alloc_skb(msgsize);
+ if (!skb) {
+ if (printk_ratelimit())
+ dev_notice(shrm->dev,
+ "isa rx: low on mem - packet dropped\n");
+ dev->stats.rx_dropped++;
+ goto out;
+ }
+
+ if ((q->readptr+msgsize) >= q->size) {
+ size = (q->size-q->readptr);
+ /*Copy First Part of msg*/
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base + q->readptr), size);
+ skb_put(skb, size);
+
+ /*Copy Second Part of msg at the top of fifo*/
+ skb_copy_to_linear_data_offset(skb, size,
+ (u8 *)(q->fifo_base), (msgsize - size));
+ skb_put(skb, msgsize-size);
+
+ } else {
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base+q->readptr), msgsize);
+ skb_put(skb, msgsize);
+ }
+
+ spin_lock_bh(&q->update_lock);
+ remove_msg_from_queue(q);
+ spin_unlock_bh(&q->update_lock);
+
+ skb_reset_mac_header(skb);
+ __skb_pull(skb, dev->hard_header_len);
+ /*Write metadata, and then pass to the receive level*/
+ skb->dev = dev;/*kmalloc(sizeof(struct net_device), GFP_ATOMIC);*/
+ skb->protocol = htons(ETH_P_PHONET);
+ skb->priority = 0;
+ skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
+ if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) {
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += msgsize;
+ } else
+ dev->stats.rx_dropped++;
+
+ return msgsize;
+out:
+ return -ENOMEM;
+}
+
+static int netdev_isa_open(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ shrm->netdev_flag_up = 1;
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_wake_queue(dev);
+ return 0;
+}
+
+static int netdev_isa_close(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ shrm->netdev_flag_up = 0;
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+ return 0;
+}
+
+static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct if_phonet_req *req = (struct if_phonet_req *)ifr;
+
+ switch (cmd) {
+ case SIOCPNGAUTOCONF:
+ req->ifr_phonet_autoconf.device = PN_DEV_HOST;
+ return 0;
+ }
+ return -ENOIOCTLCMD;
+}
+
+static struct net_device_stats *netdev_isa_stats(struct net_device *dev)
+{
+ return &dev->stats;
+}
+
+/**
+ * netdev_isa_write() - write through the net interface
+ * @skb: pointer to the socket buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copies data(ISI message) from the user buffer to the kernel buffer and
+ * schedule transfer thread to transmit the message to the modem via FIFO.
+ */
+static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev)
+{
+ int err;
+ int retval = 0;
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ /*
+ * FIXME:
+ * U8500 modem requires that Pipe created/enabled Indication should
+ * be sent from the port corresponding to GPRS socket.
+ * Also, the U8500 modem does not implement Pipe controller
+ * which takes care of port manipulations for GPRS traffic.
+ *
+ * Now, APE has GPRS socket and the socket for sending
+ * Indication msgs bound to different ports.
+ * Phonet stack does not allow an indication msg to be sent
+ * from GPRS socket, since Phonet stack assumes the presence
+ * of Pipe controller in modem.
+ *
+ * So, due to lack of Pipe controller implementation in the
+ * U8500 modem, carry out the port manipulation related to
+ * GPRS traffic here.
+ * Ideally, it should be done either by Pipe controller in
+ * modem OR some implementation of Pipe controller on APE side
+ */
+ if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) {
+ if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND))
+ skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX];
+ }
+
+ spin_lock_bh(&shrm->isa_context->common_tx);
+ err = shm_write_msg(shrm, ISI_MESSAGING, skb->data,
+ skb->len);
+ if (!err) {
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+ retval = NETDEV_TX_OK;
+ dev_kfree_skb(skb);
+ } else {
+ dev->stats.tx_dropped++;
+ retval = NETDEV_TX_BUSY;
+ }
+ spin_unlock_bh(&shrm->isa_context->common_tx);
+
+ return retval;
+}
+
+static const struct net_device_ops shrm_netdev_ops = {
+ .ndo_open = netdev_isa_open,
+ .ndo_stop = netdev_isa_close,
+ .ndo_do_ioctl = netdev_isa_ioctl,
+ .ndo_start_xmit = netdev_isa_write,
+ .ndo_get_stats = netdev_isa_stats,
+};
+
+static void shm_net_init(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv;
+
+ dev->netdev_ops = &shrm_netdev_ops;
+ dev->header_ops = &phonet_header_ops;
+ dev->type = ARPHRD_PHONET;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->mtu = PHONET_MAX_MTU;
+ dev->hard_header_len = SHRM_HLEN;
+ dev->addr_len = PHONET_ALEN;
+ dev->tx_queue_len = PN_TX_QUEUE_LEN;
+ dev->destructor = free_netdev;
+ dev->dev_addr[0] = PN_LINK_ADDR;
+ net_iface_priv = netdev_priv(dev);
+ memset(net_iface_priv, 0 , sizeof(struct shrm_net_iface_priv));
+}
+
+int shrm_register_netdev(struct shrm_dev *shrm)
+{
+ struct net_device *nw_device;
+ struct shrm_net_iface_priv *net_iface_priv;
+ char *devname = "shrm%d";
+ int err;
+
+ /* allocate the net device */
+ nw_device = shrm->ndev = alloc_netdev(
+ sizeof(struct shrm_net_iface_priv),
+ devname, shm_net_init);
+ if (nw_device == NULL) {
+ dev_err(shrm->dev, "Failed to allocate SHRM Netdev\n");
+ return -ENOMEM;
+ }
+ err = register_netdev(shrm->ndev);
+ if (err) {
+ dev_err(shrm->dev, "Err %i in reg shrm-netdev\n", err);
+ free_netdev(shrm->ndev);
+ return -ENODEV;
+ }
+ dev_info(shrm->dev, "Registered shrm netdev\n");
+
+ net_iface_priv = (struct shrm_net_iface_priv *)netdev_priv(nw_device);
+ net_iface_priv->shrm_device = shrm;
+ net_iface_priv->iface_num = 0;
+
+ return err;
+}
+
+int shrm_stop_netdev(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+int shrm_restart_netdev(struct net_device *dev)
+{
+ if (netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+ return 0;
+}
+
+int shrm_start_netdev(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_start_queue(dev);
+ shrm->netdev_flag_up = 1;
+ return 0;
+}
+
+int shrm_suspend_netdev(struct net_device *dev)
+{
+ if (netif_running(dev)) {
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+ }
+ netif_device_detach(dev);
+
+ return 0;
+}
+
+int shrm_resume_netdev(struct net_device *dev)
+{
+ netif_device_attach(dev);
+ if (netif_running(dev)) {
+ netif_carrier_on(dev);
+ netif_wake_queue(dev);
+ }
+
+ return 0;
+}
+
+void shrm_unregister_netdev(struct shrm_dev *shrm)
+{
+ unregister_netdev(shrm->ndev);
+}