Commit 7e6133aa authored by David Vrabel's avatar David Vrabel Committed by David Vrabel

wusb: WHCI host controller driver

A driver for Wireless USB host controllers that comply with the
Wireless Host Controller Interface (HCI) specification as published by
Intel.

The latest publically available version of the specification (0.95) is
supported (except for isochronous transfers).

Build fixes by Randy Dunlap <rdunlap@xenotime.net>
Signed-off-by: default avatarDavid Vrabel <david.vrabel@csr.com>
parent 870d5395
...@@ -305,3 +305,15 @@ config SUPERH_ON_CHIP_R8A66597 ...@@ -305,3 +305,15 @@ config SUPERH_ON_CHIP_R8A66597
help help
This driver enables support for the on-chip R8A66597 in the This driver enables support for the on-chip R8A66597 in the
SH7366 and SH7723 processors. SH7366 and SH7723 processors.
config USB_WHCI_HCD
tristate "Wireless USB Host Controller Interface (WHCI) driver"
depends on PCI && USB
select USB_WUSB
select UWB_WHCI
help
A driver for PCI-based Wireless USB Host Controllers that are
compliant with the WHCI specification.
To compile this driver a module, choose M here: the module
will be called "whci-hcd".
...@@ -8,6 +8,8 @@ endif ...@@ -8,6 +8,8 @@ endif
isp1760-objs := isp1760-hcd.o isp1760-if.o isp1760-objs := isp1760-hcd.o isp1760-if.o
obj-$(CONFIG_USB_WHCI_HCD) += whci/
obj-$(CONFIG_PCI) += pci-quirks.o obj-$(CONFIG_PCI) += pci-quirks.o
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
......
obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o
whci-hcd-y := \
asl.o \
hcd.o \
hw.o \
init.o \
int.o \
pzl.o \
qset.o \
wusb.o
/*
* Wireless Host Controller (WHC) asynchronous schedule management.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/uwb/umc.h>
#include <linux/usb.h>
#define D_LOCAL 0
#include <linux/uwb/debug.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
#if D_LOCAL >= 4
static void dump_asl(struct whc *whc, const char *tag)
{
struct device *dev = &whc->umc->dev;
struct whc_qset *qset;
d_printf(4, dev, "ASL %s\n", tag);
list_for_each_entry(qset, &whc->async_list, list_node) {
dump_qset(qset, dev);
}
}
#else
static inline void dump_asl(struct whc *whc, const char *tag)
{
}
#endif
static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
struct whc_qset **next, struct whc_qset **prev)
{
struct list_head *n, *p;
BUG_ON(list_empty(&whc->async_list));
n = qset->list_node.next;
if (n == &whc->async_list)
n = n->next;
p = qset->list_node.prev;
if (p == &whc->async_list)
p = p->prev;
*next = container_of(n, struct whc_qset, list_node);
*prev = container_of(p, struct whc_qset, list_node);
}
static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset)
{
list_move(&qset->list_node, &whc->async_list);
qset->in_sw_list = true;
}
static void asl_qset_insert(struct whc *whc, struct whc_qset *qset)
{
struct whc_qset *next, *prev;
qset_clear(whc, qset);
/* Link into ASL. */
qset_get_next_prev(whc, qset, &next, &prev);
whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma);
whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma);
qset->in_hw_list = true;
}
static void asl_qset_remove(struct whc *whc, struct whc_qset *qset)
{
struct whc_qset *prev, *next;
qset_get_next_prev(whc, qset, &next, &prev);
list_move(&qset->list_node, &whc->async_removed_list);
qset->in_sw_list = false;
/*
* No more qsets in the ASL? The caller must stop the ASL as
* it's no longer valid.
*/
if (list_empty(&whc->async_list))
return;
/* Remove from ASL. */
whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma);
qset->in_hw_list = false;
}
/**
* process_qset - process any recently inactivated or halted qTDs in a
* qset.
*
* After inactive qTDs are removed, new qTDs can be added if the
* urb queue still contains URBs.
*
* Returns any additional WUSBCMD bits for the ASL sync command (i.e.,
* WUSBCMD_ASYNC_QSET_RM if a halted qset was removed).
*/
static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
{
enum whc_update update = 0;
uint32_t status = 0;
while (qset->ntds) {
struct whc_qtd *td;
int t;
t = qset->td_start;
td = &qset->qtd[qset->td_start];
status = le32_to_cpu(td->status);
/*
* Nothing to do with a still active qTD.
*/
if (status & QTD_STS_ACTIVE)
break;
if (status & QTD_STS_HALTED) {
/* Ug, an error. */
process_halted_qtd(whc, qset, td);
goto done;
}
/* Mmm, a completed qTD. */
process_inactive_qtd(whc, qset, td);
}
update |= qset_add_qtds(whc, qset);
done:
/*
* Remove this qset from the ASL if requested, but only if has
* no qTDs.
*/
if (qset->remove && qset->ntds == 0) {
asl_qset_remove(whc, qset);
update |= WHC_UPDATE_REMOVED;
}
return update;
}
void asl_start(struct whc *whc)
{
struct whc_qset *qset;
qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR);
whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN);
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED,
1000, "start ASL");
}
void asl_stop(struct whc *whc)
{
whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0);
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
WUSBSTS_ASYNC_SCHED, 0,
1000, "stop ASL");
}
void asl_update(struct whc *whc, uint32_t wusbcmd)
{
whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
wait_event(whc->async_list_wq,
(le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0);
}
/**
* scan_async_work - scan the ASL for qsets to process.
*
* Process each qset in the ASL in turn and then signal the WHC that
* the ASL has been updated.
*
* Then start, stop or update the asynchronous schedule as required.
*/
void scan_async_work(struct work_struct *work)
{
struct whc *whc = container_of(work, struct whc, async_work);
struct whc_qset *qset, *t;
enum whc_update update = 0;
spin_lock_irq(&whc->lock);
dump_asl(whc, "before processing");
/*
* Transerve the software list backwards so new qsets can be
* safely inserted into the ASL without making it non-circular.
*/
list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) {
if (!qset->in_hw_list) {
asl_qset_insert(whc, qset);
update |= WHC_UPDATE_ADDED;
}
update |= process_qset(whc, qset);
}
dump_asl(whc, "after processing");
spin_unlock_irq(&whc->lock);
if (update) {
uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB;
if (update & WHC_UPDATE_REMOVED)
wusbcmd |= WUSBCMD_ASYNC_QSET_RM;
asl_update(whc, wusbcmd);
}
/*
* Now that the ASL is updated, complete the removal of any
* removed qsets.
*/
spin_lock(&whc->lock);
list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
qset_remove_complete(whc, qset);
}
spin_unlock(&whc->lock);
}
/**
* asl_urb_enqueue - queue an URB onto the asynchronous list (ASL).
* @whc: the WHCI host controller
* @urb: the URB to enqueue
* @mem_flags: flags for any memory allocations
*
* The qset for the endpoint is obtained and the urb queued on to it.
*
* Work is scheduled to update the hardware's view of the ASL.
*/
int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
{
struct whc_qset *qset;
int err;
unsigned long flags;
spin_lock_irqsave(&whc->lock, flags);
qset = get_qset(whc, urb, GFP_ATOMIC);
if (qset == NULL)
err = -ENOMEM;
else
err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
if (!err) {
usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
if (!qset->in_sw_list)
asl_qset_insert_begin(whc, qset);
}
spin_unlock_irqrestore(&whc->lock, flags);
if (!err)
queue_work(whc->workqueue, &whc->async_work);
return 0;
}
/**
* asl_urb_dequeue - remove an URB (qset) from the async list.
* @whc: the WHCI host controller
* @urb: the URB to dequeue
* @status: the current status of the URB
*
* URBs that do yet have qTDs can simply be removed from the software
* queue, otherwise the qset must be removed from the ASL so the qTDs
* can be removed.
*/
int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
{
struct whc_urb *wurb = urb->hcpriv;
struct whc_qset *qset = wurb->qset;
struct whc_std *std, *t;
int ret;
unsigned long flags;
spin_lock_irqsave(&whc->lock, flags);
ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
if (ret < 0)
goto out;
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
if (std->urb == urb)
qset_free_std(whc, std);
else
std->qtd = NULL; /* so this std is re-added when the qset is */
}
asl_qset_remove(whc, qset);
wurb->status = status;
wurb->is_async = true;
queue_work(whc->workqueue, &wurb->dequeue_work);
out:
spin_unlock_irqrestore(&whc->lock, flags);
return ret;
}
/**
* asl_qset_delete - delete a qset from the ASL
*/
void asl_qset_delete(struct whc *whc, struct whc_qset *qset)
{
qset->remove = 1;
queue_work(whc->workqueue, &whc->async_work);
qset_delete(whc, qset);
}
/**
* asl_init - initialize the asynchronous schedule list
*
* A dummy qset with no qTDs is added to the ASL to simplify removing
* qsets (no need to stop the ASL when the last qset is removed).
*/
int asl_init(struct whc *whc)
{
struct whc_qset *qset;
qset = qset_alloc(whc, GFP_KERNEL);
if (qset == NULL)
return -ENOMEM;
asl_qset_insert_begin(whc, qset);
asl_qset_insert(whc, qset);
return 0;
}
/**
* asl_clean_up - free ASL resources
*
* The ASL is stopped and empty except for the dummy qset.
*/
void asl_clean_up(struct whc *whc)
{
struct whc_qset *qset;
if (!list_empty(&whc->async_list)) {
qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
list_del(&qset->list_node);
qset_free(whc, qset);
}
}
/*
* Wireless Host Controller (WHC) driver.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/uwb/umc.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
/*
* One time initialization.
*
* Nothing to do here.
*/
static int whc_reset(struct usb_hcd *usb_hcd)
{
return 0;
}
/*
* Start the wireless host controller.
*
* Start device notification.
*
* Put hc into run state, set DNTS parameters.
*/
static int whc_start(struct usb_hcd *usb_hcd)
{
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
struct whc *whc = wusbhc_to_whc(wusbhc);
u8 bcid;
int ret;
mutex_lock(&wusbhc->mutex);
le_writel(WUSBINTR_GEN_CMD_DONE
| WUSBINTR_HOST_ERR
| WUSBINTR_ASYNC_SCHED_SYNCED
| WUSBINTR_DNTS_INT
| WUSBINTR_ERR_INT
| WUSBINTR_INT,
whc->base + WUSBINTR);
/* set cluster ID */
bcid = wusb_cluster_id_get();
ret = whc_set_cluster_id(whc, bcid);
if (ret < 0)
goto out;
wusbhc->cluster_id = bcid;
/* start HC */
whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN);
usb_hcd->uses_new_polling = 1;
usb_hcd->poll_rh = 1;
usb_hcd->state = HC_STATE_RUNNING;
out:
mutex_unlock(&wusbhc->mutex);
return ret;
}
/*
* Stop the wireless host controller.
*
* Stop device notification.
*
* Wait for pending transfer to stop? Put hc into stop state?
*/
static void whc_stop(struct usb_hcd *usb_hcd)
{
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
struct whc *whc = wusbhc_to_whc(wusbhc);
mutex_lock(&wusbhc->mutex);
wusbhc_stop(wusbhc);
/* stop HC */
le_writel(0, whc->base + WUSBINTR);
whc_write_wusbcmd(whc, WUSBCMD_RUN, 0);
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
WUSBSTS_HCHALTED, WUSBSTS_HCHALTED,
100, "HC to halt");
wusb_cluster_id_put(wusbhc->cluster_id);
mutex_unlock(&wusbhc->mutex);
}
static int whc_get_frame_number(struct usb_hcd *usb_hcd)
{
/* Frame numbers are not applicable to WUSB. */
return -ENOSYS;
}
/*
* Queue an URB to the ASL or PZL
*/
static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
gfp_t mem_flags)
{
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
struct whc *whc = wusbhc_to_whc(wusbhc);
int ret;
switch (usb_pipetype(urb->pipe)) {
case PIPE_INTERRUPT:
ret = pzl_urb_enqueue(whc, urb, mem_flags);
break;
case PIPE_ISOCHRONOUS:
dev_err(&whc->umc->dev, "isochronous transfers unsupported\n");
ret = -ENOTSUPP;
break;
case PIPE_CONTROL:
case PIPE_BULK:
default:
ret = asl_urb_enqueue(whc, urb, mem_flags);
break;
};
return ret;
}
/*
* Remove a queued URB from the ASL or PZL.
*/
static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status)
{
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
struct whc *whc = wusbhc_to_whc(wusbhc);
int ret;
switch (usb_pipetype(urb->pipe)) {
case PIPE_INTERRUPT:
ret = pzl_urb_dequeue(whc, urb, status);
break;
case PIPE_ISOCHRONOUS:
ret = -ENOTSUPP;
break;
case PIPE_CONTROL:
case PIPE_BULK:
default:
ret = asl_urb_dequeue(whc, urb, status);
break;
};
return ret;
}
/*
* Wait for all URBs to the endpoint to be completed, then delete the
* qset.
*/
static void whc_endpoint_disable(struct usb_hcd *usb_hcd,
struct usb_host_endpoint *ep)
{
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
struct whc *whc = wusbhc_to_whc(wusbhc);
struct whc_qset *qset;
qset = ep->hcpriv;
if (qset) {
ep->hcpriv = NULL;
if (usb_endpoint_xfer_bulk(&ep->desc)
|| usb_endpoint_xfer_control(&ep->desc))
asl_qset_delete(whc, qset);
else
pzl_qset_delete(whc, qset);
}
}
static struct hc_driver whc_hc_driver = {
.description = "whci-hcd",
.product_desc = "Wireless host controller",
.hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd),
.irq = whc_int_handler,
.flags = HCD_USB2,
.reset = whc_reset,
.start = whc_start,
.stop = whc_stop,
.get_frame_number = whc_get_frame_number,
.urb_enqueue = whc_urb_enqueue,
.urb_dequeue = whc_urb_dequeue,
.endpoint_disable = whc_endpoint_disable,
.hub_status_data = wusbhc_rh_status_data,
.hub_control = wusbhc_rh_control,
.bus_suspend = wusbhc_rh_suspend,
.bus_resume = wusbhc_rh_resume,
.start_port_reset = wusbhc_rh_start_port_reset,
};
static int whc_probe(struct umc_dev *umc)
{
int ret = -ENOMEM;
struct usb_hcd *usb_hcd;
struct wusbhc *wusbhc = NULL;
struct whc *whc = NULL;
struct device *dev = &umc->dev;
usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci");
if (usb_hcd == NULL) {
dev_err(dev, "unable to create hcd\n");
goto error;
}
usb_hcd->wireless = 1;
wusbhc = usb_hcd_to_wusbhc(usb_hcd);
whc = wusbhc_to_whc(wusbhc);
whc->umc = umc;
ret = whc_init(whc);
if (ret)
goto error;
wusbhc->dev = dev;
wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent);
if (!wusbhc->uwb_rc) {
ret = -ENODEV;
dev_err(dev, "cannot get radio controller\n");
goto error;
}
if (whc->n_devices > USB_MAXCHILDREN) {
dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n",
whc->n_devices);
wusbhc->ports_max = USB_MAXCHILDREN;
} else
wusbhc->ports_max = whc->n_devices;
wusbhc->mmcies_max = whc->n_mmc_ies;
wusbhc->start = whc_wusbhc_start;
wusbhc->stop = whc_wusbhc_stop;
wusbhc->mmcie_add = whc_mmcie_add;
wusbhc->mmcie_rm = whc_mmcie_rm;
wusbhc->dev_info_set = whc_dev_info_set;
wusbhc->bwa_set = whc_bwa_set;
wusbhc->set_num_dnts = whc_set_num_dnts;
wusbhc->set_ptk = whc_set_ptk;
wusbhc->set_gtk = whc_set_gtk;
ret = wusbhc_create(wusbhc);
if (ret)
goto error_wusbhc_create;
ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED);
if (ret) {
dev_err(dev, "cannot add HCD: %d\n", ret);
goto error_usb_add_hcd;
}
ret = wusbhc_b_create(wusbhc);
if (ret) {
dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret);
goto error_wusbhc_b_create;
}
return 0;
error_wusbhc_b_create:
usb_remove_hcd(usb_hcd);
error_usb_add_hcd:
wusbhc_destroy(wusbhc);
error_wusbhc_create:
uwb_rc_put(wusbhc->uwb_rc);
error:
whc_clean_up(whc);
if (usb_hcd)
usb_put_hcd(usb_hcd);
return ret;
}
static void whc_remove(struct umc_dev *umc)
{
struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev);
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
struct whc *whc = wusbhc_to_whc(wusbhc);
if (usb_hcd) {
wusbhc_b_destroy(wusbhc);
usb_remove_hcd(usb_hcd);
wusbhc_destroy(wusbhc);
uwb_rc_put(wusbhc->uwb_rc);
whc_clean_up(whc);
usb_put_hcd(usb_hcd);
}
}
static struct umc_driver whci_hc_driver = {
.name = "whci-hcd",
.cap_id = UMC_CAP_ID_WHCI_WUSB_HC,
.probe = whc_probe,
.remove = whc_remove,
};
static int __init whci_hc_driver_init(void)
{
return umc_driver_register(&whci_hc_driver);
}
module_init(whci_hc_driver_init);
static void __exit whci_hc_driver_exit(void)
{
umc_driver_unregister(&whci_hc_driver);
}
module_exit(whci_hc_driver_exit);
/* PCI device ID's that we handle (so it gets loaded) */
static struct pci_device_id whci_hcd_id_table[] = {
{ PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
{ /* empty last entry */ }
};
MODULE_DEVICE_TABLE(pci, whci_hcd_id_table);
MODULE_DESCRIPTION("WHCI Wireless USB host controller driver");
MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
MODULE_LICENSE("GPL");
/*
* Wireless Host Controller (WHC) hardware access helpers.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/uwb/umc.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val)
{
unsigned long flags;
u32 cmd;
spin_lock_irqsave(&whc->lock, flags);
cmd = le_readl(whc->base + WUSBCMD);
cmd = (cmd & ~mask) | val;
le_writel(cmd, whc->base + WUSBCMD);
spin_unlock_irqrestore(&whc->lock, flags);
}
/**
* whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register
* @whc: the WHCI HC
* @cmd: command to start.
* @params: parameters for the command (the WUSBGENCMDPARAMS register value).
* @addr: pointer to any data for the command (may be NULL).
* @len: length of the data (if any).
*/
int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
{
unsigned long flags;
dma_addr_t dma_addr;
int t;
mutex_lock(&whc->mutex);
/* Wait for previous command to complete. */
t = wait_event_timeout(whc->cmd_wq,
(le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0,
WHC_GENCMD_TIMEOUT_MS);
if (t == 0) {
dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n",
le_readl(whc->base + WUSBGENCMDSTS),
le_readl(whc->base + WUSBGENCMDPARAMS));
return -ETIMEDOUT;
}
if (addr) {
memcpy(whc->gen_cmd_buf, addr, len);
dma_addr = whc->gen_cmd_buf_dma;
} else
dma_addr = 0;
/* Poke registers to start cmd. */
spin_lock_irqsave(&whc->lock, flags);
le_writel(params, whc->base + WUSBGENCMDPARAMS);
le_writeq(dma_addr, whc->base + WUSBGENADDR);
le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd,
whc->base + WUSBGENCMDSTS);
spin_unlock_irqrestore(&whc->lock, flags);
mutex_unlock(&whc->mutex);
return 0;
}
/*
* Wireless Host Controller (WHC) initialization.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/uwb/umc.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
/*
* Reset the host controller.
*/
static void whc_hw_reset(struct whc *whc)
{
le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD);
whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0,
100, "reset");
}
static void whc_hw_init_di_buf(struct whc *whc)
{
int d;
/* Disable all entries in the Device Information buffer. */
for (d = 0; d < whc->n_devices; d++)
whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE;
le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR);
}
static void whc_hw_init_dn_buf(struct whc *whc)
{
/* Clear the Device Notification buffer to ensure the V (valid)
* bits are clear. */
memset(whc->dn_buf, 0, 4096);
le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR);
}
int whc_init(struct whc *whc)
{
u32 whcsparams;
int ret, i;
resource_size_t start, len;
spin_lock_init(&whc->lock);
mutex_init(&whc->mutex);
init_waitqueue_head(&whc->cmd_wq);
init_waitqueue_head(&whc->async_list_wq);
init_waitqueue_head(&whc->periodic_list_wq);
whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev));
if (whc->workqueue == NULL) {
ret = -ENOMEM;
goto error;
}
INIT_WORK(&whc->dn_work, whc_dn_work);
INIT_WORK(&whc->async_work, scan_async_work);
INIT_LIST_HEAD(&whc->async_list);
INIT_LIST_HEAD(&whc->async_removed_list);
INIT_WORK(&whc->periodic_work, scan_periodic_work);
for (i = 0; i < 5; i++)
INIT_LIST_HEAD(&whc->periodic_list[i]);
INIT_LIST_HEAD(&whc->periodic_removed_list);
/* Map HC registers. */
start = whc->umc->resource.start;
len = whc->umc->resource.end - start + 1;
if (!request_mem_region(start, len, "whci-hc")) {
dev_err(&whc->umc->dev, "can't request HC region\n");
ret = -EBUSY;
goto error;
}
whc->base_phys = start;
whc->base = ioremap(start, len);
if (!whc->base) {
dev_err(&whc->umc->dev, "ioremap\n");
ret = -ENOMEM;
goto error;
}
whc_hw_reset(whc);
/* Read maximum number of devices, keys and MMC IEs. */
whcsparams = le_readl(whc->base + WHCSPARAMS);
whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams);
whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams);
whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams);
dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n",
whc->n_devices, whc->n_keys, whc->n_mmc_ies);
whc->qset_pool = dma_pool_create("qset", &whc->umc->dev,
sizeof(struct whc_qset), 64, 0);
if (whc->qset_pool == NULL) {
ret = -ENOMEM;
goto error;
}
ret = asl_init(whc);
if (ret < 0)
goto error;
ret = pzl_init(whc);
if (ret < 0)
goto error;
/* Allocate and initialize a buffer for generic commands, the
Device Information buffer, and the Device Notification
buffer. */
whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
&whc->gen_cmd_buf_dma, GFP_KERNEL);
if (whc->gen_cmd_buf == NULL) {
ret = -ENOMEM;
goto error;
}
whc->dn_buf = dma_alloc_coherent(&whc->umc->dev,
sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
&whc->dn_buf_dma, GFP_KERNEL);
if (!whc->dn_buf) {
ret = -ENOMEM;
goto error;
}
whc_hw_init_dn_buf(whc);
whc->di_buf = dma_alloc_coherent(&whc->umc->dev,
sizeof(struct di_buf_entry) * whc->n_devices,
&whc->di_buf_dma, GFP_KERNEL);
if (!whc->di_buf) {
ret = -ENOMEM;
goto error;
}
whc_hw_init_di_buf(whc);
return 0;
error:
whc_clean_up(whc);
return ret;
}
void whc_clean_up(struct whc *whc)
{
resource_size_t len;
if (whc->di_buf)
dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices,
whc->di_buf, whc->di_buf_dma);
if (whc->dn_buf)
dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
whc->dn_buf, whc->dn_buf_dma);
if (whc->gen_cmd_buf)
dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
whc->gen_cmd_buf, whc->gen_cmd_buf_dma);
pzl_clean_up(whc);
asl_clean_up(whc);
if (whc->qset_pool)
dma_pool_destroy(whc->qset_pool);
len = whc->umc->resource.end - whc->umc->resource.start + 1;
if (whc->base)
iounmap(whc->base);
if (whc->base_phys)
release_mem_region(whc->base_phys, len);
if (whc->workqueue)
destroy_workqueue(whc->workqueue);
}
/*
* Wireless Host Controller (WHC) interrupt handling.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/uwb/umc.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
static void transfer_done(struct whc *whc)
{
queue_work(whc->workqueue, &whc->async_work);
queue_work(whc->workqueue, &whc->periodic_work);
}
irqreturn_t whc_int_handler(struct usb_hcd *hcd)
{
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd);
struct whc *whc = wusbhc_to_whc(wusbhc);
u32 sts;
sts = le_readl(whc->base + WUSBSTS);
if (!(sts & WUSBSTS_INT_MASK))
return IRQ_NONE;
le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS);
if (sts & WUSBSTS_GEN_CMD_DONE)
wake_up(&whc->cmd_wq);
if (sts & WUSBSTS_HOST_ERR)
dev_err(&whc->umc->dev, "FIXME: host system error\n");
if (sts & WUSBSTS_ASYNC_SCHED_SYNCED)
wake_up(&whc->async_list_wq);
if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED)
wake_up(&whc->periodic_list_wq);
if (sts & WUSBSTS_DNTS_INT)
queue_work(whc->workqueue, &whc->dn_work);
/*
* A transfer completed (see [WHCI] section 4.7.1.2 for when
* this occurs).
*/
if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT))
transfer_done(whc);
return IRQ_HANDLED;
}
static int process_dn_buf(struct whc *whc)
{
struct wusbhc *wusbhc = &whc->wusbhc;
struct dn_buf_entry *dn;
int processed = 0;
for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) {
if (dn->status & WHC_DN_STATUS_VALID) {
wusbhc_handle_dn(wusbhc, dn->src_addr,
(struct wusb_dn_hdr *)dn->dn_data,
dn->msg_size);
dn->status &= ~WHC_DN_STATUS_VALID;
processed++;
}
}
return processed;
}
void whc_dn_work(struct work_struct *work)
{
struct whc *whc = container_of(work, struct whc, dn_work);
int processed;
do {
processed = process_dn_buf(whc);
} while (processed);
}
/*
* Wireless Host Controller (WHC) periodic schedule management.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/uwb/umc.h>
#include <linux/usb.h>
#define D_LOCAL 0
#include <linux/uwb/debug.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
#if D_LOCAL >= 4
static void dump_pzl(struct whc *whc, const char *tag)
{
struct device *dev = &whc->umc->dev;
struct whc_qset *qset;
int period = 0;
d_printf(4, dev, "PZL %s\n", tag);
for (period = 0; period < 5; period++) {
d_printf(4, dev, "Period %d\n", period);
list_for_each_entry(qset, &whc->periodic_list[period], list_node) {
dump_qset(qset, dev);
}
}
}
#else
static inline void dump_pzl(struct whc *whc, const char *tag)
{
}
#endif
static void update_pzl_pointers(struct whc *whc, int period, u64 addr)
{
switch (period) {
case 0:
whc_qset_set_link_ptr(&whc->pz_list[0], addr);
whc_qset_set_link_ptr(&whc->pz_list[2], addr);
whc_qset_set_link_ptr(&whc->pz_list[4], addr);
whc_qset_set_link_ptr(&whc->pz_list[6], addr);
whc_qset_set_link_ptr(&whc->pz_list[8], addr);
whc_qset_set_link_ptr(&whc->pz_list[10], addr);
whc_qset_set_link_ptr(&whc->pz_list[12], addr);
whc_qset_set_link_ptr(&whc->pz_list[14], addr);
break;
case 1:
whc_qset_set_link_ptr(&whc->pz_list[1], addr);
whc_qset_set_link_ptr(&whc->pz_list[5], addr);
whc_qset_set_link_ptr(&whc->pz_list[9], addr);
whc_qset_set_link_ptr(&whc->pz_list[13], addr);
break;
case 2:
whc_qset_set_link_ptr(&whc->pz_list[3], addr);
whc_qset_set_link_ptr(&whc->pz_list[11], addr);
break;
case 3:
whc_qset_set_link_ptr(&whc->pz_list[7], addr);
break;
case 4:
whc_qset_set_link_ptr(&whc->pz_list[15], addr);
break;
}
}
/*
* Return the 'period' to use for this qset. The minimum interval for
* the endpoint is used so whatever urbs are submitted the device is
* polled often enough.
*/
static int qset_get_period(struct whc *whc, struct whc_qset *qset)
{
uint8_t bInterval = qset->ep->desc.bInterval;
if (bInterval < 6)
bInterval = 6;
if (bInterval > 10)
bInterval = 10;
return bInterval - 6;
}
static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset)
{
int period;
period = qset_get_period(whc, qset);
qset_clear(whc, qset);
list_move(&qset->list_node, &whc->periodic_list[period]);
qset->in_sw_list = true;
}
static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset)
{
list_move(&qset->list_node, &whc->periodic_removed_list);
qset->in_hw_list = false;
qset->in_sw_list = false;
}
/**
* pzl_process_qset - process any recently inactivated or halted qTDs
* in a qset.
*
* After inactive qTDs are removed, new qTDs can be added if the
* urb queue still contains URBs.
*
* Returns the schedule updates required.
*/
static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
{
enum whc_update update = 0;
uint32_t status = 0;
while (qset->ntds) {
struct whc_qtd *td;
int t;
t = qset->td_start;
td = &qset->qtd[qset->td_start];
status = le32_to_cpu(td->status);
/*
* Nothing to do with a still active qTD.
*/
if (status & QTD_STS_ACTIVE)
break;
if (status & QTD_STS_HALTED) {
/* Ug, an error. */
process_halted_qtd(whc, qset, td);
goto done;
}
/* Mmm, a completed qTD. */
process_inactive_qtd(whc, qset, td);
}
update |= qset_add_qtds(whc, qset);
done:
/*
* If there are no qTDs in this qset, remove it from the PZL.
*/
if (qset->remove && qset->ntds == 0) {
pzl_qset_remove(whc, qset);
update |= WHC_UPDATE_REMOVED;
}
return update;
}
/**
* pzl_start - start the periodic schedule
* @whc: the WHCI host controller
*
* The PZL must be valid (e.g., all entries in the list should have
* the T bit set).
*/
void pzl_start(struct whc *whc)
{
le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN);
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED,
1000, "start PZL");
}
/**
* pzl_stop - stop the periodic schedule
* @whc: the WHCI host controller
*/
void pzl_stop(struct whc *whc)
{
whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0);
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
WUSBSTS_PERIODIC_SCHED, 0,
1000, "stop PZL");
}
void pzl_update(struct whc *whc, uint32_t wusbcmd)
{
whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
wait_event(whc->periodic_list_wq,
(le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0);
}
static void update_pzl_hw_view(struct whc *whc)
{
struct whc_qset *qset, *t;
int period;
u64 tmp_qh = 0;
for (period = 0; period < 5; period++) {
list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
whc_qset_set_link_ptr(&qset->qh.link, tmp_qh);
tmp_qh = qset->qset_dma;
qset->in_hw_list = true;
}
update_pzl_pointers(whc, period, tmp_qh);
}
}
/**
* scan_periodic_work - scan the PZL for qsets to process.
*
* Process each qset in the PZL in turn and then signal the WHC that
* the PZL has been updated.
*
* Then start, stop or update the periodic schedule as required.
*/
void scan_periodic_work(struct work_struct *work)
{
struct whc *whc = container_of(work, struct whc, periodic_work);
struct whc_qset *qset, *t;
enum whc_update update = 0;
int period;
spin_lock_irq(&whc->lock);
dump_pzl(whc, "before processing");
for (period = 4; period >= 0; period--) {
list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
if (!qset->in_hw_list)
update |= WHC_UPDATE_ADDED;
update |= pzl_process_qset(whc, qset);
}
}
if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED))
update_pzl_hw_view(whc);
dump_pzl(whc, "after processing");
spin_unlock_irq(&whc->lock);
if (update) {
uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB;
if (update & WHC_UPDATE_REMOVED)
wusbcmd |= WUSBCMD_PERIODIC_QSET_RM;
pzl_update(whc, wusbcmd);
}
/*
* Now that the PZL is updated, complete the removal of any
* removed qsets.
*/
spin_lock(&whc->lock);
list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
qset_remove_complete(whc, qset);
}
spin_unlock(&whc->lock);
}
/**
* pzl_urb_enqueue - queue an URB onto the periodic list (PZL)
* @whc: the WHCI host controller
* @urb: the URB to enqueue
* @mem_flags: flags for any memory allocations
*
* The qset for the endpoint is obtained and the urb queued on to it.
*
* Work is scheduled to update the hardware's view of the PZL.
*/
int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
{
struct whc_qset *qset;
int err;
unsigned long flags;
spin_lock_irqsave(&whc->lock, flags);
qset = get_qset(whc, urb, GFP_ATOMIC);
if (qset == NULL)
err = -ENOMEM;
else
err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
if (!err) {
usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
if (!qset->in_sw_list)
qset_insert_in_sw_list(whc, qset);
}
spin_unlock_irqrestore(&whc->lock, flags);
if (!err)
queue_work(whc->workqueue, &whc->periodic_work);
return 0;
}
/**
* pzl_urb_dequeue - remove an URB (qset) from the periodic list
* @whc: the WHCI host controller
* @urb: the URB to dequeue
* @status: the current status of the URB
*
* URBs that do yet have qTDs can simply be removed from the software
* queue, otherwise the qset must be removed so the qTDs can be safely
* removed.
*/
int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
{
struct whc_urb *wurb = urb->hcpriv;
struct whc_qset *qset = wurb->qset;
struct whc_std *std, *t;
int ret;
unsigned long flags;
spin_lock_irqsave(&whc->lock, flags);
ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
if (ret < 0)
goto out;
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
if (std->urb == urb)
qset_free_std(whc, std);
else
std->qtd = NULL; /* so this std is re-added when the qset is */
}
pzl_qset_remove(whc, qset);
wurb->status = status;
wurb->is_async = false;
queue_work(whc->workqueue, &wurb->dequeue_work);
out:
spin_unlock_irqrestore(&whc->lock, flags);
return ret;
}
/**
* pzl_qset_delete - delete a qset from the PZL
*/
void pzl_qset_delete(struct whc *whc, struct whc_qset *qset)
{
qset->remove = 1;
queue_work(whc->workqueue, &whc->periodic_work);
qset_delete(whc, qset);
}
/**
* pzl_init - initialize the periodic zone list
* @whc: the WHCI host controller
*/
int pzl_init(struct whc *whc)
{
int i;
whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16,
&whc->pz_list_dma, GFP_KERNEL);
if (whc->pz_list == NULL)
return -ENOMEM;
/* Set T bit on all elements in PZL. */
for (i = 0; i < 16; i++)
whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
return 0;
}
/**
* pzl_clean_up - free PZL resources
* @whc: the WHCI host controller
*
* The PZL is stopped and empty.
*/
void pzl_clean_up(struct whc *whc)
{
if (whc->pz_list)
dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list,
whc->pz_list_dma);
}
This diff is collapsed.
/*
* Wireless Host Controller (WHC) private header.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef __WHCD_H
#define __WHCD_H
#include <linux/uwb/whci.h>
#include <linux/workqueue.h>
#include "whci-hc.h"
/* Generic command timeout. */
#define WHC_GENCMD_TIMEOUT_MS 100
struct whc {
struct wusbhc wusbhc;
struct umc_dev *umc;
resource_size_t base_phys;
void __iomem *base;
int irq;
u8 n_devices;
u8 n_keys;
u8 n_mmc_ies;
u64 *pz_list;
struct dn_buf_entry *dn_buf;
struct di_buf_entry *di_buf;
dma_addr_t pz_list_dma;
dma_addr_t dn_buf_dma;
dma_addr_t di_buf_dma;
spinlock_t lock;
struct mutex mutex;
void * gen_cmd_buf;
dma_addr_t gen_cmd_buf_dma;
wait_queue_head_t cmd_wq;
struct workqueue_struct *workqueue;
struct work_struct dn_work;
struct dma_pool *qset_pool;
struct list_head async_list;
struct list_head async_removed_list;
wait_queue_head_t async_list_wq;
struct work_struct async_work;
struct list_head periodic_list[5];
struct list_head periodic_removed_list;
wait_queue_head_t periodic_list_wq;
struct work_struct periodic_work;
};
#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc))
/**
* struct whc_std - a software TD.
* @urb: the URB this sTD is for.
* @offset: start of the URB's data for this TD.
* @len: the length of data in the associated TD.
* @ntds_remaining: number of TDs (starting from this one) in this transfer.
*
* Queued URBs may require more TDs than are available in a qset so we
* use a list of these "software TDs" (sTDs) to hold per-TD data.
*/
struct whc_std {
struct urb *urb;
size_t len;
int ntds_remaining;
struct whc_qtd *qtd;
struct list_head list_node;
int num_pointers;
dma_addr_t dma_addr;
struct whc_page_list_entry *pl_virt;
};
/**
* struct whc_urb - per URB host controller structure.
* @urb: the URB this struct is for.
* @qset: the qset associated to the URB.
* @dequeue_work: the work to remove the URB when dequeued.
* @is_async: the URB belongs to async sheduler or not.
* @status: the status to be returned when calling wusbhc_giveback_urb.
*/
struct whc_urb {
struct urb *urb;
struct whc_qset *qset;
struct work_struct dequeue_work;
bool is_async;
int status;
};
/**
* whc_std_last - is this sTD the URB's last?
* @std: the sTD to check.
*/
static inline bool whc_std_last(struct whc_std *std)
{
return std->ntds_remaining <= 1;
}
enum whc_update {
WHC_UPDATE_ADDED = 0x01,
WHC_UPDATE_REMOVED = 0x02,
WHC_UPDATE_UPDATED = 0x04,
};
/* init.c */
int whc_init(struct whc *whc);
void whc_clean_up(struct whc *whc);
/* hw.c */
void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val);
int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len);
/* wusb.c */
int whc_wusbhc_start(struct wusbhc *wusbhc);
void whc_wusbhc_stop(struct wusbhc *wusbhc);
int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
u8 handle, struct wuie_hdr *wuie);
int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle);
int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm);
int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev);
int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots);
int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
const void *ptk, size_t key_size);
int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
const void *gtk, size_t key_size);
int whc_set_cluster_id(struct whc *whc, u8 bcid);
/* int.c */
irqreturn_t whc_int_handler(struct usb_hcd *hcd);
void whc_dn_work(struct work_struct *work);
/* asl.c */
void asl_start(struct whc *whc);
void asl_stop(struct whc *whc);
int asl_init(struct whc *whc);
void asl_clean_up(struct whc *whc);
int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
void asl_qset_delete(struct whc *whc, struct whc_qset *qset);
void scan_async_work(struct work_struct *work);
/* pzl.c */
int pzl_init(struct whc *whc);
void pzl_clean_up(struct whc *whc);
void pzl_start(struct whc *whc);
void pzl_stop(struct whc *whc);
int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
void pzl_qset_delete(struct whc *whc, struct whc_qset *qset);
void scan_periodic_work(struct work_struct *work);
/* qset.c */
struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags);
void qset_free(struct whc *whc, struct whc_qset *qset);
struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags);
void qset_delete(struct whc *whc, struct whc_qset *qset);
void qset_clear(struct whc *whc, struct whc_qset *qset);
int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
gfp_t mem_flags);
void qset_free_std(struct whc *whc, struct whc_std *std);
void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
struct urb *urb, int status);
void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
struct whc_qtd *qtd);
void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
struct whc_qtd *qtd);
enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset);
void qset_remove_complete(struct whc *whc, struct whc_qset *qset);
void dump_qset(struct whc_qset *qset, struct device *dev);
void pzl_update(struct whc *whc, uint32_t wusbcmd);
void asl_update(struct whc *whc, uint32_t wusbcmd);
#endif /* #ifndef __WHCD_H */
This diff is collapsed.
/*
* Wireless Host Controller (WHC) WUSB operations.
*
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/uwb/umc.h>
#define D_LOCAL 1
#include <linux/uwb/debug.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
#if D_LOCAL >= 1
static void dump_di(struct whc *whc, int idx)
{
struct di_buf_entry *di = &whc->di_buf[idx];
struct device *dev = &whc->umc->dev;
char buf[128];
bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS);
d_printf(1, dev, "DI[%d]\n", idx);
d_printf(1, dev, " availability: %s\n", buf);
d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n",
(di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ',
(di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ',
(di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8,
(di->addr_sec_info & WHC_DI_DEV_ADDR_MASK));
}
#else
static inline void dump_di(struct whc *whc, int idx)
{
}
#endif
static int whc_update_di(struct whc *whc, int idx)
{
int offset = idx / 32;
u32 bit = 1 << (idx % 32);
dump_di(whc, idx);
le_writel(bit, whc->base + WUSBDIBUPDATED + offset);
return whci_wait_for(&whc->umc->dev,
whc->base + WUSBDIBUPDATED + offset, bit, 0,
100, "DI update");
}
/*
* WHCI starts and stops MMCs based on there being a valid GTK so
* these need only start/stop the asynchronous and periodic schedules.
*/
int whc_wusbhc_start(struct wusbhc *wusbhc)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
asl_start(whc);
pzl_start(whc);
return 0;
}
void whc_wusbhc_stop(struct wusbhc *wusbhc)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
pzl_stop(whc);
asl_stop(whc);
}
int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
u8 handle, struct wuie_hdr *wuie)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
u32 params;
params = (interval << 24)
| (repeat_cnt << 16)
| (wuie->bLength << 8)
| handle;
return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength);
}
int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
u32 params;
params = handle;
return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0);
}
int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
if (stream_index >= 0)
whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index));
return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm));
}
int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
int idx = wusb_dev->port_idx;
struct di_buf_entry *di = &whc->di_buf[idx];
int ret;
mutex_lock(&whc->mutex);
uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability);
di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK);
di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr);
ret = whc_update_di(whc, idx);
mutex_unlock(&whc->mutex);
return ret;
}
/*
* Set the number of Device Notification Time Slots (DNTS) and enable
* device notifications.
*/
int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
u32 dntsctrl;
dntsctrl = WUSBDNTSCTRL_ACTIVE
| WUSBDNTSCTRL_INTERVAL(interval)
| WUSBDNTSCTRL_SLOTS(slots);
le_writel(dntsctrl, whc->base + WUSBDNTSCTRL);
return 0;
}
static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid,
const void *key, size_t key_size, bool is_gtk)
{
uint32_t setkeycmd;
uint32_t seckey[4];
int i;
int ret;
memcpy(seckey, key, key_size);
setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index);
if (is_gtk)
setkeycmd |= WUSBSETSECKEYCMD_GTK;
le_writel(tkid, whc->base + WUSBTKID);
for (i = 0; i < 4; i++)
le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i);
le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD);
ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD,
WUSBSETSECKEYCMD_SET, 0, 100, "set key");
return ret;
}
/**
* whc_set_ptk - set the PTK to use for a device.
*
* The index into the key table for this PTK is the same as the
* device's port index.
*/
int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
const void *ptk, size_t key_size)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
struct di_buf_entry *di = &whc->di_buf[port_idx];
int ret;
mutex_lock(&whc->mutex);
if (ptk) {
ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false);
if (ret)
goto out;
di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK;
di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx);
} else
di->addr_sec_info &= ~WHC_DI_SECURE;
ret = whc_update_di(whc, port_idx);
out:
mutex_unlock(&whc->mutex);
return ret;
}
/**
* whc_set_gtk - set the GTK for subsequent broadcast packets
*
* The GTK is stored in the last entry in the key table (the previous
* N_DEVICES entries are for the per-device PTKs).
*/
int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
const void *gtk, size_t key_size)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
int ret;
mutex_lock(&whc->mutex);
ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true);
mutex_unlock(&whc->mutex);
return ret;
}
int whc_set_cluster_id(struct whc *whc, u8 bcid)
{
whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid));
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment