/*
DesignWare HS OTG Controller host-mode interrupt handling
*/
/*
* This file contains the interrupt handlers for Host mode
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/usb/ch11.h>
#include "core.h"
#include "hcd.h"
/* This function is for debug only */
static void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg)
{
#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS
u16 curr_frame_number = hsotg->frame_number;
if (hsotg->frame_num_idx < FRAME_NUM_ARRAY_SIZE) {
if (((hsotg->last_frame_num + 1) & HFNUM_MAX_FRNUM) !=
curr_frame_number) {
hsotg->frame_num_array[hsotg->frame_num_idx] =
curr_frame_number;
hsotg->last_frame_num_array[hsotg->frame_num_idx] =
hsotg->last_frame_num;
hsotg->frame_num_idx++;
}
} else if (!hsotg->dumped_frame_num_array) {
int i;
dev_info(hsotg->dev, "Frame Last Frame\n");
dev_info(hsotg->dev, "----- ----------\n");
for (i = 0; i < FRAME_NUM_ARRAY_SIZE; i++) {
dev_info(hsotg->dev, "0x%04x 0x%04x\n",
hsotg->frame_num_array[i],
hsotg->last_frame_num_array[i]);
}
hsotg->dumped_frame_num_array = 1;
}
hsotg->last_frame_num = curr_frame_number;
#endif
}
static void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg,
struct dwc2_host_chan *chan,
struct dwc2_qtd *qtd)
{
struct urb *usb_urb;
if (!chan->qh)
return;
if (chan->qh->dev_speed == USB_SPEED_HIGH)
return;
if (!qtd->urb)
return;
usb_urb = qtd->urb->priv;
if (!usb_urb || !usb_urb->dev || !usb_urb->dev->tt)
return;
if (qtd->urb->status != -EPIPE && qtd->urb->status != -EREMOTEIO) {
chan->qh->tt_buffer_dirty = 1;
if (usb_hub_clear_tt_buffer(usb_urb))
/* Clear failed; let's hope things work anyway */
chan->qh->tt_buffer_dirty = 0;
}
}
/*
* Handles the start-of-frame interrupt in host mode. Non-periodic
* transactions may be queued to the DWC_otg controller for the current
* (micro)frame. Periodic transactions may be queued to the controller
* for the next (micro)frame.
*/
static void dwc2_sof_intr(struct dwc2_hsotg *hsotg)
{
struct list_head *qh_entry;
struct dwc2_qh *qh;
enum dwc2_transaction_type tr_type;
#ifdef DEBUG_SOF
dev_vdbg(hsotg->dev, "--Start of Frame Interrupt--\n");
#endif
hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg);
dwc2_track_missed_sofs(hsotg);
/* Determine whether any periodic QHs should be executed */
qh_entry = hsotg->periodic_sched_inactive.next;
while (qh_entry != &hsotg->periodic_sched_inactive) {
qh = list_entry(qh_entry, struct dwc2_qh, qh_list_entry);
qh_entry = qh_entry->next;
if (dwc2_frame_num_le(qh->sched_frame, hsotg->frame_number))
/*
* Move QH to the ready list to be executed next
* (micro)frame
*/
list_move(&qh->qh_list_entry,
&hsotg->periodic_sched_ready);
}
tr_type = dwc2_hcd_select_transactions(hsotg);
if (tr_type != DWC2_TRANSACTION_NONE)
dwc2_hcd_queue_transactions(hsotg, tr_type);
/* Clear interrupt */
writel(GINTSTS_SOF, hsotg->regs + GINTSTS);
}
/*
* Handles the Rx FIFO Level Interrupt, which indicates that there is
* at least one packet in the Rx FIFO. The packets are moved from the FIFO to
* memory if the DWC_otg controller is operating in Slave mode.
*/
static void dwc2_rx_fifo_level_intr(struct dwc2_hsotg *hsotg)
{
u32 grxsts, chnum, bcnt, dpid, pktsts;
struct dwc2_host_chan *chan;
if (dbg_perio())
dev_vdbg(hsotg->dev, "--RxFIFO Level Interrupt--\n");
grxsts = readl(hsotg->regs + GRXSTSP);
chnum = (grxsts & GRXSTS_HCHNUM_MASK) >> GRXSTS_HCHNUM_SHIFT;
chan = hsotg->hc_ptr_array[chnum];
if (!chan) {
dev_err(hsotg->dev, "Unable to get corresponding channel\n");
return;
}
bcnt = (grxsts & GRXSTS_BYTECNT_MASK) >> GRXSTS_BYTECNT_SHIFT;
dpid = (grxsts & GRXSTS_DPID_MASK) >> GRXSTS_DPID_SHIFT;
pktsts = (grxsts & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT;
/* Packet Status */
if (dbg_perio()) {
dev_vdbg(hsotg->dev, " Ch num = %d\n", chnum);
dev_vdbg(hsotg->dev, " Count = %d\n", bcnt);
dev_vdbg(hsotg->dev, " DPID = %d, chan.dpid = %d\n", dpid,
chan->data_pid_start);
dev_vdbg(hsotg->dev, " PStatus = %d\n", pktsts);
}
switch (pktsts) {
case GRXSTS_PKTSTS_HCHIN:
/* Read the data into the host buffer */
if (bcnt > 0) {
dwc2_read_packet(hsotg, chan->xfer_buf, bcnt);
/* Update the HC fields for the next packet received */
chan->xfer_count += bcnt;
chan->xfer_buf += bcnt;
}
break;
case GRXSTS_PKTSTS_HCHIN_XFER_COMP:
case GRXSTS_PKTSTS_DATATOGGLEERR:
case GRXSTS_PKTSTS_HCHHALTED:
/* Handled in interrupt, just ignore data */
break;
default:
dev_err(hsotg->dev,
"RxFIFO Level Interrupt: Unknown status %d\n", pktsts);
break;
}
}
/*
* This interrupt occurs when the non-periodic Tx FIFO is half-empty. More
* data packets may be written to the FIFO for OUT transfers. More requests
* may be written to the non-periodic request queue for IN transfers. This
* interrupt is enabled only in Slave mode.
*/
static void dwc2_np_tx_fifo_empty_intr(struct dwc2_hsotg *hsotg)
{
dev_vdbg(hsotg->dev, "--Non-Periodic TxFIFO Empty Interrupt--\n");
dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_NON_PERIODIC);
}
/*
* This interrupt occurs when the periodic Tx FIFO is half-empty. More data
* packets may be written to the FIFO for OUT transfers. More requests may be
* written to the periodic request queue for IN transfers. This interrupt is
* enabled only in Slave mode.
*/
static void dwc2_perio_tx_fifo_empty_intr(struct dwc2_hsotg *hsotg)
{
if (dbg_perio())
dev_vdbg(hsotg->dev, "--Periodic TxFIFO Empty Interrupt--\n");
dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_PERIODIC);
}
static void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0,
u32 *hprt0_modify)
{
struct dwc2_core_params *params = hsotg->core_params;
int do_reset = 0;
u32 usbcfg;
u32 prtspd;
u32 hcfg;
u32 fslspclksel;
u32 hfir;
dev_vdbg(hsotg->dev, "%s(%p)\n", __func__, hsotg);
/* Every time when port enables calculate HFIR.FrInterval */
hfir = readl(hsotg->regs + HFIR);
hfir &= ~HFIR_FRINT_MASK;
hfir |= dwc2_calc_frame_interval(hsotg) << HFIR_FRINT_SHIFT &
HFIR_FRINT_MASK;
writel(hfir, hsotg->regs + HFIR);
/* Check if we need to adjust the PHY clock speed for low power */
if (!params->host_support_fs_ls_low_power) {
/* Port has been enabled, set the reset change flag */
hsotg->flags.b.port_reset_change = 1;
return;
}
usbcfg = readl(hsotg->regs + GUSBCFG);
prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT;
if (prtspd == HPRT0_SPD_LOW_SPEED || prtspd == HPRT0_SPD_FULL_SPEED) {
/* Low power */
if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL)) {
/* Set PHY low power clock select for FS/LS devices */
usbcfg |= GUSBCFG_PHY_LP_CLK_SEL;
writel(usbcfg, hsotg->regs + GUSBCFG);
do_reset = 1;
}
hcfg = readl(hsotg->regs + HCFG);
fslspclksel = (hcfg & HCFG_FSLSPCLKSEL_MASK) >>
HCFG_FSLSPCLKSEL_SHIFT;
if (prtspd == HPRT0_SPD_LOW_SPEED &&
params->host_ls_low_power_phy_clk ==
DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) {
/* 6 MHZ */
dev_vdbg(hsotg->dev,
"FS_PHY programming HCFG to 6 MHz\n");
if (fslspclksel != HCFG_FSLSPCLKSEL_6_MHZ) {
fslspclksel = HCFG_FSLSPCLKSEL_6_MHZ;
hcfg &= ~HCFG_FSLSPCLKSEL_MASK;
hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT;
writel(hcfg, hsotg->regs + HCFG);
do_reset = 1;
}
} else {
/* 48 MHZ */
dev_vdbg(hsotg->dev,
"FS_PHY programming HCFG to 48 MHz\n");
if (fslspclksel != HCFG_FSLSPCLKSEL_48_MHZ) {
fslspclksel = HCFG_FSLSPCLKSEL_48_MHZ;
hcfg &= ~HCFG_FSLSPCLKSEL_MASK;
hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT;
writel(hcfg, hsotg->regs + HCFG);
do_reset = 1;
}
}
} else {
/* Not low power */
if (usbcfg & GUSBCFG_PHY_LP_CLK_SEL) {
usbcfg