Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
#
# GCT GDM72xx WiMAX driver configuration
#
menuconfig WIMAX_GDM72XX
tristate "GCT GDM72xx WiMAX support"
depends on NET && (USB || MMC)
help
Support for the GCT GDM72xx WiMAX chip
if WIMAX_GDM72XX
config WIMAX_GDM72XX_QOS
bool "Enable QoS support"
default n
config WIMAX_GDM72XX_K_MODE
bool "Enable K mode"
default n
config WIMAX_GDM72XX_WIMAX2
bool "Enable WIMAX2 support"
default n
choice
prompt "Select interface"
config WIMAX_GDM72XX_USB
bool "USB interface"
depends on (USB = y || USB = WIMAX_GDM72XX)
config WIMAX_GDM72XX_SDIO
bool "SDIO interface"
depends on (MMC = y || MMC = WIMAX_GDM72XX)
endchoice
if WIMAX_GDM72XX_USB
config WIMAX_GDM72XX_USB_PM
bool "Enable power managerment support"
depends on PM_RUNTIME
endif # WIMAX_GDM72XX_USB
endif # WIMAX_GDM72XX

View File

@@ -0,0 +1,6 @@
obj-$(CONFIG_WIMAX_GDM72XX) := gdmwm.o
gdmwm-y += gdm_wimax.o netlink_k.o
gdmwm-$(CONFIG_WIMAX_GDM72XX_QOS) += gdm_qos.o
gdmwm-$(CONFIG_WIMAX_GDM72XX_SDIO) += gdm_sdio.o sdio_boot.o
gdmwm-$(CONFIG_WIMAX_GDM72XX_USB) += gdm_usb.o usb_boot.o

View File

@@ -0,0 +1,5 @@
TODO:
- Replace kernel_thread with kthread in gdm_usb.c
- Replace hard-coded firmware paths with request_firmware in
sdio_boot.c and usb_boot.c
- Clean up coding style to meet kernel standard.

View File

@@ -0,0 +1,444 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/etherdevice.h>
#include <asm/byteorder.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/if_ether.h>
#include "gdm_wimax.h"
#include "hci.h"
#include "gdm_qos.h"
#define B2H(x) __be16_to_cpu(x)
#define MAX_FREE_LIST_CNT 32
static struct {
struct list_head head;
int cnt;
spinlock_t lock;
} qos_free_list;
static void init_qos_entry_list(void)
{
qos_free_list.cnt = 0;
INIT_LIST_HEAD(&qos_free_list.head);
spin_lock_init(&qos_free_list.lock);
}
static void *alloc_qos_entry(void)
{
struct qos_entry_s *entry;
unsigned long flags;
spin_lock_irqsave(&qos_free_list.lock, flags);
if (qos_free_list.cnt) {
entry = list_entry(qos_free_list.head.prev, struct qos_entry_s,
list);
list_del(&entry->list);
qos_free_list.cnt--;
spin_unlock_irqrestore(&qos_free_list.lock, flags);
return entry;
}
spin_unlock_irqrestore(&qos_free_list.lock, flags);
entry = kmalloc(sizeof(struct qos_entry_s), GFP_ATOMIC);
return entry;
}
static void free_qos_entry(void *entry)
{
struct qos_entry_s *qentry = (struct qos_entry_s *) entry;
unsigned long flags;
spin_lock_irqsave(&qos_free_list.lock, flags);
if (qos_free_list.cnt < MAX_FREE_LIST_CNT) {
list_add(&qentry->list, &qos_free_list.head);
qos_free_list.cnt++;
spin_unlock_irqrestore(&qos_free_list.lock, flags);
return;
}
spin_unlock_irqrestore(&qos_free_list.lock, flags);
kfree(entry);
}
static void free_qos_entry_list(struct list_head *free_list)
{
struct qos_entry_s *entry, *n;
int total_free = 0;
list_for_each_entry_safe(entry, n, free_list, list) {
list_del(&entry->list);
kfree(entry);
total_free++;
}
pr_debug("%s: total_free_cnt=%d\n", __func__, total_free);
}
void gdm_qos_init(void *nic_ptr)
{
struct nic *nic = nic_ptr;
struct qos_cb_s *qcb = &nic->qos;
int i;
for (i = 0 ; i < QOS_MAX; i++) {
INIT_LIST_HEAD(&qcb->qos_list[i]);
qcb->csr[i].qos_buf_count = 0;
qcb->csr[i].enabled = 0;
}
qcb->qos_list_cnt = 0;
qcb->qos_null_idx = QOS_MAX-1;
qcb->qos_limit_size = 255;
spin_lock_init(&qcb->qos_lock);
init_qos_entry_list();
}
void gdm_qos_release_list(void *nic_ptr)
{
struct nic *nic = nic_ptr;
struct qos_cb_s *qcb = &nic->qos;
unsigned long flags;
struct qos_entry_s *entry, *n;
struct list_head free_list;
int i;
INIT_LIST_HEAD(&free_list);
spin_lock_irqsave(&qcb->qos_lock, flags);
for (i = 0; i < QOS_MAX; i++) {
qcb->csr[i].qos_buf_count = 0;
qcb->csr[i].enabled = 0;
}
qcb->qos_list_cnt = 0;
qcb->qos_null_idx = QOS_MAX-1;
for (i = 0; i < QOS_MAX; i++) {
list_for_each_entry_safe(entry, n, &qcb->qos_list[i], list) {
list_move_tail(&entry->list, &free_list);
}
}
spin_unlock_irqrestore(&qcb->qos_lock, flags);
free_qos_entry_list(&free_list);
}
static u32 chk_ipv4_rule(struct gdm_wimax_csr_s *csr, u8 *Stream, u8 *port)
{
int i;
if (csr->classifier_rule_en&IPTYPEOFSERVICE) {
if (((Stream[1] & csr->ip2s_mask) < csr->ip2s_lo) ||
((Stream[1] & csr->ip2s_mask) > csr->ip2s_hi))
return 1;
}
if (csr->classifier_rule_en&PROTOCOL) {
if (Stream[9] != csr->protocol)
return 1;
}
if (csr->classifier_rule_en&IPMASKEDSRCADDRESS) {
for (i = 0; i < 4; i++) {
if ((Stream[12 + i] & csr->ipsrc_addrmask[i]) !=
(csr->ipsrc_addr[i] & csr->ipsrc_addrmask[i]))
return 1;
}
}
if (csr->classifier_rule_en&IPMASKEDDSTADDRESS) {
for (i = 0; i < 4; i++) {
if ((Stream[16 + i] & csr->ipdst_addrmask[i]) !=
(csr->ipdst_addr[i] & csr->ipdst_addrmask[i]))
return 1;
}
}
if (csr->classifier_rule_en&PROTOCOLSRCPORTRANGE) {
i = ((port[0]<<8)&0xff00)+port[1];
if ((i < csr->srcport_lo) || (i > csr->srcport_hi))
return 1;
}
if (csr->classifier_rule_en&PROTOCOLDSTPORTRANGE) {
i = ((port[2]<<8)&0xff00)+port[3];
if ((i < csr->dstport_lo) || (i > csr->dstport_hi))
return 1;
}
return 0;
}
static u32 get_qos_index(struct nic *nic, u8 *iph, u8 *tcpudph)
{
u32 IP_Ver, Header_Len, i;
struct qos_cb_s *qcb = &nic->qos;
if (iph == NULL || tcpudph == NULL)
return -1;
IP_Ver = (iph[0]>>4)&0xf;
Header_Len = iph[0]&0xf;
if (IP_Ver == 4) {
for (i = 0; i < QOS_MAX; i++) {
if (qcb->csr[i].enabled) {
if (qcb->csr[i].classifier_rule_en) {
if (chk_ipv4_rule(&qcb->csr[i], iph,
tcpudph) == 0)
return i;
}
}
}
}
return -1;
}
static u32 extract_qos_list(struct nic *nic, struct list_head *head)
{
struct qos_cb_s *qcb = &nic->qos;
struct qos_entry_s *entry;
int i;
INIT_LIST_HEAD(head);
for (i = 0; i < QOS_MAX; i++) {
if (qcb->csr[i].enabled) {
if (qcb->csr[i].qos_buf_count < qcb->qos_limit_size) {
if (!list_empty(&qcb->qos_list[i])) {
entry = list_entry(
qcb->qos_list[i].prev,
struct qos_entry_s, list);
list_move_tail(&entry->list, head);
qcb->csr[i].qos_buf_count++;
if (!list_empty(&qcb->qos_list[i]))
netdev_warn(nic->netdev,
"Index(%d) is piled!!\n",
i);
}
}
}
}
return 0;
}
static void send_qos_list(struct nic *nic, struct list_head *head)
{
struct qos_entry_s *entry, *n;
list_for_each_entry_safe(entry, n, head, list) {
list_del(&entry->list);
gdm_wimax_send_tx(entry->skb, entry->dev);
free_qos_entry(entry);
}
}
int gdm_qos_send_hci_pkt(struct sk_buff *skb, struct net_device *dev)
{
struct nic *nic = netdev_priv(dev);
int index;
struct qos_cb_s *qcb = &nic->qos;
unsigned long flags;
struct ethhdr *ethh = (struct ethhdr *) (skb->data + HCI_HEADER_SIZE);
struct iphdr *iph = (struct iphdr *) ((char *) ethh + ETH_HLEN);
struct tcphdr *tcph;
struct qos_entry_s *entry = NULL;
struct list_head send_list;
int ret = 0;
tcph = (struct tcphdr *) iph + iph->ihl*4;
if (B2H(ethh->h_proto) == ETH_P_IP) {
if (qcb->qos_list_cnt && !qos_free_list.cnt) {
entry = alloc_qos_entry();
entry->skb = skb;
entry->dev = dev;
netdev_dbg(dev, "qcb->qos_list_cnt=%d\n",
qcb->qos_list_cnt);
}
spin_lock_irqsave(&qcb->qos_lock, flags);
if (qcb->qos_list_cnt) {
index = get_qos_index(nic, (u8 *)iph, (u8 *) tcph);
if (index == -1)
index = qcb->qos_null_idx;
if (!entry) {
entry = alloc_qos_entry();
entry->skb = skb;
entry->dev = dev;
}
list_add_tail(&entry->list, &qcb->qos_list[index]);
extract_qos_list(nic, &send_list);
spin_unlock_irqrestore(&qcb->qos_lock, flags);
send_qos_list(nic, &send_list);
goto out;
}
spin_unlock_irqrestore(&qcb->qos_lock, flags);
if (entry)
free_qos_entry(entry);
}
ret = gdm_wimax_send_tx(skb, dev);
out:
return ret;
}
static u32 get_csr(struct qos_cb_s *qcb, u32 SFID, int mode)
{
int i;
for (i = 0; i < qcb->qos_list_cnt; i++) {
if (qcb->csr[i].SFID == SFID)
return i;
}
if (mode) {
for (i = 0; i < QOS_MAX; i++) {
if (qcb->csr[i].enabled == 0) {
qcb->csr[i].enabled = 1;
qcb->qos_list_cnt++;
return i;
}
}
}
return -1;
}
#define QOS_CHANGE_DEL 0xFC
#define QOS_ADD 0xFD
#define QOS_REPORT 0xFE
void gdm_recv_qos_hci_packet(void *nic_ptr, u8 *buf, int size)
{
struct nic *nic = nic_ptr;
u32 i, SFID, index, pos;
u8 subCmdEvt;
struct qos_cb_s *qcb = &nic->qos;
struct qos_entry_s *entry, *n;
struct list_head send_list;
struct list_head free_list;
unsigned long flags;
subCmdEvt = (u8)buf[4];
if (subCmdEvt == QOS_REPORT) {
spin_lock_irqsave(&qcb->qos_lock, flags);
for (i = 0; i < qcb->qos_list_cnt; i++) {
SFID = ((buf[(i*5)+6]<<24)&0xff000000);
SFID += ((buf[(i*5)+7]<<16)&0xff0000);
SFID += ((buf[(i*5)+8]<<8)&0xff00);
SFID += (buf[(i*5)+9]);
index = get_csr(qcb, SFID, 0);
if (index == -1) {
spin_unlock_irqrestore(&qcb->qos_lock, flags);
netdev_err(nic->netdev, "QoS ERROR: No SF\n");
return;
}
qcb->csr[index].qos_buf_count = buf[(i*5)+10];
}
extract_qos_list(nic, &send_list);
spin_unlock_irqrestore(&qcb->qos_lock, flags);
send_qos_list(nic, &send_list);
return;
}
/* subCmdEvt == QOS_ADD || subCmdEvt == QOS_CHANG_DEL */
pos = 6;
SFID = ((buf[pos++]<<24)&0xff000000);
SFID += ((buf[pos++]<<16)&0xff0000);
SFID += ((buf[pos++]<<8)&0xff00);
SFID += (buf[pos++]);
index = get_csr(qcb, SFID, 1);
if (index == -1) {
netdev_err(nic->netdev,
"QoS ERROR: csr Update Error / Wrong index (%d) \n",
index);
return;
}
if (subCmdEvt == QOS_ADD) {
netdev_dbg(nic->netdev, "QOS_ADD SFID = 0x%x, index=%d\n",
SFID, index);
spin_lock_irqsave(&qcb->qos_lock, flags);
qcb->csr[index].SFID = SFID;
qcb->csr[index].classifier_rule_en = ((buf[pos++]<<8)&0xff00);
qcb->csr[index].classifier_rule_en += buf[pos++];
if (qcb->csr[index].classifier_rule_en == 0)
qcb->qos_null_idx = index;
qcb->csr[index].ip2s_mask = buf[pos++];
qcb->csr[index].ip2s_lo = buf[pos++];
qcb->csr[index].ip2s_hi = buf[pos++];
qcb->csr[index].protocol = buf[pos++];
qcb->csr[index].ipsrc_addrmask[0] = buf[pos++];
qcb->csr[index].ipsrc_addrmask[1] = buf[pos++];
qcb->csr[index].ipsrc_addrmask[2] = buf[pos++];
qcb->csr[index].ipsrc_addrmask[3] = buf[pos++];
qcb->csr[index].ipsrc_addr[0] = buf[pos++];
qcb->csr[index].ipsrc_addr[1] = buf[pos++];
qcb->csr[index].ipsrc_addr[2] = buf[pos++];
qcb->csr[index].ipsrc_addr[3] = buf[pos++];
qcb->csr[index].ipdst_addrmask[0] = buf[pos++];
qcb->csr[index].ipdst_addrmask[1] = buf[pos++];
qcb->csr[index].ipdst_addrmask[2] = buf[pos++];
qcb->csr[index].ipdst_addrmask[3] = buf[pos++];
qcb->csr[index].ipdst_addr[0] = buf[pos++];
qcb->csr[index].ipdst_addr[1] = buf[pos++];
qcb->csr[index].ipdst_addr[2] = buf[pos++];
qcb->csr[index].ipdst_addr[3] = buf[pos++];
qcb->csr[index].srcport_lo = ((buf[pos++]<<8)&0xff00);
qcb->csr[index].srcport_lo += buf[pos++];
qcb->csr[index].srcport_hi = ((buf[pos++]<<8)&0xff00);
qcb->csr[index].srcport_hi += buf[pos++];
qcb->csr[index].dstport_lo = ((buf[pos++]<<8)&0xff00);
qcb->csr[index].dstport_lo += buf[pos++];
qcb->csr[index].dstport_hi = ((buf[pos++]<<8)&0xff00);
qcb->csr[index].dstport_hi += buf[pos++];
qcb->qos_limit_size = 254/qcb->qos_list_cnt;
spin_unlock_irqrestore(&qcb->qos_lock, flags);
} else if (subCmdEvt == QOS_CHANGE_DEL) {
netdev_dbg(nic->netdev, "QOS_CHANGE_DEL SFID = 0x%x, index=%d\n",
SFID, index);
INIT_LIST_HEAD(&free_list);
spin_lock_irqsave(&qcb->qos_lock, flags);
qcb->csr[index].enabled = 0;
qcb->qos_list_cnt--;
qcb->qos_limit_size = 254/qcb->qos_list_cnt;
list_for_each_entry_safe(entry, n, &qcb->qos_list[index],
list) {
list_move_tail(&entry->list, &free_list);
}
spin_unlock_irqrestore(&qcb->qos_lock, flags);
free_qos_entry_list(&free_list);
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#if !defined(GDM_QOS_H_20090403)
#define GDM_QOS_H_20090403
#include <linux/types.h>
#include <linux/usb.h>
#include <linux/list.h>
#define BOOLEAN u8
#define QOS_MAX 16
#define IPTYPEOFSERVICE 0x8000
#define PROTOCOL 0x4000
#define IPMASKEDSRCADDRESS 0x2000
#define IPMASKEDDSTADDRESS 0x1000
#define PROTOCOLSRCPORTRANGE 0x800
#define PROTOCOLDSTPORTRANGE 0x400
#define DSTMACADDR 0x200
#define SRCMACADDR 0x100
#define ETHERTYPE 0x80
#define IEEE802_1DUSERPRIORITY 0x40
#define IEEE802_1QVLANID 0x10
struct gdm_wimax_csr_s {
/* union{
U16 all;
struct _CS_CLASSIFIER_RULE_ENABLE{
IPTypeOfService:1,
Protocol:1,
IPMaskedSrcAddress:1,
IPMaskedDstAddress:1,
ProtocolSrcPortRange:1,
ProtocolDstPortRange:1,
DstMacAddr:1,
SrcMacAddr:1,
Ethertype:1,
IEEE802_1DUserPriority:1,
IEEE802_1QVLANID:1,
Reserved:5;
} fields;
} */
BOOLEAN enabled;
u32 SFID;
u8 qos_buf_count;
u16 classifier_rule_en;
u8 ip2s_lo;
u8 ip2s_hi;
u8 ip2s_mask;
u8 protocol;
u8 ipsrc_addr[16];
u8 ipsrc_addrmask[16];
u8 ipdst_addr[16];
u8 ipdst_addrmask[16];
u16 srcport_lo;
u16 srcport_hi;
u16 dstport_lo;
u16 dstport_hi;
};
struct qos_entry_s {
struct list_head list;
struct sk_buff *skb;
struct net_device *dev;
};
struct qos_cb_s {
struct list_head qos_list[QOS_MAX];
u32 qos_list_cnt;
u32 qos_null_idx;
struct gdm_wimax_csr_s csr[QOS_MAX];
spinlock_t qos_lock;
u32 qos_limit_size;
};
void gdm_qos_init(void *nic_ptr);
void gdm_qos_release_list(void *nic_ptr);
int gdm_qos_send_hci_pkt(struct sk_buff *skb, struct net_device *dev);
void gdm_recv_qos_hci_packet(void *nic_ptr, u8 *buf, int size);
#endif

View File

@@ -0,0 +1,735 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio_ids.h>
#include "gdm_sdio.h"
#include "gdm_wimax.h"
#include "sdio_boot.h"
#include "hci.h"
#define TYPE_A_HEADER_SIZE 4
#define TYPE_A_LOOKAHEAD_SIZE 16
#define MAX_NR_RX_BUF 4
#define SDU_TX_BUF_SIZE 2048
#define TX_BUF_SIZE 2048
#define TX_CHUNK_SIZE (2048 - TYPE_A_HEADER_SIZE)
#define RX_BUF_SIZE (25*1024)
#define TX_HZ 2000
#define TX_INTERVAL (1000000/TX_HZ)
/*#define DEBUG*/
static int init_sdio(struct sdiowm_dev *sdev);
static void release_sdio(struct sdiowm_dev *sdev);
#ifdef DEBUG
static void hexdump(char *title, u8 *data, int len)
{
int i;
printk(KERN_DEBUG "%s: length = %d\n", title, len);
for (i = 0; i < len; i++) {
printk(KERN_DEBUG "%02x ", data[i]);
if ((i & 0xf) == 0xf)
printk(KERN_DEBUG "\n");
}
printk(KERN_DEBUG "\n");
}
#endif
static struct sdio_tx *alloc_tx_struct(struct tx_cxt *tx)
{
struct sdio_tx *t = kzalloc(sizeof(*t), GFP_ATOMIC);
if (!t)
return NULL;
t->buf = kmalloc(TX_BUF_SIZE, GFP_ATOMIC);
if (!t->buf) {
kfree(t);
return NULL;
}
t->tx_cxt = tx;
return t;
}
static void free_tx_struct(struct sdio_tx *t)
{
if (t) {
kfree(t->buf);
kfree(t);
}
}
static struct sdio_rx *alloc_rx_struct(struct rx_cxt *rx)
{
struct sdio_rx *r = kzalloc(sizeof(*r), GFP_ATOMIC);
if (r)
r->rx_cxt = rx;
return r;
}
static void free_rx_struct(struct sdio_rx *r)
{
kfree(r);
}
/* Before this function is called, spin lock should be locked. */
static struct sdio_tx *get_tx_struct(struct tx_cxt *tx, int *no_spc)
{
struct sdio_tx *t;
if (list_empty(&tx->free_list))
return NULL;
t = list_entry(tx->free_list.prev, struct sdio_tx, list);
list_del(&t->list);
*no_spc = list_empty(&tx->free_list) ? 1 : 0;
return t;
}
/* Before this function is called, spin lock should be locked. */
static void put_tx_struct(struct tx_cxt *tx, struct sdio_tx *t)
{
list_add_tail(&t->list, &tx->free_list);
}
/* Before this function is called, spin lock should be locked. */
static struct sdio_rx *get_rx_struct(struct rx_cxt *rx)
{
struct sdio_rx *r;
if (list_empty(&rx->free_list))
return NULL;
r = list_entry(rx->free_list.prev, struct sdio_rx, list);
list_del(&r->list);
return r;
}
/* Before this function is called, spin lock should be locked. */
static void put_rx_struct(struct rx_cxt *rx, struct sdio_rx *r)
{
list_add_tail(&r->list, &rx->free_list);
}
static int init_sdio(struct sdiowm_dev *sdev)
{
int ret = 0, i;
struct tx_cxt *tx = &sdev->tx;
struct rx_cxt *rx = &sdev->rx;
struct sdio_tx *t;
struct sdio_rx *r;
INIT_LIST_HEAD(&tx->free_list);
INIT_LIST_HEAD(&tx->sdu_list);
INIT_LIST_HEAD(&tx->hci_list);
spin_lock_init(&tx->lock);
tx->sdu_buf = kmalloc(SDU_TX_BUF_SIZE, GFP_KERNEL);
if (tx->sdu_buf == NULL)
goto fail;
for (i = 0; i < MAX_NR_SDU_BUF; i++) {
t = alloc_tx_struct(tx);
if (t == NULL) {
ret = -ENOMEM;
goto fail;
}
list_add(&t->list, &tx->free_list);
}
INIT_LIST_HEAD(&rx->free_list);
INIT_LIST_HEAD(&rx->req_list);
spin_lock_init(&rx->lock);
for (i = 0; i < MAX_NR_RX_BUF; i++) {
r = alloc_rx_struct(rx);
if (r == NULL) {
ret = -ENOMEM;
goto fail;
}
list_add(&r->list, &rx->free_list);
}
rx->rx_buf = kmalloc(RX_BUF_SIZE, GFP_KERNEL);
if (rx->rx_buf == NULL)
goto fail;
return 0;
fail:
release_sdio(sdev);
return ret;
}
static void release_sdio(struct sdiowm_dev *sdev)
{
struct tx_cxt *tx = &sdev->tx;
struct rx_cxt *rx = &sdev->rx;
struct sdio_tx *t, *t_next;
struct sdio_rx *r, *r_next;
kfree(tx->sdu_buf);
list_for_each_entry_safe(t, t_next, &tx->free_list, list) {
list_del(&t->list);
free_tx_struct(t);
}
list_for_each_entry_safe(t, t_next, &tx->sdu_list, list) {
list_del(&t->list);
free_tx_struct(t);
}
list_for_each_entry_safe(t, t_next, &tx->hci_list, list) {
list_del(&t->list);
free_tx_struct(t);
}
kfree(rx->rx_buf);
list_for_each_entry_safe(r, r_next, &rx->free_list, list) {
list_del(&r->list);
free_rx_struct(r);
}
list_for_each_entry_safe(r, r_next, &rx->req_list, list) {
list_del(&r->list);
free_rx_struct(r);
}
}
static void send_sdio_pkt(struct sdio_func *func, u8 *data, int len)
{
int n, blocks, ret, remain;
sdio_claim_host(func);
blocks = len / func->cur_blksize;
n = blocks * func->cur_blksize;
if (blocks) {
ret = sdio_memcpy_toio(func, 0, data, n);
if (ret < 0) {
if (ret != -ENOMEDIUM)
dev_err(&func->dev,
"gdmwms: %s error: ret = %d\n",
__func__, ret);
goto end_io;
}
}
remain = len - n;
remain = (remain + 3) & ~3;
if (remain) {
ret = sdio_memcpy_toio(func, 0, data + n, remain);
if (ret < 0) {
if (ret != -ENOMEDIUM)
dev_err(&func->dev,
"gdmwms: %s error: ret = %d\n",
__func__, ret);
goto end_io;
}
}
end_io:
sdio_release_host(func);
}
static void send_sdu(struct sdio_func *func, struct tx_cxt *tx)
{
struct list_head *l, *next;
struct hci_s *hci;
struct sdio_tx *t;
int pos, len, i, estlen, aggr_num = 0, aggr_len;
u8 *buf;
unsigned long flags;
spin_lock_irqsave(&tx->lock, flags);
pos = TYPE_A_HEADER_SIZE + HCI_HEADER_SIZE;
list_for_each_entry(t, &tx->sdu_list, list) {
estlen = ((t->len + 3) & ~3) + 4;
if ((pos + estlen) > SDU_TX_BUF_SIZE)
break;
aggr_num++;
memcpy(tx->sdu_buf + pos, t->buf, t->len);
memset(tx->sdu_buf + pos + t->len, 0, estlen - t->len);
pos += estlen;
}
aggr_len = pos;
hci = (struct hci_s *)(tx->sdu_buf + TYPE_A_HEADER_SIZE);
hci->cmd_evt = H2B(WIMAX_TX_SDU_AGGR);
hci->length = H2B(aggr_len - TYPE_A_HEADER_SIZE - HCI_HEADER_SIZE);
spin_unlock_irqrestore(&tx->lock, flags);
#ifdef DEBUG
hexdump("sdio_send", tx->sdu_buf + TYPE_A_HEADER_SIZE,
aggr_len - TYPE_A_HEADER_SIZE);
#endif
for (pos = TYPE_A_HEADER_SIZE; pos < aggr_len; pos += TX_CHUNK_SIZE) {
len = aggr_len - pos;
len = len > TX_CHUNK_SIZE ? TX_CHUNK_SIZE : len;
buf = tx->sdu_buf + pos - TYPE_A_HEADER_SIZE;
buf[0] = len & 0xff;
buf[1] = (len >> 8) & 0xff;
buf[2] = (len >> 16) & 0xff;
buf[3] = (pos + len) >= aggr_len ? 0 : 1;
send_sdio_pkt(func, buf, len + TYPE_A_HEADER_SIZE);
}
spin_lock_irqsave(&tx->lock, flags);
for (l = tx->sdu_list.next, i = 0; i < aggr_num; i++, l = next) {
next = l->next;
t = list_entry(l, struct sdio_tx, list);
if (t->callback)
t->callback(t->cb_data);
list_del(l);
put_tx_struct(t->tx_cxt, t);
}
do_gettimeofday(&tx->sdu_stamp);
spin_unlock_irqrestore(&tx->lock, flags);
}
static void send_hci(struct sdio_func *func, struct tx_cxt *tx,
struct sdio_tx *t)
{
unsigned long flags;
#ifdef DEBUG
hexdump("sdio_send", t->buf + TYPE_A_HEADER_SIZE,
t->len - TYPE_A_HEADER_SIZE);
#endif
send_sdio_pkt(func, t->buf, t->len);
spin_lock_irqsave(&tx->lock, flags);
if (t->callback)
t->callback(t->cb_data);
free_tx_struct(t);
spin_unlock_irqrestore(&tx->lock, flags);
}
static void do_tx(struct work_struct *work)
{
struct sdiowm_dev *sdev = container_of(work, struct sdiowm_dev, ws);
struct sdio_func *func = sdev->func;
struct tx_cxt *tx = &sdev->tx;
struct sdio_tx *t = NULL;
struct timeval now, *before;
int is_sdu = 0;
long diff;
unsigned long flags;
spin_lock_irqsave(&tx->lock, flags);
if (!tx->can_send) {
spin_unlock_irqrestore(&tx->lock, flags);
return;
}
if (!list_empty(&tx->hci_list)) {
t = list_entry(tx->hci_list.next, struct sdio_tx, list);
list_del(&t->list);
is_sdu = 0;
} else if (!tx->stop_sdu_tx && !list_empty(&tx->sdu_list)) {
do_gettimeofday(&now);
before = &tx->sdu_stamp;
diff = (now.tv_sec - before->tv_sec) * 1000000 +
(now.tv_usec - before->tv_usec);
if (diff >= 0 && diff < TX_INTERVAL) {
schedule_work(&sdev->ws);
spin_unlock_irqrestore(&tx->lock, flags);
return;
}
is_sdu = 1;
}
if (!is_sdu && t == NULL) {
spin_unlock_irqrestore(&tx->lock, flags);
return;
}
tx->can_send = 0;
spin_unlock_irqrestore(&tx->lock, flags);
if (is_sdu)
send_sdu(func, tx);
else
send_hci(func, tx, t);
}
static int gdm_sdio_send(void *priv_dev, void *data, int len,
void (*cb)(void *data), void *cb_data)
{
struct sdiowm_dev *sdev = priv_dev;
struct tx_cxt *tx = &sdev->tx;
struct sdio_tx *t;
u8 *pkt = data;
int no_spc = 0;
u16 cmd_evt;
unsigned long flags;
BUG_ON(len > TX_BUF_SIZE - TYPE_A_HEADER_SIZE);
spin_lock_irqsave(&tx->lock, flags);
cmd_evt = (pkt[0] << 8) | pkt[1];
if (cmd_evt == WIMAX_TX_SDU) {
t = get_tx_struct(tx, &no_spc);
if (t == NULL) {
/* This case must not happen. */
spin_unlock_irqrestore(&tx->lock, flags);
return -ENOSPC;
}
list_add_tail(&t->list, &tx->sdu_list);
memcpy(t->buf, data, len);
t->len = len;
t->callback = cb;
t->cb_data = cb_data;
} else {
t = alloc_tx_struct(tx);
if (t == NULL) {
spin_unlock_irqrestore(&tx->lock, flags);
return -ENOMEM;
}
list_add_tail(&t->list, &tx->hci_list);
t->buf[0] = len & 0xff;
t->buf[1] = (len >> 8) & 0xff;
t->buf[2] = (len >> 16) & 0xff;
t->buf[3] = 2;
memcpy(t->buf + TYPE_A_HEADER_SIZE, data, len);
t->len = len + TYPE_A_HEADER_SIZE;
t->callback = cb;
t->cb_data = cb_data;
}
if (tx->can_send)
schedule_work(&sdev->ws);
spin_unlock_irqrestore(&tx->lock, flags);
if (no_spc)
return -ENOSPC;
return 0;
}
/*
* Handle the HCI, WIMAX_SDU_TX_FLOW.
*/
static int control_sdu_tx_flow(struct sdiowm_dev *sdev, u8 *hci_data, int len)
{
struct tx_cxt *tx = &sdev->tx;
u16 cmd_evt;
unsigned long flags;
spin_lock_irqsave(&tx->lock, flags);
cmd_evt = (hci_data[0] << 8) | (hci_data[1]);
if (cmd_evt != WIMAX_SDU_TX_FLOW)
goto out;
if (hci_data[4] == 0) {
#ifdef DEBUG
printk(KERN_DEBUG "WIMAX ==> STOP SDU TX\n");
#endif
tx->stop_sdu_tx = 1;
} else if (hci_data[4] == 1) {
#ifdef DEBUG
printk(KERN_DEBUG "WIMAX ==> START SDU TX\n");
#endif
tx->stop_sdu_tx = 0;
if (tx->can_send)
schedule_work(&sdev->ws);
/*
* If free buffer for sdu tx doesn't exist, then tx queue
* should not be woken. For this reason, don't pass the command,
* START_SDU_TX.
*/
if (list_empty(&tx->free_list))
len = 0;
}
out:
spin_unlock_irqrestore(&tx->lock, flags);
return len;
}
static void gdm_sdio_irq(struct sdio_func *func)
{
struct phy_dev *phy_dev = sdio_get_drvdata(func);
struct sdiowm_dev *sdev = phy_dev->priv_dev;
struct tx_cxt *tx = &sdev->tx;
struct rx_cxt *rx = &sdev->rx;
struct sdio_rx *r;
unsigned long flags;
u8 val, hdr[TYPE_A_LOOKAHEAD_SIZE], *buf;
u32 len, blocks, n;
int ret, remain;
/* Check interrupt */
val = sdio_readb(func, 0x13, &ret);
if (val & 0x01)
sdio_writeb(func, 0x01, 0x13, &ret); /* clear interrupt */
else
return;
ret = sdio_memcpy_fromio(func, hdr, 0x0, TYPE_A_LOOKAHEAD_SIZE);
if (ret) {
dev_err(&func->dev,
"Cannot read from function %d\n", func->num);
goto done;
}
len = (hdr[2] << 16) | (hdr[1] << 8) | hdr[0];
if (len > (RX_BUF_SIZE - TYPE_A_HEADER_SIZE)) {
dev_err(&func->dev, "Too big Type-A size: %d\n", len);
goto done;
}
if (hdr[3] == 1) { /* Ack */
#ifdef DEBUG
u32 *ack_seq = (u32 *)&hdr[4];
#endif
spin_lock_irqsave(&tx->lock, flags);
tx->can_send = 1;
if (!list_empty(&tx->sdu_list) || !list_empty(&tx->hci_list))
schedule_work(&sdev->ws);
spin_unlock_irqrestore(&tx->lock, flags);
#ifdef DEBUG
printk(KERN_DEBUG "Ack... %0x\n", ntohl(*ack_seq));
#endif
goto done;
}
memcpy(rx->rx_buf, hdr + TYPE_A_HEADER_SIZE,
TYPE_A_LOOKAHEAD_SIZE - TYPE_A_HEADER_SIZE);
buf = rx->rx_buf + TYPE_A_LOOKAHEAD_SIZE - TYPE_A_HEADER_SIZE;
remain = len - TYPE_A_LOOKAHEAD_SIZE + TYPE_A_HEADER_SIZE;
if (remain <= 0)
goto end_io;
blocks = remain / func->cur_blksize;
if (blocks) {
n = blocks * func->cur_blksize;
ret = sdio_memcpy_fromio(func, buf, 0x0, n);
if (ret) {
dev_err(&func->dev,
"Cannot read from function %d\n", func->num);
goto done;
}
buf += n;
remain -= n;
}
if (remain) {
ret = sdio_memcpy_fromio(func, buf, 0x0, remain);
if (ret) {
dev_err(&func->dev,
"Cannot read from function %d\n", func->num);
goto done;
}
}
end_io:
#ifdef DEBUG
hexdump("sdio_receive", rx->rx_buf, len);
#endif
len = control_sdu_tx_flow(sdev, rx->rx_buf, len);
spin_lock_irqsave(&rx->lock, flags);
if (!list_empty(&rx->req_list)) {
r = list_entry(rx->req_list.next, struct sdio_rx, list);
spin_unlock_irqrestore(&rx->lock, flags);
if (r->callback)
r->callback(r->cb_data, rx->rx_buf, len);
spin_lock_irqsave(&rx->lock, flags);
list_del(&r->list);
put_rx_struct(rx, r);
}
spin_unlock_irqrestore(&rx->lock, flags);
done:
sdio_writeb(func, 0x00, 0x10, &ret); /* PCRRT */
if (!phy_dev->netdev)
register_wimax_device(phy_dev, &func->dev);
}
static int gdm_sdio_receive(void *priv_dev,
void (*cb)(void *cb_data, void *data, int len),
void *cb_data)
{
struct sdiowm_dev *sdev = priv_dev;
struct rx_cxt *rx = &sdev->rx;
struct sdio_rx *r;
unsigned long flags;
spin_lock_irqsave(&rx->lock, flags);
r = get_rx_struct(rx);
if (r == NULL) {
spin_unlock_irqrestore(&rx->lock, flags);
return -ENOMEM;
}
r->callback = cb;
r->cb_data = cb_data;
list_add_tail(&r->list, &rx->req_list);
spin_unlock_irqrestore(&rx->lock, flags);
return 0;
}
static int sdio_wimax_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
int ret;
struct phy_dev *phy_dev = NULL;
struct sdiowm_dev *sdev = NULL;
dev_info(&func->dev, "Found GDM SDIO VID = 0x%04x PID = 0x%04x...\n",
func->vendor, func->device);
dev_info(&func->dev, "GCT WiMax driver version %s\n", DRIVER_VERSION);
sdio_claim_host(func);
sdio_enable_func(func);
sdio_claim_irq(func, gdm_sdio_irq);
ret = sdio_boot(func);
if (ret)
return ret;
phy_dev = kzalloc(sizeof(*phy_dev), GFP_KERNEL);
if (phy_dev == NULL) {
ret = -ENOMEM;
goto out;
}
sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
if (sdev == NULL) {
ret = -ENOMEM;
goto out;
}
phy_dev->priv_dev = (void *)sdev;
phy_dev->send_func = gdm_sdio_send;
phy_dev->rcv_func = gdm_sdio_receive;
ret = init_sdio(sdev);
if (ret < 0)
goto out;
sdev->func = func;
sdio_writeb(func, 1, 0x14, &ret); /* Enable interrupt */
sdio_release_host(func);
INIT_WORK(&sdev->ws, do_tx);
sdio_set_drvdata(func, phy_dev);
out:
if (ret) {
kfree(phy_dev);
kfree(sdev);
}
return ret;
}
static void sdio_wimax_remove(struct sdio_func *func)
{
struct phy_dev *phy_dev = sdio_get_drvdata(func);
struct sdiowm_dev *sdev = phy_dev->priv_dev;
cancel_work_sync(&sdev->ws);
if (phy_dev->netdev)
unregister_wimax_device(phy_dev);
sdio_claim_host(func);
sdio_release_irq(func);
sdio_disable_func(func);
sdio_release_host(func);
release_sdio(sdev);
kfree(sdev);
kfree(phy_dev);
}
static const struct sdio_device_id sdio_wimax_ids[] = {
{ SDIO_DEVICE(0x0296, 0x5347) },
{0}
};
MODULE_DEVICE_TABLE(sdio, sdio_wimax_ids);
static struct sdio_driver sdio_wimax_driver = {
.probe = sdio_wimax_probe,
.remove = sdio_wimax_remove,
.name = "sdio_wimax",
.id_table = sdio_wimax_ids,
};
static int __init sdio_gdm_wimax_init(void)
{
return sdio_register_driver(&sdio_wimax_driver);
}
static void __exit sdio_gdm_wimax_exit(void)
{
sdio_unregister_driver(&sdio_wimax_driver);
}
module_init(sdio_gdm_wimax_init);
module_exit(sdio_gdm_wimax_exit);
MODULE_VERSION(DRIVER_VERSION);
MODULE_DESCRIPTION("GCT WiMax SDIO Device Driver");
MODULE_AUTHOR("Ethan Park");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#ifndef __GDM_SDIO_H__
#define __GDM_SDIO_H__
#include <linux/types.h>
#include <linux/time.h>
#define MAX_NR_SDU_BUF 64
struct sdio_tx {
struct list_head list;
struct tx_cxt *tx_cxt;
u8 *buf;
int len;
void (*callback)(void *cb_data);
void *cb_data;
};
struct tx_cxt {
struct list_head free_list;
struct list_head sdu_list;
struct list_head hci_list;
struct timeval sdu_stamp;
u8 *sdu_buf;
spinlock_t lock;
int can_send;
int stop_sdu_tx;
};
struct sdio_rx {
struct list_head list;
struct rx_cxt *rx_cxt;
void (*callback)(void *cb_data, void *data, int len);
void *cb_data;
};
struct rx_cxt {
struct list_head free_list;
struct list_head req_list;
u8 *rx_buf;
spinlock_t lock;
};
struct sdiowm_dev {
struct sdio_func *func;
struct tx_cxt tx;
struct rx_cxt rx;
struct work_struct ws;
};
#endif /* __GDM_SDIO_H__ */

View File

@@ -0,0 +1,827 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>
#include <asm/byteorder.h>
#include <linux/kthread.h>
#include "gdm_usb.h"
#include "gdm_wimax.h"
#include "usb_boot.h"
#include "hci.h"
#include "usb_ids.h"
MODULE_DEVICE_TABLE(usb, id_table);
#define TX_BUF_SIZE 2048
#if defined(CONFIG_WIMAX_GDM72XX_WIMAX2)
#define RX_BUF_SIZE (128*1024) /* For packet aggregation */
#else
#define RX_BUF_SIZE 2048
#endif
#define GDM7205_PADDING 256
#define H2B(x) __cpu_to_be16(x)
#define B2H(x) __be16_to_cpu(x)
#define DB2H(x) __be32_to_cpu(x)
#define DOWNLOAD_CONF_VALUE 0x21
#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
static DECLARE_WAIT_QUEUE_HEAD(k_wait);
static LIST_HEAD(k_list);
static DEFINE_SPINLOCK(k_lock);
static int k_mode_stop;
#define K_WAIT_TIME (2 * HZ / 100)
#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
static int init_usb(struct usbwm_dev *udev);
static void release_usb(struct usbwm_dev *udev);
/*#define DEBUG */
#ifdef DEBUG
static void hexdump(char *title, u8 *data, int len)
{
int i;
printk(KERN_DEBUG "%s: length = %d\n", title, len);
for (i = 0; i < len; i++) {
printk(KERN_DEBUG "%02x ", data[i]);
if ((i & 0xf) == 0xf)
printk(KERN_DEBUG "\n");
}
printk(KERN_DEBUG "\n");
}
#endif
static struct usb_tx *alloc_tx_struct(struct tx_cxt *tx)
{
struct usb_tx *t = kzalloc(sizeof(*t), GFP_ATOMIC);
if (!t)
return NULL;
t->urb = usb_alloc_urb(0, GFP_ATOMIC);
t->buf = kmalloc(TX_BUF_SIZE, GFP_ATOMIC);
if (!t->urb || !t->buf) {
usb_free_urb(t->urb);
kfree(t->buf);
kfree(t);
return NULL;
}
t->tx_cxt = tx;
return t;
}
static void free_tx_struct(struct usb_tx *t)
{
if (t) {
usb_free_urb(t->urb);
kfree(t->buf);
kfree(t);
}
}
static struct usb_rx *alloc_rx_struct(struct rx_cxt *rx)
{
struct usb_rx *r = kzalloc(sizeof(*r), GFP_ATOMIC);
if (!r)
return NULL;
r->urb = usb_alloc_urb(0, GFP_ATOMIC);
r->buf = kmalloc(RX_BUF_SIZE, GFP_ATOMIC);
if (!r->urb || !r->buf) {
usb_free_urb(r->urb);
kfree(r->buf);
kfree(r);
return NULL;
}
r->rx_cxt = rx;
return r;
}
static void free_rx_struct(struct usb_rx *r)
{
if (r) {
usb_free_urb(r->urb);
kfree(r->buf);
kfree(r);
}
}
/* Before this function is called, spin lock should be locked. */
static struct usb_tx *get_tx_struct(struct tx_cxt *tx, int *no_spc)
{
struct usb_tx *t;
if (list_empty(&tx->free_list)) {
*no_spc = 1;
return NULL;
}
t = list_entry(tx->free_list.next, struct usb_tx, list);
list_del(&t->list);
*no_spc = list_empty(&tx->free_list) ? 1 : 0;
return t;
}
/* Before this function is called, spin lock should be locked. */
static void put_tx_struct(struct tx_cxt *tx, struct usb_tx *t)
{
list_add_tail(&t->list, &tx->free_list);
}
/* Before this function is called, spin lock should be locked. */
static struct usb_rx *get_rx_struct(struct rx_cxt *rx)
{
struct usb_rx *r;
if (list_empty(&rx->free_list)) {
r = alloc_rx_struct(rx);
if (r == NULL)
return NULL;
list_add(&r->list, &rx->free_list);
}
r = list_entry(rx->free_list.next, struct usb_rx, list);
list_move_tail(&r->list, &rx->used_list);
return r;
}
/* Before this function is called, spin lock should be locked. */
static void put_rx_struct(struct rx_cxt *rx, struct usb_rx *r)
{
list_move(&r->list, &rx->free_list);
}
static int init_usb(struct usbwm_dev *udev)
{
int ret = 0, i;
struct tx_cxt *tx = &udev->tx;
struct rx_cxt *rx = &udev->rx;
struct usb_tx *t;
struct usb_rx *r;
unsigned long flags;
INIT_LIST_HEAD(&tx->free_list);
INIT_LIST_HEAD(&tx->sdu_list);
INIT_LIST_HEAD(&tx->hci_list);
#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
INIT_LIST_HEAD(&tx->pending_list);
#endif
INIT_LIST_HEAD(&rx->free_list);
INIT_LIST_HEAD(&rx->used_list);
spin_lock_init(&tx->lock);
spin_lock_init(&rx->lock);
spin_lock_irqsave(&tx->lock, flags);
for (i = 0; i < MAX_NR_SDU_BUF; i++) {
t = alloc_tx_struct(tx);
if (t == NULL) {
spin_unlock_irqrestore(&tx->lock, flags);
ret = -ENOMEM;
goto fail;
}
list_add(&t->list, &tx->free_list);
}
spin_unlock_irqrestore(&tx->lock, flags);
r = alloc_rx_struct(rx);
if (r == NULL) {
ret = -ENOMEM;
goto fail;
}
spin_lock_irqsave(&rx->lock, flags);
list_add(&r->list, &rx->free_list);
spin_unlock_irqrestore(&rx->lock, flags);
return ret;
fail:
release_usb(udev);
return ret;
}
static void release_usb(struct usbwm_dev *udev)
{
struct tx_cxt *tx = &udev->tx;
struct rx_cxt *rx = &udev->rx;
struct usb_tx *t, *t_next;
struct usb_rx *r, *r_next;
unsigned long flags;
spin_lock_irqsave(&tx->lock, flags);
list_for_each_entry_safe(t, t_next, &tx->sdu_list, list) {
list_del(&t->list);
free_tx_struct(t);
}
list_for_each_entry_safe(t, t_next, &tx->hci_list, list) {
list_del(&t->list);
free_tx_struct(t);
}
list_for_each_entry_safe(t, t_next, &tx->free_list, list) {
list_del(&t->list);
free_tx_struct(t);
}
spin_unlock_irqrestore(&tx->lock, flags);
spin_lock_irqsave(&rx->lock, flags);
list_for_each_entry_safe(r, r_next, &rx->free_list, list) {
list_del(&r->list);
free_rx_struct(r);
}
list_for_each_entry_safe(r, r_next, &rx->used_list, list) {
list_del(&r->list);
free_rx_struct(r);
}
spin_unlock_irqrestore(&rx->lock, flags);
}
static void __gdm_usb_send_complete(struct urb *urb)
{
struct usb_tx *t = urb->context;
struct tx_cxt *tx = t->tx_cxt;
u8 *pkt = t->buf;
u16 cmd_evt;
/* Completion by usb_unlink_urb */
if (urb->status == -ECONNRESET)
return;
if (t->callback)
t->callback(t->cb_data);
/* Delete from sdu list or hci list. */
list_del(&t->list);
cmd_evt = (pkt[0] << 8) | pkt[1];
if (cmd_evt == WIMAX_TX_SDU)
put_tx_struct(tx, t);
else
free_tx_struct(t);
}
static void gdm_usb_send_complete(struct urb *urb)
{
struct usb_tx *t = urb->context;
struct tx_cxt *tx = t->tx_cxt;
unsigned long flags;
spin_lock_irqsave(&tx->lock, flags);
__gdm_usb_send_complete(urb);
spin_unlock_irqrestore(&tx->lock, flags);
}
static int gdm_usb_send(void *priv_dev, void *data, int len,
void (*cb)(void *data), void *cb_data)
{
struct usbwm_dev *udev = priv_dev;
struct usb_device *usbdev = udev->usbdev;
struct tx_cxt *tx = &udev->tx;
struct usb_tx *t;
int padding = udev->padding;
int no_spc = 0, ret;
u8 *pkt = data;
u16 cmd_evt;
unsigned long flags;
#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
unsigned long flags2;
#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
if (!udev->usbdev) {
dev_err(&usbdev->dev, "%s: No such device\n", __func__);
return -ENODEV;
}
BUG_ON(len > TX_BUF_SIZE - padding - 1);
spin_lock_irqsave(&tx->lock, flags);
cmd_evt = (pkt[0] << 8) | pkt[1];
if (cmd_evt == WIMAX_TX_SDU) {
t = get_tx_struct(tx, &no_spc);
if (t == NULL) {
/* This case must not happen. */
spin_unlock_irqrestore(&tx->lock, flags);
return -ENOSPC;
}
list_add_tail(&t->list, &tx->sdu_list);
} else {
t = alloc_tx_struct(tx);
if (t == NULL) {
spin_unlock_irqrestore(&tx->lock, flags);
return -ENOMEM;
}
list_add_tail(&t->list, &tx->hci_list);
}
memcpy(t->buf + padding, data, len);
t->callback = cb;
t->cb_data = cb_data;
/*
* In some cases, USB Module of WiMax is blocked when data size is
* the multiple of 512. So, increment length by one in that case.
*/
if ((len % 512) == 0)
len++;
usb_fill_bulk_urb(t->urb,
usbdev,
usb_sndbulkpipe(usbdev, 1),
t->buf,
len + padding,
gdm_usb_send_complete,
t);
#ifdef DEBUG
hexdump("usb_send", t->buf, len + padding);
#endif
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
if (usbdev->state & USB_STATE_SUSPENDED) {
list_add_tail(&t->p_list, &tx->pending_list);
schedule_work(&udev->pm_ws);
goto out;
}
#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
if (udev->bw_switch) {
list_add_tail(&t->p_list, &tx->pending_list);
goto out;
} else if (cmd_evt == WIMAX_SCAN) {
struct rx_cxt *rx;
struct usb_rx *r;
rx = &udev->rx;
spin_lock_irqsave(&rx->lock, flags2);
list_for_each_entry(r, &rx->used_list, list)
usb_unlink_urb(r->urb);
spin_unlock_irqrestore(&rx->lock, flags2);
udev->bw_switch = 1;
spin_lock_irqsave(&k_lock, flags2);
list_add_tail(&udev->list, &k_list);
spin_unlock_irqrestore(&k_lock, flags2);
wake_up(&k_wait);
}
#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
ret = usb_submit_urb(t->urb, GFP_ATOMIC);
if (ret)
goto send_fail;
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
usb_mark_last_busy(usbdev);
#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
out:
#endif
spin_unlock_irqrestore(&tx->lock, flags);
if (no_spc)
return -ENOSPC;
return 0;
send_fail:
t->callback = NULL;
__gdm_usb_send_complete(t->urb);
spin_unlock_irqrestore(&tx->lock, flags);
return ret;
}
static void gdm_usb_rcv_complete(struct urb *urb)
{
struct usb_rx *r = urb->context;
struct rx_cxt *rx = r->rx_cxt;
struct usbwm_dev *udev = container_of(r->rx_cxt, struct usbwm_dev, rx);
struct tx_cxt *tx = &udev->tx;
struct usb_tx *t;
u16 cmd_evt;
unsigned long flags, flags2;
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
struct usb_device *dev = urb->dev;
#endif
/* Completion by usb_unlink_urb */
if (urb->status == -ECONNRESET)
return;
spin_lock_irqsave(&tx->lock, flags);
if (!urb->status) {
cmd_evt = (r->buf[0] << 8) | (r->buf[1]);
#ifdef DEBUG
hexdump("usb_receive", r->buf, urb->actual_length);
#endif
if (cmd_evt == WIMAX_SDU_TX_FLOW) {
if (r->buf[4] == 0) {
#ifdef DEBUG
printk(KERN_DEBUG "WIMAX ==> STOP SDU TX\n");
#endif
list_for_each_entry(t, &tx->sdu_list, list)
usb_unlink_urb(t->urb);
} else if (r->buf[4] == 1) {
#ifdef DEBUG
printk(KERN_DEBUG "WIMAX ==> START SDU TX\n");
#endif
list_for_each_entry(t, &tx->sdu_list, list) {
usb_submit_urb(t->urb, GFP_ATOMIC);
}
/*
* If free buffer for sdu tx doesn't
* exist, then tx queue should not be
* woken. For this reason, don't pass
* the command, START_SDU_TX.
*/
if (list_empty(&tx->free_list))
urb->actual_length = 0;
}
}
}
if (!urb->status && r->callback)
r->callback(r->cb_data, r->buf, urb->actual_length);
spin_lock_irqsave(&rx->lock, flags2);
put_rx_struct(rx, r);
spin_unlock_irqrestore(&rx->lock, flags2);
spin_unlock_irqrestore(&tx->lock, flags);
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
usb_mark_last_busy(dev);
#endif
}
static int gdm_usb_receive(void *priv_dev,
void (*cb)(void *cb_data, void *data, int len),
void *cb_data)
{
struct usbwm_dev *udev = priv_dev;
struct usb_device *usbdev = udev->usbdev;
struct rx_cxt *rx = &udev->rx;
struct usb_rx *r;
unsigned long flags;
if (!udev->usbdev) {
dev_err(&usbdev->dev, "%s: No such device\n", __func__);
return -ENODEV;
}
spin_lock_irqsave(&rx->lock, flags);
r = get_rx_struct(rx);
spin_unlock_irqrestore(&rx->lock, flags);
if (r == NULL)
return -ENOMEM;
r->callback = cb;
r->cb_data = cb_data;
usb_fill_bulk_urb(r->urb,
usbdev,
usb_rcvbulkpipe(usbdev, 0x82),
r->buf,
RX_BUF_SIZE,
gdm_usb_rcv_complete,
r);
return usb_submit_urb(r->urb, GFP_ATOMIC);
}
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
static void do_pm_control(struct work_struct *work)
{
struct usbwm_dev *udev = container_of(work, struct usbwm_dev, pm_ws);
struct tx_cxt *tx = &udev->tx;
int ret;
unsigned long flags;
ret = usb_autopm_get_interface(udev->intf);
if (!ret)
usb_autopm_put_interface(udev->intf);
spin_lock_irqsave(&tx->lock, flags);
if (!(udev->usbdev->state & USB_STATE_SUSPENDED)
&& (!list_empty(&tx->hci_list) || !list_empty(&tx->sdu_list))) {
struct usb_tx *t, *temp;
list_for_each_entry_safe(t, temp, &tx->pending_list, p_list) {
list_del(&t->p_list);
ret = usb_submit_urb(t->urb, GFP_ATOMIC);
if (ret) {
t->callback = NULL;
__gdm_usb_send_complete(t->urb);
}
}
}
spin_unlock_irqrestore(&tx->lock, flags);
}
#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
static int gdm_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
int ret = 0;
u8 bConfigurationValue;
struct phy_dev *phy_dev = NULL;
struct usbwm_dev *udev = NULL;
u16 idVendor, idProduct, bcdDevice;
struct usb_device *usbdev = interface_to_usbdev(intf);
usb_get_dev(usbdev);
bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue;
/*USB description is set up with Little-Endian*/
idVendor = L2H(usbdev->descriptor.idVendor);
idProduct = L2H(usbdev->descriptor.idProduct);
bcdDevice = L2H(usbdev->descriptor.bcdDevice);
dev_info(&intf->dev, "Found GDM USB VID = 0x%04x PID = 0x%04x...\n",
idVendor, idProduct);
dev_info(&intf->dev, "GCT WiMax driver version %s\n", DRIVER_VERSION);
if (idProduct == EMERGENCY_PID) {
ret = usb_emergency(usbdev);
goto out;
}
/* Support for EEPROM bootloader */
if (bConfigurationValue == DOWNLOAD_CONF_VALUE ||
idProduct & B_DOWNLOAD) {
ret = usb_boot(usbdev, bcdDevice);
goto out;
}
phy_dev = kzalloc(sizeof(*phy_dev), GFP_KERNEL);
if (phy_dev == NULL) {
ret = -ENOMEM;
goto out;
}
udev = kzalloc(sizeof(*udev), GFP_KERNEL);
if (udev == NULL) {
ret = -ENOMEM;
goto out;
}
if (idProduct == 0x7205 || idProduct == 0x7206)
udev->padding = GDM7205_PADDING;
else
udev->padding = 0;
phy_dev->priv_dev = (void *)udev;
phy_dev->send_func = gdm_usb_send;
phy_dev->rcv_func = gdm_usb_receive;
ret = init_usb(udev);
if (ret < 0)
goto out;
udev->usbdev = usbdev;
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
udev->intf = intf;
intf->needs_remote_wakeup = 1;
device_init_wakeup(&intf->dev, 1);
pm_runtime_set_autosuspend_delay(&usbdev->dev, 10 * 1000); /* msec */
INIT_WORK(&udev->pm_ws, do_pm_control);
#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
ret = register_wimax_device(phy_dev, &intf->dev);
out:
if (ret) {
kfree(phy_dev);
kfree(udev);
} else {
usb_set_intfdata(intf, phy_dev);
}
return ret;
}
static void gdm_usb_disconnect(struct usb_interface *intf)
{
u8 bConfigurationValue;
struct phy_dev *phy_dev;
struct usbwm_dev *udev;
u16 idProduct;
struct usb_device *usbdev = interface_to_usbdev(intf);
bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue;
phy_dev = usb_get_intfdata(intf);
/*USB description is set up with Little-Endian*/
idProduct = L2H(usbdev->descriptor.idProduct);
if (idProduct != EMERGENCY_PID &&
bConfigurationValue != DOWNLOAD_CONF_VALUE &&
(idProduct & B_DOWNLOAD) == 0) {
udev = phy_dev->priv_dev;
udev->usbdev = NULL;
unregister_wimax_device(phy_dev);
release_usb(udev);
kfree(udev);
kfree(phy_dev);
}
usb_put_dev(usbdev);
}
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
static int gdm_suspend(struct usb_interface *intf, pm_message_t pm_msg)
{
struct phy_dev *phy_dev;
struct usbwm_dev *udev;
struct rx_cxt *rx;
struct usb_rx *r;
unsigned long flags;
phy_dev = usb_get_intfdata(intf);
if (!phy_dev)
return 0;
udev = phy_dev->priv_dev;
rx = &udev->rx;
spin_lock_irqsave(&rx->lock, flags);
list_for_each_entry(r, &rx->used_list, list)
usb_unlink_urb(r->urb);
spin_unlock_irqrestore(&rx->lock, flags);
return 0;
}
static int gdm_resume(struct usb_interface *intf)
{
struct phy_dev *phy_dev;
struct usbwm_dev *udev;
struct rx_cxt *rx;
struct usb_rx *r;
unsigned long flags;
phy_dev = usb_get_intfdata(intf);
if (!phy_dev)
return 0;
udev = phy_dev->priv_dev;
rx = &udev->rx;
spin_lock_irqsave(&rx->lock, flags);
list_for_each_entry(r, &rx->used_list, list)
usb_submit_urb(r->urb, GFP_ATOMIC);
spin_unlock_irqrestore(&rx->lock, flags);
return 0;
}
#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
static int k_mode_thread(void *arg)
{
struct usbwm_dev *udev;
struct tx_cxt *tx;
struct rx_cxt *rx;
struct usb_tx *t, *temp;
struct usb_rx *r;
unsigned long flags, flags2, expire;
int ret;
while (!k_mode_stop) {
spin_lock_irqsave(&k_lock, flags2);
while (!list_empty(&k_list)) {
udev = list_entry(k_list.next, struct usbwm_dev, list);
tx = &udev->tx;
rx = &udev->rx;
list_del(&udev->list);
spin_unlock_irqrestore(&k_lock, flags2);
expire = jiffies + K_WAIT_TIME;
while (jiffies < expire)
schedule_timeout(K_WAIT_TIME);
spin_lock_irqsave(&rx->lock, flags);
list_for_each_entry(r, &rx->used_list, list)
usb_submit_urb(r->urb, GFP_ATOMIC);
spin_unlock_irqrestore(&rx->lock, flags);
spin_lock_irqsave(&tx->lock, flags);
list_for_each_entry_safe(t, temp, &tx->pending_list,
p_list) {
list_del(&t->p_list);
ret = usb_submit_urb(t->urb, GFP_ATOMIC);
if (ret) {
t->callback = NULL;
__gdm_usb_send_complete(t->urb);
}
}
udev->bw_switch = 0;
spin_unlock_irqrestore(&tx->lock, flags);
spin_lock_irqsave(&k_lock, flags2);
}
spin_unlock_irqrestore(&k_lock, flags2);
interruptible_sleep_on(&k_wait);
}
return 0;
}
#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
static struct usb_driver gdm_usb_driver = {
.name = "gdm_wimax",
.probe = gdm_usb_probe,
.disconnect = gdm_usb_disconnect,
.id_table = id_table,
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
.supports_autosuspend = 1,
.suspend = gdm_suspend,
.resume = gdm_resume,
.reset_resume = gdm_resume,
#endif
};
static int __init usb_gdm_wimax_init(void)
{
#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
kthread_run(k_mode_thread, NULL, "k_mode_wimax");
#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
return usb_register(&gdm_usb_driver);
}
static void __exit usb_gdm_wimax_exit(void)
{
#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
k_mode_stop = 1;
wake_up(&k_wait);
#endif
usb_deregister(&gdm_usb_driver);
}
module_init(usb_gdm_wimax_init);
module_exit(usb_gdm_wimax_exit);
MODULE_VERSION(DRIVER_VERSION);
MODULE_DESCRIPTION("GCT WiMax Device Driver");
MODULE_AUTHOR("Ethan Park");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#ifndef __GDM_USB_H__
#define __GDM_USB_H__
#include <linux/types.h>
#include <linux/usb.h>
#include <linux/list.h>
#define B_DIFF_DL_DRV (1 << 4)
#define B_DOWNLOAD (1 << 5)
#define MAX_NR_SDU_BUF 64
struct usb_tx {
struct list_head list;
#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
struct list_head p_list;
#endif
struct tx_cxt *tx_cxt;
struct urb *urb;
u8 *buf;
void (*callback)(void *cb_data);
void *cb_data;
};
struct tx_cxt {
struct list_head free_list;
struct list_head sdu_list;
struct list_head hci_list;
#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
struct list_head pending_list;
#endif
spinlock_t lock;
};
struct usb_rx {
struct list_head list;
struct rx_cxt *rx_cxt;
struct urb *urb;
u8 *buf;
void (*callback)(void *cb_data, void *data, int len);
void *cb_data;
};
struct rx_cxt {
struct list_head free_list;
struct list_head used_list;
spinlock_t lock;
};
struct usbwm_dev {
struct usb_device *usbdev;
#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
struct work_struct pm_ws;
struct usb_interface *intf;
#endif
#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
int bw_switch;
struct list_head list;
#endif
struct tx_cxt tx;
struct rx_cxt rx;
int padding;
};
#endif /* __GDM_USB_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#ifndef __GDM_WIMAX_H__
#define __GDM_WIMAX_H__
#include <linux/netdevice.h>
#include <linux/types.h>
#include "wm_ioctl.h"
#if defined(CONFIG_WIMAX_GDM72XX_QOS)
#include "gdm_qos.h"
#endif
#define DRIVER_VERSION "3.2.3"
/*#define ETH_P_IP 0x0800 */
/*#define ETH_P_ARP 0x0806 */
/*#define ETH_P_IPV6 0x86DD */
#define H2L(x) __cpu_to_le16(x)
#define L2H(x) __le16_to_cpu(x)
#define DH2L(x) __cpu_to_le32(x)
#define DL2H(x) __le32_to_cpu(x)
#define H2B(x) __cpu_to_be16(x)
#define B2H(x) __be16_to_cpu(x)
#define DH2B(x) __cpu_to_be32(x)
#define DB2H(x) __be32_to_cpu(x)
struct phy_dev {
void *priv_dev;
struct net_device *netdev;
int (*send_func)(void *priv_dev, void *data, int len,
void (*cb)(void *cb_data), void *cb_data);
int (*rcv_func)(void *priv_dev,
void (*cb)(void *cb_data, void *data, int len),
void *cb_data);
};
struct nic {
struct net_device *netdev;
struct phy_dev *phy_dev;
struct net_device_stats stats;
struct data_s sdk_data[SIOC_DATA_MAX];
#if defined(CONFIG_WIMAX_GDM72XX_QOS)
struct qos_cb_s qos;
#endif
};
#if 0
#define dprintk(fmt, args ...) printk(KERN_DEBUG " [GDM] " fmt, ## args)
#else
#define dprintk(...)
#endif
/*#define DEBUG_SDU */
#if defined(DEBUG_SDU)
#define DUMP_SDU_ALL (1<<0)
#define DUMP_SDU_ARP (1<<1)
#define DUMP_SDU_IP (1<<2)
#define DUMP_SDU_IP_TCP (1<<8)
#define DUMP_SDU_IP_UDP (1<<9)
#define DUMP_SDU_IP_ICMP (1<<10)
#define DUMP_PACKET (DUMP_SDU_ALL)
#endif
/*#define DEBUG_HCI */
/*#define LOOPBACK_TEST */
extern int register_wimax_device(struct phy_dev *phy_dev, struct device *pdev);
extern int gdm_wimax_send_tx(struct sk_buff *skb, struct net_device *dev);
extern void unregister_wimax_device(struct phy_dev *phy_dev);
#endif

View File

@@ -0,0 +1,218 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#ifndef HCI_H_20080801
#define HCI_H_20080801
#define HCI_HEADER_SIZE 4
#define HCI_VALUE_OFFS (HCI_HEADER_SIZE)
#define HCI_MAX_PACKET 2048
#define HCI_MAX_PARAM (HCI_MAX_PACKET-HCI_HEADER_SIZE)
#define HCI_MAX_TLV 32
/* CMD-EVT */
/* Category 0 */
#define WIMAX_RESET 0x0000
#define WIMAX_SET_INFO 0x0001
#define WIMAX_GET_INFO 0x0002
#define WIMAX_GET_INFO_RESULT 0x8003
#define WIMAX_RADIO_OFF 0x0004
#define WIMAX_RADIO_ON 0x0006
#define WIMAX_WIMAX_RESET 0x0007 /* Is this still here */
/* Category 1 */
#define WIMAX_NET_ENTRY 0x0100
#define WIMAX_NET_DISCONN 0x0102
#define WIMAX_ENTER_SLEEP 0x0103
#define WIMAX_EXIT_SLEEP 0x0104
#define WIMAX_ENTER_IDLE 0x0105
#define WIMAX_EXIT_IDLE 0x0106
#define WIMAX_MODE_CHANGE 0x8108
#define WIMAX_HANDOVER 0x8109 /* obsolete */
#define WIMAX_SCAN 0x010d
#define WIMAX_SCAN_COMPLETE 0x810e
#define WIMAX_SCAN_RESULT 0x810f
#define WIMAX_CONNECT 0x0110
#define WIMAX_CONNECT_START 0x8111
#define WIMAX_CONNECT_COMPLETE 0x8112
#define WIMAX_ASSOC_START 0x8113
#define WIMAX_ASSOC_COMPLETE 0x8114
#define WIMAX_DISCONN_IND 0x8115
#define WIMAX_ENTRY_IND 0x8116
#define WIMAX_HO_START 0x8117
#define WIMAX_HO_COMPLETE 0x8118
#define WIMAX_RADIO_STATE_IND 0x8119
#define WIMAX_IP_RENEW_IND 0x811a
#define WIMAX_DISCOVER_NSP 0x011d
#define WIMAX_DISCOVER_NSP_RESULT 0x811e
#define WIMAX_SDU_TX_FLOW 0x8125
/* Category 2 */
#define WIMAX_TX_EAP 0x0200
#define WIMAX_RX_EAP 0x8201
#define WIMAX_TX_SDU 0x0202
#define WIMAX_RX_SDU 0x8203
#define WIMAX_RX_SDU_AGGR 0x8204
#define WIMAX_TX_SDU_AGGR 0x0205
/* Category 3 */
#define WIMAX_DM_CMD 0x030a
#define WIMAX_DM_RSP 0x830b
#define WIMAX_CLI_CMD 0x030c
#define WIMAX_CLI_RSP 0x830d
#define WIMAX_DL_IMAGE 0x0310
#define WIMAX_DL_IMAGE_STATUS 0x8311
#define WIMAX_UL_IMAGE 0x0312
#define WIMAX_UL_IMAGE_RESULT 0x8313
#define WIMAX_UL_IMAGE_STATUS 0x0314
#define WIMAX_EVT_MODEM_REPORT 0x8325
/* Category 0xF */
#define WIMAX_FSM_UPDATE 0x8F01
#define WIMAX_IF_UPDOWN 0x8F02
#define WIMAX_IF_UP 1
#define WIMAX_IF_DOWN 2
/* WIMAX mode */
#define W_NULL 0
#define W_STANDBY 1
#define W_OOZ 2
#define W_AWAKE 3
#define W_IDLE 4
#define W_SLEEP 5
#define W_WAIT 6
#define W_NET_ENTRY_RNG 0x80
#define W_NET_ENTRY_SBC 0x81
#define W_NET_ENTRY_PKM 0x82
#define W_NET_ENTRY_REG 0x83
#define W_NET_ENTRY_DSX 0x84
#define W_NET_ENTRY_RNG_FAIL 0x1100100
#define W_NET_ENTRY_SBC_FAIL 0x1100200
#define W_NET_ENTRY_PKM_FAIL 0x1102000
#define W_NET_ENTRY_REG_FAIL 0x1103000
#define W_NET_ENTRY_DSX_FAIL 0x1104000
/* Scan Type */
#define W_SCAN_ALL_CHANNEL 0
#define W_SCAN_ALL_SUBSCRIPTION 1
#define W_SCAN_SPECIFIED_SUBSCRIPTION 2
/*
* TLV
*
* [31:31] indicates the type is composite.
* [30:16] is the length of the type. 0 length means length is variable.
* [15:0] is the actual type.
*
*/
#define TLV_L(x) (((x) >> 16) & 0xff)
#define TLV_T(x) ((x) & 0xff)
#define TLV_COMPOSITE(x) ((x) >> 31)
/* GENERAL */
#define T_MAC_ADDRESS (0x00 | (6 << 16))
#define T_BSID (0x01 | (6 << 16))
#define T_MSK (0x02 | (64 << 16))
#define T_RSSI_THRSHLD (0x03 | (1 << 16))
#define T_FREQUENCY (0x04 | (4 << 16))
#define T_CONN_CS_TYPE (0x05 | (1 << 16))
#define T_HOST_IP_VER (0x06 | (1 << 16))
#define T_STBY_SCAN_INTERVAL (0x07 | (4 << 16))
#define T_OOZ_SCAN_INTERVAL (0x08 | (4 << 16))
#define T_IMEI (0x09 | (8 << 16))
#define T_PID (0x0a | (12 << 16))
#define T_CAPABILITY (0x1a | (4 << 16))
#define T_RELEASE_NUMBER (0x1b | (4 << 16))
#define T_DRIVER_REVISION (0x1c | (4 << 16))
#define T_FW_REVISION (0x1d | (4 << 16))
#define T_MAC_HW_REVISION (0x1e | (4 << 16))
#define T_PHY_HW_REVISION (0x1f | (4 << 16))
/* HANDOVER */
#define T_SCAN_INTERVAL (0x20 | (1 << 16))
#define T_RSC_RETAIN_TIME (0x2f | (2 << 16))
/* SLEEP */
#define T_TYPE1_ISW (0x40 | (1 << 16))
#define T_SLP_START_TO (0x4a | (2 << 16))
/* IDLE */
#define T_IDLE_MODE_TO (0x50 | (2 << 16))
#define T_IDLE_START_TO (0x54 | (2 << 16))
/* MONITOR */
#define T_RSSI (0x60 | (1 << 16))
#define T_CINR (0x61 | (1 << 16))
#define T_TX_POWER (0x6a | (1 << 16))
#define T_CUR_FREQ (0x7f | (4 << 16))
/* WIMAX */
#define T_MAX_SUBSCRIPTION (0xa1 | (1 << 16))
#define T_MAX_SF (0xa2 | (1 << 16))
#define T_PHY_TYPE (0xa3 | (1 << 16))
#define T_PKM (0xa4 | (1 << 16))
#define T_AUTH_POLICY (0xa5 | (1 << 16))
#define T_CS_TYPE (0xa6 | (2 << 16))
#define T_VENDOR_NAME (0xa7 | (0 << 16))
#define T_MOD_NAME (0xa8 | (0 << 16))
#define T_PACKET_FILTER (0xa9 | (1 << 16))
#define T_NSP_CHANGE_COUNT (0xaa | (4 << 16))
#define T_RADIO_STATE (0xab | (1 << 16))
#define T_URI_CONTACT_TYPE (0xac | (1 << 16))
#define T_URI_TEXT (0xad | (0 << 16))
#define T_URI (0xae | (0 << 16))
#define T_ENABLE_AUTH (0xaf | (1 << 16))
#define T_TIMEOUT (0xb0 | (2 << 16))
#define T_RUN_MODE (0xb1 | (1 << 16))
#define T_OMADMT_VER (0xb2 | (4 << 16))
/* This is measured in seconds from 00:00:00 GMT January 1, 1970. */
#define T_RTC_TIME (0xb3 | (4 << 16))
#define T_CERT_STATUS (0xb4 | (4 << 16))
#define T_CERT_MASK (0xb5 | (4 << 16))
#define T_EMSK (0xb6 | (64 << 16))
/* Subscription TLV */
#define T_SUBSCRIPTION_LIST (0xd1 | (0 << 16) | (1 << 31))
#define T_H_NSPID (0xd2 | (3 << 16))
#define T_NSP_NAME (0xd3 | (0 << 16))
#define T_SUBSCRIPTION_NAME (0xd4 | (0 << 16))
#define T_SUBSCRIPTION_FLAG (0xd5 | (2 << 16))
#define T_V_NSPID (0xd6 | (3 << 16))
#define T_NAP_ID (0xd7 | (3 << 16))
#define T_PREAMBLES (0xd8 | (15 << 16))
#define T_BW (0xd9 | (4 << 16))
#define T_FFTSIZE (0xda | (4 << 16))
#define T_DUPLEX_MODE (0xdb | (4 << 16))
struct hci_s {
unsigned short cmd_evt;
unsigned short length;
unsigned char data[0];
} __packed;
#endif

View File

@@ -0,0 +1,157 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/etherdevice.h>
#include <net/netlink.h>
#include <asm/byteorder.h>
#include <net/sock.h>
#include "netlink_k.h"
#if !defined(NLMSG_HDRLEN)
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#endif
#define ND_MAX_GROUP 30
#define ND_IFINDEX_LEN sizeof(int)
#define ND_NLMSG_SPACE(len) (nlmsg_total_size(len) + ND_IFINDEX_LEN)
#define ND_NLMSG_DATA(nlh) \
((void *)((char *)nlmsg_data(nlh) + ND_IFINDEX_LEN))
#define ND_NLMSG_S_LEN(len) (len+ND_IFINDEX_LEN)
#define ND_NLMSG_R_LEN(nlh) (nlh->nlmsg_len-ND_IFINDEX_LEN)
#define ND_NLMSG_IFIDX(nlh) nlmsg_data(nlh)
#define ND_MAX_MSG_LEN 8096
#if defined(DEFINE_MUTEX)
static DEFINE_MUTEX(netlink_mutex);
#else
static struct semaphore netlink_mutex;
#define mutex_lock(x) down(x)
#define mutex_unlock(x) up(x)
#endif
static void (*rcv_cb)(struct net_device *dev, u16 type, void *msg, int len);
static void netlink_rcv_cb(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
struct net_device *dev;
u32 mlen;
void *msg;
int ifindex;
if (skb->len >= NLMSG_HDRLEN) {
nlh = (struct nlmsghdr *)skb->data;
if (skb->len < nlh->nlmsg_len ||
nlh->nlmsg_len > ND_MAX_MSG_LEN) {
netdev_err(skb->dev, "Invalid length (%d,%d)\n",
skb->len, nlh->nlmsg_len);
return;
}
memcpy(&ifindex, ND_NLMSG_IFIDX(nlh), ND_IFINDEX_LEN);
msg = ND_NLMSG_DATA(nlh);
mlen = ND_NLMSG_R_LEN(nlh);
if (rcv_cb) {
dev = dev_get_by_index(&init_net, ifindex);
if (dev) {
rcv_cb(dev, nlh->nlmsg_type, msg, mlen);
dev_put(dev);
} else
netdev_err(skb->dev,
"dev_get_by_index(%d) is not found.\n",
ifindex);
} else
netdev_err(skb->dev, "Unregistered Callback\n");
}
}
static void netlink_rcv(struct sk_buff *skb)
{
mutex_lock(&netlink_mutex);
netlink_rcv_cb(skb);
mutex_unlock(&netlink_mutex);
}
struct sock *netlink_init(int unit, void (*cb)(struct net_device *dev, u16 type,
void *msg, int len))
{
struct sock *sock;
struct netlink_kernel_cfg cfg = {
.input = netlink_rcv,
};
#if !defined(DEFINE_MUTEX)
init_MUTEX(&netlink_mutex);
#endif
sock = netlink_kernel_create(&init_net, unit, &cfg);
if (sock)
rcv_cb = cb;
return sock;
}
void netlink_exit(struct sock *sock)
{
netlink_kernel_release(sock);
}
int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len)
{
static u32 seq;
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh;
int ret = 0;
if (group > ND_MAX_GROUP) {
pr_err("Group %d is invalied.\n", group);
pr_err("Valid group is 0 ~ %d.\n", ND_MAX_GROUP);
return -EINVAL;
}
skb = nlmsg_new(len, GFP_ATOMIC);
if (!skb) {
pr_err("netlink_broadcast ret=%d\n", ret);
return -ENOMEM;
}
seq++;
nlh = nlmsg_put(skb, 0, seq, type, len, 0);
if (!nlh) {
kfree_skb(skb);
return -EMSGSIZE;
}
memcpy(nlmsg_data(nlh), msg, len);
NETLINK_CB(skb).portid = 0;
NETLINK_CB(skb).dst_group = 0;
ret = netlink_broadcast(sock, skb, 0, group+1, GFP_ATOMIC);
if (!ret)
return len;
else {
if (ret != -ESRCH) {
pr_err("netlink_broadcast g=%d, t=%d, l=%d, r=%d\n",
group, type, len, ret);
}
ret = 0;
}
return ret;
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#if !defined(NETLINK_H_20081202)
#define NETLINK_H_20081202
#include <linux/netdevice.h>
#include <net/sock.h>
struct sock *netlink_init(int unit,
void (*cb)(struct net_device *dev, u16 type, void *msg, int len));
void netlink_exit(struct sock *sock);
int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len);
#endif

View File

@@ -0,0 +1,159 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio_func.h>
#include <linux/firmware.h>
#include "gdm_sdio.h"
#include "sdio_boot.h"
#define TYPE_A_HEADER_SIZE 4
#define TYPE_A_LOOKAHEAD_SIZE 16
#define YMEM0_SIZE 0x8000 /* 32kbytes */
#define DOWNLOAD_SIZE (YMEM0_SIZE - TYPE_A_HEADER_SIZE)
#define FW_DIR "gdm72xx/"
#define FW_KRN "gdmskrn.bin"
#define FW_RFS "gdmsrfs.bin"
static u8 *tx_buf;
static int ack_ready(struct sdio_func *func)
{
unsigned long start = jiffies;
u8 val;
int ret;
while ((jiffies - start) < HZ) {
val = sdio_readb(func, 0x13, &ret);
if (val & 0x01)
return 1;
schedule();
}
return 0;
}
static int download_image(struct sdio_func *func, const char *img_name)
{
int ret = 0, len, pno;
u8 *buf = tx_buf;
loff_t pos = 0;
int img_len;
const struct firmware *firm;
ret = request_firmware(&firm, img_name, &func->dev);
if (ret < 0) {
dev_err(&func->dev,
"requesting firmware %s failed with error %d\n",
img_name, ret);
return ret;
}
buf = kmalloc(DOWNLOAD_SIZE + TYPE_A_HEADER_SIZE, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
img_len = firm->size;
if (img_len <= 0) {
ret = -1;
goto out;
}
pno = 0;
while (img_len > 0) {
if (img_len > DOWNLOAD_SIZE) {
len = DOWNLOAD_SIZE;
buf[3] = 0;
} else {
len = img_len; /* the last packet */
buf[3] = 2;
}
buf[0] = len & 0xff;
buf[1] = (len >> 8) & 0xff;
buf[2] = (len >> 16) & 0xff;
memcpy(buf+TYPE_A_HEADER_SIZE, firm->data + pos, len);
ret = sdio_memcpy_toio(func, 0, buf, len + TYPE_A_HEADER_SIZE);
if (ret < 0) {
dev_err(&func->dev,
"send image error: packet number = %d ret = %d\n",
pno, ret);
goto out;
}
if (buf[3] == 2) /* The last packet */
break;
if (!ack_ready(func)) {
ret = -EIO;
dev_err(&func->dev, "Ack is not ready.\n");
goto out;
}
ret = sdio_memcpy_fromio(func, buf, 0, TYPE_A_LOOKAHEAD_SIZE);
if (ret < 0) {
dev_err(&func->dev,
"receive ack error: packet number = %d ret = %d\n",
pno, ret);
goto out;
}
sdio_writeb(func, 0x01, 0x13, &ret);
sdio_writeb(func, 0x00, 0x10, &ret); /* PCRRT */
img_len -= DOWNLOAD_SIZE;
pos += DOWNLOAD_SIZE;
pno++;
}
out:
kfree(buf);
return ret;
}
int sdio_boot(struct sdio_func *func)
{
int ret;
const char *krn_name = FW_DIR FW_KRN;
const char *rfs_name = FW_DIR FW_RFS;
tx_buf = kmalloc(YMEM0_SIZE, GFP_KERNEL);
if (tx_buf == NULL)
return -ENOMEM;
ret = download_image(func, krn_name);
if (ret)
goto restore_fs;
dev_info(&func->dev, "GCT: Kernel download success.\n");
ret = download_image(func, rfs_name);
if (ret)
goto restore_fs;
dev_info(&func->dev, "GCT: Filesystem download success.\n");
restore_fs:
kfree(tx_buf);
return ret;
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#ifndef __SDIO_BOOT_H__
#define __SDIO_BOOT_H__
struct sdio_func;
extern int sdio_boot(struct sdio_func *func);
#endif /* __SDIO_BOOT_H__ */

View File

@@ -0,0 +1,376 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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 <linux/uaccess.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/usb.h>
#include <linux/unistd.h>
#include <linux/slab.h>
#include <linux/firmware.h>
#include <asm/byteorder.h>
#include "gdm_usb.h"
#include "usb_boot.h"
#define DN_KERNEL_MAGIC_NUMBER 0x10760001
#define DN_ROOTFS_MAGIC_NUMBER 0x10760002
#define DOWNLOAD_SIZE 1024
#define MAX_IMG_CNT 16
#define FW_DIR "gdm72xx/"
#define FW_UIMG "gdmuimg.bin"
#define FW_KERN "zImage"
#define FW_FS "ramdisk.jffs2"
struct dn_header {
u32 magic_num;
u32 file_size;
};
struct img_header {
u32 magic_code;
u32 count;
u32 len;
u32 offset[MAX_IMG_CNT];
char hostname[32];
char date[32];
};
struct fw_info {
u32 id;
u32 len;
u32 kernel_len;
u32 rootfs_len;
u32 kernel_offset;
u32 rootfs_offset;
u32 fw_ver;
u32 mac_ver;
char hostname[32];
char userid[16];
char date[32];
char user_desc[128];
};
static void array_le32_to_cpu(u32 *arr, int num)
{
int i;
for (i = 0; i < num; i++, arr++)
*arr = __le32_to_cpu(*arr);
}
static u8 *tx_buf;
static int gdm_wibro_send(struct usb_device *usbdev, void *data, int len)
{
int ret;
int actual;
ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), data, len,
&actual, 1000);
if (ret < 0) {
dev_err(&usbdev->dev, "Error : usb_bulk_msg ( result = %d )\n",
ret);
return ret;
}
return 0;
}
static int gdm_wibro_recv(struct usb_device *usbdev, void *data, int len)
{
int ret;
int actual;
ret = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), data, len,
&actual, 5000);
if (ret < 0) {
dev_err(&usbdev->dev,
"Error : usb_bulk_msg(recv) ( result = %d )\n", ret);
return ret;
}
return 0;
}
static int download_image(struct usb_device *usbdev,
const struct firmware *firm,
loff_t pos, u32 img_len, u32 magic_num)
{
struct dn_header h;
int ret = 0;
u32 size;
size = ALIGN(img_len, DOWNLOAD_SIZE);
h.magic_num = __cpu_to_be32(magic_num);
h.file_size = __cpu_to_be32(size);
ret = gdm_wibro_send(usbdev, &h, sizeof(h));
if (ret < 0)
return ret;
while (img_len > 0) {
if (img_len > DOWNLOAD_SIZE)
size = DOWNLOAD_SIZE;
else
size = img_len; /* the last chunk of data */
memcpy(tx_buf, firm->data + pos, size);
ret = gdm_wibro_send(usbdev, tx_buf, size);
if (ret < 0)
return ret;
img_len -= size;
pos += size;
}
return ret;
}
int usb_boot(struct usb_device *usbdev, u16 pid)
{
int i, ret = 0;
struct img_header hdr;
struct fw_info fw_info;
loff_t pos = 0;
char *img_name = FW_DIR FW_UIMG;
const struct firmware *firm;
ret = request_firmware(&firm, img_name, &usbdev->dev);
if (ret < 0) {
dev_err(&usbdev->dev,
"requesting firmware %s failed with error %d\n",
img_name, ret);
return ret;
}
tx_buf = kmalloc(DOWNLOAD_SIZE, GFP_KERNEL);
if (tx_buf == NULL)
return -ENOMEM;
if (firm->size < sizeof(hdr)) {
dev_err(&usbdev->dev, "Cannot read the image info.\n");
ret = -EIO;
goto out;
}
memcpy(&hdr, firm->data, sizeof(hdr));
array_le32_to_cpu((u32 *)&hdr, 19);
#if 0
if (hdr.magic_code != 0x10767fff) {
dev_err(&usbdev->dev, "Invalid magic code 0x%08x\n",
hdr.magic_code);
ret = -EINVAL;
goto out;
}
#endif
if (hdr.count > MAX_IMG_CNT) {
dev_err(&usbdev->dev, "Too many images. %d\n", hdr.count);
ret = -EINVAL;
goto out;
}
for (i = 0; i < hdr.count; i++) {
if (hdr.offset[i] > hdr.len) {
dev_err(&usbdev->dev,
"Invalid offset. Entry = %d Offset = 0x%08x Image length = 0x%08x\n",
i, hdr.offset[i], hdr.len);
ret = -EINVAL;
goto out;
}
pos = hdr.offset[i];
if (firm->size < sizeof(fw_info) + pos) {
dev_err(&usbdev->dev, "Cannot read the FW info.\n");
ret = -EIO;
goto out;
}
memcpy(&fw_info, firm->data + pos, sizeof(fw_info));
array_le32_to_cpu((u32 *)&fw_info, 8);
#if 0
if ((fw_info.id & 0xfffff000) != 0x10767000) {
dev_err(&usbdev->dev, "Invalid FW id. 0x%08x\n",
fw_info.id);
ret = -EIO;
goto out;
}
#endif
if ((fw_info.id & 0xffff) != pid)
continue;
pos = hdr.offset[i] + fw_info.kernel_offset;
if (firm->size < fw_info.kernel_len + pos) {
dev_err(&usbdev->dev, "Kernel FW is too small.\n");
goto out;
}
ret = download_image(usbdev, firm, pos,
fw_info.kernel_len, DN_KERNEL_MAGIC_NUMBER);
if (ret < 0)
goto out;
dev_info(&usbdev->dev, "GCT: Kernel download success.\n");
pos = hdr.offset[i] + fw_info.rootfs_offset;
if (firm->size < fw_info.rootfs_len + pos) {
dev_err(&usbdev->dev, "Filesystem FW is too small.\n");
goto out;
}
ret = download_image(usbdev, firm, pos, fw_info.rootfs_len,
DN_ROOTFS_MAGIC_NUMBER);
if (ret < 0)
goto out;
dev_info(&usbdev->dev, "GCT: Filesystem download success.\n");
break;
}
if (i == hdr.count) {
dev_err(&usbdev->dev, "Firmware for gsk%x is not installed.\n",
pid);
ret = -EINVAL;
}
out:
release_firmware(firm);
kfree(tx_buf);
return ret;
}
/*#define GDM7205_PADDING 256 */
#define DOWNLOAD_CHUCK 2048
#define KERNEL_TYPE_STRING "linux"
#define FS_TYPE_STRING "rootfs"
static int em_wait_ack(struct usb_device *usbdev, int send_zlp)
{
int ack;
int ret = -1;
if (send_zlp) {
/*Send ZLP*/
ret = gdm_wibro_send(usbdev, NULL, 0);
if (ret < 0)
goto out;
}
/*Wait for ACK*/
ret = gdm_wibro_recv(usbdev, &ack, sizeof(ack));
if (ret < 0)
goto out;
out:
return ret;
}
static int em_download_image(struct usb_device *usbdev, const char *img_name,
char *type_string)
{
char *buf = NULL;
loff_t pos = 0;
int ret = 0;
int len;
int img_len;
const struct firmware *firm;
#if defined(GDM7205_PADDING)
const int pad_size = GDM7205_PADDING;
#else
const int pad_size = 0;
#endif
ret = request_firmware(&firm, img_name, &usbdev->dev);
if (ret < 0) {
dev_err(&usbdev->dev,
"requesting firmware %s failed with error %d\n",
img_name, ret);
return ret;
}
buf = kmalloc(DOWNLOAD_CHUCK + pad_size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
strcpy(buf+pad_size, type_string);
ret = gdm_wibro_send(usbdev, buf, strlen(type_string)+pad_size);
if (ret < 0)
goto out;
img_len = firm->size;
if (img_len <= 0) {
ret = -1;
goto out;
}
while (img_len > 0) {
if (img_len > DOWNLOAD_CHUCK)
len = DOWNLOAD_CHUCK;
else
len = img_len; /* the last chunk of data */
memcpy(buf+pad_size, firm->data + pos, len);
ret = gdm_wibro_send(usbdev, buf, len+pad_size);
if (ret < 0)
goto out;
img_len -= DOWNLOAD_CHUCK;
pos += DOWNLOAD_CHUCK;
ret = em_wait_ack(usbdev, ((len+pad_size) % 512 == 0));
if (ret < 0)
goto out;
}
ret = em_wait_ack(usbdev, 1);
if (ret < 0)
goto out;
out:
release_firmware(firm);
kfree(buf);
return ret;
}
static int em_fw_reset(struct usb_device *usbdev)
{
int ret;
/*Send ZLP*/
ret = gdm_wibro_send(usbdev, NULL, 0);
return ret;
}
int usb_emergency(struct usb_device *usbdev)
{
int ret;
const char *kern_name = FW_DIR FW_KERN;
const char *fs_name = FW_DIR FW_FS;
ret = em_download_image(usbdev, kern_name, KERNEL_TYPE_STRING);
if (ret < 0)
return ret;
dev_err(&usbdev->dev, "GCT Emergency: Kernel download success.\n");
ret = em_download_image(usbdev, fs_name, FS_TYPE_STRING);
if (ret < 0)
return ret;
dev_info(&usbdev->dev, "GCT Emergency: Filesystem download success.\n");
ret = em_fw_reset(usbdev);
return ret;
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#ifndef __USB_BOOT_H__
#define __USB_BOOT_H__
struct usb_device;
extern int usb_boot(struct usb_device *usbdev, u16 pid);
extern int usb_emergency(struct usb_device *usbdev);
#endif /* __USB_BOOT_H__ */

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#ifndef __USB_IDS_H__
#define __USB_IDS_H__
/*You can replace vendor-ID as yours.*/
#define GCT_VID 0x1076
/*You can replace product-ID as yours.*/
#define GCT_PID1 0x7e00
#define GCT_PID2 0x7f00
#define USB_DEVICE_ID_MATCH_DEVICE_INTERFACE \
(USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_CLASS)
#define USB_DEVICE_INTF(vend, prod, intf) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE_INTERFACE, \
.idVendor = (vend), .idProduct = (prod), .bInterfaceClass = (intf)
#define EMERGENCY_PID 0x720f
#define BL_PID_MASK 0xffc0
#define USB_DEVICE_BOOTLOADER(vid, pid) \
{USB_DEVICE((vid), ((pid)&BL_PID_MASK)|B_DOWNLOAD)}, \
{USB_DEVICE((vid), ((pid)&BL_PID_MASK)|B_DOWNLOAD|B_DIFF_DL_DRV)}
#define USB_DEVICE_CDC_DATA(vid, pid) \
{USB_DEVICE_INTF((vid), (pid), USB_CLASS_CDC_DATA)}
static const struct usb_device_id id_table[] = {
USB_DEVICE_BOOTLOADER(GCT_VID, GCT_PID1),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x1),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x2),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x3),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x4),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x5),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x6),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x7),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x8),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x9),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xa),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xb),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xc),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xd),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xe),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xf),
USB_DEVICE_BOOTLOADER(GCT_VID, GCT_PID2),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x1),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x2),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x3),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x4),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x5),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x6),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x7),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x8),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x9),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xa),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xb),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xc),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xd),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xe),
USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xf),
{USB_DEVICE(GCT_VID, EMERGENCY_PID)},
{ }
};
#endif /* __USB_IDS_H__ */

View File

@@ -0,0 +1,97 @@
/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* 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.
*/
#if !defined(WM_IOCTL_H_20080714)
#define WM_IOCTL_H_20080714
#if !defined(__KERNEL__)
#include <net/if.h>
#endif
#define NETLINK_WIMAX 31
#define SIOCWMIOCTL SIOCDEVPRIVATE
#define SIOCG_DATA 0x8D10
#define SIOCS_DATA 0x8D11
enum {
SIOC_DATA_FSM,
SIOC_DATA_NETLIST,
SIOC_DATA_CONNNSP,
SIOC_DATA_CONNCOMP,
SIOC_DATA_PROFILEID,
SIOC_DATA_END
};
#define SIOC_DATA_MAX 16
/* FSM */
enum {
M_INIT = 0,
M_OPEN_OFF,
M_OPEN_ON,
M_SCAN,
M_CONNECTING,
M_CONNECTED,
M_FSM_END,
C_INIT = 0,
C_CONNSTART,
C_ASSOCSTART,
C_RNG,
C_SBC,
C_AUTH,
C_REG,
C_DSX,
C_ASSOCCOMPLETE,
C_CONNCOMPLETE,
C_FSM_END,
D_INIT = 0,
D_READY,
D_LISTEN,
D_IPACQUISITION,
END_FSM
};
struct fsm_s {
int m_status; /*main status*/
int c_status; /*connection status*/
int d_status; /*oma-dm status*/
};
struct data_s {
int size;
void *buf;
};
struct wm_req_s {
union {
char ifrn_name[IFNAMSIZ];
} ifr_ifrn;
unsigned short cmd;
unsigned short data_id;
struct data_s data;
/* NOTE: sizeof(struct wm_req_s) must be less than sizeof(struct ifreq). */
};
#ifndef ifr_name
#define ifr_name ifr_ifrn.ifrn_name
#endif
#endif