diff options
author | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-22 09:48:20 +0200 |
---|---|---|
committer | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-22 14:02:16 +0200 |
commit | 2489007e7d740ccbc3e0a202914e243ad5178787 (patch) | |
tree | b8e6380ea7b1da63474ad68a5dba997e01146043 /drivers/net/wimax_cmc/wimax_sdio.c | |
parent | 5f67568eb31e3a813c7c52461dcf66ade15fc2e7 (diff) | |
download | kernel_samsung_smdk4412-2489007e7d740ccbc3e0a202914e243ad5178787.zip kernel_samsung_smdk4412-2489007e7d740ccbc3e0a202914e243ad5178787.tar.gz kernel_samsung_smdk4412-2489007e7d740ccbc3e0a202914e243ad5178787.tar.bz2 |
merge opensource jb u5
Change-Id: I1aaec157aa196f3448eff8636134fce89a814cf2
Diffstat (limited to 'drivers/net/wimax_cmc/wimax_sdio.c')
-rw-r--r-- | drivers/net/wimax_cmc/wimax_sdio.c | 1789 |
1 files changed, 1789 insertions, 0 deletions
diff --git a/drivers/net/wimax_cmc/wimax_sdio.c b/drivers/net/wimax_cmc/wimax_sdio.c new file mode 100644 index 0000000..ece4b04 --- /dev/null +++ b/drivers/net/wimax_cmc/wimax_sdio.c @@ -0,0 +1,1789 @@ +/* + * Copyright (C) 2011 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. + * + */ + +#include "wimax_sdio.h" +#include "firmware.h" +#include "wimax_i2c.h" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/suspend.h> +#include <linux/miscdevice.h> +#include <linux/gpio.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/mmc/sdio_ids.h> +#include <linux/mmc/pm.h> +#include <linux/mmc/sdio_func.h> +#include <asm/byteorder.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/platform_device.h> +#include <linux/wimax/samsung/wimax732.h> +#include <linux/kthread.h> + +/* driver Information */ +#define WIMAX_DRIVER_VERSION_STRING "3.0.6" +#define DRIVER_AUTHOR "Samsung" +#define DRIVER_DESC "Samsung WiMAX SDIO Device Driver" +/* use ethtool to change the level for any given device */ +static int msg_level = -1; +module_param(msg_level, int, 0); + +static int hw_sdio_tx_bank_index(struct net_adapter *adapter, int *write_idx) +{ + int ret = 0; + + *write_idx = sdio_readb(adapter->func, SDIO_H2C_WP_REG, &ret); + if (ret) + return ret; + + if (((*write_idx + 1) % 15) == sdio_readb(adapter->func, + SDIO_H2C_RP_REG, &ret)) + *write_idx = -1; + + return ret; +} + +static void adapter_interrupt(struct sdio_func *func); +static void sdio_error(struct net_adapter *adapter) +{ + if ((adapter->sdio_error_count++) > MAX_SDIO_ERROR) + pr_err("Unable to recover from SDIO failure"); + schedule_work(&adapter->pdata->g_cfg->shutdown); +} + +bool sd_send(struct net_adapter *adapter, u8 *buffer, u32 len) +{ + int nRet; + int nWriteIdx; + + /*round off len to even*/ + (len & 1) ? len++ : 0; + + sdio_claim_host(adapter->func); + nRet = hw_sdio_tx_bank_index(adapter, &nWriteIdx); + + + if (unlikely(nRet)) + goto sdio_error; + + if (unlikely(nWriteIdx == -1)) { + pr_err("modem buffer full, skipping packet write"); + goto skip; + } + sdio_writeb(adapter->func, (nWriteIdx + 1) % 15, SDIO_H2C_WP_REG, NULL); + nRet = sdio_memcpy_toio(adapter->func, + SDIO_TX_BANK_ADDR+(CMC732_SDIO_BANK_SIZE * nWriteIdx)+ + CMC732_PACKET_LENGTH_SIZE, buffer, len); + if (unlikely(nRet < 0)) + goto sdio_error; + nRet = sdio_memcpy_toio(adapter->func, + SDIO_TX_BANK_ADDR + (CMC732_SDIO_BANK_SIZE * nWriteIdx), + &len, CMC732_PACKET_LENGTH_SIZE); + if (unlikely(nRet < 0)) + goto sdio_error; +skip: + sdio_release_host(adapter->func); + /*Reset SDIO error count once it is functional again*/ + adapter->sdio_error_count = 0; + return false; +sdio_error: + pr_err("SDIO error"); + if (adapter->pdata->g_cfg->power_state == CMC_POWER_ON) + sdio_error(adapter); + sdio_release_host(adapter->func); + return true; +} + +bool send_cmd_packet(struct net_adapter *adapter, u16 cmd_id) +{ + u8 tx_buf[CMD_MSG_TOTAL_LENGTH]; + + ((struct hw_packet_header *)tx_buf)->id0 = 'W'; + ((struct hw_packet_header *)tx_buf)->id1 = 'C'; + ((struct hw_packet_header *)tx_buf)->length = + be16_to_cpu(CMD_MSG_TOTAL_LENGTH); + ((struct wimax_msg_header *)(tx_buf + + sizeof(struct hw_packet_header)))->type + = be16_to_cpu(ETHERTYPE_DL); + ((struct wimax_msg_header *)(tx_buf + + sizeof(struct hw_packet_header)))->id + = be16_to_cpu(cmd_id); + ((struct wimax_msg_header *)(tx_buf + + sizeof(struct hw_packet_header)))->length + = be32_to_cpu(CMD_MSG_LENGTH); + return sd_send(adapter, tx_buf, CMD_MSG_TOTAL_LENGTH); +} + +bool send_image_info_packet(struct net_adapter *adapter, u16 cmd_id) +{ + struct hw_packet_header *pkt_hdr; + struct wimax_msg_header *msg_hdr; + u32 image_info[4]; + u8 tx_buf[IMAGE_INFO_MSG_TOTAL_LENGTH]; + u32 offset; + + pkt_hdr = (struct hw_packet_header *)tx_buf; + pkt_hdr->id0 = 'W'; + pkt_hdr->id1 = 'C'; + pkt_hdr->length = be16_to_cpu(IMAGE_INFO_MSG_TOTAL_LENGTH); + + offset = sizeof(struct hw_packet_header); + msg_hdr = (struct wimax_msg_header *)(tx_buf + offset); + msg_hdr->type = be16_to_cpu(ETHERTYPE_DL); + msg_hdr->id = be16_to_cpu(cmd_id); + msg_hdr->length = be32_to_cpu(IMAGE_INFO_MSG_LENGTH); + + image_info[0] = 0; + image_info[1] = be32_to_cpu(adapter->fw->size); + image_info[2] = be32_to_cpu(CMC732_WIMAX_ADDRESS); + image_info[3] = 0; + + offset += sizeof(struct wimax_msg_header); + memcpy(&(tx_buf[offset]), image_info, sizeof(image_info)); + return sd_send(adapter, tx_buf, IMAGE_INFO_MSG_TOTAL_LENGTH); +} + +bool send_image_data_packet(struct net_adapter *adapter, u16 cmd_id) +{ + struct hw_packet_header *pkt_hdr; + struct image_data_payload *pImageDataPayload; + struct wimax_msg_header *msg_hdr; + u8 *tx_buf = NULL; + u32 len; + u32 offset; + u32 size; + bool status; + + tx_buf = kmalloc(MAX_IMAGE_DATA_MSG_LENGTH, GFP_KERNEL); + if (tx_buf == NULL) { + pr_err("%s malloc fail", __func__); + return -ENOMEM; + } + + pkt_hdr = (struct hw_packet_header *)tx_buf; + pkt_hdr->id0 = 'W'; + pkt_hdr->id1 = 'C'; + + offset = sizeof(struct hw_packet_header); + msg_hdr = (struct wimax_msg_header *)(tx_buf + offset); + msg_hdr->type = be16_to_cpu(ETHERTYPE_DL); + msg_hdr->id = be16_to_cpu(cmd_id); + + if (adapter->image_offset < + (adapter->fw->size - MAX_IMAGE_DATA_LENGTH)) + len = MAX_IMAGE_DATA_LENGTH; + else + len = adapter->fw->size - adapter->image_offset; + + offset += sizeof(struct wimax_msg_header); + pImageDataPayload = (struct image_data_payload *)(tx_buf + offset); + pImageDataPayload->offset = be32_to_cpu(adapter->image_offset); + pImageDataPayload->size = be32_to_cpu(len); + + memcpy(pImageDataPayload->data, + adapter->fw->data + adapter->image_offset, len); + + size = len + 8; /* length of Payload offset + length + data */ + pkt_hdr->length = be16_to_cpu(CMD_MSG_TOTAL_LENGTH + size); + msg_hdr->length = be32_to_cpu(size); + + status = sd_send(adapter, tx_buf, CMD_MSG_TOTAL_LENGTH + size); + + kfree(tx_buf); + + adapter->image_offset += len; + + return status; +} + + +static u32 process_private_cmd(struct net_adapter *adapter, void *buffer) +{ + struct hw_private_packet *cmd; + struct wimax_cfg *g_cfg = adapter->pdata->g_cfg; + u8 *bufp = (u8 *)buffer; + + cmd = (struct hw_private_packet *)buffer; + + switch (cmd->code) { + case HWCODEMACRESPONSE: { + + if (!completion_done(&adapter->mac)) + complete(&adapter->mac); + + /* processing for mac_req request */ + #ifndef PRODUCT_SHIP + pr_debug("MAC address = %02x:%02x:%02x:%02x:%02x:%02x", + bufp[3], bufp[4], bufp[5], + bufp[6], bufp[7], bufp[8]); + #endif + /* create ethernet header */ + memcpy(adapter->eth_header, + bufp + 3, ETHERNET_ADDRESS_LENGTH); + memcpy(adapter->eth_header + ETHERNET_ADDRESS_LENGTH, + bufp + 3, ETHERNET_ADDRESS_LENGTH); + adapter->eth_header[(ETHERNET_ADDRESS_LENGTH * 2) - 1]++; + + memcpy(adapter->net->dev_addr, bufp + 3, + ETHERNET_ADDRESS_LENGTH); + + return sizeof(*cmd) + ETHERNET_ADDRESS_LENGTH - sizeof(u8); + } + case HWCODEIDLENTFY: { + pr_debug("%s HWCODEIDLENTFY", __func__); + + s3c_bat_use_wimax(0); + break; + } + case HWCODELINKINDICATION: { + if (cmd->value == HW_PROT_VALUE_LINK_DOWN) { + pr_debug("LINK_DOWN_INDICATION"); + + s3c_bat_use_wimax(0); + /* indicate link down */ + netif_stop_queue(adapter->net); + netif_carrier_off(adapter->net); + } else if (cmd->value == HW_PROT_VALUE_LINK_UP) { + pr_debug("LINK_UP_INDICATION"); + + s3c_bat_use_wimax(1); + /* indicate link up */ + netif_start_queue(adapter->net); + netif_carrier_on(adapter->net); + } + break; + } + case HWCODEWAKEUPNTFY: { + /* + *dont suspend for at least + *4 sec after modem wake up + */ + s3c_bat_use_wimax(1); + wake_lock_timeout(&g_cfg->wimax_driver_lock, 4 * HZ); + pr_debug("%s HWCODEWAKEUPNTFY", __func__); + break; + } + case HWCODEHALTEDINDICATION: { + pr_debug("%s HWCODEHALTEDINDICATION, stop driver", __func__); + schedule_work(&g_cfg->shutdown); + break; + } + case HWCODERXREADYINDICATION: { + pr_debug("Device RxReady"); + break; + } + default: + pr_debug("%s packet not supported ", __func__); + break; + } + return sizeof(*cmd); +} +static void process_indicate_packet(struct net_adapter *adapter, u8 *buffer) +{ + struct wimax_msg_header *packet; + char *tmp_byte; + + packet = (struct wimax_msg_header *)buffer; + + if (packet->type != be16_to_cpu(ETHERTYPE_DL)) { + pr_warn("%s: not a download packet\n", __func__); + return; + } + + switch (be16_to_cpu(packet->id)) { + case MSG_DRIVER_OK_RESP: + pr_debug("%s: MSG_DRIVER_OK_RESP\n", __func__); + adapter->modem_resp = true; + wake_up_interruptible(&adapter->modem_resp_event); + send_image_info_packet(adapter, MSG_IMAGE_INFO_REQ); + break; + case MSG_IMAGE_INFO_RESP: + pr_debug("%s: MSG_IMAGE_INFO_RESP\n", __func__); + send_image_data_packet(adapter, MSG_IMAGE_DATA_REQ); + break; + case MSG_IMAGE_DATA_RESP: + if (adapter->image_offset == adapter->fw->size) { + pr_debug("%s: Image Download Complete\n", __func__); + send_cmd_packet(adapter, MSG_RUN_REQ); + } else { + send_image_data_packet(adapter, MSG_IMAGE_DATA_REQ); + } + break; + case MSG_RUN_RESP: + tmp_byte = (char *)(buffer + sizeof(*packet)); + + if (*tmp_byte != 0x01) + break; + complete(&adapter->firmware_download); + pr_debug("%s: MSG_RUN_RESP\n", __func__); + break; + default: + pr_warn("%s: Unknown packet type\n", __func__); + break; + } +} + +/* receive control data */ +void control_recv(struct net_adapter *adapter, void *buffer, u32 length) +{ + struct process_descriptor *procdsc; + struct buffer_descriptor *bufdsc; + struct list_head *pos, *nxt; + + mutex_lock(&adapter->control_lock); + list_for_each_safe(pos, nxt, &adapter->control_process_list) { + procdsc = list_entry(pos, struct process_descriptor, list); + if (procdsc->type != *((u16 *)buffer)) + continue; + bufdsc = kmalloc(sizeof(*bufdsc), GFP_KERNEL); + bufdsc->buffer = kmalloc( + (length + (ETHERNET_ADDRESS_LENGTH * 2)), GFP_KERNEL); + memcpy(bufdsc->buffer, adapter->eth_header, + (ETHERNET_ADDRESS_LENGTH * 2)); + memcpy(bufdsc->buffer + (ETHERNET_ADDRESS_LENGTH * 2), + buffer, length); + + /* fill out descriptor */ + bufdsc->length = length + (ETHERNET_ADDRESS_LENGTH * 2); + list_add_tail(&bufdsc->list, &procdsc->buffer_list); + wake_up_interruptible(&procdsc->read_wait); + } + mutex_unlock(&adapter->control_lock); +} + +void prepare_skb(struct net_adapter *adapter, struct sk_buff *rx_skb) +{ + skb_reserve(rx_skb, + (ETHERNET_ADDRESS_LENGTH * 2) + + NET_IP_ALIGN); + + memcpy(skb_push(rx_skb, + (ETHERNET_ADDRESS_LENGTH * 2)), + adapter->eth_header, + (ETHERNET_ADDRESS_LENGTH * 2)); + + rx_skb->dev = adapter->net; + rx_skb->ip_summed = CHECKSUM_UNNECESSARY; +} +void flush_skb(struct net_adapter *adapter) +{ + if (adapter->rx_skb) { + dev_kfree_skb(adapter->rx_skb); + adapter->rx_skb = NULL; + } +} +struct sk_buff *fetch_skb(struct net_adapter *adapter) +{ + struct sk_buff *ret_skb; + if (adapter->rx_skb) { + ret_skb = adapter->rx_skb; + adapter->rx_skb = NULL; + return ret_skb; + } + ret_skb = dev_alloc_skb(WIMAX_MTU_SIZE+2+ + (ETHERNET_ADDRESS_LENGTH * 2) + + NET_IP_ALIGN); + if (!ret_skb) { + pr_debug("unable to allocate skb"); + return NULL; + } + prepare_skb(adapter, ret_skb); + return ret_skb; +} +void pull_skb(struct net_adapter *adapter) +{ + struct sk_buff *t_skb; + if (adapter->rx_skb == NULL) { + t_skb = dev_alloc_skb(WIMAX_MTU_SIZE+2+ + (ETHERNET_ADDRESS_LENGTH * 2) + + NET_IP_ALIGN); + if (!t_skb) { + pr_debug("unable to allocate skb"); + return; + } + prepare_skb(adapter, t_skb); + adapter->rx_skb = t_skb; + } +} + +static void adapter_rx_packet(struct net_adapter *adapter) +{ + struct hw_packet_header *hdr; + s32 rlen = adapter->buff_len; + u32 l; + u8 *ofs; + struct sk_buff *rx_skb; + ofs = adapter->receive_buffer; + + while (rlen > 0) { + hdr = (struct hw_packet_header *)ofs; + + /* "WD", "WC", "WP" or "WE" */ + if (unlikely(hdr->id0 != 'W')) { + /*Ignore if it is the 4 byte allignment*/ + if (rlen > 4) { + pr_warn("Wrong packet ID (%02x %02x)", + hdr->id0, hdr->id1); + } + /* skip rest of packets */ + break; + } + + /* change offset */ + ofs += sizeof(*hdr); + rlen -= sizeof(*hdr); + + /* check packet type */ + switch (hdr->id1) { + case 'P': { + /* revert offset */ + ofs -= sizeof(*hdr); + rlen += sizeof(*hdr); + /* process packet */ + l = process_private_cmd(adapter, ofs); + /* shift */ + ofs += l; + rlen -= l; + + /* process next packet */ + continue; + } + case 'C': + if (adapter->pdata->g_cfg->power_state == + CMC_POWER_ON) { + ofs += 2; + rlen -= 2; + control_recv(adapter, (u8 *)ofs, hdr->length); + break; + } else { + hdr->length -= sizeof(*hdr); + process_indicate_packet(adapter, ofs); + break; + } + case 'D': + ofs += 2; + rlen -= 2; + + if (hdr->length > BUFFER_DATA_SIZE) { + pr_err("Data packet too large"); + adapter->netstats.rx_dropped++; + break; + } + + if (likely(hdr->length <= (WIMAX_MTU_SIZE + 2))) { + rx_skb = fetch_skb(adapter); + if (!rx_skb) { + pr_err("unable to allocate skb"); + break; + } + } else { + rx_skb = dev_alloc_skb(hdr->length + + (ETHERNET_ADDRESS_LENGTH * 2) + + NET_IP_ALIGN); + if (!rx_skb) { + pr_err("unable to allocate skb"); + break; + } + prepare_skb(adapter, rx_skb); + } + + memcpy(skb_put(rx_skb, hdr->length), + (u8 *)ofs, + hdr->length); + + rx_skb->protocol = + eth_type_trans(rx_skb, adapter->net); + + if (netif_rx_ni(rx_skb) == NET_RX_DROP) { + pr_err("packet dropped!"); + adapter->netstats.rx_dropped++; + } + adapter->netstats.rx_packets++; + adapter->netstats.rx_bytes += + (hdr->length + + (ETHERNET_ADDRESS_LENGTH * 2)); + + break; + case 'E': + /* skip rest of buffer */ + break; + default: + pr_warn("%s :Wrong packet ID [%02x %02x]", + __func__, hdr->id0, hdr->id1); + /* skip rest of buffer */ + break; + } + + ofs += hdr->length; + rlen -= hdr->length; + } + + return; +} + +void rx_packet(struct net_adapter *adapter) +{ + int ret = 0; + int read_idx; + s32 t_len; + s32 t_index; + s32 t_size; + u8 *t_buff; + + sdio_claim_host(adapter->func); + read_idx = sdio_readb(adapter->func, SDIO_C2H_RP_REG, &ret); + + t_len = sdio_readl(adapter->func, (SDIO_RX_BANK_ADDR + + (read_idx * CMC732_SDIO_BANK_SIZE)), &ret); + if (unlikely(ret)) { + pr_err("%s sdio_readl error", __func__); + sdio_error(adapter); + goto err; + } + + if (unlikely(t_len > CMC732_MAX_PACKET_SIZE)) { + pr_err("%s length out of bound", __func__); + t_len = CMC732_MAX_PACKET_SIZE; + } + + sdio_writeb(adapter->func, (read_idx + 1) % 16, + SDIO_C2H_RP_REG, NULL); + if (unlikely(!t_len)) + goto err; + + + adapter->buff_len = t_len; + t_index = (SDIO_RX_BANK_ADDR + (CMC732_SDIO_BANK_SIZE * read_idx) + 4); + t_buff = adapter->receive_buffer; + + while (likely(t_len)) { + t_size = (t_len > CMC_BLOCK_SIZE) ? + (CMC_BLOCK_SIZE) : t_len; + ret = sdio_memcpy_fromio(adapter->func, (void *)t_buff, + t_index, t_size); + if (unlikely(ret)) { + pr_err("%s sdio_memcpy_fromio fail", __func__); + sdio_error(adapter); + goto err; + } + t_len -= t_size; + t_buff += t_size; + t_index += t_size; + } + sdio_release_host(adapter->func); + return; +err: + adapter->netstats.rx_dropped++; + sdio_release_host(adapter->func); + return; +} +static void rx_process_data(struct work_struct *rx_work) +{ + struct net_adapter *adapter = container_of(rx_work, + struct net_adapter, rx_work); + + rx_packet(adapter); + adapter_rx_packet(adapter); + +} + +static void adapter_interrupt(struct sdio_func *func) +{ + struct net_adapter *adapter = sdio_get_drvdata(func); + int intrd = 0; + struct buffer_descriptor *bufdsc; + + /* read interrupt identification register and clear the interrupt */ + intrd = sdio_readb(func, SDIO_INT_STATUS_REG, NULL); + sdio_writeb(func, intrd, SDIO_INT_STATUS_CLR_REG, NULL); + + if (likely(intrd & SDIO_INT_DATA_READY)) { + queue_work(adapter->wimax_workqueue, + &adapter->rx_work); + } else { + adapter->netstats.rx_errors++; + pr_err("%s intrd = SDIO_INT_ERROR occurred", + __func__); + } +} + +bool send_mac_request(struct net_adapter *adapter) +{ + struct hw_private_packet req; + req.id0 = 'W'; + req.id1 = 'P'; + req.code = HWCODEMACREQUEST; + req.value = 0; + return sd_send(adapter, (u8 *)&req, sizeof(req)); +} + +int hw_device_wakeup(struct net_adapter *adapter) +{ + int rc = 0; + int ret = 0; + struct wimax732_platform_data *pdata = adapter->pdata; + adapter->pdata->wakeup_assert(1); + + while (unlikely(!pdata->is_modem_awake())) { + rc++; + if (rc > WAKEUP_MAX_TRY) { + pr_err("%s (CON0 status): modem wake up time out", + __func__); + ret = -EIO; + break; + } + + if (rc == 1) + pr_debug("%s (CON0 status): waiting for modem awake", + __func__); + msleep(WAKEUP_ASSERT_T); + if (pdata->is_modem_awake()) + break; + pdata->wakeup_assert(0); + msleep(WAKEUP_ASSERT_T); + pdata->wakeup_assert(1); + } + + s3c_bat_use_wimax(1); + if (unlikely((rc > 0) && (rc <= WAKEUP_MAX_TRY))) + pr_debug("%s (CON0 status): modem awake", __func__); + pdata->wakeup_assert(0); + return ret; +} + +static void tx_process_data(struct work_struct *tx_work) +{ + struct buffer_descriptor *bufdsc; + struct net_adapter *adapter = container_of(tx_work, + struct net_adapter, tx_work); + struct wimax_cfg *g_cfg = adapter->pdata->g_cfg; + unsigned long flags; + bool nRet; + + while (1) { + + if (!mutex_trylock(&g_cfg->suspend_mutex)) + return; + spin_lock_irqsave(&adapter->send_lock, flags); + if (likely(!list_empty(&adapter->q_send))) { + bufdsc = list_first_entry(&adapter->q_send, + struct buffer_descriptor, list); + list_del(&bufdsc->list); + } else { + spin_unlock_irqrestore(&adapter->send_lock, flags); + mutex_unlock(&g_cfg->suspend_mutex); + return; + } + spin_unlock_irqrestore(&adapter->send_lock, flags); + if (unlikely(hw_device_wakeup(adapter))) { + schedule_work(&g_cfg->shutdown); + mutex_unlock(&g_cfg->suspend_mutex); + kfree(bufdsc->buffer); + kfree(bufdsc); + return; + } + if (bufdsc->length > CMC_MAX_BYTE_SIZE) + bufdsc->length = (bufdsc->length + CMC_MAX_BYTE_SIZE) & + ~(CMC_MAX_BYTE_SIZE); + nRet = sd_send(adapter, bufdsc->buffer, bufdsc->length); + mutex_unlock(&g_cfg->suspend_mutex); + kfree(bufdsc->buffer); + kfree(bufdsc); + if (unlikely(nRet)) + return; + }; +} + +struct process_descriptor *process_by_id(struct net_adapter *adapter, u32 id) +{ + struct process_descriptor *procdsc; + + list_for_each_entry(procdsc, &adapter->control_process_list, list) { + if (procdsc->id == id) /* process found */ + return procdsc; + } + return NULL; +} + +struct process_descriptor *fetch_process_by_id(struct net_adapter *adapter, + u32 id) +{ + struct process_descriptor *procdsc; + procdsc = process_by_id(adapter, id); + if (!procdsc) { + procdsc = kzalloc(sizeof(*procdsc), GFP_KERNEL); + if (!procdsc) + return NULL; + procdsc->id = id; + init_waitqueue_head(&procdsc->read_wait); + INIT_LIST_HEAD(&procdsc->buffer_list); + mutex_lock(&adapter->control_lock); + list_add_tail(&procdsc->list, &adapter->control_process_list); + mutex_unlock(&adapter->control_lock); + } + return procdsc; +} + +u32 control_send(struct net_adapter *adapter, void *buffer, u32 length) +{ + struct buffer_descriptor *bufdsc; + struct hw_packet_header *hdr; + unsigned long flags; + u8 *ptr; + + if ((length + sizeof(*hdr)) >= WIMAX_MAX_TOTAL_SIZE) + return -ENOMEM;/* changed from SUCCESS return status */ + + bufdsc = (struct buffer_descriptor *) + kmalloc(sizeof(*bufdsc), GFP_KERNEL); + if (bufdsc == NULL) + return -ENOMEM; + bufdsc->buffer = kmalloc(BUFFER_DATA_SIZE, GFP_KERNEL); + if (bufdsc->buffer == NULL) + return -ENOMEM; + + ptr = bufdsc->buffer; + hdr = (struct hw_packet_header *)bufdsc->buffer; + + ptr += sizeof(*hdr); + ptr += 2; + + memcpy(ptr, buffer + (ETHERNET_ADDRESS_LENGTH * 2), + length - (ETHERNET_ADDRESS_LENGTH * 2)); + + /* add packet header */ + hdr->id0 = 'W'; + hdr->id1 = 'C'; + hdr->length = (u16)length - (ETHERNET_ADDRESS_LENGTH * 2); + + /* set length */ + bufdsc->length = (u16)length - (ETHERNET_ADDRESS_LENGTH * 2) + + sizeof(*hdr); + bufdsc->length += 2; + + spin_lock_irqsave(&adapter->send_lock, flags); + list_add_tail(&bufdsc->list, &adapter->q_send); + spin_unlock_irqrestore(&adapter->send_lock, flags); + schedule_work(&adapter->tx_work); + return 0; +} + +/* +* uwibro functions +* (send and receive control packet with WiMAX modem) +*/ +static int uwbrdev_open(struct inode *inode, struct file *file) +{ + struct net_adapter *adapter = container_of(file->private_data, + struct net_adapter, uwibro_dev); + file->private_data = adapter; + return 0; +} + +static long uwbrdev_ioctl(struct file *file, u32 cmd, + unsigned long arg) +{ + struct process_descriptor *procdsc; + int ret = 0; + u8 *tx_buffer; + int length; + struct net_adapter *adapter = + (struct net_adapter *)(file->private_data); + + if (adapter->pdata->g_cfg->power_state != CMC_POWER_ON) + return -ENODEV; + + if (cmd != CONTROL_IOCTL_WRITE_REQUEST) { + pr_debug("uwbrdev_ioctl: unknown ioctl cmd: 0x%x", cmd); + return -EINVAL; + } + + if ((char *)arg == NULL) { + pr_debug("arg == NULL: return -EFAULT"); + return -EFAULT; + } + + procdsc = fetch_process_by_id(adapter, current->tgid); + if (!procdsc) + return -ENOMEM; + + length = ((int *)arg)[0]; + + if (length >= WIMAX_MAX_TOTAL_SIZE) + return -EFBIG; + + tx_buffer = kmalloc(length, GFP_KERNEL); + if (!tx_buffer) { + pr_err("%s: not enough memory to allocate tx_buffer\n", + __func__); + return -ENOMEM; + } + + if (copy_from_user(tx_buffer, (void *)(arg + sizeof(int)), length)) { + pr_err("%s: error copying buffer from user space\n", __func__); + ret = -EFAULT; + goto err_copy; + } + + procdsc->type = ((struct eth_header *)tx_buffer)->type; + control_send(adapter, tx_buffer, length); + +err_copy: + kfree(tx_buffer); + return ret; +} + +static ssize_t uwbrdev_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + struct net_adapter *adapter; + struct process_descriptor *procdsc; + struct buffer_descriptor *bufdsc; + u32 len = 0; + adapter = (struct net_adapter *)(file->private_data); + + if (buf == NULL) { + pr_debug("BUFFER is NULL"); + return -EFAULT; /* bad address */ + } + if (adapter->pdata->g_cfg->power_state == CMC_POWERING_OFF) + return -ENODEV; + procdsc = fetch_process_by_id(adapter, current->tgid); + if (procdsc == NULL) + return -ENOMEM; + + if (wait_event_interruptible(procdsc->read_wait, + (!list_empty(&procdsc->buffer_list)) || + (adapter->pdata->g_cfg->power_state == + CMC_POWERING_OFF))) + return -ERESTARTSYS; + if (adapter->pdata->g_cfg->power_state == CMC_POWERING_OFF) + return -ENODEV; + + if (count == 1500) { /* app passes read count as 1500 */ + mutex_lock(&adapter->control_lock); + bufdsc = list_first_entry(&procdsc->buffer_list, + struct buffer_descriptor, list); + list_del(&bufdsc->list); + mutex_unlock(&adapter->control_lock); + len = bufdsc->length; + if (copy_to_user(buf, bufdsc->buffer, len)) { + pr_debug("%s: copy_to_user failed len=%u !!", + __func__, bufdsc->length); + kfree(bufdsc->buffer); + kfree(bufdsc); + return -EFAULT; + } + kfree(bufdsc->buffer); + kfree(bufdsc); + return len; + } + + return 0; +} + +static const struct file_operations uwbr_fops = { + .owner = THIS_MODULE, + .open = uwbrdev_open, + .unlocked_ioctl = uwbrdev_ioctl, + .read = uwbrdev_read, +}; + +u32 hw_send_data(struct net_adapter *adapter, void *buffer , u32 length) +{ + struct buffer_descriptor *bufdsc; + struct hw_packet_header *hdr; + struct net_device *net = adapter->net; + unsigned long flags; + u8 *ptr; + + bufdsc = (struct buffer_descriptor *) + kmalloc(sizeof(*bufdsc), GFP_ATOMIC); + if (bufdsc == NULL) + return -ENOMEM; + + bufdsc->buffer = kmalloc(BUFFER_DATA_SIZE, GFP_ATOMIC); + if (bufdsc->buffer == NULL) { + kfree(bufdsc); + return -ENOMEM; + } + + ptr = bufdsc->buffer; + + /* shift data pointer */ + ptr = ptr + sizeof(*hdr) + 2; + hdr = (struct hw_packet_header *)bufdsc->buffer; + + length -= (ETHERNET_ADDRESS_LENGTH * 2); + buffer += (ETHERNET_ADDRESS_LENGTH * 2); + + memcpy(ptr, buffer, length); + + hdr->id0 = 'W'; + hdr->id1 = 'D'; + hdr->length = (u16)length; + + bufdsc->length = length + sizeof(*hdr) + 2; + + /* add statistics */ + adapter->netstats.tx_packets++; + adapter->netstats.tx_bytes += bufdsc->length; + + spin_lock_irqsave(&adapter->send_lock, flags); + list_add_tail(&bufdsc->list, &adapter->q_send); + spin_unlock_irqrestore(&adapter->send_lock, flags); + + queue_work(adapter->wimax_workqueue, &adapter->tx_work); + if (!netif_running(net)) + pr_debug("!netif_running"); + + return 0; +} + +static int netdev_ethtool_ioctl(struct net_device *dev, + void *useraddr) +{ + u32 ethcmd; + struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO}; + + if (copy_from_user(ðcmd, useraddr, sizeof(ethcmd))) + return -EFAULT; + + if (ethcmd != ETHTOOL_GDRVINFO) + return -EOPNOTSUPP; + + strncpy(info.driver, "C732SDIO", sizeof(info.driver) - 1); + if (copy_to_user(useraddr, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static struct net_device_stats *adapter_netdev_stats(struct net_device *dev) +{ + return &((struct net_adapter *)netdev_priv(dev))->netstats; +} + +static int adapter_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct net_adapter *adapter = netdev_priv(net); + hw_send_data(adapter, skb->data, skb->len); + dev_kfree_skb(skb); + return 0; +} + +static int adapter_ioctl(struct net_device *net, struct ifreq *rq, int cmd) +{ + switch (cmd) { + case SIOCETHTOOL: + return netdev_ethtool_ioctl(net, (void *)rq->ifr_data); + default: + return -EOPNOTSUPP; + } + + return 0; +} + + +static struct net_device_ops wimax_net_ops = { + .ndo_get_stats = adapter_netdev_stats, + .ndo_do_ioctl = adapter_ioctl, + .ndo_start_xmit = adapter_start_xmit, +}; + +static irqreturn_t wimax_hostwake_isr(int irq, void *dev) +{ + struct net_adapter *adapter = dev; + wake_lock_timeout(&adapter->pdata->g_cfg->wimax_driver_lock, 1 * HZ); + + return IRQ_HANDLED; +} + +static int cmc732_setup_wake_irq(struct net_adapter *adapter) +{ + struct wimax732_platform_data *pdata = adapter->pdata; + int rc; + int irq; + + rc = gpio_request(pdata->wimax_int, "gpio_wimax_int"); + if (rc < 0) { + pr_debug("%s: gpio %d request failed (%d)\n", + __func__, pdata->wimax_int, rc); + return rc; + } + + rc = gpio_direction_input(pdata->wimax_int); + if (rc < 0) { + pr_debug("%s: failed to set gpio %d as input (%d)\n", + __func__, pdata->wimax_int, rc); + goto err_gpio_direction_input; + } + + irq = gpio_to_irq(pdata->wimax_int); + + rc = request_threaded_irq(irq, NULL, wimax_hostwake_isr, + IRQF_TRIGGER_FALLING, "wimax_int", adapter); + if (rc < 0) { + pr_debug("%s: request_irq(%d) failed for gpio %d (%d)\n", + __func__, irq, + pdata->wimax_int, rc); + goto err_request_irq; + } + + rc = enable_irq_wake(irq); + if (rc < 0) { + pr_err("%s: enable_irq_wake(%d) failed for gpio %d (%d)\n", + __func__, irq, pdata->wimax_int, rc); + goto err_enable_irq_wake; + } + + adapter->wake_irq = irq; + + return 0; + +err_enable_irq_wake: + free_irq(irq, adapter); +err_request_irq: +err_gpio_direction_input: + gpio_free(pdata->wimax_int); + return rc; +} + +static void cmc732_release_wake_irq(struct net_adapter *adapter) +{ + if (!adapter->wake_irq) + return; + disable_irq_wake(adapter->wake_irq); + free_irq(adapter->wake_irq, adapter); + gpio_free(adapter->pdata->wimax_int); +} + +#ifdef WIMAX_CON0_POLL +int con0_poll_thread(void *data) +{ + struct net_adapter *adapter = (struct net_adapter *)data; + struct wimax_cfg *g_cfg = adapter->pdata->g_cfg; + int prev_val = 0; + int curr_val; + + wake_lock(&g_cfg->wimax_driver_lock); + + while ((g_cfg->power_state != CMC_POWERING_OFF) && + (g_cfg->power_state != CMC_POWER_OFF)) { + curr_val = adapter->pdata->is_modem_awake(); + if ((prev_val && (!curr_val)) || (!curr_val)) { + adapter->pdata->restore_uart_path(); + break; + } + prev_val = curr_val; + wait_event_interruptible_timeout(adapter->con0_poll, + (g_cfg->power_state == CMC_POWERING_OFF) || + (g_cfg->power_state == CMC_POWER_OFF), + msecs_to_jiffies(40)); + } + wake_unlock(&g_cfg->wimax_driver_lock); + do_exit(0); + return 0; +} +#endif + +static int wimax_power_on(struct wimax732_platform_data *pdata) +{ + + struct net_adapter *adapter = NULL; + struct net_device *net; + struct wimax_cfg *g_cfg = pdata->g_cfg; + struct buffer_descriptor *bufdsc; + struct process_descriptor *procdsc; + struct list_head *pos, *nxt, *pos1, *nxt1; + int count; + long ret; + int err = 0; + u8 node_id[ETH_ALEN]; + + mutex_lock(&g_cfg->power_mutex); + /*dont sleep when turning on*/ + mutex_lock(&g_cfg->suspend_mutex); + + /*Exit if wimax is already ON*/ + if (g_cfg->power_state == CMC_POWER_ON) { + pr_debug("WiMAX already ON"); + err = WIMAX_ALREADY_POWER_ON; + goto exit; + } + + g_cfg->power_state = CMC_POWERING_ON; + pr_debug("WIMAX POWERING ON"); + + net = alloc_etherdev(sizeof(*adapter)); + if (!net) { + pr_debug("%s: error can't allocate device", __func__); + goto alloceth_fail; + } + + adapter = netdev_priv(net); + memset(adapter, 0, sizeof(*adapter)); + adapter->pdata = pdata; + pdata->adapter_data = adapter; + adapter->net = net; + + init_completion(&adapter->probe); + init_completion(&adapter->remove); + init_completion(&adapter->firmware_download); + init_completion(&adapter->mac); + + pdata->power(1); /*power ON wimax chipset*/ + + /*We need to sleep wait for atleast CMC_BOOT_LOAD duration. + *using msleep sometimes returns before the requested time + * hence the alternative below*/ + if (wait_for_completion_interruptible_timeout( + &adapter->probe, + msecs_to_jiffies(CMC_BOOTLOAD_TIME))) { + pr_warn("-ERESTARTSYS during CMC_BOOTLOAD_TIME"); + goto probe_timeout; + } + + pdata->detect(1); /*detect the presence of SDIO card*/ + + /*wait for SDIO driver probe*/ + ret = wait_for_completion_interruptible_timeout( + &adapter->probe, + msecs_to_jiffies(CMC_PROBE_TIMEOUT)); + if (ret) { + if (ret == -ERESTARTSYS) { + pr_err("-ERESTARTSYS during CMC_PROBE_TIMEOUT"); + goto probe_timeout; + } + } else { + pr_err("%s CMC_PROBE_TIMEOUT", __func__); + err = WIMAX_POWER_FAIL; + goto probe_timeout; + } + + /*set the mode pins of modem for the firmware to boot in desired mode*/ + pdata->set_mode(); + + mutex_init(&adapter->control_lock); + + /* For sending data and control packets */ + INIT_LIST_HEAD(&adapter->q_send); + spin_lock_init(&adapter->send_lock); + INIT_LIST_HEAD(&adapter->control_process_list); + + INIT_WORK(&adapter->tx_work, tx_process_data); + + /*load the wimax firmware*/ + if (request_firmware(&adapter->fw, (g_cfg->wimax_mode == AUTH_MODE) ? + WIMAX_LOADER_PATH : WIMAX_IMAGE_PATH, + &adapter->func->dev)) { + dev_err(&adapter->func->dev, "%s: Can't open firmware file\n", + __func__); + goto firmwareload_fail; + } + + sdio_claim_host(adapter->func); + err = sdio_enable_func(adapter->func); + if (err < 0) { + pr_err("sdio_enable func error = %d", err); + release_firmware(adapter->fw); + goto sdioen_fail; + } + + adapter->wimax_workqueue = create_workqueue("wimax_queue"); + INIT_WORK(&adapter->rx_work, rx_process_data); + adapter->receive_buffer = kmalloc(CMC732_MAX_PACKET_SIZE, GFP_KERNEL); + if (!adapter->receive_buffer) { + pr_err("receive_buffer alloc error"); + goto buffer_fail; + } + err = sdio_claim_irq(adapter->func, adapter_interrupt); + if (err < 0) { + pr_err("sdio_claim_irq = %d", err); + release_firmware(adapter->fw); + goto sdioirq_fail; + } + sdio_set_block_size(adapter->func, CMC_BLOCK_SIZE); + sdio_release_host(adapter->func); + init_waitqueue_head(&adapter->modem_resp_event); + count = 0; + + while (!adapter->modem_resp) { + /*This command will start the + firmware download sequence through sdio*/ + send_cmd_packet(adapter, MSG_DRIVER_OK_REQ); + ret = wait_event_interruptible_timeout( + adapter->modem_resp_event, + adapter->modem_resp, HZ/10); + if (!adapter->modem_resp) + pr_err("no modem response"); + if ((++count > MODEM_RESP_RETRY) || (ret == -ERESTARTSYS)) { + release_firmware(adapter->fw); + goto firmware_download_fail; + } + } + + ret = wait_for_completion_interruptible_timeout( + &adapter->firmware_download, + msecs_to_jiffies(CMC_FIRMWARE_DOWNLOAD_TIMEOUT)); + if (ret) { + if (ret == -ERESTARTSYS) { + pr_err("-ERESTARTSYS firmware download fail"); + release_firmware(adapter->fw); + goto firmware_download_fail; + } + } else { + pr_err("%s CMC_FIRMWARE_DOWNLOAD_TIMEOUT", __func__); + release_firmware(adapter->fw); + goto firmware_download_fail; + } + release_firmware(adapter->fw); + + /*wait for firmware to initialize before proceeding*/ + msleep(1700); + + + /* Dummy value for "ifconfig up" for 2.6.24 */ + random_ether_addr(node_id); + memcpy(net->dev_addr, node_id, sizeof(node_id)); + + /*get the MAC when in the following modes*/ + if (g_cfg->wimax_mode == SDIO_MODE + || g_cfg->wimax_mode == DM_MODE + || g_cfg->wimax_mode == USB_MODE + || g_cfg->wimax_mode == USIM_RELAY_MODE) { + + count = MAC_RETRY_COUNT; + do { + if (!(count--)) { + pr_err("%s MAC request timeout", __func__); + goto mac_request_fail; + } + if (send_mac_request(adapter)) + goto mac_request_fail; + ret = wait_for_completion_interruptible_timeout( + &adapter->mac, + msecs_to_jiffies((MAC_RETRY_COUNT - count) * + MAC_RETRY_INTERVAL)); + if (ret == -ERESTARTSYS) { + pr_err("-ERESTARTSYS MAC request fail"); + goto mac_request_fail; + } + } while (!ret); + + } +#ifdef WIMAX_CON0_POLL + if (g_cfg->wimax_mode == WTM_MODE) { + init_waitqueue_head(&adapter->con0_poll); + adapter->wtm_task = kthread_create(con0_poll_thread, + adapter, "%s", "wimax_con0_poll_thread"); + if (adapter->wtm_task) + wake_up_process(adapter->wtm_task); + } +#endif + adapter->uwibro_dev.minor = MISC_DYNAMIC_MINOR; + adapter->uwibro_dev.name = "uwibro"; + adapter->uwibro_dev.fops = &uwbr_fops; + + strcpy(net->name, "uwbr%d"); + net->netdev_ops = &wimax_net_ops; + net->watchdog_timeo = ADAPTER_TIMEOUT; + net->mtu = WIMAX_MTU_SIZE; + adapter->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV + | NETIF_MSG_PROBE | NETIF_MSG_LINK); + + ether_setup(net); + net->flags |= IFF_NOARP; + + SET_NETDEV_DEV(net, &adapter->func->dev); + if (register_netdev(net)) + goto regnetdev_fail; + + netif_carrier_off(net); + netif_tx_stop_all_queues(net); + + if (misc_register(&adapter->uwibro_dev)) { + pr_err("adapter_probe: misc_register() failed"); + goto miscreg_fail; + } + + cmc732_setup_wake_irq(adapter); + + g_cfg->power_state = CMC_POWER_ON; + mutex_unlock(&g_cfg->suspend_mutex); + mutex_unlock(&g_cfg->power_mutex); + pr_debug("WIMAX ON"); + return 0; + + misc_deregister(&adapter->uwibro_dev); +miscreg_fail: + unregister_netdev(net); +regnetdev_fail: +#ifdef WIMAX_CON0_POLL + if (g_cfg->wimax_mode == WTM_MODE) { + g_cfg->power_state = CMC_POWERING_OFF; + wake_up_interruptible(&adapter->con0_poll); + } +#endif +mac_request_fail: +firmware_download_fail: + g_cfg->power_state = CMC_POWERING_OFF; + + /*wake up everyone waiting on uwbr_dev read wait*/ + list_for_each_safe(pos, nxt, &adapter->control_process_list) { + procdsc = list_entry(pos, struct process_descriptor, list); + wake_up_interruptible(&procdsc->read_wait); + } + + list_for_each_safe(pos, nxt, &adapter->q_send) { + bufdsc = list_entry(pos, struct buffer_descriptor, list); + list_del(pos); + kfree(bufdsc->buffer); + kfree(bufdsc); + } + list_for_each_safe(pos, nxt, &adapter->control_process_list) { + procdsc = list_entry(pos, struct process_descriptor, list); + list_del(pos); + list_for_each_safe(pos1, nxt1, &procdsc->buffer_list) { + bufdsc = list_entry(pos1, + struct buffer_descriptor, list); + list_del(pos1); + kfree(bufdsc->buffer); + kfree(bufdsc); + } + kfree(procdsc); + } + sdio_claim_host(adapter->func); + sdio_release_irq(adapter->func); + +sdioirq_fail: + kfree(adapter->receive_buffer); +buffer_fail: + destroy_workqueue(adapter->wimax_workqueue); + sdio_disable_func(adapter->func); +sdioen_fail: + sdio_release_host(adapter->func); +firmwareload_fail: + mutex_destroy(&adapter->control_lock); + +probe_timeout: + + pdata->power(0); + if (adapter->func) { + mdelay(10); + pdata->detect(0); + if (!wait_for_completion_interruptible_timeout( + &adapter->remove, + msecs_to_jiffies(2000))) { + pr_err("%s CMC_REMOVE_TIMEOUT", __func__); + } + } + + /*wait for power off transients*/ + msleep(250); + + free_netdev(net); + pdata->adapter_data = NULL; +alloceth_fail: + g_cfg->power_state = CMC_POWER_OFF; + err = WIMAX_POWER_FAIL; +exit: + mutex_unlock(&g_cfg->suspend_mutex); + mutex_unlock(&pdata->g_cfg->power_mutex); + return err; + +} + + +static int wimax_power_off(struct wimax732_platform_data *pdata) +{ + int err = 0; + struct net_adapter *adapter = NULL; + struct wimax_cfg *g_cfg = pdata->g_cfg; + struct buffer_descriptor *bufdsc; + struct process_descriptor *procdsc; + struct list_head *pos, *nxt, *pos1, *nxt1; + + mutex_lock(&g_cfg->power_mutex); + + /*dont sleep when turning off*/ + mutex_lock(&g_cfg->suspend_mutex); + + if (g_cfg->power_state == CMC_POWER_OFF) { + pr_debug("WiMAX already OFF"); + err = WIMAX_ALREADY_POWER_OFF; + goto exit; + } + adapter = (struct net_adapter *) pdata->adapter_data; + g_cfg->power_state = CMC_POWERING_OFF; +#ifdef WIMAX_CON0_POLL + if (g_cfg->wimax_mode == WTM_MODE) + wake_up_interruptible(&adapter->con0_poll); +#endif + + cmc732_release_wake_irq(adapter); + misc_deregister(&adapter->uwibro_dev); + netif_stop_queue(adapter->net); + netif_carrier_off(adapter->net); + unregister_netdev(adapter->net); + + sdio_claim_host(adapter->func); + sdio_release_irq(adapter->func); + sdio_disable_func(adapter->func); + sdio_release_host(adapter->func); + + /*wake up everyone waiting on uwbr_dev read wait*/ + list_for_each_safe(pos, nxt, &adapter->control_process_list) { + procdsc = list_entry(pos, struct process_descriptor, list); + wake_up_interruptible(&procdsc->read_wait); + } + + /*clean up and remove the send queue*/ + list_for_each_safe(pos, nxt, &adapter->q_send) { + bufdsc = list_entry(pos, struct buffer_descriptor, list); + list_del(pos); + kfree(bufdsc->buffer); + kfree(bufdsc); + } + + /*wait for modem to flush eeprom data*/ + msleep(500); + + pdata->power(0); + mdelay(10); + pdata->detect(0); + if (!wait_for_completion_interruptible_timeout( + &adapter->remove, + msecs_to_jiffies(2000))) { + pr_err("%s CMC_REMOVE_TIMEOUT", __func__); + } + + /*wait for power off transients*/ + msleep(250); + + list_for_each_safe(pos, nxt, &adapter->control_process_list) { + procdsc = list_entry(pos, struct process_descriptor, list); + list_del(pos); + list_for_each_safe(pos1, nxt1, &procdsc->buffer_list) { + bufdsc = list_entry(pos1, + struct buffer_descriptor, list); + list_del(pos1); + kfree(bufdsc->buffer); + kfree(bufdsc); + } + kfree(procdsc); + } + mutex_destroy(&adapter->control_lock); + free_netdev(adapter->net); + kfree(adapter->receive_buffer); + destroy_workqueue(adapter->wimax_workqueue); + pdata->adapter_data = NULL; + g_cfg->power_state = CMC_POWER_OFF; +exit: + mutex_unlock(&g_cfg->suspend_mutex); + mutex_unlock(&g_cfg->power_mutex); + return err; +} + +void wimax_shutdown(struct work_struct *work) +{ + struct wimax_cfg *g_cfg = + container_of(work, + struct wimax_cfg, shutdown); + wimax_power_off(g_cfg->pdata); +} + +static int swmxdev_open(struct inode *inode, struct file *file) +{ + struct wimax732_platform_data *pdata = + container_of(file->private_data, + struct wimax732_platform_data, swmxctl_dev); + file->private_data = pdata; + return 0; +} + + +static long swmxdev_ioctl(struct file *file, u32 cmd, + unsigned long arg) { + int ret = 0; + u8 val = ((u8 *)arg)[0]; + struct wimax732_platform_data *gpdata = + (struct wimax732_platform_data *)(file->private_data); + + pr_debug(" %s CMD: %x, PID: %d", __func__, cmd, current->tgid); + + switch (cmd) { + case CONTROL_IOCTL_WIMAX_POWER_CTL: { + pr_debug("CONTROL_IOCTL_WIMAX_POWER_CTL.."); + if (val == 0) + wimax_power_off(gpdata); + else + ret = wimax_power_on(gpdata); + break; + } + case CONTROL_IOCTL_WIMAX_MODE_CHANGE: { + pr_debug("CONTROL_IOCTL_WIMAX_MODE_CHANGE to 0x%02x..", val); + + if ((val < 0) || (val > AUTH_MODE)) { + pr_debug("Wrong mode 0x%02x", val); + return 0; + } + + wimax_power_off(gpdata); + gpdata->g_cfg->wimax_mode = val; + ret = wimax_power_on(gpdata); + break; + } + case CONTROL_IOCTL_WIMAX_EEPROM_DOWNLOAD: { + pr_debug("CNT_IOCTL_WIMAX_EEPROM_DOWNLOAD"); + wimax_power_off(gpdata); + ret = eeprom_write_boot(); + break; + } + case CONTROL_IOCTL_WIMAX_WRITE_REV: { + pr_debug("CONTROL_IOCTL_WIMAX_WRITE_REV"); + wimax_power_off(gpdata); + ret = eeprom_write_rev(); + break; + } + case CONTROL_IOCTL_WIMAX_CHECK_CERT: { + pr_debug("CONTROL_IOCTL_WIMAX_CHECK_CERT"); + wimax_power_off(gpdata); + ret = eeprom_check_cert(); + break; + } + case CONTROL_IOCTL_WIMAX_CHECK_CAL: { + pr_debug("CONTROL_IOCTL_WIMAX_CHECK_CAL"); + wimax_power_off(gpdata); + ret = eeprom_check_cal(); + break; + } + } /* switch (cmd) */ + + return ret; +} + + +static const struct file_operations swmx_fops = { + .owner = THIS_MODULE, + .open = swmxdev_open, + .unlocked_ioctl = swmxdev_ioctl, +}; + + +static struct sdio_device_id adapter_table[] = { + { SDIO_DEVICE(0x98, 0x1) }, + { } /* Terminating entry */ +}; + + +static int adapter_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct wimax732_platform_data *pdata = + (struct wimax732_platform_data *) id->driver_data; + struct net_adapter *adapter = pdata->adapter_data; + pr_debug("%s", __func__); + adapter->func = func; + sdio_set_drvdata(func, adapter); + complete(&adapter->probe); + return 0; +} + +static void adapter_remove(struct sdio_func *func) +{ + struct net_adapter *adapter = sdio_get_drvdata(func); + pr_debug("%s", __func__); + complete(&adapter->remove); +} + +static struct sdio_driver adapter_driver = { + .name = "C732SDIO", + .probe = adapter_probe, + .remove = adapter_remove, + .id_table = adapter_table, +}; + +static int wimax_probe(struct platform_device *pdev) +{ + struct wimax732_platform_data *pdata = pdev->dev.platform_data; + int err = 0; + int i; + + pdata->g_cfg = kzalloc(sizeof(struct wimax_cfg), GFP_KERNEL); + if (pdata->g_cfg == NULL) { + dev_err(&pdev->dev, + "failed to allocate memory for module data\n"); + err = -ENOMEM; + goto alloc_fail; + } + pdata->g_cfg->pdata = pdata; + + /*This mutex prevents simultaneous instances of wimax_power()*/ + mutex_init(&pdata->g_cfg->power_mutex); + + /*This mutex ensures that driver does not suspend + *with modem wakeup pin asserted*/ + mutex_init(&pdata->g_cfg->suspend_mutex); + + /*To make plaform data available at SDIO driver probe*/ + for (i = 0; i < ARRAY_SIZE(adapter_table); i++) + adapter_table[i].driver_data = + (unsigned long) pdev->dev.platform_data; + + pdata->power(0); + pdata->g_cfg->power_state = CMC_POWER_OFF; + + /* This node is used for turning on/off wimax*/ + pdata->swmxctl_dev.minor = MISC_DYNAMIC_MINOR; + pdata->swmxctl_dev.name = "swmxctl"; + pdata->swmxctl_dev.fops = &swmx_fops; + err = misc_register(&pdata->swmxctl_dev); + if (err) { + pr_err("swmxctl: misc_register failed\n"); + goto err_swmxctl_register; + } + + /* register SDIO driver */ + err = sdio_register_driver(&adapter_driver); + if (err < 0) { + pr_err("%s: sdio_register_driver() failed", + adapter_driver.name); + goto sdio_register_fail; + } + wake_lock_init(&pdata->g_cfg->wimax_driver_lock, + WAKE_LOCK_SUSPEND, "wimax_driver"); + + INIT_WORK(&pdata->g_cfg->shutdown, wimax_shutdown); + +#ifndef DRIVER_BIT_BANG + if (wmxeeprom_init()) + pr_err("wmxeeprom_init() failed"); +#endif + + goto exit; + +sdio_register_fail: + misc_deregister(&pdata->swmxctl_dev); +err_swmxctl_register: + mutex_destroy(&pdata->g_cfg->suspend_mutex); + mutex_destroy(&pdata->g_cfg->power_mutex); + kfree(pdata->g_cfg); +alloc_fail: + +exit: + return err; +} + +/* wimax suspend function */ +int wimax_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct wimax732_platform_data *pdata = pdev->dev.platform_data; + + /*Dont go to sleep when turining on or turning off*/ + if (!mutex_trylock(&pdata->g_cfg->power_mutex)) { + pr_debug("wimax is turning on/off"); + return -EBUSY; + } + + if (!mutex_trylock(&pdata->g_cfg->suspend_mutex)) { + pr_debug("wimax send processing"); + mutex_unlock(&pdata->g_cfg->power_mutex); + return -EBUSY; + } + + /* AP active pin LOW */ + if (pdata->g_cfg->power_state == CMC_POWER_ON) { + pdata->signal_ap_active(0); + pr_debug("wimax_suspend"); + } + return 0; +} + +/* wimax resume function */ +int wimax_resume(struct platform_device *pdev) +{ + struct wimax732_platform_data *pdata = pdev->dev.platform_data; + struct net_adapter *adapter; + /* AP active pin HIGH */ + if (pdata->g_cfg->power_state == CMC_POWER_ON) { + pdata->signal_ap_active(1); + /* wait wakeup noti for 1 sec otherwise suspend again */ + wake_lock_timeout(&pdata->g_cfg->wimax_driver_lock, 1 * HZ); + pr_debug("wimax_resume"); + } + mutex_unlock(&pdata->g_cfg->power_mutex); + mutex_unlock(&pdata->g_cfg->suspend_mutex); + if (pdata->g_cfg->power_state == CMC_POWER_ON) { + adapter = (struct net_adapter *)pdata->adapter_data; + schedule_work(&adapter->tx_work); + } + return 0; +} +static int wimax_remove(struct platform_device *pdev) +{ + struct wimax732_platform_data *pdata = pdev->dev.platform_data; + +#ifndef DRIVER_BIT_BANG + wmxeeprom_exit(); +#endif + misc_deregister(&pdata->swmxctl_dev); + pdata->power(0); + wake_lock_destroy(&pdata->g_cfg->wimax_driver_lock); + sdio_unregister_driver(&adapter_driver); + mutex_destroy(&pdata->g_cfg->power_mutex); + mutex_destroy(&pdata->g_cfg->suspend_mutex); + kfree(pdata->g_cfg); + return 0; +} + + +static struct platform_driver wimax_driver = { + .probe = wimax_probe, + .remove = wimax_remove, + .suspend = wimax_suspend, + .resume = wimax_resume, + .driver = { + .name = "wimax732_driver", + } +}; + +static int __init adapter_init_module(void) +{ + return platform_driver_register(&wimax_driver); +} + + +static void __exit adapter_deinit_module(void) +{ + platform_driver_unregister(&wimax_driver); +} + +module_init(adapter_init_module); +module_exit(adapter_deinit_module); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_PARM_DESC(msg_level, "Override default message level"); +MODULE_DEVICE_TABLE(sdio, adapter_table); +MODULE_VERSION(WIMAX_DRIVER_VERSION_STRING); +MODULE_LICENSE("GPL"); |