[v10,3/6] usb: dwc3: Resize TX FIFOs to meet EP bursting requirements
diff mbox series

Message ID 1623923899-16759-4-git-send-email-wcheng@codeaurora.org
State New, archived
Headers show
Series
  • Re-introduce TX FIFO resize for larger EP bursting
Related show

Commit Message

Wesley Cheng June 17, 2021, 9:58 a.m. UTC
Some devices have USB compositions which may require multiple endpoints
that support EP bursting.  HW defined TX FIFO sizes may not always be
sufficient for these compositions.  By utilizing flexible TX FIFO
allocation, this allows for endpoints to request the required FIFO depth to
achieve higher bandwidth.  With some higher bMaxBurst configurations, using
a larger TX FIFO size results in better TX throughput.

By introducing the check_config() callback, the resizing logic can fetch
the maximum number of endpoints used in the USB composition (can contain
multiple configurations), which helps ensure that the resizing logic can
fulfill the configuration(s), or return an error to the gadget layer
otherwise during bind time.

Signed-off-by: Wesley Cheng <wcheng@codeaurora.org>
---
 drivers/usb/dwc3/core.c   |   9 ++
 drivers/usb/dwc3/core.h   |  15 ++++
 drivers/usb/dwc3/ep0.c    |   2 +
 drivers/usb/dwc3/gadget.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 238 insertions(+)

Comments

Greg Kroah-Hartman June 17, 2021, 11:10 a.m. UTC | #1
On Thu, Jun 17, 2021 at 02:58:16AM -0700, Wesley Cheng wrote:
> +static int dwc3_gadget_check_config(struct usb_gadget *g, unsigned long ep_map)
> +{
> +	struct dwc3 *dwc = gadget_to_dwc(g);
> +	unsigned long in_ep_map;
> +	int fifo_size = 0;
> +	int ram1_depth;
> +	int ep_num;
> +
> +	if (!dwc->do_fifo_resize)
> +		return 0;
> +
> +	/* Only interested in the IN endpoints */
> +	in_ep_map = ep_map >> 16;

Wait, this "map" is split up into 16/16 somehow?  So it's only 32bits
big?

Where did you document this map structure?  Why is it needed at all, you
have the gadget, don't you have access to the full list of endpoints
here as well?

confused,

greg k-h
kernel test robot June 17, 2021, 5:56 p.m. UTC | #2
Hi Wesley,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on usb/usb-testing]
[also build test WARNING on robh/for-next v5.13-rc6 next-20210617]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Wesley-Cheng/Re-introduce-TX-FIFO-resize-for-larger-EP-bursting/20210617-180037
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
config: x86_64-randconfig-a011-20210617 (attached as .config)
compiler: clang version 13.0.0 (https://github.com/llvm/llvm-project 64720f57bea6a6bf033feef4a5751ab9c0c3b401)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install x86_64 cross compiling tool for clang build
        # apt-get install binutils-x86-64-linux-gnu
        # https://github.com/0day-ci/linux/commit/94892083cf17e46a29d4ef33a044af04854162e6
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Wesley-Cheng/Re-introduce-TX-FIFO-resize-for-larger-EP-bursting/20210617-180037
        git checkout 94892083cf17e46a29d4ef33a044af04854162e6
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/usb/dwc3/gadget.c:671:18: warning: variable 'dep' set but not used [-Wunused-but-set-variable]
           struct dwc3_ep *dep;
                           ^
   1 warning generated.
--
   drivers/usb/dwc3/gadget.c:648: warning: Function parameter or member 'mult' not described in 'dwc3_gadget_calc_tx_fifo_size'
   drivers/usb/dwc3/gadget.c:648: warning: Excess function parameter 'nfifos' description in 'dwc3_gadget_calc_tx_fifo_size'
>> drivers/usb/dwc3/gadget.c:670: warning: expecting prototype for dwc3_gadget_clear_tx_fifo_size(). Prototype was for dwc3_gadget_clear_tx_fifos() instead


vim +/dep +671 drivers/usb/dwc3/gadget.c

   661	
   662	/**
   663	 * dwc3_gadget_clear_tx_fifo_size - Clears txfifo allocation
   664	 * @dwc: pointer to the DWC3 context
   665	 *
   666	 * Iterates through all the endpoint registers and clears the previous txfifo
   667	 * allocations.
   668	 */
   669	void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc)
 > 670	{
 > 671		struct dwc3_ep *dep;
   672		int fifo_depth;
   673		int size;
   674		int num;
   675	
   676		if (!dwc->do_fifo_resize)
   677			return;
   678	
   679		/* Read ep0IN related TXFIFO size */
   680		dep = dwc->eps[1];
   681		size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
   682		if (DWC3_IP_IS(DWC3))
   683			fifo_depth = DWC3_GTXFIFOSIZ_TXFDEP(size);
   684		else
   685			fifo_depth = DWC31_GTXFIFOSIZ_TXFDEP(size);
   686	
   687		dwc->last_fifo_depth = fifo_depth;
   688		/* Clear existing TXFIFO for all IN eps except ep0 */
   689		for (num = 3; num < min_t(int, dwc->num_eps, DWC3_ENDPOINTS_NUM);
   690		     num += 2) {
   691			dep = dwc->eps[num];
   692			/* Don't change TXFRAMNUM on usb31 version */
   693			size = DWC3_IP_IS(DWC3) ? 0 :
   694				dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1)) &
   695					   DWC31_GTXFIFOSIZ_TXFRAMNUM;
   696	
   697			dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1), size);
   698		}
   699		dwc->num_ep_resized = 0;
   700	}
   701	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
kernel test robot June 17, 2021, 6:01 p.m. UTC | #3
Hi Wesley,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on usb/usb-testing]
[also build test WARNING on robh/for-next v5.13-rc6 next-20210617]
[cannot apply to balbi-usb/testing/next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Wesley-Cheng/Re-introduce-TX-FIFO-resize-for-larger-EP-bursting/20210617-180037
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
config: m68k-allmodconfig (attached as .config)
compiler: m68k-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/94892083cf17e46a29d4ef33a044af04854162e6
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Wesley-Cheng/Re-introduce-TX-FIFO-resize-for-larger-EP-bursting/20210617-180037
        git checkout 94892083cf17e46a29d4ef33a044af04854162e6
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=m68k 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   drivers/usb/dwc3/gadget.c: In function 'dwc3_gadget_clear_tx_fifos':
>> drivers/usb/dwc3/gadget.c:671:18: warning: variable 'dep' set but not used [-Wunused-but-set-variable]
     671 |  struct dwc3_ep *dep;
         |                  ^~~
--
   drivers/usb/dwc3/gadget.c:648: warning: Function parameter or member 'mult' not described in 'dwc3_gadget_calc_tx_fifo_size'
   drivers/usb/dwc3/gadget.c:648: warning: Excess function parameter 'nfifos' description in 'dwc3_gadget_calc_tx_fifo_size'
>> drivers/usb/dwc3/gadget.c:670: warning: expecting prototype for dwc3_gadget_clear_tx_fifo_size(). Prototype was for dwc3_gadget_clear_tx_fifos() instead


vim +/dep +671 drivers/usb/dwc3/gadget.c

   661	
   662	/**
   663	 * dwc3_gadget_clear_tx_fifo_size - Clears txfifo allocation
   664	 * @dwc: pointer to the DWC3 context
   665	 *
   666	 * Iterates through all the endpoint registers and clears the previous txfifo
   667	 * allocations.
   668	 */
   669	void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc)
 > 670	{
 > 671		struct dwc3_ep *dep;
   672		int fifo_depth;
   673		int size;
   674		int num;
   675	
   676		if (!dwc->do_fifo_resize)
   677			return;
   678	
   679		/* Read ep0IN related TXFIFO size */
   680		dep = dwc->eps[1];
   681		size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
   682		if (DWC3_IP_IS(DWC3))
   683			fifo_depth = DWC3_GTXFIFOSIZ_TXFDEP(size);
   684		else
   685			fifo_depth = DWC31_GTXFIFOSIZ_TXFDEP(size);
   686	
   687		dwc->last_fifo_depth = fifo_depth;
   688		/* Clear existing TXFIFO for all IN eps except ep0 */
   689		for (num = 3; num < min_t(int, dwc->num_eps, DWC3_ENDPOINTS_NUM);
   690		     num += 2) {
   691			dep = dwc->eps[num];
   692			/* Don't change TXFRAMNUM on usb31 version */
   693			size = DWC3_IP_IS(DWC3) ? 0 :
   694				dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1)) &
   695					   DWC31_GTXFIFOSIZ_TXFRAMNUM;
   696	
   697			dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1), size);
   698		}
   699		dwc->num_ep_resized = 0;
   700	}
   701	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Wesley Cheng June 22, 2021, 5:27 a.m. UTC | #4
On 6/17/2021 4:10 AM, Greg KH wrote:
> On Thu, Jun 17, 2021 at 02:58:16AM -0700, Wesley Cheng wrote:
>> +static int dwc3_gadget_check_config(struct usb_gadget *g, unsigned long ep_map)
>> +{
>> +	struct dwc3 *dwc = gadget_to_dwc(g);
>> +	unsigned long in_ep_map;
>> +	int fifo_size = 0;
>> +	int ram1_depth;
>> +	int ep_num;
>> +
>> +	if (!dwc->do_fifo_resize)
>> +		return 0;
>> +
>> +	/* Only interested in the IN endpoints */
>> +	in_ep_map = ep_map >> 16;

Hi Greg,

> 
> Wait, this "map" is split up into 16/16 somehow?  So it's only 32bits
> big?
>

Yes, correct. Upper 16 carries IN eps, lower 16 carries OUT eps.  Will
fix that based off your other comment.

> Where did you document this map structure?  Why is it needed at all, you
> have the gadget, don't you have access to the full list of endpoints
> here as well?
> 
> confused,
> 

Unfortunately, we do not have the entire list of endpoints for each
function until the composite driver receives the SET_CONFIG packet from
the host.  By this time, if we incorrectly allowed the configuration to
start enumeration w/ the host, and there were some EPs which have no
FIFO memory allocated, that would lead to those interfaces/functions
being broken.

The USB UDC driver does have the in_epnum and out_epnum, but those are
currently only being incremented in case we don't match EPs by string.
(in usb_ep_autoconfig_ss())

Thanks
Wesley Cheng

> greg k-h
>
Greg Kroah-Hartman June 22, 2021, 6:06 a.m. UTC | #5
On Mon, Jun 21, 2021 at 10:27:13PM -0700, Wesley Cheng wrote:
> 
> 
> On 6/17/2021 4:10 AM, Greg KH wrote:
> > On Thu, Jun 17, 2021 at 02:58:16AM -0700, Wesley Cheng wrote:
> >> +static int dwc3_gadget_check_config(struct usb_gadget *g, unsigned long ep_map)
> >> +{
> >> +	struct dwc3 *dwc = gadget_to_dwc(g);
> >> +	unsigned long in_ep_map;
> >> +	int fifo_size = 0;
> >> +	int ram1_depth;
> >> +	int ep_num;
> >> +
> >> +	if (!dwc->do_fifo_resize)
> >> +		return 0;
> >> +
> >> +	/* Only interested in the IN endpoints */
> >> +	in_ep_map = ep_map >> 16;
> 
> Hi Greg,
> 
> > 
> > Wait, this "map" is split up into 16/16 somehow?  So it's only 32bits
> > big?
> >
> 
> Yes, correct. Upper 16 carries IN eps, lower 16 carries OUT eps.  Will
> fix that based off your other comment.
> 
> > Where did you document this map structure?  Why is it needed at all, you
> > have the gadget, don't you have access to the full list of endpoints
> > here as well?
> > 
> > confused,
> > 
> 
> Unfortunately, we do not have the entire list of endpoints for each
> function until the composite driver receives the SET_CONFIG packet from
> the host.  By this time, if we incorrectly allowed the configuration to
> start enumeration w/ the host, and there were some EPs which have no
> FIFO memory allocated, that would lead to those interfaces/functions
> being broken.

So how would this magic bitmap be any different here?

I'm not understanding why you need to create something new here to
represent data we already have.

thanks,

greg k-h

Patch
diff mbox series

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index e0a8e79..a7bcdb9d 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -1267,6 +1267,7 @@  static void dwc3_get_properties(struct dwc3 *dwc)
 	u8			rx_max_burst_prd;
 	u8			tx_thr_num_pkt_prd;
 	u8			tx_max_burst_prd;
+	u8			tx_fifo_resize_max_num;
 	const char		*usb_psy_name;
 	int			ret;
 
@@ -1282,6 +1283,8 @@  static void dwc3_get_properties(struct dwc3 *dwc)
 	 */
 	hird_threshold = 12;
 
+	tx_fifo_resize_max_num = 6;
+
 	dwc->maximum_speed = usb_get_maximum_speed(dev);
 	dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev);
 	dwc->dr_mode = usb_get_dr_mode(dev);
@@ -1325,6 +1328,10 @@  static void dwc3_get_properties(struct dwc3 *dwc)
 				&tx_thr_num_pkt_prd);
 	device_property_read_u8(dev, "snps,tx-max-burst-prd",
 				&tx_max_burst_prd);
+	dwc->do_fifo_resize = device_property_read_bool(dev,
+							"tx-fifo-resize");
+	device_property_read_u8(dev, "tx-fifo-max-num",
+				&tx_fifo_resize_max_num);
 
 	dwc->disable_scramble_quirk = device_property_read_bool(dev,
 				"snps,disable_scramble_quirk");
@@ -1390,6 +1397,8 @@  static void dwc3_get_properties(struct dwc3 *dwc)
 	dwc->tx_max_burst_prd = tx_max_burst_prd;
 
 	dwc->imod_interval = 0;
+
+	dwc->tx_fifo_resize_max_num = tx_fifo_resize_max_num;
 }
 
 /* check whether the core supports IMOD */
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index dccdf13..dd985ba 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1023,6 +1023,7 @@  struct dwc3_scratchpad_array {
  * @rx_max_burst_prd: max periodic ESS receive burst size
  * @tx_thr_num_pkt_prd: periodic ESS transmit packet count
  * @tx_max_burst_prd: max periodic ESS transmit burst size
+ * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize
  * @hsphy_interface: "utmi" or "ulpi"
  * @connected: true when we're connected to a host, false otherwise
  * @delayed_status: true when gadget driver asks for delayed status
@@ -1079,6 +1080,11 @@  struct dwc3_scratchpad_array {
  * @dis_split_quirk: set to disable split boundary.
  * @imod_interval: set the interrupt moderation interval in 250ns
  *			increments or 0 to disable.
+ * @max_cfg_eps: current max number of IN eps used across all USB configs.
+ * @last_fifo_depth: last fifo depth used to determine next fifo ram start
+ *		     address.
+ * @num_ep_resized: carries the current number endpoints which have had its tx
+ *		    fifo resized.
  */
 struct dwc3 {
 	struct work_struct	drd_work;
@@ -1233,6 +1239,7 @@  struct dwc3 {
 	u8			rx_max_burst_prd;
 	u8			tx_thr_num_pkt_prd;
 	u8			tx_max_burst_prd;
+	u8			tx_fifo_resize_max_num;
 
 	const char		*hsphy_interface;
 
@@ -1246,6 +1253,7 @@  struct dwc3 {
 	unsigned		is_utmi_l1_suspend:1;
 	unsigned		is_fpga:1;
 	unsigned		pending_events:1;
+	unsigned		do_fifo_resize:1;
 	unsigned		pullups_connected:1;
 	unsigned		setup_packet_pending:1;
 	unsigned		three_stage_setup:1;
@@ -1281,6 +1289,10 @@  struct dwc3 {
 	unsigned		dis_split_quirk:1;
 
 	u16			imod_interval;
+
+	int			max_cfg_eps;
+	int			last_fifo_depth;
+	int			num_ep_resized;
 };
 
 #define INCRX_BURST_MODE 0
@@ -1512,6 +1524,7 @@  int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd,
 		struct dwc3_gadget_ep_cmd_params *params);
 int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd,
 		u32 param);
+void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc);
 #else
 static inline int dwc3_gadget_init(struct dwc3 *dwc)
 { return 0; }
@@ -1531,6 +1544,8 @@  static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd,
 static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
 		int cmd, u32 param)
 { return 0; }
+static inline void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc)
+{ }
 #endif
 
 #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index 3cd2942..d28d085 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -619,6 +619,8 @@  static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
 		return -EINVAL;
 
 	case USB_STATE_ADDRESS:
+		dwc3_gadget_clear_tx_fifos(dwc);
+
 		ret = dwc3_ep0_delegate_req(dwc, ctrl);
 		/* if the cfg matches and the cfg is non zero */
 		if (cfg && (!ret || (ret == USB_GADGET_DELAYED_STATUS))) {
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index af6d7f1..8855481a 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -632,6 +632,179 @@  static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
 		bool interrupt);
 
 /**
+ * dwc3_gadget_calc_tx_fifo_size - calculates the txfifo size value
+ * @dwc: pointer to the DWC3 context
+ * @nfifos: number of fifos to calculate for
+ *
+ * Calculates the size value based on the equation below:
+ *
+ * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1
+ *
+ * The max packet size is set to 1024, as the txfifo requirements mainly apply
+ * to super speed USB use cases.  However, it is safe to overestimate the fifo
+ * allocations for other scenarios, i.e. high speed USB.
+ */
+static int dwc3_gadget_calc_tx_fifo_size(struct dwc3 *dwc, int mult)
+{
+	int max_packet = 1024;
+	int fifo_size;
+	int mdwidth;
+
+	mdwidth = dwc3_mdwidth(dwc);
+
+	/* MDWIDTH is represented in bits, we need it in bytes */
+	mdwidth >>= 3;
+
+	fifo_size = mult * ((max_packet + mdwidth) / mdwidth) + 1;
+	return fifo_size;
+}
+
+/**
+ * dwc3_gadget_clear_tx_fifo_size - Clears txfifo allocation
+ * @dwc: pointer to the DWC3 context
+ *
+ * Iterates through all the endpoint registers and clears the previous txfifo
+ * allocations.
+ */
+void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc)
+{
+	struct dwc3_ep *dep;
+	int fifo_depth;
+	int size;
+	int num;
+
+	if (!dwc->do_fifo_resize)
+		return;
+
+	/* Read ep0IN related TXFIFO size */
+	dep = dwc->eps[1];
+	size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
+	if (DWC3_IP_IS(DWC3))
+		fifo_depth = DWC3_GTXFIFOSIZ_TXFDEP(size);
+	else
+		fifo_depth = DWC31_GTXFIFOSIZ_TXFDEP(size);
+
+	dwc->last_fifo_depth = fifo_depth;
+	/* Clear existing TXFIFO for all IN eps except ep0 */
+	for (num = 3; num < min_t(int, dwc->num_eps, DWC3_ENDPOINTS_NUM);
+	     num += 2) {
+		dep = dwc->eps[num];
+		/* Don't change TXFRAMNUM on usb31 version */
+		size = DWC3_IP_IS(DWC3) ? 0 :
+			dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1)) &
+				   DWC31_GTXFIFOSIZ_TXFRAMNUM;
+
+		dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1), size);
+	}
+	dwc->num_ep_resized = 0;
+}
+
+/*
+ * dwc3_gadget_resize_tx_fifos - reallocate fifo spaces for current use-case
+ * @dwc: pointer to our context structure
+ *
+ * This function will a best effort FIFO allocation in order
+ * to improve FIFO usage and throughput, while still allowing
+ * us to enable as many endpoints as possible.
+ *
+ * Keep in mind that this operation will be highly dependent
+ * on the configured size for RAM1 - which contains TxFifo -,
+ * the amount of endpoints enabled on coreConsultant tool, and
+ * the width of the Master Bus.
+ *
+ * In general, FIFO depths are represented with the following equation:
+ *
+ * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1
+ *
+ * Conversions can be done to the equation to derive the number of packets that
+ * will fit to a particular FIFO size value.
+ */
+static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep)
+{
+	struct dwc3 *dwc = dep->dwc;
+	int fifo_0_start;
+	int ram1_depth;
+	int fifo_size;
+	int min_depth;
+	int num_in_ep;
+	int remaining;
+	int num_fifos = 1;
+	int fifo;
+	int tmp;
+
+	if (!dwc->do_fifo_resize)
+		return 0;
+
+	/* resize IN endpoints except ep0 */
+	if (!usb_endpoint_dir_in(dep->endpoint.desc) || dep->number <= 1)
+		return 0;
+
+	ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
+
+	if ((dep->endpoint.maxburst > 1 &&
+	     usb_endpoint_xfer_bulk(dep->endpoint.desc)) ||
+	    usb_endpoint_xfer_isoc(dep->endpoint.desc))
+		num_fifos = 3;
+
+	if (dep->endpoint.maxburst > 6 &&
+	    usb_endpoint_xfer_bulk(dep->endpoint.desc) && DWC3_IP_IS(DWC31))
+		num_fifos = dwc->tx_fifo_resize_max_num;
+
+	/* FIFO size for a single buffer */
+	fifo = dwc3_gadget_calc_tx_fifo_size(dwc, 1);
+
+	/* Calculate the number of remaining EPs w/o any FIFO */
+	num_in_ep = dwc->max_cfg_eps;
+	num_in_ep -= dwc->num_ep_resized;
+
+	/* Reserve at least one FIFO for the number of IN EPs */
+	min_depth = num_in_ep * (fifo + 1);
+	remaining = ram1_depth - min_depth - dwc->last_fifo_depth;
+	remaining = max_t(int, 0, remaining);
+	/*
+	 * We've already reserved 1 FIFO per EP, so check what we can fit in
+	 * addition to it.  If there is not enough remaining space, allocate
+	 * all the remaining space to the EP.
+	 */
+	fifo_size = (num_fifos - 1) * fifo;
+	if (remaining < fifo_size)
+		fifo_size = remaining;
+
+	fifo_size += fifo;
+	/* Last increment according to the TX FIFO size equation */
+	fifo_size++;
+
+	/* Check if TXFIFOs start at non-zero addr */
+	tmp = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
+	fifo_0_start = DWC3_GTXFIFOSIZ_TXFSTADDR(tmp);
+
+	fifo_size |= (fifo_0_start + (dwc->last_fifo_depth << 16));
+	if (DWC3_IP_IS(DWC3))
+		dwc->last_fifo_depth += DWC3_GTXFIFOSIZ_TXFDEP(fifo_size);
+	else
+		dwc->last_fifo_depth += DWC31_GTXFIFOSIZ_TXFDEP(fifo_size);
+
+	/* Check fifo size allocation doesn't exceed available RAM size. */
+	if (dwc->last_fifo_depth >= ram1_depth) {
+		dev_err(dwc->dev, "Fifosize(%d) > RAM size(%d) %s depth:%d\n",
+			dwc->last_fifo_depth, ram1_depth,
+			dep->endpoint.name, fifo_size);
+		if (DWC3_IP_IS(DWC3))
+			fifo_size = DWC3_GTXFIFOSIZ_TXFDEP(fifo_size);
+		else
+			fifo_size = DWC31_GTXFIFOSIZ_TXFDEP(fifo_size);
+
+		dwc->last_fifo_depth -= fifo_size;
+		return -ENOMEM;
+	}
+
+	dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(dep->number >> 1), fifo_size);
+	dwc->num_ep_resized++;
+
+	return 0;
+}
+
+/**
  * __dwc3_gadget_ep_enable - initializes a hw endpoint
  * @dep: endpoint to be initialized
  * @action: one of INIT, MODIFY or RESTORE
@@ -648,6 +821,10 @@  static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
 	int			ret;
 
 	if (!(dep->flags & DWC3_EP_ENABLED)) {
+		ret = dwc3_gadget_resize_tx_fifos(dep);
+		if (ret)
+			return ret;
+
 		ret = dwc3_gadget_start_config(dep);
 		if (ret)
 			return ret;
@@ -2498,6 +2675,7 @@  static int dwc3_gadget_stop(struct usb_gadget *g)
 
 	spin_lock_irqsave(&dwc->lock, flags);
 	dwc->gadget_driver	= NULL;
+	dwc->max_cfg_eps = 0;
 	spin_unlock_irqrestore(&dwc->lock, flags);
 
 	free_irq(dwc->irq_gadget, dwc->ev_buf);
@@ -2585,6 +2763,39 @@  static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
 	return ret;
 }
 
+static int dwc3_gadget_check_config(struct usb_gadget *g, unsigned long ep_map)
+{
+	struct dwc3 *dwc = gadget_to_dwc(g);
+	unsigned long in_ep_map;
+	int fifo_size = 0;
+	int ram1_depth;
+	int ep_num;
+
+	if (!dwc->do_fifo_resize)
+		return 0;
+
+	/* Only interested in the IN endpoints */
+	in_ep_map = ep_map >> 16;
+	ep_num = hweight_long(in_ep_map);
+
+	if (ep_num <= dwc->max_cfg_eps)
+		return 0;
+
+	/* Update the max number of eps in the composition */
+	dwc->max_cfg_eps = ep_num;
+
+	fifo_size = dwc3_gadget_calc_tx_fifo_size(dwc, dwc->max_cfg_eps);
+	/* Based on the equation, increment by one for every ep */
+	fifo_size += dwc->max_cfg_eps;
+
+	/* Check if we can fit a single fifo per endpoint */
+	ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
+	if (fifo_size > ram1_depth)
+		return -ENOMEM;
+
+	return 0;
+}
+
 static const struct usb_gadget_ops dwc3_gadget_ops = {
 	.get_frame		= dwc3_gadget_get_frame,
 	.wakeup			= dwc3_gadget_wakeup,
@@ -2596,6 +2807,7 @@  static const struct usb_gadget_ops dwc3_gadget_ops = {
 	.udc_set_ssp_rate	= dwc3_gadget_set_ssp_rate,
 	.get_config_params	= dwc3_gadget_config_params,
 	.vbus_draw		= dwc3_gadget_vbus_draw,
+	.check_config		= dwc3_gadget_check_config,
 };
 
 /* -------------------------------------------------------------------------- */