Commit 0673cb26 authored by Gordon Hollingworth's avatar Gordon Hollingworth Committed by popcornmix
Browse files

USB fix using a FIQ to implement split transactions

This commit adds a FIQ implementaion that schedules
the split transactions using a FIQ so we don't get
held off by the interrupt latency of Linux
parent 118e5867
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -580,7 +580,12 @@ void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value)

void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask)
{
	unsigned long flags;

	local_irq_save(flags);
	local_fiq_disable();
	writel((readl(reg) & ~clear_mask) | set_mask, reg);
	local_irq_restore(flags);
}

#if 0
+27 −10
Original line number Diff line number Diff line
@@ -47,8 +47,6 @@
#include "dwc_otg_hcd.h"
#include "dwc_otg_mphi_fix.h"

extern bool fiq_fix_enable;

#ifdef DEBUG
inline const char *op_state_str(dwc_otg_core_if_t * core_if)
{
@@ -1321,7 +1319,7 @@ static int32_t dwc_otg_handle_lpm_intr(dwc_otg_core_if_t * core_if)
/**
 * This function returns the Core Interrupt register.
 */
static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if)
static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if, gintmsk_data_t *reenable_gintmsk)
{
	gahbcfg_data_t gahbcfg = {.d32 = 0 };
	gintsts_data_t gintsts;
@@ -1338,19 +1336,33 @@ static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if)
	gintmsk_common.b.lpmtranrcvd = 1;
#endif
	gintmsk_common.b.restoredone = 1;
	if(dwc_otg_is_device_mode(core_if))
	{
		/** @todo: The port interrupt occurs while in device
		 * mode. Added code to CIL to clear the interrupt for now!
		 */
		gintmsk_common.b.portintr = 1;

	}
	gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
	gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
	{
		unsigned long flags;

		// Re-enable the saved interrupts
		local_irq_save(flags);
		local_fiq_disable();
		gintmsk.d32 |= gintmsk_common.d32;
		gintsts_saved.d32 &= ~gintmsk_common.d32;
		reenable_gintmsk->d32 = gintmsk.d32;
		local_irq_restore(flags);
	}

	gahbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg);

#ifdef DEBUG
	/* if any common interrupts set */
	if (gintsts.d32 & gintmsk_common.d32) {
		DWC_DEBUGPL(DBG_ANY, "gintsts=%08x  gintmsk=%08x\n",
		DWC_DEBUGPL(DBG_ANY, "common_intr: gintsts=%08x  gintmsk=%08x\n",
			    gintsts.d32, gintmsk.d32);
	}
#endif
@@ -1394,6 +1406,7 @@ int32_t dwc_otg_handle_common_intr(void *dev)
{
	int retval = 0;
	gintsts_data_t gintsts;
	gintmsk_data_t reenable_gintmsk;
	gpwrdn_data_t gpwrdn = {.d32 = 0 };
	dwc_otg_device_t *otg_dev = dev;
	dwc_otg_core_if_t *core_if = otg_dev->core_if;
@@ -1415,7 +1428,7 @@ int32_t dwc_otg_handle_common_intr(void *dev)
	}

	if (core_if->hibernation_suspend <= 0) {
		gintsts.d32 = dwc_otg_read_common_intr(core_if);
		gintsts.d32 = dwc_otg_read_common_intr(core_if, &reenable_gintmsk);

		if (gintsts.b.modemismatch) {
			retval |= dwc_otg_handle_mode_mismatch_intr(core_if);
@@ -1512,8 +1525,12 @@ int32_t dwc_otg_handle_common_intr(void *dev)
			gintsts.b.portintr = 1;
			DWC_WRITE_REG32(&core_if->core_global_regs->gintsts,gintsts.d32);
			retval |= 1;
			reenable_gintmsk.b.portintr = 1;

		}

		DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, reenable_gintmsk.d32);

	} else {
		DWC_DEBUGPL(DBG_ANY, "gpwrdn=%08x\n", gpwrdn.d32);

+5 −1
Original line number Diff line number Diff line
@@ -242,7 +242,8 @@ static struct dwc_otg_driver_module_params dwc_otg_module_params = {

//Global variable to switch the fiq fix on or off (declared in bcm2708.c)
extern bool fiq_fix_enable;

// Global variable to enable the split transaction fix
bool fiq_split_enable = true;
//Global variable to switch the nak holdoff on or off
bool nak_holdoff_enable = true;

@@ -1090,6 +1091,7 @@ static int __init dwc_otg_driver_init(void)
	}
	printk(KERN_DEBUG "dwc_otg: FIQ %s\n", fiq_fix_enable ? "enabled":"disabled");
	printk(KERN_DEBUG "dwc_otg: NAK holdoff %s\n", nak_holdoff_enable ? "enabled":"disabled");
	printk(KERN_DEBUG "dwc_otg: FIQ split fix %s\n", fiq_split_enable ? "enabled":"disabled");

	error = driver_create_file(drv, &driver_attr_version);
#ifdef DEBUG
@@ -1374,6 +1376,8 @@ module_param(fiq_fix_enable, bool, 0444);
MODULE_PARM_DESC(fiq_fix_enable, "Enable the fiq fix");
module_param(nak_holdoff_enable, bool, 0444);
MODULE_PARM_DESC(nak_holdoff_enable, "Enable the NAK holdoff");
module_param(fiq_split_enable, bool, 0444);
MODULE_PARM_DESC(fiq_split_enable, "Enable the FIQ fix on split transactions");

/** @page "Module Parameters"
 *
+116 −9
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@

#include "dwc_otg_hcd.h"
#include "dwc_otg_regs.h"
#include "dwc_otg_mphi_fix.h"

extern bool microframe_schedule, nak_holdoff_enable;

@@ -581,6 +582,8 @@ int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd,
			 */
			dwc_otg_hc_halt(hcd->core_if, qh->channel,
					DWC_OTG_HC_XFER_URB_DEQUEUE);

			dwc_otg_hcd_release_port(hcd, qh);
		}
	}

@@ -716,6 +719,8 @@ static void completion_tasklet_func(void *ptr)

		usb_hcd_giveback_urb(hcd->priv, urb, urb->status);

		fiq_print(FIQDBG_PORTHUB, "COMPLETE");

		DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
	}
	DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
@@ -979,6 +984,10 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if)
	hcd->frame_list = NULL;
	hcd->frame_list_dma = 0;
	hcd->periodic_qh_count = 0;

	DWC_MEMSET(hcd->hub_port, 0, sizeof(hcd->hub_port));
	DWC_MEMSET(hcd->hub_port_alloc, -1, sizeof(hcd->hub_port_alloc));

out:
	return retval;
}
@@ -1124,7 +1133,12 @@ static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
		uint32_t hub_addr, port_addr;
		hc->do_split = 1;
		hc->xact_pos = qtd->isoc_split_pos;
		/* We don't need to do complete splits anymore */
		if(fiq_split_enable)
			hc->complete_split = qtd->complete_split = 0;
		else
			hc->complete_split = qtd->complete_split;

		hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &port_addr);
		hc->hub_addr = (uint8_t) hub_addr;
		hc->port_addr = (uint8_t) port_addr;
@@ -1271,6 +1285,62 @@ static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
	hc->qh = qh;
}

/*
** Check the transaction to see if the port / hub has already been assigned for
** a split transaction
**
** Return 0 - Port is already in use
*/
int dwc_otg_hcd_allocate_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh)
{
	uint32_t hub_addr, port_addr;

	if(!fiq_split_enable)
		return 0;

	hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr);

	if(hcd->hub_port[hub_addr] & (1 << port_addr))
	{
		fiq_print(FIQDBG_PORTHUB, "H%dP%d:S%02d", hub_addr, port_addr, qh->skip_count);

		qh->skip_count++;

		if(qh->skip_count > 40000)
		{
			printk_once(KERN_ERR "Error: Having to skip port allocation");
			local_fiq_disable();
			BUG();
			return 0;
		}
		return 1;
	}
	else
	{
		qh->skip_count = 0;
		hcd->hub_port[hub_addr] |= 1 << port_addr;
		fiq_print(FIQDBG_PORTHUB, "H%dP%d:A %d", hub_addr, port_addr, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->pipe_info.ep_num);
		hcd->hub_port_alloc[hub_addr * 16 + port_addr] = dwc_otg_hcd_get_frame_number(hcd);
		return 0;
	}
}
void dwc_otg_hcd_release_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh)
{
	uint32_t hub_addr, port_addr;

	if(!fiq_split_enable)
		return;

	hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr);

	hcd->hub_port[hub_addr] &= ~(1 << port_addr);
	hcd->hub_port_alloc[hub_addr * 16 + port_addr] = -1;

	fiq_print(FIQDBG_PORTHUB, "H%dP%d:RO%d", hub_addr, port_addr, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->pipe_info.ep_num);

}


/**
 * This function selects transactions from the HCD transfer schedule and
 * assigns them to available host channels. It is called from HCD interrupt
@@ -1304,11 +1374,22 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)

	while (qh_ptr != &hcd->periodic_sched_ready &&
	       !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {

		qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);

		if(qh->do_split && dwc_otg_hcd_allocate_port(hcd, qh))
		{
			qh_ptr = DWC_LIST_NEXT(qh_ptr);
			g_next_sched_frame = dwc_frame_num_inc(dwc_otg_hcd_get_frame_number(hcd), 1);
			continue;
		}

		if (microframe_schedule) {
			// Make sure we leave one channel for non periodic transactions.
			DWC_SPINLOCK_IRQSAVE(channel_lock, &flags);
			if (hcd->available_host_channels <= 1) {
				DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags);
				if(qh->do_split) dwc_otg_hcd_release_port(hcd, qh);
				break;
			}
			hcd->available_host_channels--;
@@ -1329,8 +1410,6 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)
		DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned,
				   &qh->qh_list_entry);
		DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags);

		ret_val = DWC_OTG_TRANSACTION_PERIODIC;
	}

	/*
@@ -1369,10 +1448,19 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)
				qh->nak_frame = 0xffff;
			}
		}

		if (qh->do_split && dwc_otg_hcd_allocate_port(hcd, qh))
		{
			g_next_sched_frame = dwc_frame_num_inc(dwc_otg_hcd_get_frame_number(hcd), 1);
			qh_ptr = DWC_LIST_NEXT(qh_ptr);
			continue;
		}

		if (microframe_schedule) {
				DWC_SPINLOCK_IRQSAVE(channel_lock, &flags);
				if (hcd->available_host_channels < 1) {
					DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags);
					if(qh->do_split) dwc_otg_hcd_release_port(hcd, qh);
					break;
				}
				hcd->available_host_channels--;
@@ -1396,16 +1484,17 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)

		g_np_sent++;

		if (ret_val == DWC_OTG_TRANSACTION_NONE) {
			ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
		} else {
			ret_val = DWC_OTG_TRANSACTION_ALL;
		}

		if (!microframe_schedule)
			hcd->non_periodic_channels++;
	}

	if(!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned))
		ret_val |= DWC_OTG_TRANSACTION_PERIODIC;

	if(!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active))
		ret_val |= DWC_OTG_TRANSACTION_NON_PERIODIC;


#ifdef DEBUG_HOST_CHANNELS
	last_sel_trans_num_avail_hc_at_end = hcd->available_host_channels;
#endif /* DEBUG_HOST_CHANNELS */
@@ -1522,6 +1611,15 @@ static void process_periodic_channels(dwc_otg_hcd_t * hcd)

		qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);

		// Do not send a split start transaction any later than frame .6
		// Note, we have to schedule a periodic in .5 to make it go in .6
		if(fiq_split_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6)
		{
			qh_ptr = qh_ptr->next;
			g_next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7;
			continue;
		}

		/*
		 * Set a flag if we're queuing high-bandwidth in slave mode.
		 * The flag prevents any halts to get into the request queue in
@@ -1651,6 +1749,15 @@ static void process_non_periodic_channels(dwc_otg_hcd_t * hcd)

		qh = DWC_LIST_ENTRY(hcd->non_periodic_qh_ptr, dwc_otg_qh_t,
				    qh_list_entry);

		// Do not send a split start transaction any later than frame .5
		// non periodic transactions will start immediately in this uframe
		if(fiq_split_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6)
		{
			g_next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7;
			break;
		}

		status =
		    queue_transaction(hcd, qh->channel,
				      tx_status.b.nptxfspcavail);
+15 −5
Original line number Diff line number Diff line
@@ -168,10 +168,10 @@ typedef enum dwc_otg_control_phase {

/** Transaction types. */
typedef enum dwc_otg_transaction_type {
	DWC_OTG_TRANSACTION_NONE,
	DWC_OTG_TRANSACTION_PERIODIC,
	DWC_OTG_TRANSACTION_NON_PERIODIC,
	DWC_OTG_TRANSACTION_ALL
	DWC_OTG_TRANSACTION_NONE          = 0,
	DWC_OTG_TRANSACTION_PERIODIC      = 1,
	DWC_OTG_TRANSACTION_NON_PERIODIC  = 2,
	DWC_OTG_TRANSACTION_ALL           = DWC_OTG_TRANSACTION_PERIODIC + DWC_OTG_TRANSACTION_NON_PERIODIC
} dwc_otg_transaction_type_e;

struct dwc_otg_qh;
@@ -370,6 +370,8 @@ typedef struct dwc_otg_qh {

	uint16_t speed;
	uint16_t frame_usecs[8];

	uint32_t skip_count;
} dwc_otg_qh_t;

DWC_CIRCLEQ_HEAD(hc_list, dwc_hc);
@@ -574,6 +576,10 @@ struct dwc_otg_hcd {
	/** Frame List */
	uint32_t *frame_list;

	/** Hub - Port assignment */
	int hub_port[16];
	int hub_port_alloc[256];

	/** Frame List DMA address */
	dma_addr_t frame_list_dma;

@@ -604,12 +610,16 @@ extern dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t
extern void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd,
					   dwc_otg_transaction_type_e tr_type);

int dwc_otg_hcd_allocate_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh);
void dwc_otg_hcd_release_port(dwc_otg_hcd_t * dwc_otg_hcd, dwc_otg_qh_t *qh);


/** @} */

/** @name Interrupt Handler Functions */
/** @{ */
extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * dwc_otg_hcd, int32_t);
extern int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t *
							 dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t *
Loading