/** * whcrc_stop_rc - stop a WHCI radio controller * @whcrc: the radio controller to stop * * Disable interrupts and cancel any pending event processing work * before clearing the Run/Stop bit. */ static void whcrc_stop_rc(struct uwb_rc *rc) { struct whcrc *whcrc = rc->priv; struct umc_dev *umc_dev = whcrc->umc_dev; le_writel(0, whcrc->rc_base + URCINTR); cancel_work_sync(&whcrc->event_work); le_writel(0, whcrc->rc_base + URCCMD); whci_wait_for(&umc_dev->dev, whcrc->rc_base + URCSTS, URCSTS_HALTED, URCSTS_HALTED, 100, "radio controller stop"); }
/* * 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; set_bit(HCD_FLAG_POLL_RH, &usb_hcd->flags); usb_hcd->state = HC_STATE_RUNNING; out: mutex_unlock(&wusbhc->mutex); return ret; }
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); if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT)) transfer_done(whc); return IRQ_HANDLED; }
static int whc_update_di(struct whc *whc, int idx) { int offset = idx / 32; u32 bit = 1 << (idx % 32); le_writel(bit, whc->base + WUSBDIBUPDATED + offset); return whci_wait_for(&whc->umc->dev, whc->base + WUSBDIBUPDATED + offset, bit, 0, 100, "DI update"); }
/** * whcrc_start_rc - start a WHCI radio controller * @whcrc: the radio controller to start * * Reset the UMC device, start the radio controller, enable events and * finally enable interrupts. */ static int whcrc_start_rc(struct uwb_rc *rc) { struct whcrc *whcrc = rc->priv; struct device *dev = &whcrc->umc_dev->dev; /* Reset the thing */ le_writel(URCCMD_RESET, whcrc->rc_base + URCCMD); if (whci_wait_for(dev, whcrc->rc_base + URCCMD, URCCMD_RESET, 0, 5000, "hardware reset") < 0) return -EBUSY; /* Set the event buffer, start the controller (enable IRQs later) */ le_writel(0, whcrc->rc_base + URCINTR); le_writel(URCCMD_RS, whcrc->rc_base + URCCMD); if (whci_wait_for(dev, whcrc->rc_base + URCSTS, URCSTS_HALTED, 0, 5000, "radio controller start") < 0) return -ETIMEDOUT; whcrc_enable_events(whcrc); le_writel(URCINTR_EN_ALL, whcrc->rc_base + URCINTR); return 0; }
/** * Reset event reception mechanism and tell hw we are ready to get more * * We have read all the events in the event buffer, so we are ready to * reset it to the beginning. * * This is only called during initialization or after an event buffer * has been retired. This means we can be sure that event processing * is disabled and it's safe to update the URCEVTADDR register. * * There's no need to wait for the event processing to start as the * URC will not clear URCCMD_ACTIVE until (internal) event buffer * space is available. */ static void whcrc_enable_events(struct whcrc *whcrc) { u32 urccmd; le_writeq(whcrc->evt_dma_buf, whcrc->rc_base + URCEVTADDR); spin_lock(&whcrc->irq_lock); urccmd = le_readl(whcrc->rc_base + URCCMD) & ~URCCMD_ACTIVE; le_writel(urccmd | URCCMD_EARV, whcrc->rc_base + URCCMD); spin_unlock(&whcrc->irq_lock); }
/* * 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; }
/* * 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); /* 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); }
/** * Execute an UWB RC command on WHCI/RC * * @rc: Instance of a Radio Controller that is a whcrc * @cmd: Buffer containing the RCCB and payload to execute * @cmd_size: Size of the command buffer. * * We copy the command into whcrc->cmd_buf (as it is pretty and * aligned`and physically contiguous) and then press the right keys in * the controller's URCCMD register to get it to read it. We might * have to wait for the cmd_sem to be open to us. * * NOTE: rc's mutex has to be locked */ static int whcrc_cmd(struct uwb_rc *uwb_rc, const struct uwb_rccb *cmd, size_t cmd_size) { int result = 0; struct whcrc *whcrc = uwb_rc->priv; struct device *dev = &whcrc->umc_dev->dev; u32 urccmd; if (cmd_size >= 4096) return -EINVAL; /* * If the URC is halted, then the hardware has reset itself. * Attempt to recover by restarting the device and then return * an error as it's likely that the current command isn't * valid for a newly started RC. */ if (le_readl(whcrc->rc_base + URCSTS) & URCSTS_HALTED) { dev_err(dev, "requesting reset of halted radio controller\n"); uwb_rc_reset_all(uwb_rc); return -EIO; } result = wait_event_timeout(whcrc->cmd_wq, !(le_readl(whcrc->rc_base + URCCMD) & URCCMD_ACTIVE), HZ/2); if (result == 0) { dev_err(dev, "device is not ready to execute commands\n"); return -ETIMEDOUT; } memmove(whcrc->cmd_buf, cmd, cmd_size); le_writeq(whcrc->cmd_dma_buf, whcrc->rc_base + URCCMDADDR); spin_lock(&whcrc->irq_lock); urccmd = le_readl(whcrc->rc_base + URCCMD); urccmd &= ~(URCCMD_EARV | URCCMD_SIZE_MASK); le_writel(urccmd | URCCMD_ACTIVE | URCCMD_IWR | cmd_size, whcrc->rc_base + URCCMD); spin_unlock(&whcrc->irq_lock); return 0; }
/** * Catch interrupts? * * We ack inmediately (and expect the hw to do the right thing and * raise another IRQ if things have changed :) */ static irqreturn_t whcrc_irq_cb(int irq, void *_whcrc) { struct whcrc *whcrc = _whcrc; struct device *dev = &whcrc->umc_dev->dev; u32 urcsts; urcsts = le_readl(whcrc->rc_base + URCSTS); if (!(urcsts & URCSTS_INT_MASK)) return IRQ_NONE; le_writel(urcsts & URCSTS_INT_MASK, whcrc->rc_base + URCSTS); if (urcsts & URCSTS_HSE) { dev_err(dev, "host system error -- hardware halted\n"); /* FIXME: do something sensible here */ goto out; } if (urcsts & URCSTS_ER) schedule_work(&whcrc->event_work); if (urcsts & URCSTS_RCI) wake_up_all(&whcrc->cmd_wq); out: return IRQ_HANDLED; }
/* * 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"); }