diff options
Diffstat (limited to 'drivers/misc/modem_if_na/modem_link_device_usb.c')
-rw-r--r-- | drivers/misc/modem_if_na/modem_link_device_usb.c | 1031 |
1 files changed, 1031 insertions, 0 deletions
diff --git a/drivers/misc/modem_if_na/modem_link_device_usb.c b/drivers/misc/modem_if_na/modem_link_device_usb.c new file mode 100644 index 0000000..6bdd7b5 --- /dev/null +++ b/drivers/misc/modem_if_na/modem_link_device_usb.c @@ -0,0 +1,1031 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#define DEBUG + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> + +#include <linux/platform_data/modem_na.h> +#include "modem_prj.h" +#include "modem_link_device_usb.h" +#include "modem_link_pm_usb.h" + +#include <mach/regs-gpio.h> + +#define URB_COUNT 4 + +extern int factory_mode; + +static int enable_autosuspend; +static int wakelock_held; + +static int usb_tx_urb_with_skb(struct usb_link_device *usb_ld, + struct sk_buff *skb, struct if_usb_devdata *pipe_data); + +static void +usb_change_modem_state(struct usb_link_device *usb_ld, enum modem_state state); + +static int usb_attach_io_dev(struct link_device *ld, + struct io_device *iod) +{ + struct usb_link_device *usb_ld = to_usb_link_device(ld); + + iod->link = ld; + + /* list up io devices */ + list_add(&iod->list, &usb_ld->list_of_io_devices); + + return 0; +} + +static void +usb_free_urbs(struct usb_link_device *usb_ld, struct if_usb_devdata *pipe) +{ + struct usb_device *usbdev = usb_ld->usbdev; + struct urb *urb; + + while ((urb = usb_get_from_anchor(&pipe->urbs))) { + usb_poison_urb(urb); + usb_free_coherent(usbdev, pipe->rx_buf_size, + urb->transfer_buffer, urb->transfer_dma); + urb->transfer_buffer = NULL; + usb_put_urb(urb); + usb_free_urb(urb); + } +} + +static int usb_init_communication(struct link_device *ld, + struct io_device *iod) +{ + int err = 0; + switch (iod->format) { + case IPC_BOOT: + ld->com_state = COM_BOOT; + skb_queue_purge(&ld->sk_fmt_tx_q); + break; + + case IPC_RAMDUMP: + ld->com_state = COM_CRASH; + break; + + case IPC_FMT: + case IPC_RFS: + case IPC_RAW: + + default: + ld->com_state = COM_ONLINE; + break; + } + + mif_debug("com_state = %d\n", ld->com_state); + return err; +} + +static void usb_terminate_communication( + struct link_device *ld, struct io_device *iod) +{ + if (iod->format != IPC_BOOT && iod->format != IPC_RAMDUMP) + mif_debug("com_state = %d\n", ld->com_state); +} + +static int usb_rx_submit(struct if_usb_devdata *pipe, struct urb *urb, + gfp_t gfp_flags) +{ + int ret; + + usb_anchor_urb(urb, &pipe->reading); + ret = usb_submit_urb(urb, gfp_flags); + if (ret) { + usb_unanchor_urb(urb); + usb_anchor_urb(urb, &pipe->urbs); + mif_err("submit urb fail with ret (%d)\n", ret); + } + + usb_mark_last_busy(urb->dev); + return ret; +} + +static void usb_rx_complete(struct urb *urb) +{ + struct if_usb_devdata *pipe_data = urb->context; + struct usb_link_device *usb_ld = usb_get_intfdata(pipe_data->data_intf); + struct io_device *iod; + int iod_format = IPC_FMT; + int ret; + + usb_mark_last_busy(urb->dev); + + switch (urb->status) { + case 0: + case -ENOENT: + if (!urb->actual_length) + goto re_submit; + /* call iod recv */ + /* how we can distinguish boot ch with fmt ch ?? */ + switch (pipe_data->format) { + case IF_USB_FMT_EP: + iod_format = IPC_FMT; + /* + print_hex_dump(KERN_INFO, "[FMT-RX] ", + DUMP_PREFIX_OFFSET, 16, 1, + urb->transfer_buffer, + min(urb->actual_length, (u32)64), true); + */ + break; + case IF_USB_RAW_EP: + iod_format = IPC_MULTI_RAW; + break; + case IF_USB_RFS_EP: + iod_format = IPC_RFS; + break; + default: + break; + } + + list_for_each_entry(iod, &usb_ld->list_of_io_devices, list) { + /* during boot stage fmt end point */ + /* shared with boot io device */ + /* when we use fmt device only, at boot and ipc exchange + it can be reduced to 1 device */ + if (iod_format == IPC_FMT && + usb_ld->ld.com_state == COM_BOOT) + iod_format = IPC_BOOT; + if (iod_format == IPC_FMT && + usb_ld->ld.com_state == COM_CRASH) + iod_format = IPC_RAMDUMP; + + if (iod->format == iod_format) { + ret = iod->recv(iod, + (char *)urb->transfer_buffer, + urb->actual_length); + if (ret < 0) + mif_err("io device recv error :%d\n", + ret); + break; + } + } +re_submit: + if (urb->status || atomic_read(&usb_ld->suspend_count)) + break; + + usb_mark_last_busy(urb->dev); + usb_rx_submit(pipe_data, urb, GFP_ATOMIC); + return; + case -ESHUTDOWN: + case -EPROTO: + break; + case -EOVERFLOW: + mif_err("RX overflow\n"); + break; + default: + mif_err("RX complete Status (%d)\n", urb->status); + break; + } + + usb_anchor_urb(urb, &pipe_data->urbs); +} + +static int usb_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + struct sk_buff_head *txq; + + if (iod->format == IPC_RAW) + txq = &ld->sk_raw_tx_q; + else + txq = &ld->sk_fmt_tx_q; + + /* save io device into cb area */ + *((struct io_device **)skb->cb) = iod; + /* en queue skb data */ + skb_queue_tail(txq, skb); + + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + + return skb->len; +} + +static void usb_tx_complete(struct urb *urb) +{ + int ret = 0; + struct sk_buff *skb = urb->context; + + switch (urb->status) { + case 0: + break; + default: + mif_err("TX error (%d)\n", urb->status); + } + + usb_mark_last_busy(urb->dev); + ret = pm_runtime_put_autosuspend(&urb->dev->dev); + if (ret < 0) + mif_debug("pm_runtime_put_autosuspend failed : ret(%d)\n", ret); + usb_free_urb(urb); + dev_kfree_skb_any(skb); +} + +static void if_usb_force_disconnect(struct work_struct *work) +{ + struct usb_link_device *usb_ld = + container_of(work, struct usb_link_device, disconnect_work); + struct usb_device *udev = usb_ld->usbdev; + + if (!udev || !(&udev->dev)) + return; + + pm_runtime_get_sync(&udev->dev); + if (udev->state != USB_STATE_NOTATTACHED) { + usb_force_disconnect(udev); + mif_info("force disconnect by modem not responding!!\n"); + } + pm_runtime_put_autosuspend(&udev->dev); +} + +static void +usb_change_modem_state(struct usb_link_device *usb_ld, enum modem_state state) +{ + struct io_device *iod; + + list_for_each_entry(iod, &usb_ld->list_of_io_devices, list) { + if (iod->format == IPC_FMT) { + iod->modem_state_changed(iod, state); + return; + } + } +} + +static int usb_tx_urb_with_skb(struct usb_link_device *usb_ld, + struct sk_buff *skb, struct if_usb_devdata *pipe_data) +{ + int ret, cnt = 0; + struct urb *urb; + struct usb_device *usbdev = usb_ld->usbdev; + unsigned long flags; + + if (!usbdev || (usbdev->state == USB_STATE_NOTATTACHED) || + usb_ld->host_wake_timeout_flag) + return -ENODEV; + + pm_runtime_get_noresume(&usbdev->dev); + + if (usbdev->dev.power.runtime_status == RPM_SUSPENDED || + usbdev->dev.power.runtime_status == RPM_SUSPENDING) { + usb_ld->resume_status = AP_INITIATED_RESUME; + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + + while (!wait_event_interruptible_timeout(usb_ld->l2_wait, + usbdev->dev.power.runtime_status == RPM_ACTIVE + || pipe_data->disconnected, + HOST_WAKEUP_TIMEOUT_JIFFIES)) { + + if (cnt == MAX_RETRY) { + mif_err("host wakeup timeout !!\n"); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + pm_runtime_put_autosuspend(&usbdev->dev); + schedule_work(&usb_ld->disconnect_work); + usb_ld->host_wake_timeout_flag = 1; + return -1; + } + mif_err("host wakeup timeout ! retry..\n"); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + udelay(100); + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + cnt++; + } + + if (pipe_data->disconnected) { + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + pm_runtime_put_autosuspend(&usbdev->dev); + return -ENODEV; + } + + mif_debug("wait_q done (runtime_status=%d)\n", + usbdev->dev.power.runtime_status); + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + mif_err("alloc urb error\n"); + if (pm_runtime_put_autosuspend(&usbdev->dev) < 0) + mif_debug("pm_runtime_put_autosuspend fail\n"); + return -ENOMEM; + } + + urb->transfer_flags = URB_ZERO_PACKET; + usb_fill_bulk_urb(urb, usbdev, pipe_data->tx_pipe, skb->data, + skb->len, usb_tx_complete, (void *)skb); + + spin_lock_irqsave(&usb_ld->lock, flags); + if (atomic_read(&usb_ld->suspend_count)) { + /* transmission will be done in resume */ + usb_anchor_urb(urb, &usb_ld->deferred); + usb_put_urb(urb); + mif_debug("anchor urb (0x%p)\n", urb); + spin_unlock_irqrestore(&usb_ld->lock, flags); + return 0; + } + spin_unlock_irqrestore(&usb_ld->lock, flags); + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + mif_err("usb_submit_urb with ret(%d)\n", ret); + if (pm_runtime_put_autosuspend(&usbdev->dev) < 0) + mif_debug("pm_runtime_put_autosuspend fail\n"); + } + return ret; +} + +static void usb_tx_work(struct work_struct *work) +{ + int ret = 0; + struct link_device *ld = + container_of(work, struct link_device, tx_delayed_work.work); + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct io_device *iod; + struct sk_buff *skb; + struct if_usb_devdata *pipe_data; + struct link_pm_data *pm_data = usb_ld->link_pm_data; + + /*TODO: check the PHONE ACTIVE STATES */ + /* because tx data wait until hub on with wait_for_complettion, it + should queue to single_threaded work queue */ + if (!link_pm_set_active(usb_ld)) + return; + + while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) { + /* send skb from fmt_txq and raw_txq, + * one by one for fair flow control */ + skb = skb_dequeue(&ld->sk_fmt_tx_q); + if (skb) { + iod = *((struct io_device **)skb->cb); + switch (iod->format) { + case IPC_FMT: + /* + print_hex_dump(KERN_INFO, "[FMT-TX] ", + DUMP_PREFIX_OFFSET, 16, 1, + skb->data, + min(skb->len, (u32)64), true); + */ + case IPC_BOOT: + case IPC_RAMDUMP: + /* boot device uses same intf with fmt*/ + pipe_data = &usb_ld->devdata[IF_USB_FMT_EP]; + break; + case IPC_RFS: + pipe_data = &usb_ld->devdata[IF_USB_RFS_EP]; + break; + default: + /* wrong packet for fmt tx q , drop it */ + dev_kfree_skb_any(skb); + continue; + } + + ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data); + if (ret < 0) { + mif_err("usb_tx_urb_with_skb, ret(%d)\n", + ret); + skb_queue_head(&ld->sk_fmt_tx_q, skb); + + if (edc_inc(&usb_ld->urb_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + mif_err("maximum error in URB exceeded\n"); + usb_change_modem_state(usb_ld, + STATE_CRASH_EXIT); + } + return; + } + } + + skb = skb_dequeue(&ld->sk_raw_tx_q); + if (skb) { + pipe_data = &usb_ld->devdata[IF_USB_RAW_EP]; + ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data); + if (ret < 0) { + mif_err("usb_tx_urb_with_skb " + "for raw, ret(%d)\n", + ret); + skb_queue_head(&ld->sk_raw_tx_q, skb); + + if (edc_inc(&usb_ld->urb_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + mif_err("maximum error in URB exceeded\n"); + usb_change_modem_state(usb_ld, + STATE_CRASH_EXIT); + } + return; + } + } + } +} + +static int if_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + int i; + + if (atomic_inc_return(&usb_ld->suspend_count) == IF_USB_DEVNUM_MAX) { + mif_debug("L2\n"); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) + usb_kill_anchored_urbs(&usb_ld->devdata[i].reading); + + /* + if (usb_ld->link_pm_data->cpufreq_unlock) + usb_ld->link_pm_data->cpufreq_unlock(); + */ + if (wakelock_held) { + wakelock_held = 0; + wake_unlock(&usb_ld->susplock); + } + } + + return 0; +} + +static void runtime_pm_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = container_of(work, + struct usb_link_device, runtime_pm_work.work); + int ret; + + ret = pm_request_autosuspend(&usb_ld->usbdev->dev); + + if (ret == -EAGAIN || ret == 1) + queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work, + msecs_to_jiffies(50)); +} + +static void post_resume_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = container_of(work, + struct usb_link_device, post_resume_work.work); + + /* lock cpu frequency when L2->L0 */ + /* + if (usb_ld->link_pm_data->cpufreq_lock) + usb_ld->link_pm_data->cpufreq_lock(); + */ +} + +static void wait_enumeration_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = container_of(work, + struct usb_link_device, wait_enumeration.work); + + /* Work around code for factory device test & verification. + * To measure device sleep current speedly, do not call silent reset in + * factory mode. CMC22x disconect usb forcely and go sleep + * without normal L3 process if in factory mode. Also AP do. */ + if (factory_mode) + return; + + if (usb_ld->if_usb_connected == 0) { + mif_err("USB disconnected and not enumerated for long time\n"); + usb_change_modem_state(usb_ld, STATE_CRASH_EXIT); + } +} + +static int if_usb_resume(struct usb_interface *intf) +{ + int i, ret; + struct sk_buff *skb; + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + struct if_usb_devdata *pipe; + struct urb *urb; + + spin_lock_irq(&usb_ld->lock); + if (!atomic_dec_return(&usb_ld->suspend_count)) { + spin_unlock_irq(&usb_ld->lock); + + mif_debug("\n"); + wake_lock(&usb_ld->susplock); + wakelock_held = 1; + + /* HACK: Runtime pm does not allow requesting autosuspend from + * resume callback, delayed it after resume */ + queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work, + msecs_to_jiffies(50)); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + pipe = &usb_ld->devdata[i]; + while ((urb = usb_get_from_anchor(&pipe->urbs))) { + ret = usb_rx_submit(pipe, urb, GFP_KERNEL); + if (ret < 0) { + usb_put_urb(urb); + mif_err( + "usb_rx_submit error with (%d)\n", + ret); + return ret; + } + usb_put_urb(urb); + } + } + + while ((urb = usb_get_from_anchor(&usb_ld->deferred))) { + mif_debug("got urb (0x%p) from anchor & resubmit\n", + urb); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + mif_err("resubmit failed\n"); + skb = urb->context; + dev_kfree_skb_any(skb); + usb_free_urb(urb); + if (pm_runtime_put_autosuspend( + &usb_ld->usbdev->dev) < 0) + mif_debug( + "pm_runtime_put_autosuspend fail\n"); + } + } + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + udelay(100); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + + if (!link_pm_is_connected(usb_ld)) + enable_autosuspend = 1; + + /* if_usb_resume() is atomic. post_resume_work is + * a kind of bottom halves + */ + /* + queue_delayed_work(system_nrt_wq, &usb_ld->post_resume_work, 0); + */ + + return 0; + } + + spin_unlock_irq(&usb_ld->lock); + return 0; +} + +static int if_usb_reset_resume(struct usb_interface *intf) +{ + int ret; + + mif_debug("\n"); + ret = if_usb_resume(intf); + return ret; +} + +static struct usb_device_id if_usb_ids[] = { + { USB_DEVICE(0x04e8, 0x6999), /* CMC221 LTE Modem */ + /*.driver_info = 0,*/ + }, + { } /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, if_usb_ids); + +static struct usb_driver if_usb_driver; +static void if_usb_disconnect(struct usb_interface *intf) +{ + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + struct usb_device *usbdev = usb_ld->usbdev; + int dev_id = intf->altsetting->desc.bInterfaceNumber; + struct if_usb_devdata *pipe_data = &usb_ld->devdata[dev_id]; + + usb_set_intfdata(intf, NULL); + + pipe_data->disconnected = 1; + smp_wmb(); + + wake_up(&usb_ld->l2_wait); + if (wakelock_held) { + wakelock_held = 0; + wake_unlock(&usb_ld->susplock); + } + /* + if (usb_ld->if_usb_connected) { + disable_irq_wake(usb_ld->pdata->irq_host_wakeup); + free_irq(usb_ld->pdata->irq_host_wakeup, usb_ld); + } + */ + + usb_ld->if_usb_connected = 0; + usb_ld->flow_suspend = 1; + + dev_dbg(&usbdev->dev, "%s\n", __func__); + usb_ld->dev_count--; + usb_driver_release_interface(&if_usb_driver, pipe_data->data_intf); + + usb_kill_anchored_urbs(&pipe_data->reading); + usb_free_urbs(usb_ld, pipe_data); + + if (usb_ld->dev_count == 0) { + cancel_delayed_work_sync(&usb_ld->runtime_pm_work); + /* + cancel_delayed_work_sync(&usb_ld->post_resume_work); + */ + cancel_delayed_work_sync(&usb_ld->ld.tx_delayed_work); + cancel_work_sync(&usb_ld->disconnect_work); + usb_put_dev(usbdev); + usb_ld->usbdev = NULL; + + wake_lock(&usb_ld->link_pm_data->boot_wake); + + schedule_delayed_work(&usb_ld->wait_enumeration, + WAIT_ENUMURATION_TIMEOUT_JIFFIES); + } +} + +static int __devinit if_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_host_interface *data_desc; + struct usb_link_device *usb_ld = + (struct usb_link_device *)id->driver_info; + struct link_device *ld = &usb_ld->ld; + struct usb_interface *data_intf; + struct usb_device *usbdev = interface_to_usbdev(intf); + struct device *dev; + struct if_usb_devdata *pipe; + struct urb *urb; + int i; + int j; + int dev_id; + int err; + + /* To detect usb device order probed */ + dev_id = intf->cur_altsetting->desc.bInterfaceNumber; + + if (dev_id >= IF_USB_DEVNUM_MAX) { + dev_err(&intf->dev, "Device id %d cannot support\n", + dev_id); + return -EINVAL; + } + + if (!usb_ld) { + dev_err(&intf->dev, + "if_usb device doesn't be allocated\n"); + err = ENOMEM; + goto out; + } + + mif_info("probe dev_id=%d usb_device_id(0x%p), usb_ld (0x%p)\n", + dev_id, id, usb_ld); + + usb_ld->usbdev = usbdev; + + usb_get_dev(usbdev); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + data_intf = usb_ifnum_to_if(usbdev, i); + + /* remap endpoint of RAW to no.1 for LTE modem */ + if (i == 0) + pipe = &usb_ld->devdata[1]; + else if (i == 1) + pipe = &usb_ld->devdata[0]; + else + pipe = &usb_ld->devdata[i]; + + pipe->disconnected = 0; + pipe->data_intf = data_intf; + data_desc = data_intf->cur_altsetting; + + /* Endpoints */ + if (usb_pipein(data_desc->endpoint[0].desc.bEndpointAddress)) { + pipe->rx_pipe = usb_rcvbulkpipe(usbdev, + data_desc->endpoint[0].desc.bEndpointAddress); + pipe->tx_pipe = usb_sndbulkpipe(usbdev, + data_desc->endpoint[1].desc.bEndpointAddress); + pipe->rx_buf_size = 1024*4; + } else { + pipe->rx_pipe = usb_rcvbulkpipe(usbdev, + data_desc->endpoint[1].desc.bEndpointAddress); + pipe->tx_pipe = usb_sndbulkpipe(usbdev, + data_desc->endpoint[0].desc.bEndpointAddress); + pipe->rx_buf_size = 1024*4; + } + + if (i == 0) { + dev_info(&usbdev->dev, "USB IF USB device found\n"); + } else { + err = usb_driver_claim_interface(&if_usb_driver, + data_intf, usb_ld); + if (err < 0) { + mif_err("failed to cliam usb interface\n"); + goto out; + } + } + + usb_set_intfdata(data_intf, usb_ld); + usb_ld->dev_count++; + pm_suspend_ignore_children(&data_intf->dev, true); + + for (j = 0; j < URB_COUNT; j++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + mif_err("alloc urb fail\n"); + err = -ENOMEM; + goto out2; + } + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = usb_alloc_coherent(usbdev, + pipe->rx_buf_size, GFP_KERNEL, + &urb->transfer_dma); + if (!urb->transfer_buffer) { + mif_err( + "Failed to allocate transfer buffer\n"); + usb_free_urb(urb); + err = -ENOMEM; + goto out2; + } + + usb_fill_bulk_urb(urb, usbdev, pipe->rx_pipe, + urb->transfer_buffer, pipe->rx_buf_size, + usb_rx_complete, pipe); + usb_anchor_urb(urb, &pipe->urbs); + } + } + + /* temporary call reset_resume */ + atomic_set(&usb_ld->suspend_count, 1); + if_usb_reset_resume(data_intf); + atomic_set(&usb_ld->suspend_count, 0); + + SET_HOST_ACTIVE(usb_ld->pdata, 1); + usb_ld->host_wake_timeout_flag = 0; + + if (gpio_get_value(usb_ld->pdata->gpio_phone_active)) { + struct link_pm_data *pm_data = usb_ld->link_pm_data; + int delay = AUTOSUSPEND_DELAY_MS; + pm_runtime_set_autosuspend_delay(&usbdev->dev, delay); + dev = &usb_ld->usbdev->dev; + if (dev->parent) { + dev_dbg(&usbdev->dev, "if_usb Runtime PM Start!!\n"); + usb_enable_autosuspend(usb_ld->usbdev); + pm_runtime_allow(dev); + + if (pm_data->block_autosuspend) + pm_runtime_forbid(dev); + } + + enable_irq_wake(usb_ld->pdata->irq_host_wakeup); + + usb_ld->flow_suspend = 0; + /* Queue work if skbs were pending before a disconnect/probe */ + if (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + + wake_unlock(&usb_ld->link_pm_data->boot_wake); + + usb_ld->if_usb_connected = 1; + usb_change_modem_state(usb_ld, STATE_ONLINE); + } else { + usb_change_modem_state(usb_ld, STATE_LOADER_DONE); + } + + edc_init(&usb_ld->urb_edc); + + return 0; + +out2: + usb_ld->dev_count--; + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) + usb_free_urbs(usb_ld, &usb_ld->devdata[i]); +out: + usb_set_intfdata(intf, NULL); + return err; +} + +irqreturn_t usb_resume_irq(int irq, void *data) +{ + int ret; + struct usb_link_device *usb_ld = data; + int hwup; + static int wake_status = -1; + struct device *dev; + + hwup = gpio_get_value(usb_ld->pdata->gpio_host_wakeup); + if (hwup == wake_status) { + mif_err("Received spurious wake irq: %d", hwup); + return IRQ_HANDLED; + } + wake_status = hwup; + + irq_set_irq_type(irq, hwup ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + /* + * exynos BSP has problem when using level interrupt. + * If we change irq type from interrupt handler, + * we can get level interrupt twice. + * this is temporary solution until SYS.LSI resolve this problem. + */ + __raw_writel(eint_irq_to_bit(irq), S5P_EINT_PEND(EINT_REG_NR(irq))); + wake_lock_timeout(&usb_ld->gpiolock, 100); + + mif_err("< H-WUP %d\n", hwup); + + if (!link_pm_is_connected(usb_ld) && + !(!hwup && enable_autosuspend)) { + return IRQ_HANDLED; + } else { + enable_autosuspend = 0; + } + + if (hwup) { + dev = &usb_ld->usbdev->dev; + mif_info("runtime status=%d\n", + dev->power.runtime_status); + + /* if usb3503 was on, usb_if was resumed by probe */ + /* + if (has_hub(usb_ld) && + (dev->power.runtime_status == RPM_ACTIVE || + dev->power.runtime_status == RPM_RESUMING)) + return IRQ_HANDLED; + */ + + device_lock(dev); + if (dev->power.is_prepared || dev->power.is_suspended) { + pm_runtime_get_noresume(dev); + ret = 0; + } else { + ret = pm_runtime_get_sync(dev); + } + device_unlock(dev); + if (ret < 0) { + mif_err("pm_runtime_get fail (%d)\n", ret); + return IRQ_HANDLED; + } + } else { + if (usb_ld->resume_status == AP_INITIATED_RESUME) + wake_up(&usb_ld->l2_wait); + usb_ld->resume_status = CP_INITIATED_RESUME; + pm_runtime_mark_last_busy(&usb_ld->usbdev->dev); + pm_runtime_put_autosuspend(&usb_ld->usbdev->dev); + } + + return IRQ_HANDLED; +} + +static int if_usb_init(struct usb_link_device *usb_ld) +{ + int ret; + int i; + struct if_usb_devdata *pipe; + + /* give it to probe, or global variable needed */ + if_usb_ids[0].driver_info = (unsigned long)usb_ld; + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + pipe = &usb_ld->devdata[i]; + pipe->format = i; + pipe->disconnected = 1; + init_usb_anchor(&pipe->urbs); + init_usb_anchor(&pipe->reading); + } + + init_waitqueue_head(&usb_ld->l2_wait); + init_usb_anchor(&usb_ld->deferred); + + ret = usb_register(&if_usb_driver); + if (ret) { + mif_err("usb_register_driver() fail : %d\n", ret); + return ret; + } + + return 0; +} + +struct link_device *usb_create_link_device(void *data) +{ + int ret; + struct modem_data *pdata; + struct platform_device *pdev = (struct platform_device *)data; + struct usb_link_device *usb_ld = NULL; + struct link_device *ld = NULL; + + pdata = pdev->dev.platform_data; + + usb_ld = kzalloc(sizeof(struct usb_link_device), GFP_KERNEL); + if (!usb_ld) + goto err; + + INIT_LIST_HEAD(&usb_ld->list_of_io_devices); + skb_queue_head_init(&usb_ld->ld.sk_fmt_tx_q); + skb_queue_head_init(&usb_ld->ld.sk_raw_tx_q); + spin_lock_init(&usb_ld->lock); + + ld = &usb_ld->ld; + usb_ld->pdata = pdata; + + ld->name = "usb"; + ld->attach = usb_attach_io_dev; + ld->init_comm = usb_init_communication; + ld->terminate_comm = usb_terminate_communication; + ld->send = usb_send; + ld->com_state = COM_NONE; + + /*ld->tx_wq = create_singlethread_workqueue("usb_tx_wq");*/ + ld->tx_wq = alloc_workqueue("usb_tx_wq", + WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1); + + if (!ld->tx_wq) { + mif_err("fail to create work Q.\n"); + goto err; + } + + usb_ld->pdata->irq_host_wakeup = gpio_to_irq(platform_get_irq(pdev, 1)); + wake_lock_init(&usb_ld->gpiolock, WAKE_LOCK_SUSPEND, + "modem_usb_gpio_wake"); + wake_lock_init(&usb_ld->susplock, WAKE_LOCK_SUSPEND, + "modem_usb_suspend_block"); + + INIT_DELAYED_WORK(&ld->tx_delayed_work, usb_tx_work); + INIT_DELAYED_WORK(&usb_ld->runtime_pm_work, runtime_pm_work); + /* + INIT_DELAYED_WORK(&usb_ld->post_resume_work, post_resume_work); + */ + INIT_DELAYED_WORK(&usb_ld->wait_enumeration, wait_enumeration_work); + INIT_WORK(&usb_ld->disconnect_work, if_usb_force_disconnect); + + /* create link pm device */ + ret = link_pm_init(usb_ld, data); + if (ret) + goto err; + + ret = if_usb_init(usb_ld); + if (ret) + goto err; + + return ld; +err: + if (ld && ld->tx_wq) + destroy_workqueue(ld->tx_wq); + + kfree(usb_ld); + + return NULL; +} + +static struct usb_driver if_usb_driver = { + .name = "if_usb_driver", + .probe = if_usb_probe, + .disconnect = if_usb_disconnect, + .id_table = if_usb_ids, + .suspend = if_usb_suspend, + .resume = if_usb_resume, + .reset_resume = if_usb_reset_resume, + .supports_autosuspend = 1, +}; + +static void __exit if_usb_exit(void) +{ + usb_deregister(&if_usb_driver); +} + + +/* lte specific functions */ + +static int lte_wake_resume(struct device *pdev) +{ + struct modem_data *pdata = pdev->platform_data; + int val; + + val = gpio_get_value(pdata->gpio_host_wakeup); + if (!val) { + mif_debug("> S-WUP 1\n"); + gpio_set_value(pdata->gpio_slave_wakeup, 1); + } + + return 0; +} + +static const struct dev_pm_ops lte_wake_pm_ops = { + .resume = lte_wake_resume, +}; + +static struct platform_driver lte_wake_driver = { + .driver = { + .name = "modem_lte_wake", + .pm = <e_wake_pm_ops, + }, +}; + +static int __init lte_wake_init(void) +{ + return platform_driver_register(<e_wake_driver); +} +module_init(lte_wake_init); |