Devicetree
 help / color / mirror / Atom feed
* [PATCH v2] scripts: printing file name correctly
From: Maninder Singh @ 2016-11-30  4:55 UTC (permalink / raw)
  To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	rowand.list-Re5JQEeQqe8AvxtiuMwx3w
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	v.narang-Sze3O3UU22JBDgjK7y7TUQ, pankaj.m-Sze3O3UU22JBDgjK7y7TUQ,
	ajeet.y-Sze3O3UU22JBDgjK7y7TUQ, majordomo-u79uwXL29TY76Z2rM5mHXA,
	Maninder Singh

Currently we are printing file name wrongly.
pos->file ? (char *) pos->file : "<no file>")

It should be pos->file->name, and secondly if we got NULL
file struture,we will anyway derefer it in last printf.

So, Fixing the same.

caught with static analysis tool.
Signed-off-by: Maninder Singh <maninder1.s-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Vaneet Narang <v.narang-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
---
v1 -> v2: changing description of patch.

 scripts/dtc/srcpos.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/scripts/dtc/srcpos.c b/scripts/dtc/srcpos.c
index f534c22..360fd14 100644
--- a/scripts/dtc/srcpos.c
+++ b/scripts/dtc/srcpos.c
@@ -252,12 +252,11 @@ struct srcpos *
 srcpos_dump(struct srcpos *pos)
 {
 	printf("file        : \"%s\"\n",
-	       pos->file ? (char *) pos->file : "<no file>");
+	       pos->file ?  pos->file->name : "<no file>");
 	printf("first_line  : %d\n", pos->first_line);
 	printf("first_column: %d\n", pos->first_column);
 	printf("last_line   : %d\n", pos->last_line);
 	printf("last_column : %d\n", pos->last_column);
-	printf("file        : %s\n", pos->file->name);
 }
 
 
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH V6 00/10] PM / OPP: Multiple regulator support
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki
  Cc: linaro-kernel, linux-pm, Stephen Boyd, nm, Vincent Guittot, robh,
	d-gerlach, broonie, devicetree, Viresh Kumar

Hi,

Some platforms (like TI) have complex DVFS configuration for CPU
devices, where multiple regulators are required to be configured to
change DVFS state of the device. This was explained well by Nishanth
earlier [1].

One of the major complaints around multiple regulators case was that the
DT isn't responsible in any way to represent the order in which multiple
supplies need to be programmed, before or after a frequency change. It
was considered in this patch and such information is left for the
platform specific OPP driver now, which can register its own
opp_set_rate() callback with the OPP core and the OPP core will then
call it during DVFS.

The patches are tested on Exynos5250 (Dual A15). I have hacked around DT
and code to pass values for multiple regulators and verified that they
are all properly read by the kernel (using debugfs interface).

Dave Gerlach has already tested [2] it on the real TI platforms and it
works well for him.

This is rebased over: linux-next branch in the PM tree.

V5->V6:
- Rebased over a recent fix (and resolved rebase conflicts) that will
  get applied before this series:

  https://marc.info/?l=linux-kernel&m=148047843010418&w=2

V4->V5:
- Stephen boyd had some minor review comments and gave his Reviewed-by
  tag for the rest. Only 2 patches don't have his RBY tag.
- Individual patches contain the version history from V4 to V5.

V3->V4:
- Separate out cpu-supply fix in the binding in a separate patch (Mark).
- Add more documentation to the binding to explain that the relation to
  the supplies and the order of programming them is left for the
  platform specific bindings and that every platform using multiple
  regulators for their devices needs to provide a separate binding
  document explaining their implementation (Mark).
- @Rob and Stephen: I have kept your Acks for the bindings as the
  bindings only got a bit reworded (improved) since the time you guys
  Acked them. Please let me know if you want more improvement in the
  bindings now.
- V4 for 10/10 was sent earlier, which added a missing
  rcu_read_unlock(). Nothing else changed in it.
- Added some missing Kernel documentation comments

V2->V3:
- The last patch is new
- Removed a debug leftover pr_info() message
- Renamed few names as s/set_rate/set_opp
- Removed a TODO comment (as it is done now with this series)
- created struct for min_uV and max_uV
- kerneldoc comments for structures in pm_opp.h
- s/const char */const char * const
- use kasprintf()
- Some more minor reformatting
- More Ack/RBY tags added

V1->V2:
- Ack from Rob for 1st patch
- Moved the supplies structure to pm_opp.h (Dave)
- Fixed an compilation warning.

--
viresh

[1] https://marc.info/?l=linux-pm&m=145684495832764&w=2
[2] https://marc.info/?l=linux-kernel&m=147924789305276&w=2

Viresh Kumar (10):
  PM / OPP: Fix incorrect cpu-supply property in binding
  PM / OPP: Reword binding supporting multiple regulators per device
  PM / OPP: Don't use OPP structure outside of rcu protected section
  PM / OPP: Manage supply's voltage/current in a separate structure
  PM / OPP: Pass struct dev_pm_opp_supply to _set_opp_voltage()
  PM / OPP: Add infrastructure to manage multiple regulators
  PM / OPP: Separate out _generic_set_opp()
  PM / OPP: Allow platform specific custom set_opp() callbacks
  PM / OPP: Don't WARN on multiple calls to dev_pm_opp_set_regulators()
  PM / OPP: Don't assume platform doesn't have regulators

 Documentation/devicetree/bindings/opp/opp.txt |  27 +-
 drivers/base/power/opp/core.c                 | 535 ++++++++++++++++++++------
 drivers/base/power/opp/debugfs.c              |  52 ++-
 drivers/base/power/opp/of.c                   | 105 +++--
 drivers/base/power/opp/opp.h                  |  22 +-
 drivers/cpufreq/cpufreq-dt.c                  |   9 +-
 include/linux/pm_opp.h                        |  69 +++-
 7 files changed, 635 insertions(+), 184 deletions(-)

-- 
2.7.1.410.g6faf27b


^ permalink raw reply

* [PATCH V6 01/10] PM / OPP: Fix incorrect cpu-supply property in binding
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
	linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
	robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
	broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
	Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

The regulator bindings allow the "<name>-supply" property to define a
single parent supply and not a list of parents.

Fix the wrong example code present in OPP bindings.

While at it also change the compatible string as Rob pointed out earlier
that none of A7 implementation have multiple supplies for the CPU core.

Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Reviewed-by: Stephen Boyd <sboyd-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
 Documentation/devicetree/bindings/opp/opp.txt | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/opp/opp.txt b/Documentation/devicetree/bindings/opp/opp.txt
index ee91cbdd95ee..f0239f68d186 100644
--- a/Documentation/devicetree/bindings/opp/opp.txt
+++ b/Documentation/devicetree/bindings/opp/opp.txt
@@ -386,10 +386,12 @@ Example 4: Handling multiple regulators
 / {
 	cpus {
 		cpu@0 {
-			compatible = "arm,cortex-a7";
+			compatible = "vendor,cpu-type";
 			...
 
-			cpu-supply = <&cpu_supply0>, <&cpu_supply1>, <&cpu_supply2>;
+			vcc0-supply = <&cpu_supply0>;
+			vcc1-supply = <&cpu_supply1>;
+			vcc2-supply = <&cpu_supply2>;
 			operating-points-v2 = <&cpu0_opp_table>;
 		};
 	};
-- 
2.7.1.410.g6faf27b

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH V6 02/10] PM / OPP: Reword binding supporting multiple regulators per device
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel, linux-pm, Vincent Guittot, robh, d-gerlach,
	broonie, devicetree, Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar@linaro.org>

On certain platforms (like TI), DVFS for a single device (CPU) requires
configuring multiple power supplies.

The OPP bindings already contains binding and example to explain this
case, but it isn't sufficient.

- There is no way for the code parsing these bindings to know which
  voltage values belong to which power supply.

- It is not possible to know the order in which the supplies need to be
  configured while switching OPPs.

This patch clarifies on those details by mentioning that such
information is left for the implementation specific bindings to explain.
They may want to hardcode such details or implement their own properties
to get such information. All implementations using multiple regulators
for their devices must provide a binding document explaining their
implementation.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Acked-by: Rob Herring <robh@kernel.org>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
---
 Documentation/devicetree/bindings/opp/opp.txt | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/opp/opp.txt b/Documentation/devicetree/bindings/opp/opp.txt
index f0239f68d186..9f5ca4457b5f 100644
--- a/Documentation/devicetree/bindings/opp/opp.txt
+++ b/Documentation/devicetree/bindings/opp/opp.txt
@@ -86,8 +86,14 @@ properties.
   Single entry is for target voltage and three entries are for <target min max>
   voltages.
 
-  Entries for multiple regulators must be present in the same order as
-  regulators are specified in device's DT node.
+  Entries for multiple regulators shall be provided in the same field separated
+  by angular brackets <>. The OPP binding doesn't provide any provisions to
+  relate the values to their power supplies or the order in which the supplies
+  need to be configured and that is left for the implementation specific
+  binding.
+
+  Entries for all regulators shall be of the same size, i.e. either all use a
+  single value or triplets.
 
 - opp-microvolt-<name>: Named opp-microvolt property. This is exactly similar to
   the above opp-microvolt property, but allows multiple voltage ranges to be
@@ -104,10 +110,13 @@ properties.
 
   Should only be set if opp-microvolt is set for the OPP.
 
-  Entries for multiple regulators must be present in the same order as
-  regulators are specified in device's DT node. If this property isn't required
-  for few regulators, then this should be marked as zero for them. If it isn't
-  required for any regulator, then this property need not be present.
+  Entries for multiple regulators shall be provided in the same field separated
+  by angular brackets <>. If current values aren't required for a regulator,
+  then it shall be filled with 0. If current values aren't required for any of
+  the regulators, then this field is not required. The OPP binding doesn't
+  provide any provisions to relate the values to their power supplies or the
+  order in which the supplies need to be configured and that is left for the
+  implementation specific binding.
 
 - opp-microamp-<name>: Named opp-microamp property. Similar to
   opp-microvolt-<name> property, but for microamp instead.
-- 
2.7.1.410.g6faf27b


^ permalink raw reply related

* [PATCH V6 03/10] PM / OPP: Don't use OPP structure outside of rcu protected section
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
	linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
	robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
	broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
	Viresh Kumar, # v4 . 6+
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

The OPP structure must not be used out of the rcu protected section.
Cache the values to be used in separate variables instead.

Cc:  # v4.6+ <stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Reviewed-by: Stephen Boyd <sboyd-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
Tested-by: Dave Gerlach <d-gerlach-l0cyMroinI0@public.gmane.org>
---
 drivers/base/power/opp/core.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 60a4210ec6d1..705137da3954 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -584,6 +584,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 	struct clk *clk;
 	unsigned long freq, old_freq;
 	unsigned long u_volt, u_volt_min, u_volt_max;
+	unsigned long old_u_volt, old_u_volt_min, old_u_volt_max;
 	int ret;
 
 	if (unlikely(!target_freq)) {
@@ -633,6 +634,14 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 		return ret;
 	}
 
+	if (IS_ERR(old_opp)) {
+		old_u_volt = 0;
+	} else {
+		old_u_volt = old_opp->u_volt;
+		old_u_volt_min = old_opp->u_volt_min;
+		old_u_volt_max = old_opp->u_volt_max;
+	}
+
 	u_volt = opp->u_volt;
 	u_volt_min = opp->u_volt_min;
 	u_volt_max = opp->u_volt_max;
@@ -677,9 +686,10 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 			__func__, old_freq);
 restore_voltage:
 	/* This shouldn't harm even if the voltages weren't updated earlier */
-	if (!IS_ERR(old_opp))
-		_set_opp_voltage(dev, reg, old_opp->u_volt,
-				 old_opp->u_volt_min, old_opp->u_volt_max);
+	if (old_u_volt) {
+		_set_opp_voltage(dev, reg, old_u_volt, old_u_volt_min,
+				 old_u_volt_max);
+	}
 
 	return ret;
 }
-- 
2.7.1.410.g6faf27b

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH V6 04/10] PM / OPP: Manage supply's voltage/current in a separate structure
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel, linux-pm, Vincent Guittot, robh, d-gerlach,
	broonie, devicetree, Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar@linaro.org>

This is a preparatory step for multiple regulator per device support.
Move the voltage/current variables to a new structure.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Dave Gerlach <d-gerlach@ti.com>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
---
 drivers/base/power/opp/core.c    | 44 +++++++++++++++++++++-------------------
 drivers/base/power/opp/debugfs.c |  8 ++++----
 drivers/base/power/opp/of.c      | 18 ++++++++--------
 drivers/base/power/opp/opp.h     | 11 +++-------
 include/linux/pm_opp.h           | 16 +++++++++++++++
 5 files changed, 55 insertions(+), 42 deletions(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 705137da3954..f706a961815d 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -112,7 +112,7 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
 	if (IS_ERR_OR_NULL(tmp_opp))
 		pr_err("%s: Invalid parameters\n", __func__);
 	else
-		v = tmp_opp->u_volt;
+		v = tmp_opp->supply.u_volt;
 
 	return v;
 }
@@ -246,10 +246,10 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
 		if (!opp->available)
 			continue;
 
-		if (opp->u_volt_min < min_uV)
-			min_uV = opp->u_volt_min;
-		if (opp->u_volt_max > max_uV)
-			max_uV = opp->u_volt_max;
+		if (opp->supply.u_volt_min < min_uV)
+			min_uV = opp->supply.u_volt_min;
+		if (opp->supply.u_volt_max > max_uV)
+			max_uV = opp->supply.u_volt_max;
 	}
 
 	rcu_read_unlock();
@@ -637,14 +637,14 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 	if (IS_ERR(old_opp)) {
 		old_u_volt = 0;
 	} else {
-		old_u_volt = old_opp->u_volt;
-		old_u_volt_min = old_opp->u_volt_min;
-		old_u_volt_max = old_opp->u_volt_max;
+		old_u_volt = old_opp->supply.u_volt;
+		old_u_volt_min = old_opp->supply.u_volt_min;
+		old_u_volt_max = old_opp->supply.u_volt_max;
 	}
 
-	u_volt = opp->u_volt;
-	u_volt_min = opp->u_volt_min;
-	u_volt_max = opp->u_volt_max;
+	u_volt = opp->supply.u_volt;
+	u_volt_min = opp->supply.u_volt_min;
+	u_volt_max = opp->supply.u_volt_max;
 
 	reg = opp_table->regulator;
 
@@ -957,10 +957,11 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
 	struct regulator *reg = opp_table->regulator;
 
 	if (!IS_ERR(reg) &&
-	    !regulator_is_supported_voltage(reg, opp->u_volt_min,
-					    opp->u_volt_max)) {
+	    !regulator_is_supported_voltage(reg, opp->supply.u_volt_min,
+					    opp->supply.u_volt_max)) {
 		pr_warn("%s: OPP minuV: %lu maxuV: %lu, not supported by regulator\n",
-			__func__, opp->u_volt_min, opp->u_volt_max);
+			__func__, opp->supply.u_volt_min,
+			opp->supply.u_volt_max);
 		return false;
 	}
 
@@ -993,11 +994,12 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
 
 		/* Duplicate OPPs */
 		dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n",
-			 __func__, opp->rate, opp->u_volt, opp->available,
-			 new_opp->rate, new_opp->u_volt, new_opp->available);
+			 __func__, opp->rate, opp->supply.u_volt,
+			 opp->available, new_opp->rate, new_opp->supply.u_volt,
+			 new_opp->available);
 
-		return opp->available && new_opp->u_volt == opp->u_volt ?
-			0 : -EEXIST;
+		return opp->available &&
+		       new_opp->supply.u_volt == opp->supply.u_volt ? 0 : -EEXIST;
 	}
 
 	new_opp->opp_table = opp_table;
@@ -1064,9 +1066,9 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt,
 	/* populate the opp table */
 	new_opp->rate = freq;
 	tol = u_volt * opp_table->voltage_tolerance_v1 / 100;
-	new_opp->u_volt = u_volt;
-	new_opp->u_volt_min = u_volt - tol;
-	new_opp->u_volt_max = u_volt + tol;
+	new_opp->supply.u_volt = u_volt;
+	new_opp->supply.u_volt_min = u_volt - tol;
+	new_opp->supply.u_volt_max = u_volt + tol;
 	new_opp->available = true;
 	new_opp->dynamic = dynamic;
 
diff --git a/drivers/base/power/opp/debugfs.c b/drivers/base/power/opp/debugfs.c
index ef1ae6b52042..c897676ca35f 100644
--- a/drivers/base/power/opp/debugfs.c
+++ b/drivers/base/power/opp/debugfs.c
@@ -63,16 +63,16 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
 	if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate))
 		return -ENOMEM;
 
-	if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->u_volt))
+	if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->supply.u_volt))
 		return -ENOMEM;
 
-	if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->u_volt_min))
+	if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->supply.u_volt_min))
 		return -ENOMEM;
 
-	if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->u_volt_max))
+	if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->supply.u_volt_max))
 		return -ENOMEM;
 
-	if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->u_amp))
+	if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->supply.u_amp))
 		return -ENOMEM;
 
 	if (!debugfs_create_ulong("clock_latency_ns", S_IRUGO, d,
diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c
index 5b3755e49731..bdf409d42126 100644
--- a/drivers/base/power/opp/of.c
+++ b/drivers/base/power/opp/of.c
@@ -148,14 +148,14 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
 		return -EINVAL;
 	}
 
-	opp->u_volt = microvolt[0];
+	opp->supply.u_volt = microvolt[0];
 
 	if (count == 1) {
-		opp->u_volt_min = opp->u_volt;
-		opp->u_volt_max = opp->u_volt;
+		opp->supply.u_volt_min = opp->supply.u_volt;
+		opp->supply.u_volt_max = opp->supply.u_volt;
 	} else {
-		opp->u_volt_min = microvolt[1];
-		opp->u_volt_max = microvolt[2];
+		opp->supply.u_volt_min = microvolt[1];
+		opp->supply.u_volt_max = microvolt[2];
 	}
 
 	/* Search for "opp-microamp-<name>" */
@@ -173,7 +173,7 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
 	}
 
 	if (prop && !of_property_read_u32(opp->np, name, &val))
-		opp->u_amp = val;
+		opp->supply.u_amp = val;
 
 	return 0;
 }
@@ -303,9 +303,9 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
 	mutex_unlock(&opp_table_lock);
 
 	pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n",
-		 __func__, new_opp->turbo, new_opp->rate, new_opp->u_volt,
-		 new_opp->u_volt_min, new_opp->u_volt_max,
-		 new_opp->clock_latency_ns);
+		 __func__, new_opp->turbo, new_opp->rate,
+		 new_opp->supply.u_volt, new_opp->supply.u_volt_min,
+		 new_opp->supply.u_volt_max, new_opp->clock_latency_ns);
 
 	/*
 	 * Notify the changes in the availability of the operable
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 96cd30ac6c1d..8a02516542c2 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -61,10 +61,7 @@ extern struct list_head opp_tables;
  * @turbo:	true if turbo (boost) OPP
  * @suspend:	true if suspend OPP
  * @rate:	Frequency in hertz
- * @u_volt:	Target voltage in microvolts corresponding to this OPP
- * @u_volt_min:	Minimum voltage in microvolts corresponding to this OPP
- * @u_volt_max:	Maximum voltage in microvolts corresponding to this OPP
- * @u_amp:	Maximum current drawn by the device in microamperes
+ * @supply:	Power supply voltage/current values
  * @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
  *		frequency from any other OPP's frequency.
  * @opp_table:	points back to the opp_table struct this opp belongs to
@@ -83,10 +80,8 @@ struct dev_pm_opp {
 	bool suspend;
 	unsigned long rate;
 
-	unsigned long u_volt;
-	unsigned long u_volt_min;
-	unsigned long u_volt_max;
-	unsigned long u_amp;
+	struct dev_pm_opp_supply supply;
+
 	unsigned long clock_latency_ns;
 
 	struct opp_table *opp_table;
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index f6bc76501912..824f7268f687 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -25,6 +25,22 @@ enum dev_pm_opp_event {
 	OPP_EVENT_ADD, OPP_EVENT_REMOVE, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE,
 };
 
+/**
+ * struct dev_pm_opp_supply - Power supply voltage/current values
+ * @u_volt:	Target voltage in microvolts corresponding to this OPP
+ * @u_volt_min:	Minimum voltage in microvolts corresponding to this OPP
+ * @u_volt_max:	Maximum voltage in microvolts corresponding to this OPP
+ * @u_amp:	Maximum current drawn by the device in microamperes
+ *
+ * This structure stores the voltage/current values for a single power supply.
+ */
+struct dev_pm_opp_supply {
+	unsigned long u_volt;
+	unsigned long u_volt_min;
+	unsigned long u_volt_max;
+	unsigned long u_amp;
+};
+
 #if defined(CONFIG_PM_OPP)
 
 unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
-- 
2.7.1.410.g6faf27b


^ permalink raw reply related

* [PATCH V6 05/10] PM / OPP: Pass struct dev_pm_opp_supply to _set_opp_voltage()
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel, linux-pm, Vincent Guittot, robh, d-gerlach,
	broonie, devicetree, Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar@linaro.org>

Pass the entire supply structure instead of all of its fields.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Dave Gerlach <d-gerlach@ti.com>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
---
 drivers/base/power/opp/core.c | 44 +++++++++++++++++--------------------------
 1 file changed, 17 insertions(+), 27 deletions(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index f706a961815d..8328ff06cc08 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -542,8 +542,7 @@ static struct clk *_get_opp_clk(struct device *dev)
 }
 
 static int _set_opp_voltage(struct device *dev, struct regulator *reg,
-			    unsigned long u_volt, unsigned long u_volt_min,
-			    unsigned long u_volt_max)
+			    struct dev_pm_opp_supply *supply)
 {
 	int ret;
 
@@ -554,14 +553,15 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg,
 		return 0;
 	}
 
-	dev_dbg(dev, "%s: voltages (mV): %lu %lu %lu\n", __func__, u_volt_min,
-		u_volt, u_volt_max);
+	dev_dbg(dev, "%s: voltages (mV): %lu %lu %lu\n", __func__,
+		supply->u_volt_min, supply->u_volt, supply->u_volt_max);
 
-	ret = regulator_set_voltage_triplet(reg, u_volt_min, u_volt,
-					    u_volt_max);
+	ret = regulator_set_voltage_triplet(reg, supply->u_volt_min,
+					    supply->u_volt, supply->u_volt_max);
 	if (ret)
 		dev_err(dev, "%s: failed to set voltage (%lu %lu %lu mV): %d\n",
-			__func__, u_volt_min, u_volt, u_volt_max, ret);
+			__func__, supply->u_volt_min, supply->u_volt,
+			supply->u_volt_max, ret);
 
 	return ret;
 }
@@ -583,8 +583,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 	struct regulator *reg;
 	struct clk *clk;
 	unsigned long freq, old_freq;
-	unsigned long u_volt, u_volt_min, u_volt_max;
-	unsigned long old_u_volt, old_u_volt_min, old_u_volt_max;
+	struct dev_pm_opp_supply old_supply, new_supply;
 	int ret;
 
 	if (unlikely(!target_freq)) {
@@ -634,17 +633,12 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 		return ret;
 	}
 
-	if (IS_ERR(old_opp)) {
-		old_u_volt = 0;
-	} else {
-		old_u_volt = old_opp->supply.u_volt;
-		old_u_volt_min = old_opp->supply.u_volt_min;
-		old_u_volt_max = old_opp->supply.u_volt_max;
-	}
+	if (IS_ERR(old_opp))
+		old_supply.u_volt = 0;
+	else
+		memcpy(&old_supply, &old_opp->supply, sizeof(old_supply));
 
-	u_volt = opp->supply.u_volt;
-	u_volt_min = opp->supply.u_volt_min;
-	u_volt_max = opp->supply.u_volt_max;
+	memcpy(&new_supply, &opp->supply, sizeof(new_supply));
 
 	reg = opp_table->regulator;
 
@@ -652,8 +646,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 
 	/* Scaling up? Scale voltage before frequency */
 	if (freq > old_freq) {
-		ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
-				       u_volt_max);
+		ret = _set_opp_voltage(dev, reg, &new_supply);
 		if (ret)
 			goto restore_voltage;
 	}
@@ -672,8 +665,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 
 	/* Scaling down? Scale voltage after frequency */
 	if (freq < old_freq) {
-		ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
-				       u_volt_max);
+		ret = _set_opp_voltage(dev, reg, &new_supply);
 		if (ret)
 			goto restore_freq;
 	}
@@ -686,10 +678,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 			__func__, old_freq);
 restore_voltage:
 	/* This shouldn't harm even if the voltages weren't updated earlier */
-	if (old_u_volt) {
-		_set_opp_voltage(dev, reg, old_u_volt, old_u_volt_min,
-				 old_u_volt_max);
-	}
+	if (old_supply.u_volt)
+		_set_opp_voltage(dev, reg, &old_supply);
 
 	return ret;
 }
-- 
2.7.1.410.g6faf27b


^ permalink raw reply related

* [PATCH V6 06/10] PM / OPP: Add infrastructure to manage multiple regulators
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
	linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
	robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
	broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
	Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

This patch adds infrastructure to manage multiple regulators and updates
the only user (cpufreq-dt) of dev_pm_opp_set{put}_regulator().

This is preparatory work for adding full support for devices with
multiple regulators.

Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Tested-by: Dave Gerlach <d-gerlach-l0cyMroinI0@public.gmane.org>

---
V4->V5:
- Don't allocate from within rcu locked section.
- s/+//
---
 drivers/base/power/opp/core.c    | 245 +++++++++++++++++++++++++++------------
 drivers/base/power/opp/debugfs.c |  52 +++++++--
 drivers/base/power/opp/of.c      | 103 +++++++++++-----
 drivers/base/power/opp/opp.h     |  10 +-
 drivers/cpufreq/cpufreq-dt.c     |   9 +-
 include/linux/pm_opp.h           |   8 +-
 6 files changed, 302 insertions(+), 125 deletions(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 8328ff06cc08..f0dd0bd29ce8 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -93,6 +93,8 @@ struct opp_table *_find_opp_table(struct device *dev)
  * Return: voltage in micro volt corresponding to the opp, else
  * return 0
  *
+ * This is useful only for devices with single power supply.
+ *
  * Locking: This function must be called under rcu_read_lock(). opp is a rcu
  * protected pointer. This means that opp which could have been fetched by
  * opp_find_freq_{exact,ceil,floor} functions is valid as long as we are
@@ -112,7 +114,7 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
 	if (IS_ERR_OR_NULL(tmp_opp))
 		pr_err("%s: Invalid parameters\n", __func__);
 	else
-		v = tmp_opp->supply.u_volt;
+		v = tmp_opp->supplies[0].u_volt;
 
 	return v;
 }
@@ -210,6 +212,24 @@ unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_clock_latency);
 
+static int _get_regulator_count(struct device *dev)
+{
+	struct opp_table *opp_table;
+	int count;
+
+	rcu_read_lock();
+
+	opp_table = _find_opp_table(dev);
+	if (!IS_ERR(opp_table))
+		count = opp_table->regulator_count;
+	else
+		count = 0;
+
+	rcu_read_unlock();
+
+	return count;
+}
+
 /**
  * dev_pm_opp_get_max_volt_latency() - Get max voltage latency in nanoseconds
  * @dev: device for which we do this operation
@@ -222,34 +242,51 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
 {
 	struct opp_table *opp_table;
 	struct dev_pm_opp *opp;
-	struct regulator *reg;
+	struct regulator *reg, **regulators;
 	unsigned long latency_ns = 0;
-	unsigned long min_uV = ~0, max_uV = 0;
-	int ret;
+	int ret, i, count;
+	struct {
+		unsigned long min;
+		unsigned long max;
+	} *uV;
+
+	count = _get_regulator_count(dev);
+
+	/* Regulator may not be required for the device */
+	if (!count)
+		return 0;
+
+	regulators = kmalloc_array(count, sizeof(*regulators), GFP_KERNEL);
+	if (!regulators)
+		return 0;
+
+	uV = kmalloc_array(count, sizeof(*uV), GFP_KERNEL);
+	if (!uV)
+		goto free_regulators;
 
 	rcu_read_lock();
 
 	opp_table = _find_opp_table(dev);
 	if (IS_ERR(opp_table)) {
 		rcu_read_unlock();
-		return 0;
+		goto free_uV;
 	}
 
-	reg = opp_table->regulator;
-	if (IS_ERR(reg)) {
-		/* Regulator may not be required for device */
-		rcu_read_unlock();
-		return 0;
-	}
+	memcpy(regulators, opp_table->regulators, count * sizeof(*regulators));
 
-	list_for_each_entry_rcu(opp, &opp_table->opp_list, node) {
-		if (!opp->available)
-			continue;
+	for (i = 0; i < count; i++) {
+		uV[i].min = ~0;
+		uV[i].max = 0;
+
+		list_for_each_entry_rcu(opp, &opp_table->opp_list, node) {
+			if (!opp->available)
+				continue;
 
-		if (opp->supply.u_volt_min < min_uV)
-			min_uV = opp->supply.u_volt_min;
-		if (opp->supply.u_volt_max > max_uV)
-			max_uV = opp->supply.u_volt_max;
+			if (opp->supplies[i].u_volt_min < uV[i].min)
+				uV[i].min = opp->supplies[i].u_volt_min;
+			if (opp->supplies[i].u_volt_max > uV[i].max)
+				uV[i].max = opp->supplies[i].u_volt_max;
+		}
 	}
 
 	rcu_read_unlock();
@@ -258,9 +295,16 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
 	 * The caller needs to ensure that opp_table (and hence the regulator)
 	 * isn't freed, while we are executing this routine.
 	 */
-	ret = regulator_set_voltage_time(reg, min_uV, max_uV);
-	if (ret > 0)
-		latency_ns = ret * 1000;
+	for (i = 0; reg = regulators[i], i < count; i++) {
+		ret = regulator_set_voltage_time(reg, uV[i].min, uV[i].max);
+		if (ret > 0)
+			latency_ns += ret * 1000;
+	}
+
+free_uV:
+	kfree(uV);
+free_regulators:
+	kfree(regulators);
 
 	return latency_ns;
 }
@@ -580,7 +624,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 {
 	struct opp_table *opp_table;
 	struct dev_pm_opp *old_opp, *opp;
-	struct regulator *reg;
+	struct regulator *reg = ERR_PTR(-ENXIO);
 	struct clk *clk;
 	unsigned long freq, old_freq;
 	struct dev_pm_opp_supply old_supply, new_supply;
@@ -633,14 +677,23 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 		return ret;
 	}
 
+	if (opp_table->regulators) {
+		/* This function only supports single regulator per device */
+		if (WARN_ON(opp_table->regulator_count > 1)) {
+			dev_err(dev, "multiple regulators not supported\n");
+			rcu_read_unlock();
+			return -EINVAL;
+		}
+
+		reg = opp_table->regulators[0];
+	}
+
 	if (IS_ERR(old_opp))
 		old_supply.u_volt = 0;
 	else
-		memcpy(&old_supply, &old_opp->supply, sizeof(old_supply));
+		memcpy(&old_supply, old_opp->supplies, sizeof(old_supply));
 
-	memcpy(&new_supply, &opp->supply, sizeof(new_supply));
-
-	reg = opp_table->regulator;
+	memcpy(&new_supply, opp->supplies, sizeof(new_supply));
 
 	rcu_read_unlock();
 
@@ -764,9 +817,6 @@ static struct opp_table *_add_opp_table(struct device *dev)
 
 	_of_init_opp_table(opp_table, dev);
 
-	/* Set regulator to a non-NULL error value */
-	opp_table->regulator = ERR_PTR(-ENXIO);
-
 	/* Find clk for the device */
 	opp_table->clk = clk_get(dev, NULL);
 	if (IS_ERR(opp_table->clk)) {
@@ -815,7 +865,7 @@ static void _remove_opp_table(struct opp_table *opp_table)
 	if (opp_table->prop_name)
 		return;
 
-	if (!IS_ERR(opp_table->regulator))
+	if (opp_table->regulators)
 		return;
 
 	/* Release clk */
@@ -924,35 +974,50 @@ struct dev_pm_opp *_allocate_opp(struct device *dev,
 				 struct opp_table **opp_table)
 {
 	struct dev_pm_opp *opp;
+	int count, supply_size;
+	struct opp_table *table;
 
-	/* allocate new OPP node */
-	opp = kzalloc(sizeof(*opp), GFP_KERNEL);
-	if (!opp)
+	table = _add_opp_table(dev);
+	if (!table)
 		return NULL;
 
-	INIT_LIST_HEAD(&opp->node);
+	/* Allocate space for at least one supply */
+	count = table->regulator_count ? table->regulator_count : 1;
+	supply_size = sizeof(*opp->supplies) * count;
 
-	*opp_table = _add_opp_table(dev);
-	if (!*opp_table) {
-		kfree(opp);
+	/* allocate new OPP node and supplies structures */
+	opp = kzalloc(sizeof(*opp) + supply_size, GFP_KERNEL);
+	if (!opp) {
+		kfree(table);
 		return NULL;
 	}
 
+	/* Put the supplies at the end of the OPP structure as an empty array */
+	opp->supplies = (struct dev_pm_opp_supply *)(opp + 1);
+	INIT_LIST_HEAD(&opp->node);
+
+	*opp_table = table;
+
 	return opp;
 }
 
 static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
 					 struct opp_table *opp_table)
 {
-	struct regulator *reg = opp_table->regulator;
-
-	if (!IS_ERR(reg) &&
-	    !regulator_is_supported_voltage(reg, opp->supply.u_volt_min,
-					    opp->supply.u_volt_max)) {
-		pr_warn("%s: OPP minuV: %lu maxuV: %lu, not supported by regulator\n",
-			__func__, opp->supply.u_volt_min,
-			opp->supply.u_volt_max);
-		return false;
+	struct regulator *reg;
+	int i;
+
+	for (i = 0; i < opp_table->regulator_count; i++) {
+		reg = opp_table->regulators[i];
+
+		if (!regulator_is_supported_voltage(reg,
+					opp->supplies[i].u_volt_min,
+					opp->supplies[i].u_volt_max)) {
+			pr_warn("%s: OPP minuV: %lu maxuV: %lu, not supported by regulator\n",
+				__func__, opp->supplies[i].u_volt_min,
+				opp->supplies[i].u_volt_max);
+			return false;
+		}
 	}
 
 	return true;
@@ -984,12 +1049,13 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
 
 		/* Duplicate OPPs */
 		dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n",
-			 __func__, opp->rate, opp->supply.u_volt,
-			 opp->available, new_opp->rate, new_opp->supply.u_volt,
-			 new_opp->available);
+			 __func__, opp->rate, opp->supplies[0].u_volt,
+			 opp->available, new_opp->rate,
+			 new_opp->supplies[0].u_volt, new_opp->available);
 
+		/* Should we compare voltages for all regulators here ? */
 		return opp->available &&
-		       new_opp->supply.u_volt == opp->supply.u_volt ? 0 : -EEXIST;
+		       new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? 0 : -EEXIST;
 	}
 
 	new_opp->opp_table = opp_table;
@@ -1056,9 +1122,9 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt,
 	/* populate the opp table */
 	new_opp->rate = freq;
 	tol = u_volt * opp_table->voltage_tolerance_v1 / 100;
-	new_opp->supply.u_volt = u_volt;
-	new_opp->supply.u_volt_min = u_volt - tol;
-	new_opp->supply.u_volt_max = u_volt + tol;
+	new_opp->supplies[0].u_volt = u_volt;
+	new_opp->supplies[0].u_volt_min = u_volt - tol;
+	new_opp->supplies[0].u_volt_max = u_volt + tol;
 	new_opp->available = true;
 	new_opp->dynamic = dynamic;
 
@@ -1303,12 +1369,14 @@ void dev_pm_opp_put_prop_name(struct device *dev)
 EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name);
 
 /**
- * dev_pm_opp_set_regulator() - Set regulator name for the device
+ * dev_pm_opp_set_regulators() - Set regulator names for the device
  * @dev: Device for which regulator name is being set.
- * @name: Name of the regulator.
+ * @names: Array of pointers to the names of the regulator.
+ * @count: Number of regulators.
  *
  * In order to support OPP switching, OPP layer needs to know the name of the
- * device's regulator, as the core would be required to switch voltages as well.
+ * device's regulators, as the core would be required to switch voltages as
+ * well.
  *
  * This must be called before any OPPs are initialized for the device.
  *
@@ -1318,10 +1386,13 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name);
  * that this function is *NOT* called under RCU protection or in contexts where
  * mutex cannot be locked.
  */
-struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
+struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
+					    const char * const names[],
+					    unsigned int count)
 {
 	struct opp_table *opp_table;
 	struct regulator *reg;
+	int ret, i;
 
 	mutex_lock(&opp_table_lock);
 
@@ -1337,26 +1408,44 @@ struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
 		goto err;
 	}
 
-	/* Already have a regulator set */
-	if (WARN_ON(!IS_ERR(opp_table->regulator))) {
+	/* Already have regulators set */
+	if (WARN_ON(opp_table->regulators)) {
 		opp_table = ERR_PTR(-EBUSY);
 		goto err;
 	}
-	/* Allocate the regulator */
-	reg = regulator_get_optional(dev, name);
-	if (IS_ERR(reg)) {
-		opp_table = (struct opp_table *)reg;
-		if (PTR_ERR(reg) != -EPROBE_DEFER)
-			dev_err(dev, "%s: no regulator (%s) found: %ld\n",
-				__func__, name, PTR_ERR(reg));
+
+	opp_table->regulators = kmalloc_array(count,
+					      sizeof(*opp_table->regulators),
+					      GFP_KERNEL);
+	if (!opp_table->regulators) {
+		opp_table = ERR_PTR(-ENOMEM);
 		goto err;
 	}
 
-	opp_table->regulator = reg;
+	for (i = 0; i < count; i++) {
+		reg = regulator_get_optional(dev, names[i]);
+		if (IS_ERR(reg)) {
+			opp_table = (struct opp_table *)reg;
+			if (PTR_ERR(reg) != -EPROBE_DEFER)
+				dev_err(dev, "%s: no regulator (%s) found: %ld\n",
+					__func__, names[i], PTR_ERR(reg));
+			goto free_regulators;
+		}
+
+		opp_table->regulators[i] = reg;
+	}
+
+	opp_table->regulator_count = count;
 
 	mutex_unlock(&opp_table_lock);
 	return opp_table;
 
+free_regulators:
+	while (i != 0)
+		regulator_put(opp_table->regulators[--i]);
+
+	kfree(opp_table->regulators);
+	opp_table->regulators = NULL;
 err:
 	_remove_opp_table(opp_table);
 unlock:
@@ -1364,10 +1453,10 @@ struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
 
 	return opp_table;
 }
-EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulator);
+EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulators);
 
 /**
- * dev_pm_opp_put_regulator() - Releases resources blocked for regulator
+ * dev_pm_opp_put_regulators() - Releases resources blocked for regulators
  * @opp_table: opp_table returned from dev_pm_opp_set_regulator
  *
  * Locking: The internal opp_table and opp structures are RCU protected.
@@ -1376,20 +1465,26 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulator);
  * that this function is *NOT* called under RCU protection or in contexts where
  * mutex cannot be locked.
  */
-void dev_pm_opp_put_regulator(struct opp_table *opp_table)
+void dev_pm_opp_put_regulators(struct opp_table *opp_table)
 {
+	int i;
+
 	mutex_lock(&opp_table_lock);
 
-	if (IS_ERR(opp_table->regulator)) {
-		pr_err("%s: Doesn't have regulator set\n", __func__);
+	if (!opp_table->regulators) {
+		pr_err("%s: Doesn't have regulators set\n", __func__);
 		goto unlock;
 	}
 
 	/* Make sure there are no concurrent readers while updating opp_table */
 	WARN_ON(!list_empty(&opp_table->opp_list));
 
-	regulator_put(opp_table->regulator);
-	opp_table->regulator = ERR_PTR(-ENXIO);
+	for (i = opp_table->regulator_count - 1; i >= 0; i--)
+		regulator_put(opp_table->regulators[i]);
+
+	kfree(opp_table->regulators);
+	opp_table->regulators = NULL;
+	opp_table->regulator_count = 0;
 
 	/* Try freeing opp_table if this was the last blocking resource */
 	_remove_opp_table(opp_table);
@@ -1397,7 +1492,7 @@ void dev_pm_opp_put_regulator(struct opp_table *opp_table)
 unlock:
 	mutex_unlock(&opp_table_lock);
 }
-EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulator);
+EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulators);
 
 /**
  * dev_pm_opp_add()  - Add an OPP table from a table definitions
diff --git a/drivers/base/power/opp/debugfs.c b/drivers/base/power/opp/debugfs.c
index c897676ca35f..95f433db4ac7 100644
--- a/drivers/base/power/opp/debugfs.c
+++ b/drivers/base/power/opp/debugfs.c
@@ -15,6 +15,7 @@
 #include <linux/err.h>
 #include <linux/init.h>
 #include <linux/limits.h>
+#include <linux/slab.h>
 
 #include "opp.h"
 
@@ -34,6 +35,46 @@ void opp_debug_remove_one(struct dev_pm_opp *opp)
 	debugfs_remove_recursive(opp->dentry);
 }
 
+static bool opp_debug_create_supplies(struct dev_pm_opp *opp,
+				      struct opp_table *opp_table,
+				      struct dentry *pdentry)
+{
+	struct dentry *d;
+	int i = 0;
+	char *name;
+
+	/* Always create at least supply-0 directory */
+	do {
+		name = kasprintf(GFP_KERNEL, "supply-%d", i);
+
+		/* Create per-opp directory */
+		d = debugfs_create_dir(name, pdentry);
+
+		kfree(name);
+
+		if (!d)
+			return false;
+
+		if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d,
+					  &opp->supplies[i].u_volt))
+			return false;
+
+		if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d,
+					  &opp->supplies[i].u_volt_min))
+			return false;
+
+		if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d,
+					  &opp->supplies[i].u_volt_max))
+			return false;
+
+		if (!debugfs_create_ulong("u_amp", S_IRUGO, d,
+					  &opp->supplies[i].u_amp))
+			return false;
+	} while (++i < opp_table->regulator_count);
+
+	return true;
+}
+
 int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
 {
 	struct dentry *pdentry = opp_table->dentry;
@@ -63,16 +104,7 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
 	if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate))
 		return -ENOMEM;
 
-	if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->supply.u_volt))
-		return -ENOMEM;
-
-	if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->supply.u_volt_min))
-		return -ENOMEM;
-
-	if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->supply.u_volt_max))
-		return -ENOMEM;
-
-	if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->supply.u_amp))
+	if (!opp_debug_create_supplies(opp, opp_table, d))
 		return -ENOMEM;
 
 	if (!debugfs_create_ulong("clock_latency_ns", S_IRUGO, d,
diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c
index bdf409d42126..3f7d2591b173 100644
--- a/drivers/base/power/opp/of.c
+++ b/drivers/base/power/opp/of.c
@@ -17,6 +17,7 @@
 #include <linux/errno.h>
 #include <linux/device.h>
 #include <linux/of.h>
+#include <linux/slab.h>
 #include <linux/export.h>
 
 #include "opp.h"
@@ -101,16 +102,16 @@ static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
 	return true;
 }
 
-/* TODO: Support multiple regulators */
 static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
 			      struct opp_table *opp_table)
 {
-	u32 microvolt[3] = {0};
-	u32 val;
-	int count, ret;
+	u32 *microvolt, *microamp = NULL;
+	int supplies, vcount, icount, ret, i, j;
 	struct property *prop = NULL;
 	char name[NAME_MAX];
 
+	supplies = opp_table->regulator_count ? opp_table->regulator_count : 1;
+
 	/* Search for "opp-microvolt-<name>" */
 	if (opp_table->prop_name) {
 		snprintf(name, sizeof(name), "opp-microvolt-%s",
@@ -128,34 +129,29 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
 			return 0;
 	}
 
-	count = of_property_count_u32_elems(opp->np, name);
-	if (count < 0) {
+	vcount = of_property_count_u32_elems(opp->np, name);
+	if (vcount < 0) {
 		dev_err(dev, "%s: Invalid %s property (%d)\n",
-			__func__, name, count);
-		return count;
+			__func__, name, vcount);
+		return vcount;
 	}
 
-	/* There can be one or three elements here */
-	if (count != 1 && count != 3) {
-		dev_err(dev, "%s: Invalid number of elements in %s property (%d)\n",
-			__func__, name, count);
+	/* There can be one or three elements per supply */
+	if (vcount != supplies && vcount != supplies * 3) {
+		dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n",
+			__func__, name, vcount, supplies);
 		return -EINVAL;
 	}
 
-	ret = of_property_read_u32_array(opp->np, name, microvolt, count);
+	microvolt = kmalloc_array(vcount, sizeof(*microvolt), GFP_KERNEL);
+	if (!microvolt)
+		return -ENOMEM;
+
+	ret = of_property_read_u32_array(opp->np, name, microvolt, vcount);
 	if (ret) {
 		dev_err(dev, "%s: error parsing %s: %d\n", __func__, name, ret);
-		return -EINVAL;
-	}
-
-	opp->supply.u_volt = microvolt[0];
-
-	if (count == 1) {
-		opp->supply.u_volt_min = opp->supply.u_volt;
-		opp->supply.u_volt_max = opp->supply.u_volt;
-	} else {
-		opp->supply.u_volt_min = microvolt[1];
-		opp->supply.u_volt_max = microvolt[2];
+		ret = -EINVAL;
+		goto free_microvolt;
 	}
 
 	/* Search for "opp-microamp-<name>" */
@@ -172,10 +168,59 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
 		prop = of_find_property(opp->np, name, NULL);
 	}
 
-	if (prop && !of_property_read_u32(opp->np, name, &val))
-		opp->supply.u_amp = val;
+	if (prop) {
+		icount = of_property_count_u32_elems(opp->np, name);
+		if (icount < 0) {
+			dev_err(dev, "%s: Invalid %s property (%d)\n", __func__,
+				name, icount);
+			ret = icount;
+			goto free_microvolt;
+		}
 
-	return 0;
+		if (icount != supplies) {
+			dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n",
+				__func__, name, icount, supplies);
+			ret = -EINVAL;
+			goto free_microvolt;
+		}
+
+		microamp = kmalloc_array(icount, sizeof(*microamp), GFP_KERNEL);
+		if (!microamp) {
+			ret = -EINVAL;
+			goto free_microvolt;
+		}
+
+		ret = of_property_read_u32_array(opp->np, name, microamp,
+						 icount);
+		if (ret) {
+			dev_err(dev, "%s: error parsing %s: %d\n", __func__,
+				name, ret);
+			ret = -EINVAL;
+			goto free_microamp;
+		}
+	}
+
+	for (i = 0, j = 0; i < supplies; i++) {
+		opp->supplies[i].u_volt = microvolt[j++];
+
+		if (vcount == supplies) {
+			opp->supplies[i].u_volt_min = opp->supplies[i].u_volt;
+			opp->supplies[i].u_volt_max = opp->supplies[i].u_volt;
+		} else {
+			opp->supplies[i].u_volt_min = microvolt[j++];
+			opp->supplies[i].u_volt_max = microvolt[j++];
+		}
+
+		if (microamp)
+			opp->supplies[i].u_amp = microamp[i];
+	}
+
+free_microamp:
+	kfree(microamp);
+free_microvolt:
+	kfree(microvolt);
+
+	return ret;
 }
 
 /**
@@ -304,8 +349,8 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
 
 	pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n",
 		 __func__, new_opp->turbo, new_opp->rate,
-		 new_opp->supply.u_volt, new_opp->supply.u_volt_min,
-		 new_opp->supply.u_volt_max, new_opp->clock_latency_ns);
+		 new_opp->supplies[0].u_volt, new_opp->supplies[0].u_volt_min,
+		 new_opp->supplies[0].u_volt_max, new_opp->clock_latency_ns);
 
 	/*
 	 * Notify the changes in the availability of the operable
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 8a02516542c2..5b0f7e53bede 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -61,7 +61,7 @@ extern struct list_head opp_tables;
  * @turbo:	true if turbo (boost) OPP
  * @suspend:	true if suspend OPP
  * @rate:	Frequency in hertz
- * @supply:	Power supply voltage/current values
+ * @supplies:	Power supplies voltage/current values
  * @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
  *		frequency from any other OPP's frequency.
  * @opp_table:	points back to the opp_table struct this opp belongs to
@@ -80,7 +80,7 @@ struct dev_pm_opp {
 	bool suspend;
 	unsigned long rate;
 
-	struct dev_pm_opp_supply supply;
+	struct dev_pm_opp_supply *supplies;
 
 	unsigned long clock_latency_ns;
 
@@ -139,7 +139,8 @@ enum opp_table_access {
  * @supported_hw_count: Number of elements in supported_hw array.
  * @prop_name: A name to postfix to many DT properties, while parsing them.
  * @clk: Device's clock handle
- * @regulator: Supply regulator
+ * @regulators: Supply regulators
+ * @regulator_count: Number of power supply regulators
  * @dentry:	debugfs dentry pointer of the real device directory (not links).
  * @dentry_name: Name of the real dentry.
  *
@@ -174,7 +175,8 @@ struct opp_table {
 	unsigned int supported_hw_count;
 	const char *prop_name;
 	struct clk *clk;
-	struct regulator *regulator;
+	struct regulator **regulators;
+	unsigned int regulator_count;
 
 #ifdef CONFIG_DEBUG_FS
 	struct dentry *dentry;
diff --git a/drivers/cpufreq/cpufreq-dt.c b/drivers/cpufreq/cpufreq-dt.c
index 4d3ec92cbabf..1cd1fcfe835a 100644
--- a/drivers/cpufreq/cpufreq-dt.c
+++ b/drivers/cpufreq/cpufreq-dt.c
@@ -188,7 +188,10 @@ static int cpufreq_init(struct cpufreq_policy *policy)
 	 */
 	name = find_supply_name(cpu_dev);
 	if (name) {
-		opp_table = dev_pm_opp_set_regulator(cpu_dev, name);
+		const char *names[] = {name};
+
+		opp_table = dev_pm_opp_set_regulators(cpu_dev, names,
+						ARRAY_SIZE(names));
 		if (IS_ERR(opp_table)) {
 			ret = PTR_ERR(opp_table);
 			dev_err(cpu_dev, "Failed to set regulator for cpu%d: %d\n",
@@ -289,7 +292,7 @@ static int cpufreq_init(struct cpufreq_policy *policy)
 out_free_opp:
 	dev_pm_opp_of_cpumask_remove_table(policy->cpus);
 	if (name)
-		dev_pm_opp_put_regulator(opp_table);
+		dev_pm_opp_put_regulators(opp_table);
 out_put_clk:
 	clk_put(cpu_clk);
 
@@ -304,7 +307,7 @@ static int cpufreq_exit(struct cpufreq_policy *policy)
 	dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
 	dev_pm_opp_of_cpumask_remove_table(policy->related_cpus);
 	if (priv->reg_name)
-		dev_pm_opp_put_regulator(priv->opp_table);
+		dev_pm_opp_put_regulators(priv->opp_table);
 
 	clk_put(policy->clk);
 	kfree(priv);
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 824f7268f687..9a825ae78653 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -79,8 +79,8 @@ int dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions,
 void dev_pm_opp_put_supported_hw(struct device *dev);
 int dev_pm_opp_set_prop_name(struct device *dev, const char *name);
 void dev_pm_opp_put_prop_name(struct device *dev);
-struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name);
-void dev_pm_opp_put_regulator(struct opp_table *opp_table);
+struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count);
+void dev_pm_opp_put_regulators(struct opp_table *opp_table);
 int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
 int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask);
 int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
@@ -187,12 +187,12 @@ static inline int dev_pm_opp_set_prop_name(struct device *dev, const char *name)
 
 static inline void dev_pm_opp_put_prop_name(struct device *dev) {}
 
-static inline struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
+static inline struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count)
 {
 	return ERR_PTR(-ENOTSUPP);
 }
 
-static inline void dev_pm_opp_put_regulator(struct opp_table *opp_table) {}
+static inline void dev_pm_opp_put_regulators(struct opp_table *opp_table) {}
 
 static inline int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 {
-- 
2.7.1.410.g6faf27b

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH V6 07/10] PM / OPP: Separate out _generic_set_opp()
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel, linux-pm, Vincent Guittot, robh, d-gerlach,
	broonie, devicetree, Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar@linaro.org>

Later patches would add support for custom set_opp() callbacks. This
patch separates out the code for _generic_set_opp() handler in order to
prepare for that.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Dave Gerlach <d-gerlach@ti.com>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>

---
V4->V5:
- Make 'dev' part of struct dev_pm_set_opp_data
- Fix commit log: s/opp_set_rate/set_opp
- Same in a comment as well
---
 drivers/base/power/opp/core.c | 181 +++++++++++++++++++++++++++++-------------
 drivers/base/power/opp/opp.h  |   3 +
 include/linux/pm_opp.h        |  35 ++++++++
 3 files changed, 166 insertions(+), 53 deletions(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index f0dd0bd29ce8..0a53ae132d7f 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -610,6 +610,69 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg,
 	return ret;
 }
 
+static inline int
+_generic_set_opp_clk_only(struct device *dev, struct clk *clk,
+			  unsigned long old_freq, unsigned long freq)
+{
+	int ret;
+
+	ret = clk_set_rate(clk, freq);
+	if (ret) {
+		dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
+			ret);
+	}
+
+	return ret;
+}
+
+static int _generic_set_opp(struct dev_pm_set_opp_data *data)
+{
+	struct dev_pm_opp_supply *old_supply = data->old_opp.supplies;
+	struct dev_pm_opp_supply *new_supply = data->new_opp.supplies;
+	unsigned long old_freq = data->old_opp.rate, freq = data->new_opp.rate;
+	struct regulator *reg = data->regulators[0];
+	struct device *dev= data->dev;
+	int ret;
+
+	/* This function only supports single regulator per device */
+	if (WARN_ON(data->regulator_count > 1)) {
+		dev_err(dev, "multiple regulators are not supported\n");
+		return -EINVAL;
+	}
+
+	/* Scaling up? Scale voltage before frequency */
+	if (freq > old_freq) {
+		ret = _set_opp_voltage(dev, reg, new_supply);
+		if (ret)
+			goto restore_voltage;
+	}
+
+	/* Change frequency */
+	ret = _generic_set_opp_clk_only(dev, data->clk, old_freq, freq);
+	if (ret)
+		goto restore_voltage;
+
+	/* Scaling down? Scale voltage after frequency */
+	if (freq < old_freq) {
+		ret = _set_opp_voltage(dev, reg, new_supply);
+		if (ret)
+			goto restore_freq;
+	}
+
+	return 0;
+
+restore_freq:
+	if (_generic_set_opp_clk_only(dev, data->clk, freq, old_freq))
+		dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
+			__func__, old_freq);
+restore_voltage:
+	/* This shouldn't harm even if the voltages weren't updated earlier */
+	if (old_supply->u_volt)
+		_set_opp_voltage(dev, reg, old_supply);
+
+	return ret;
+}
+
 /**
  * dev_pm_opp_set_rate() - Configure new OPP based on frequency
  * @dev:	 device for which we do this operation
@@ -623,12 +686,12 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg,
 int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 {
 	struct opp_table *opp_table;
+	unsigned long freq, old_freq;
 	struct dev_pm_opp *old_opp, *opp;
-	struct regulator *reg = ERR_PTR(-ENXIO);
+	struct regulator **regulators;
+	struct dev_pm_set_opp_data *data;
 	struct clk *clk;
-	unsigned long freq, old_freq;
-	struct dev_pm_opp_supply old_supply, new_supply;
-	int ret;
+	int ret, size;
 
 	if (unlikely(!target_freq)) {
 		dev_err(dev, "%s: Invalid target frequency %lu\n", __func__,
@@ -677,64 +740,36 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 		return ret;
 	}
 
-	if (opp_table->regulators) {
-		/* This function only supports single regulator per device */
-		if (WARN_ON(opp_table->regulator_count > 1)) {
-			dev_err(dev, "multiple regulators not supported\n");
-			rcu_read_unlock();
-			return -EINVAL;
-		}
+	dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__,
+		old_freq, freq);
 
-		reg = opp_table->regulators[0];
+	regulators = opp_table->regulators;
+
+	/* Only frequency scaling */
+	if (!regulators) {
+		rcu_read_unlock();
+		return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
 	}
 
+	data = opp_table->set_opp_data;
+	data->regulators = regulators;
+	data->regulator_count = opp_table->regulator_count;
+	data->clk = clk;
+	data->dev = dev;
+
+	data->old_opp.rate = old_freq;
+	size = sizeof(*opp->supplies) * opp_table->regulator_count;
 	if (IS_ERR(old_opp))
-		old_supply.u_volt = 0;
+		memset(data->old_opp.supplies, 0, size);
 	else
-		memcpy(&old_supply, old_opp->supplies, sizeof(old_supply));
+		memcpy(data->old_opp.supplies, old_opp->supplies, size);
 
-	memcpy(&new_supply, opp->supplies, sizeof(new_supply));
+	data->new_opp.rate = freq;
+	memcpy(data->new_opp.supplies, opp->supplies, size);
 
 	rcu_read_unlock();
 
-	/* Scaling up? Scale voltage before frequency */
-	if (freq > old_freq) {
-		ret = _set_opp_voltage(dev, reg, &new_supply);
-		if (ret)
-			goto restore_voltage;
-	}
-
-	/* Change frequency */
-
-	dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n",
-		__func__, old_freq, freq);
-
-	ret = clk_set_rate(clk, freq);
-	if (ret) {
-		dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
-			ret);
-		goto restore_voltage;
-	}
-
-	/* Scaling down? Scale voltage after frequency */
-	if (freq < old_freq) {
-		ret = _set_opp_voltage(dev, reg, &new_supply);
-		if (ret)
-			goto restore_freq;
-	}
-
-	return 0;
-
-restore_freq:
-	if (clk_set_rate(clk, old_freq))
-		dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
-			__func__, old_freq);
-restore_voltage:
-	/* This shouldn't harm even if the voltages weren't updated earlier */
-	if (old_supply.u_volt)
-		_set_opp_voltage(dev, reg, &old_supply);
-
-	return ret;
+	return _generic_set_opp(data);
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);
 
@@ -1368,6 +1403,38 @@ void dev_pm_opp_put_prop_name(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name);
 
+static int _allocate_set_opp_data(struct opp_table *opp_table)
+{
+	struct dev_pm_set_opp_data *data;
+	int len, count = opp_table->regulator_count;
+
+	if (WARN_ON(!count))
+		return -EINVAL;
+
+	/* space for set_opp_data */
+	len = sizeof(*data);
+
+	/* space for old_opp.supplies and new_opp.supplies */
+	len += 2 * sizeof(struct dev_pm_opp_supply) * count;
+
+	data = kzalloc(len, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->old_opp.supplies = (void *)(data + 1);
+	data->new_opp.supplies = data->old_opp.supplies + count;
+
+	opp_table->set_opp_data = data;
+
+	return 0;
+}
+
+static void _free_set_opp_data(struct opp_table *opp_table)
+{
+	kfree(opp_table->set_opp_data);
+	opp_table->set_opp_data = NULL;
+}
+
 /**
  * dev_pm_opp_set_regulators() - Set regulator names for the device
  * @dev: Device for which regulator name is being set.
@@ -1437,6 +1504,11 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
 
 	opp_table->regulator_count = count;
 
+	/* Allocate block only once to pass to set_opp() routines */
+	ret = _allocate_set_opp_data(opp_table);
+	if (ret)
+		goto free_regulators;
+
 	mutex_unlock(&opp_table_lock);
 	return opp_table;
 
@@ -1446,6 +1518,7 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
 
 	kfree(opp_table->regulators);
 	opp_table->regulators = NULL;
+	opp_table->regulator_count = 0;
 err:
 	_remove_opp_table(opp_table);
 unlock:
@@ -1482,6 +1555,8 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table)
 	for (i = opp_table->regulator_count - 1; i >= 0; i--)
 		regulator_put(opp_table->regulators[i]);
 
+	_free_set_opp_data(opp_table);
+
 	kfree(opp_table->regulators);
 	opp_table->regulators = NULL;
 	opp_table->regulator_count = 0;
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 5b0f7e53bede..a05e43912c6b 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -141,6 +141,7 @@ enum opp_table_access {
  * @clk: Device's clock handle
  * @regulators: Supply regulators
  * @regulator_count: Number of power supply regulators
+ * @set_opp_data: Data to be passed to set_opp callback
  * @dentry:	debugfs dentry pointer of the real device directory (not links).
  * @dentry_name: Name of the real dentry.
  *
@@ -178,6 +179,8 @@ struct opp_table {
 	struct regulator **regulators;
 	unsigned int regulator_count;
 
+	struct dev_pm_set_opp_data *set_opp_data;
+
 #ifdef CONFIG_DEBUG_FS
 	struct dentry *dentry;
 	char dentry_name[NAME_MAX];
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 9a825ae78653..779b40a9287d 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -17,6 +17,8 @@
 #include <linux/err.h>
 #include <linux/notifier.h>
 
+struct clk;
+struct regulator;
 struct dev_pm_opp;
 struct device;
 struct opp_table;
@@ -41,6 +43,39 @@ struct dev_pm_opp_supply {
 	unsigned long u_amp;
 };
 
+/**
+ * struct dev_pm_opp_info - OPP freq/voltage/current values
+ * @rate:	Target clk rate in hz
+ * @supplies:	Array of voltage/current values for all power supplies
+ *
+ * This structure stores the freq/voltage/current values for a single OPP.
+ */
+struct dev_pm_opp_info {
+	unsigned long rate;
+	struct dev_pm_opp_supply *supplies;
+};
+
+/**
+ * struct dev_pm_set_opp_data - Set OPP data
+ * @old_opp:	Old OPP info
+ * @new_opp:	New OPP info
+ * @regulators:	Array of regulator pointers
+ * @regulator_count: Number of regulators
+ * @clk:	Pointer to clk
+ * @dev:	Pointer to the struct device
+ *
+ * This structure contains all information required for setting an OPP.
+ */
+struct dev_pm_set_opp_data {
+	struct dev_pm_opp_info old_opp;
+	struct dev_pm_opp_info new_opp;
+
+	struct regulator **regulators;
+	unsigned int regulator_count;
+	struct clk *clk;
+	struct device *dev;
+};
+
 #if defined(CONFIG_PM_OPP)
 
 unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
-- 
2.7.1.410.g6faf27b


^ permalink raw reply related

* [PATCH V6 08/10] PM / OPP: Allow platform specific custom set_opp() callbacks
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
	linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
	robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
	broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
	Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

The generic set_opp() handler isn't sufficient for platforms with
complex DVFS.  For example, some TI platforms have multiple regulators
for a CPU device. The order in which various supplies need to be
programmed is only known to the platform code and its best to leave it
to it.

This patch implements APIs to register platform specific set_opp()
callback.

Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Tested-by: Dave Gerlach <d-gerlach-l0cyMroinI0@public.gmane.org>

---
V4->V5:
- s/custom OPP set rate/custom set OPP/
- set_opp() doesn't have a separate 'dev' argument now.
---
 drivers/base/power/opp/core.c | 114 +++++++++++++++++++++++++++++++++++++++++-
 drivers/base/power/opp/opp.h  |   2 +
 include/linux/pm_opp.h        |  10 ++++
 3 files changed, 125 insertions(+), 1 deletion(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 0a53ae132d7f..1fb188b424c7 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -687,6 +687,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 {
 	struct opp_table *opp_table;
 	unsigned long freq, old_freq;
+	int (*set_opp)(struct dev_pm_set_opp_data *data);
 	struct dev_pm_opp *old_opp, *opp;
 	struct regulator **regulators;
 	struct dev_pm_set_opp_data *data;
@@ -751,6 +752,11 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 		return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
 	}
 
+	if (opp_table->set_opp)
+		set_opp = opp_table->set_opp;
+	else
+		set_opp = _generic_set_opp;
+
 	data = opp_table->set_opp_data;
 	data->regulators = regulators;
 	data->regulator_count = opp_table->regulator_count;
@@ -769,7 +775,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 
 	rcu_read_unlock();
 
-	return _generic_set_opp(data);
+	return set_opp(data);
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);
 
@@ -903,6 +909,9 @@ static void _remove_opp_table(struct opp_table *opp_table)
 	if (opp_table->regulators)
 		return;
 
+	if (opp_table->set_opp)
+		return;
+
 	/* Release clk */
 	if (!IS_ERR(opp_table->clk))
 		clk_put(opp_table->clk);
@@ -1570,6 +1579,109 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table)
 EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulators);
 
 /**
+ * dev_pm_opp_register_set_opp_helper() - Register custom set OPP helper
+ * @dev: Device for which the helper is getting registered.
+ * @set_opp: Custom set OPP helper.
+ *
+ * This is useful to support complex platforms (like platforms with multiple
+ * regulators per device), instead of the generic OPP set rate helper.
+ *
+ * This must be called before any OPPs are initialized for the device.
+ *
+ * Locking: The internal opp_table and opp structures are RCU protected.
+ * Hence this function internally uses RCU updater strategy with mutex locks
+ * to keep the integrity of the internal data structures. Callers should ensure
+ * that this function is *NOT* called under RCU protection or in contexts where
+ * mutex cannot be locked.
+ */
+int dev_pm_opp_register_set_opp_helper(struct device *dev,
+			int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+	struct opp_table *opp_table;
+	int ret;
+
+	if (!set_opp)
+		return -EINVAL;
+
+	mutex_lock(&opp_table_lock);
+
+	opp_table = _add_opp_table(dev);
+	if (!opp_table) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	/* This should be called before OPPs are initialized */
+	if (WARN_ON(!list_empty(&opp_table->opp_list))) {
+		ret = -EBUSY;
+		goto err;
+	}
+
+	/* Already have custom set_opp helper */
+	if (WARN_ON(opp_table->set_opp)) {
+		ret = -EBUSY;
+		goto err;
+	}
+
+	opp_table->set_opp = set_opp;
+
+	mutex_unlock(&opp_table_lock);
+	return 0;
+
+err:
+	_remove_opp_table(opp_table);
+unlock:
+	mutex_unlock(&opp_table_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_register_set_opp_helper);
+
+/**
+ * dev_pm_opp_register_put_opp_helper() - Releases resources blocked for
+ *					   set_opp helper
+ * @dev: Device for which custom set_opp helper has to be cleared.
+ *
+ * Locking: The internal opp_table and opp structures are RCU protected.
+ * Hence this function internally uses RCU updater strategy with mutex locks
+ * to keep the integrity of the internal data structures. Callers should ensure
+ * that this function is *NOT* called under RCU protection or in contexts where
+ * mutex cannot be locked.
+ */
+void dev_pm_opp_register_put_opp_helper(struct device *dev)
+{
+	struct opp_table *opp_table;
+
+	mutex_lock(&opp_table_lock);
+
+	/* Check for existing table for 'dev' first */
+	opp_table = _find_opp_table(dev);
+	if (IS_ERR(opp_table)) {
+		dev_err(dev, "Failed to find opp_table: %ld\n",
+			PTR_ERR(opp_table));
+		goto unlock;
+	}
+
+	if (!opp_table->set_opp) {
+		dev_err(dev, "%s: Doesn't have custom set_opp helper set\n",
+			__func__);
+		goto unlock;
+	}
+
+	/* Make sure there are no concurrent readers while updating opp_table */
+	WARN_ON(!list_empty(&opp_table->opp_list));
+
+	opp_table->set_opp = NULL;
+
+	/* Try freeing opp_table if this was the last blocking resource */
+	_remove_opp_table(opp_table);
+
+unlock:
+	mutex_unlock(&opp_table_lock);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_register_put_opp_helper);
+
+/**
  * dev_pm_opp_add()  - Add an OPP table from a table definitions
  * @dev:	device for which we do this operation
  * @freq:	Frequency in Hz for this OPP
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index a05e43912c6b..af9f2b849a66 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -141,6 +141,7 @@ enum opp_table_access {
  * @clk: Device's clock handle
  * @regulators: Supply regulators
  * @regulator_count: Number of power supply regulators
+ * @set_opp: Platform specific set_opp callback
  * @set_opp_data: Data to be passed to set_opp callback
  * @dentry:	debugfs dentry pointer of the real device directory (not links).
  * @dentry_name: Name of the real dentry.
@@ -179,6 +180,7 @@ struct opp_table {
 	struct regulator **regulators;
 	unsigned int regulator_count;
 
+	int (*set_opp)(struct dev_pm_set_opp_data *data);
 	struct dev_pm_set_opp_data *set_opp_data;
 
 #ifdef CONFIG_DEBUG_FS
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 779b40a9287d..0edd88f93904 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -116,6 +116,8 @@ int dev_pm_opp_set_prop_name(struct device *dev, const char *name);
 void dev_pm_opp_put_prop_name(struct device *dev);
 struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count);
 void dev_pm_opp_put_regulators(struct opp_table *opp_table);
+int dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
+void dev_pm_opp_register_put_opp_helper(struct device *dev);
 int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
 int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask);
 int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
@@ -215,6 +217,14 @@ static inline int dev_pm_opp_set_supported_hw(struct device *dev,
 
 static inline void dev_pm_opp_put_supported_hw(struct device *dev) {}
 
+static inline int dev_pm_opp_register_set_opp_helper(struct device *dev,
+			int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+	return -ENOTSUPP;
+}
+
+static inline void dev_pm_opp_register_put_opp_helper(struct device *dev) {}
+
 static inline int dev_pm_opp_set_prop_name(struct device *dev, const char *name)
 {
 	return -ENOTSUPP;
-- 
2.7.1.410.g6faf27b

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH V6 09/10] PM / OPP: Don't WARN on multiple calls to dev_pm_opp_set_regulators()
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
	linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
	robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
	broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
	Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

If a platform specific OPP driver has called this routine first and set
the regulators, then the second call from cpufreq-dt driver will hit the
WARN_ON(). Remove the WARN_ON(), but continue to return error in such
cases.

Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Reviewed-by: Stephen Boyd <sboyd-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
Tested-by: Dave Gerlach <d-gerlach-l0cyMroinI0@public.gmane.org>
---
 drivers/base/power/opp/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 1fb188b424c7..f60d7f943b64 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -1485,7 +1485,7 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
 	}
 
 	/* Already have regulators set */
-	if (WARN_ON(opp_table->regulators)) {
+	if (opp_table->regulators) {
 		opp_table = ERR_PTR(-EBUSY);
 		goto err;
 	}
-- 
2.7.1.410.g6faf27b

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH V6 10/10] PM / OPP: Don't assume platform doesn't have regulators
From: Viresh Kumar @ 2016-11-30  5:14 UTC (permalink / raw)
  To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
  Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
	linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
	robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
	broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
	Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

If the regulators aren't set explicitly by the platform, the OPP core
assumes that the platform doesn't have any regulator and uses the
clk-only callback.

If the platform failed to register a regulator with the core, then this
can turn out to be a dangerous assumption as the OPP core will try to
change clk without changing regulators.

Handle that properly by making sure that the DT didn't have any entries
for supply voltages as well.

Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Reviewed-by: Stephen Boyd <sboyd-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
 drivers/base/power/opp/core.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index f60d7f943b64..979403448cd6 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -748,7 +748,20 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 
 	/* Only frequency scaling */
 	if (!regulators) {
+		unsigned long u_volt = opp->supplies[0].u_volt;
+
 		rcu_read_unlock();
+
+		/*
+		 * DT contained supply ratings? Consider platform failed to set
+		 * regulators.
+		 */
+		if (unlikely(u_volt)) {
+			dev_err(dev, "%s: Regulator not registered with OPP core\n",
+				__func__);
+			return -EINVAL;
+		}
+
 		return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
 	}
 
-- 
2.7.1.410.g6faf27b

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 0/3] Support for USB DRD Phy driver for NS2
From: Raviteja Garimella @ 2016-11-30  5:55 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
	Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

This patch adds support for USB Dual Role Device Phy for Broadcom
Northstar2 SoC. Apart from the new phy driver, this patchset contains
changes to Kconfig, Makefile, and Device tree files.

This patchset is tested on Broadcom NS2 BCM958712K reference board.

Repo: https://github.com/Broadcom/arm64-linux.git
Branch: ns2_drdphy

Raviteja Garimella (3):
  Add DT bindings documentation for NS2 USB DRD phy
  Broadcom USB DRD Phy driver for Northstar2
  DT nodes for Broadcom Northstar2 USB DRD Phy

 .../devicetree/bindings/phy/brcm,ns2-drd-phy.txt   |  40 ++
 arch/arm64/boot/dts/broadcom/ns2.dtsi              |  14 +
 drivers/phy/Kconfig                                |  13 +
 drivers/phy/Makefile                               |   1 +
 drivers/phy/phy-bcm-ns2-usbdrd.c                   | 587 +++++++++++++++++++++
 5 files changed, 655 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt
 create mode 100644 drivers/phy/phy-bcm-ns2-usbdrd.c

-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCH 1/3] Add DT bindings documentation for NS2 USB DRD phy
From: Raviteja Garimella @ 2016-11-30  5:55 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
	Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1480485338-23451-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>

This patch adds documentation for NS2 DRD Phy driver DT bindings

Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
 .../devicetree/bindings/phy/brcm,ns2-drd-phy.txt   | 40 ++++++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt

diff --git a/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt
new file mode 100644
index 0000000..5857f99
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt
@@ -0,0 +1,40 @@
+BROADCOM NORTHSTAR2 USB2 (DUAL ROLE DEVICE) PHY
+
+Required properties:
+ - compatible: brcm,ns2-drd-phy
+ - reg: offset and length of the NS2 PHY related registers.
+ - reg-names
+   The below registers must be provided.
+   icfg - for DRD ICFG configurations
+   rst-ctrl - for DRD IDM reset
+   crmu-ctrl - for CRMU core vdd, PHY and PHY PLL reset
+   usb2-strap - for port over current polarity reversal
+ - #phy-cells: Must be 0. No args required.
+ - vbus-gpios: vbus gpio binding
+ - id-gpios: id gpio binding
+
+Refer to phy/phy-bindings.txt for the generic PHY binding properties
+
+Example:
+	gpio_g: gpio@660a0000 {
+			compatible = "brcm,iproc-gpio";
+			reg = <0x660a0000 0x50>;
+			ngpios = <32>;
+			#gpio-cells = <2>;
+			gpio-controller;
+			interrupt-controller;
+			interrupts = <GIC_SPI 400 IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	usbdrd_phy: phy@66000960 {
+			#phy-cells = <0>;
+			compatible = "brcm,ns2-drd-phy";
+			reg = <0x66000960 0x24>,
+			      <0x67012800 0x4>,
+			      <0x6501d148 0x4>,
+			      <0x664d0700 0x4>;
+			reg-names = "icfg", "rst-ctrl",
+				    "crmu-ctrl", "usb2-strap";
+			id-gpios = <&gpio_g 30 0>;
+			vbus-gpios = <&gpio_g 31 0>;
+	};
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 2/3] Broadcom USB DRD Phy driver for Northstar2
From: Raviteja Garimella @ 2016-11-30  5:55 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
	Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1480485338-23451-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>

This is driver for USB DRD Phy used in Broadcom's Northstar2
SoC. The phy can be configured to be in Device mode or Host
mode based on the type of cable connected to the port. The
driver registers to  extcon framework to get appropriate
connect events for Host/Device cables connect/disconnect
states based on VBUS and ID interrupts.

Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
 drivers/phy/Kconfig              |  13 +
 drivers/phy/Makefile             |   1 +
 drivers/phy/phy-bcm-ns2-usbdrd.c | 587 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 601 insertions(+)
 create mode 100644 drivers/phy/phy-bcm-ns2-usbdrd.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index fe00f91..b3b6a73 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -479,6 +479,19 @@ config PHY_CYGNUS_PCIE
 	  Enable this to support the Broadcom Cygnus PCIe PHY.
 	  If unsure, say N.
 
+config PHY_NS2_USB_DRD
+	tristate "Broadcom Northstar2 USB DRD PHY support"
+	depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
+	select GENERIC_PHY
+	select EXTCON
+	default ARCH_BCM_IPROC
+	help
+	  Enable this to support the Broadcom Northstar2 USB DRD PHY.
+	  This driver initializes the PHY in either HOST or DEVICE mode.
+	  The host or device configuration is read from device tree.
+
+	  If unsure, say N.
+
 source "drivers/phy/tegra/Kconfig"
 
 config PHY_NS2_PCIE
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index a534cf5..b733ba2 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -58,5 +58,6 @@ obj-$(CONFIG_PHY_TUSB1210)		+= phy-tusb1210.o
 obj-$(CONFIG_PHY_BRCM_SATA)		+= phy-brcm-sata.o
 obj-$(CONFIG_PHY_PISTACHIO_USB)		+= phy-pistachio-usb.o
 obj-$(CONFIG_PHY_CYGNUS_PCIE)		+= phy-bcm-cygnus-pcie.o
+obj-$(CONFIG_PHY_NS2_USB_DRD)		+= phy-bcm-ns2-usbdrd.o
 obj-$(CONFIG_ARCH_TEGRA) += tegra/
 obj-$(CONFIG_PHY_NS2_PCIE)		+= phy-bcm-ns2-pcie.o
diff --git a/drivers/phy/phy-bcm-ns2-usbdrd.c b/drivers/phy/phy-bcm-ns2-usbdrd.c
new file mode 100644
index 0000000..460040d
--- /dev/null
+++ b/drivers/phy/phy-bcm-ns2-usbdrd.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/extcon.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define ICFG_DRD_AFE		0x0
+#define ICFG_MISC_STAT		0x18
+#define ICFG_DRD_P0CTL		0x1C
+#define ICFG_STRAP_CTRL		0x20
+#define ICFG_FSM_CTRL		0x24
+
+#define IDM_RST_BIT		BIT(0)
+#define AFE_CORERDY_VDDC	BIT(18)
+#define PHY_PLL_RESETB		BIT(15)
+#define PHY_RESETB		BIT(14)
+#define PHY_PLL_LOCK		BIT(0)
+
+#define DRD_DEV_MODE		BIT(20)
+#define OHCI_OVRCUR_POL		BIT(11)
+#define ICFG_OFF_MODE		BIT(6)
+#define PLL_LOCK_RETRY		1000
+
+#define EVT_DEVICE		0
+#define EVT_HOST		1
+#define EVT_IDLE		2
+
+#define DRD_HOST_MODE		(BIT(2) | BIT(3))
+#define DRD_DEVICE_MODE		(BIT(4) | BIT(5))
+#define DRD_HOST_VAL		0x803
+#define DRD_DEV_VAL		0x807
+#define GPIO_DELAY		20
+#define PHY_WQ_DELAY		msecs_to_jiffies(600)
+
+struct ns2_phy_data;
+struct ns2_phy_driver {
+	void __iomem *icfgdrd_regs;
+	void __iomem *idmdrd_rst_ctrl;
+	void __iomem *crmu_usb2_ctrl;
+	void __iomem *usb2h_strap_reg;
+	spinlock_t lock; /* spin lock for phy driver */
+	bool host_mode;
+	struct ns2_phy_data *data;
+	struct extcon_specific_cable_nb extcon_dev;
+	struct extcon_specific_cable_nb extcon_host;
+	struct notifier_block host_nb;
+	struct notifier_block dev_nb;
+	struct delayed_work conn_work;
+	struct extcon_dev *edev;
+	struct gpio_desc *vbus_gpiod;
+	struct gpio_desc *id_gpiod;
+	int id_irq;
+	int vbus_irq;
+	unsigned long debounce_jiffies;
+	struct delayed_work wq_extcon;
+};
+
+struct ns2_phy_data {
+	struct ns2_phy_driver *driver;
+	struct phy *phy;
+	int new_state;
+	bool poweron;
+};
+
+static const unsigned int usb_extcon_cable[] = {
+	EXTCON_USB,
+	EXTCON_USB_HOST,
+	EXTCON_NONE,
+};
+
+static inline int pll_lock_stat(u32 usb_reg, int reg_mask,
+				struct ns2_phy_driver *driver)
+{
+	int retry = PLL_LOCK_RETRY;
+	u32 val;
+
+	do {
+		udelay(1);
+		val = readl(driver->icfgdrd_regs + usb_reg);
+		if (val & reg_mask)
+			return 0;
+	} while (--retry > 0);
+
+	return -EBUSY;
+}
+
+static int ns2_drd_phy_init(struct phy *phy)
+{
+	struct ns2_phy_data *data = phy_get_drvdata(phy);
+	struct ns2_phy_driver *driver = data->driver;
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(&driver->lock, flags);
+
+	val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+	if (data->new_state == EVT_HOST) {
+		val &= ~DRD_DEVICE_MODE;
+		val |= DRD_HOST_MODE;
+	} else {
+		val &= ~DRD_HOST_MODE;
+		val |= DRD_DEVICE_MODE;
+	}
+	writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+	spin_unlock_irqrestore(&driver->lock, flags);
+	return 0;
+}
+
+static int ns2_drd_phy_shutdown(struct phy *phy)
+{
+	struct ns2_phy_data *data = phy_get_drvdata(phy);
+	struct ns2_phy_driver *driver = data->driver;
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(&driver->lock, flags);
+	if (!data->poweron)
+		goto exit;
+
+	val = readl(driver->crmu_usb2_ctrl);
+	val &= ~AFE_CORERDY_VDDC;
+	writel(val, driver->crmu_usb2_ctrl);
+
+	driver->host_mode = 0;
+	val = readl(driver->crmu_usb2_ctrl);
+	val &= ~DRD_DEV_MODE;
+	writel(val, driver->crmu_usb2_ctrl);
+
+	/* Disable Host and Device Mode */
+	val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+	val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE);
+	writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+	data->poweron = 0;
+exit:
+	spin_unlock_irqrestore(&driver->lock, flags);
+	return 0;
+}
+
+static int ns2_drd_phy_poweron(struct phy *phy)
+{
+	struct ns2_phy_data *data = phy_get_drvdata(phy);
+	struct ns2_phy_driver *driver = data->driver;
+	u32 extcon_event = data->new_state;
+	unsigned long flags;
+	int ret;
+	u32 val;
+
+	spin_lock_irqsave(&driver->lock, flags);
+	if (extcon_event == EVT_DEVICE) {
+		if (data->poweron)
+			goto exit;
+
+		writel(DRD_DEV_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+
+		val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+		val &= ~(DRD_HOST_MODE | ICFG_OFF_MODE);
+		val |= DRD_DEVICE_MODE;
+		writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+		val = readl(driver->idmdrd_rst_ctrl);
+		val &= ~IDM_RST_BIT;
+		writel(val, driver->idmdrd_rst_ctrl);
+
+		val = readl(driver->crmu_usb2_ctrl);
+		val |= (AFE_CORERDY_VDDC | DRD_DEV_MODE);
+		writel(val, driver->crmu_usb2_ctrl);
+
+		/* Bring PHY and PHY_PLL out of Reset */
+		val = readl(driver->crmu_usb2_ctrl);
+		val |= (PHY_PLL_RESETB | PHY_RESETB);
+		writel(val, driver->crmu_usb2_ctrl);
+
+		ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
+		if (ret < 0) {
+			dev_err(&phy->dev, "Phy PLL lock failed\n");
+			goto err_shutdown;
+		}
+	} else {
+		if (data->poweron && driver->host_mode)
+			goto exit;
+
+		writel(DRD_HOST_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+
+		val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+		val &= ~(DRD_DEVICE_MODE | ICFG_OFF_MODE);
+		val |= DRD_HOST_MODE;
+		writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+		val = readl(driver->crmu_usb2_ctrl);
+		val |= AFE_CORERDY_VDDC;
+		writel(val, driver->crmu_usb2_ctrl);
+
+		ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
+		if (ret < 0) {
+			dev_err(&phy->dev, "Phy PLL lock failed\n");
+			goto err_shutdown;
+		}
+
+		val = readl(driver->idmdrd_rst_ctrl);
+		val &= ~IDM_RST_BIT;
+		writel(val, driver->idmdrd_rst_ctrl);
+
+		/* port over current Polarity */
+		val = readl(driver->usb2h_strap_reg);
+		val |= OHCI_OVRCUR_POL;
+		writel(val, driver->usb2h_strap_reg);
+
+		driver->host_mode = 1;
+	}
+
+	data->poweron = 1;
+exit:
+	spin_unlock_irqrestore(&driver->lock, flags);
+	return 0;
+
+err_shutdown:
+	data->poweron = 1;
+	spin_unlock_irqrestore(&driver->lock, flags);
+	ns2_drd_phy_shutdown(phy);
+	return ret;
+}
+
+static void connect_work(struct work_struct *work)
+{
+	struct ns2_phy_driver *driver;
+	struct ns2_phy_data *data;
+	u32 extcon_event;
+
+	driver  = container_of(to_delayed_work(work),
+			       struct ns2_phy_driver, conn_work);
+	data = driver->data;
+	extcon_event = data->new_state;
+
+	if (extcon_event == EVT_DEVICE || extcon_event == EVT_HOST) {
+		ns2_drd_phy_init(data->phy);
+		ns2_drd_phy_poweron(data->phy);
+	} else if (extcon_event == EVT_IDLE) {
+		ns2_drd_phy_shutdown(data->phy);
+	}
+}
+
+static int drd_device_notify(struct notifier_block *self,
+			     unsigned long event, void *ptr)
+{
+	struct ns2_phy_driver *driver = container_of(self,
+					struct ns2_phy_driver, dev_nb);
+
+	if (event) {
+		pr_debug("Device connected\n");
+		driver->data->new_state = EVT_DEVICE;
+		schedule_delayed_work(&driver->conn_work, 0);
+	} else {
+		pr_debug("Device disconnected\n");
+		driver->data->new_state = EVT_IDLE;
+		schedule_delayed_work(&driver->conn_work, PHY_WQ_DELAY);
+	}
+
+	return NOTIFY_DONE;
+}
+
+static int drd_host_notify(struct notifier_block *self,
+			   unsigned long event, void *ptr)
+{
+	struct ns2_phy_driver *driver = container_of(self,
+					struct ns2_phy_driver, host_nb);
+
+	if (event) {
+		pr_debug("Host connected\n");
+		driver->data->new_state = EVT_HOST;
+		schedule_delayed_work(&driver->conn_work, 0);
+	} else {
+		pr_debug("Host disconnected\n");
+		driver->data->new_state = EVT_IDLE;
+		schedule_delayed_work(&driver->conn_work, PHY_WQ_DELAY);
+	}
+
+	return NOTIFY_DONE;
+}
+
+static void extcon_work(struct work_struct *work)
+{
+	struct ns2_phy_driver *driver;
+	int vbus;
+	int id;
+
+	driver  = container_of(to_delayed_work(work),
+			       struct ns2_phy_driver, wq_extcon);
+
+	id = gpiod_get_value_cansleep(driver->id_gpiod);
+	vbus = gpiod_get_value_cansleep(driver->vbus_gpiod);
+
+	if (!id && vbus) {
+		extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, true);
+	} else if (id && !vbus) {
+		extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, false);
+		extcon_set_cable_state_(driver->edev, EXTCON_USB, false);
+	} else if (id && vbus) {
+		extcon_set_cable_state_(driver->edev, EXTCON_USB, true);
+	}
+}
+
+static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
+{
+	struct ns2_phy_driver *driver = dev_id;
+
+	queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
+			   driver->debounce_jiffies);
+
+	return IRQ_HANDLED;
+}
+
+static int register_extcon_notifier(struct ns2_phy_driver *phy_driver,
+				    struct device *dev)
+{
+	struct extcon_dev *edev;
+	int ret;
+
+	phy_driver->host_nb.notifier_call = drd_host_notify;
+	phy_driver->dev_nb.notifier_call = drd_device_notify;
+
+	edev = phy_driver->edev;
+
+	/* Register for device change notification */
+	ret = extcon_register_notifier(edev, EXTCON_USB,
+				       &phy_driver->dev_nb);
+	if (ret < 0) {
+		dev_err(dev, "can't register extcon_dev for %s\n", edev->name);
+		return ret;
+	}
+
+	/* Register for host change notification */
+	ret = extcon_register_notifier(edev, EXTCON_USB_HOST,
+				       &phy_driver->host_nb);
+	if (ret < 0) {
+		dev_err(dev, "can't register extcon_dev for %s\n", edev->name);
+		goto err_dev;
+	}
+
+	/* Check the device cable connect state */
+	ret = extcon_get_cable_state_(edev, EXTCON_USB);
+	if (ret < 0) {
+		dev_err(dev, "can't get extcon_dev state for %s\n", edev->name);
+		goto err_host;
+	} else if (ret) {
+		phy_driver->data->new_state = EVT_DEVICE;
+	}
+
+	/* Check the host cable connect state */
+	ret = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
+	if (ret < 0) {
+		dev_err(dev, "can't get extcon_dev state for %s\n", edev->name);
+		goto err_host;
+	} else if (ret) {
+		phy_driver->data->new_state = EVT_HOST;
+	}
+
+	return 0;
+
+err_host:
+	ret = extcon_unregister_notifier(edev, EXTCON_USB_HOST,
+					&phy_driver->host_nb);
+err_dev:
+	ret = extcon_unregister_notifier(edev, EXTCON_USB,
+					&phy_driver->dev_nb);
+	return ret;
+}
+
+static struct phy_ops ops = {
+	.init		= ns2_drd_phy_init,
+	.power_on	= ns2_drd_phy_poweron,
+	.power_off	= ns2_drd_phy_shutdown,
+};
+
+static const struct of_device_id ns2_drd_phy_dt_ids[] = {
+	{ .compatible = "brcm,ns2-drd-phy", },
+	{ }
+};
+
+static int ns2_drd_phy_remove(struct platform_device *pdev)
+{
+	struct ns2_phy_driver *driver = dev_get_drvdata(&pdev->dev);
+
+	if (driver->edev) {
+		extcon_unregister_notifier(driver->edev, EXTCON_USB_HOST,
+					  &driver->host_nb);
+		extcon_unregister_notifier(driver->edev, EXTCON_USB,
+					  &driver->dev_nb);
+	}
+
+	return 0;
+}
+static int ns2_drd_phy_probe(struct platform_device *pdev)
+{
+	struct phy_provider *phy_provider;
+	struct device *dev = &pdev->dev;
+	struct ns2_phy_driver *driver;
+	struct ns2_phy_data *data;
+	struct resource *res;
+	int ret;
+	u32 val;
+
+	driver = devm_kzalloc(dev, sizeof(struct ns2_phy_driver),
+			      GFP_KERNEL);
+	if (!driver)
+		return -ENOMEM;
+
+	driver->data = devm_kzalloc(dev, sizeof(struct ns2_phy_data),
+				  GFP_KERNEL);
+	if (!driver->data)
+		return -ENOMEM;
+
+	spin_lock_init(&driver->lock);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "icfg");
+	driver->icfgdrd_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(driver->icfgdrd_regs))
+		return PTR_ERR(driver->icfgdrd_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rst-ctrl");
+	driver->idmdrd_rst_ctrl = devm_ioremap_resource(dev, res);
+	if (IS_ERR(driver->idmdrd_rst_ctrl))
+		return PTR_ERR(driver->idmdrd_rst_ctrl);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "crmu-ctrl");
+	driver->crmu_usb2_ctrl = devm_ioremap_resource(dev, res);
+	if (IS_ERR(driver->crmu_usb2_ctrl))
+		return PTR_ERR(driver->crmu_usb2_ctrl);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2-strap");
+	driver->usb2h_strap_reg = devm_ioremap_resource(dev, res);
+	if (IS_ERR(driver->usb2h_strap_reg))
+		return PTR_ERR(driver->usb2h_strap_reg);
+
+	 /* create extcon */
+	driver->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN);
+	if (IS_ERR(driver->id_gpiod)) {
+		dev_err(dev, "failed to get ID GPIO\n");
+		return PTR_ERR(driver->id_gpiod);
+	}
+	driver->vbus_gpiod = devm_gpiod_get(&pdev->dev, "vbus", GPIOD_IN);
+	if (IS_ERR(driver->vbus_gpiod)) {
+		dev_err(dev, "failed to get VBUS GPIO\n");
+		return PTR_ERR(driver->vbus_gpiod);
+	}
+
+	driver->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
+	if (IS_ERR(driver->edev)) {
+		dev_err(dev, "failed to allocate extcon device\n");
+		return -ENOMEM;
+	}
+
+	ret = devm_extcon_dev_register(dev, driver->edev);
+	if (ret < 0) {
+		dev_err(dev, "failed to register extcon device\n");
+		goto extcon_free;
+	}
+
+	ret = gpiod_set_debounce(driver->id_gpiod, GPIO_DELAY * 1000);
+	if (ret < 0)
+		driver->debounce_jiffies = msecs_to_jiffies(GPIO_DELAY);
+
+	INIT_DELAYED_WORK(&driver->wq_extcon, extcon_work);
+
+	driver->id_irq = gpiod_to_irq(driver->id_gpiod);
+	if (driver->id_irq < 0) {
+		dev_err(dev, "failed to get ID IRQ\n");
+		ret = driver->id_irq;
+		goto extcon_unregister;
+	}
+	driver->vbus_irq = gpiod_to_irq(driver->vbus_gpiod);
+	if (driver->vbus_irq < 0) {
+		dev_err(dev, "failed to get ID IRQ\n");
+		ret = driver->vbus_irq;
+		goto extcon_unregister;
+	}
+
+	ret = devm_request_threaded_irq(dev, driver->id_irq, NULL,
+					gpio_irq_handler,
+					IRQF_TRIGGER_RISING |
+					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+					"usb_id", driver);
+	if (ret < 0) {
+		dev_err(dev, "failed to request handler for ID IRQ\n");
+		goto extcon_unregister;
+	}
+	ret = devm_request_threaded_irq(dev, driver->vbus_irq, NULL,
+					gpio_irq_handler,
+					IRQF_TRIGGER_RISING |
+					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+					"usb_vbus", driver);
+	if (ret < 0) {
+		dev_err(dev, "failed to request handler for VBUS IRQ\n");
+		goto extcon_unregister;
+	}
+
+	dev_set_drvdata(dev, driver);
+	driver->host_mode = 0;
+
+	/* Shutdown all ports. They can be powered up as required */
+	val = readl(driver->crmu_usb2_ctrl);
+	val &= ~(AFE_CORERDY_VDDC | PHY_RESETB);
+	writel(val, driver->crmu_usb2_ctrl);
+
+	data = driver->data;
+	data->phy = devm_phy_create(dev, dev->of_node, &ops);
+	if (IS_ERR(data->phy)) {
+		dev_err(dev, "Failed to create usb drd phy\n");
+		ret = PTR_ERR(data->phy);
+		goto extcon_unregister;
+	}
+
+	data->driver = driver;
+	data->poweron = 0;
+	phy_set_drvdata(data->phy, data);
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy_provider)) {
+		dev_err(dev, "Failed to register as phy provider\n");
+		ret = PTR_ERR(phy_provider);
+		goto extcon_unregister;
+	}
+
+	INIT_DELAYED_WORK(&driver->conn_work, connect_work);
+	platform_set_drvdata(pdev, driver);
+
+	ret = register_extcon_notifier(driver, dev);
+	if (ret < 0) {
+		dev_err(dev, "register extcon notifier failed (%d)\n", ret);
+		goto extcon_unregister;
+	}
+	dev_info(dev, "Registered %s\n", driver->edev->name);
+	queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
+			   driver->debounce_jiffies);
+
+	return 0;
+
+extcon_unregister:
+	devm_extcon_dev_unregister(dev, driver->edev);
+extcon_free:
+	devm_extcon_dev_free(dev, driver->edev);
+	return ret;
+}
+
+MODULE_DEVICE_TABLE(of, ns2_drd_phy_dt_ids);
+
+static struct platform_driver ns2_drd_phy_driver = {
+	.probe = ns2_drd_phy_probe,
+	.remove = ns2_drd_phy_remove,
+	.driver = {
+		.name = "bcm-ns2-usbphy",
+		.of_match_table = of_match_ptr(ns2_drd_phy_dt_ids),
+	},
+};
+module_platform_driver(ns2_drd_phy_driver);
+
+MODULE_ALIAS("platform:bcm-ns2-drd-phy");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom NS2 USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 3/3] DT nodes for Broadcom Northstar2 USB DRD Phy
From: Raviteja Garimella @ 2016-11-30  5:55 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
	Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
  Cc: devicetree, linux-kernel, bcm-kernel-feedback-list,
	linux-arm-kernel
In-Reply-To: <1480485338-23451-1-git-send-email-raviteja.garimella@broadcom.com>

This patch adds device tree nodes for USB Dual Role Device Phy for
Broadcom's Northstar2 SoC.

Signed-off-by: Raviteja Garimella <raviteja.garimella@broadcom.com>
---
 arch/arm64/boot/dts/broadcom/ns2.dtsi | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/arch/arm64/boot/dts/broadcom/ns2.dtsi b/arch/arm64/boot/dts/broadcom/ns2.dtsi
index d95dc40..e9d3496 100644
--- a/arch/arm64/boot/dts/broadcom/ns2.dtsi
+++ b/arch/arm64/boot/dts/broadcom/ns2.dtsi
@@ -299,6 +299,20 @@
 			};
 		};
 
+		usbdrd_phy: phy@66000960 {
+			#phy-cells = <0>;
+			compatible = "brcm,ns2-drd-phy";
+			reg = <0x66000960 0x24>,
+			      <0x67012800 0x4>,
+			      <0x6501d148 0x4>,
+			      <0x664d0700 0x4>;
+			reg-names = "icfg", "rst-ctrl",
+				    "crmu-ctrl", "usb2-strap";
+			id-gpios = <&gpio_g 30 0>;
+			vbus-gpios = <&gpio_g 31 0>;
+			status = "disabled";
+		};
+
 		pwm: pwm@66010000 {
 			compatible = "brcm,iproc-pwm";
 			reg = <0x66010000 0x28>;
-- 
2.1.0

^ permalink raw reply related

* [PATCH 0/2] Support for Synopsys UDC for ARM platforms
From: Raviteja Garimella @ 2016-11-30  6:05 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Greg Kroah-Hartman, Felipe Balbi
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	linux-usb-u79uwXL29TY76Z2rM5mHXA

This patchset adds support for Synposys Designware core AHB-UDC
(USB Device controller) for Arm platfoms.

New UDC driver is added to drivers/usb/gadget directory along with
updating the Kconfig and Makefile. 
DT bindings documentation is also added for the same.
Device tree entry for the same in NS2 dtsi will be sent for review
once the DRD phy driver code is pushed (which is being reviewed in a
separate patch series).

This patchset is tested on Broadcom NS2 BCM958712K reference board.

Repo: https://github.com/Broadcom/arm64-linux.git
Branch: udc_v1

Raviteja Garimella (2):
  Add DT bindings documentation for Synopsys UDC driver
  Synopsys USB 2.0 Device Controller (UDC) Driver

 .../devicetree/bindings/usb/snps,dw-ahb-udc.txt    |   29 +
 drivers/usb/gadget/udc/Kconfig                     |   12 +
 drivers/usb/gadget/udc/Makefile                    |    1 +
 drivers/usb/gadget/udc/snps_udc.c                  | 1751 ++++++++++++++++++++
 drivers/usb/gadget/udc/snps_udc.h                  | 1071 ++++++++++++
 5 files changed, 2864 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
 create mode 100644 drivers/usb/gadget/udc/snps_udc.c
 create mode 100644 drivers/usb/gadget/udc/snps_udc.h

-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCH 1/2] Add DT bindings documentation for Synopsys UDC driver
From: Raviteja Garimella @ 2016-11-30  6:05 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Greg Kroah-Hartman, Felipe Balbi
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	linux-usb-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480485910-7797-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>

This patch adds documentation for Synopsis Designware Cores AHB
Subsystem Device Controller (UDC).

Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
 .../devicetree/bindings/usb/snps,dw-ahb-udc.txt    | 29 ++++++++++++++++++++++
 1 file changed, 29 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt

diff --git a/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
new file mode 100644
index 0000000..64e1fbf
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
@@ -0,0 +1,29 @@
+Synopsys USB Device controller.
+
+The device node is used for Synopsys Designware Cores AHB
+Subsystem Device Controller (UDC).
+
+Required properties:
+ - compatible: should be "snps,dw-ahbudc"
+ - reg: Offset and length of UDC register set
+ - interrupts: description of interrupt line
+ - phys: phandle to phy node.
+ - phy-names: name of phy node. Must be usb2drd.
+ - extcon: phandle to the extcon device
+
+Example:
+
+	usbdrd_phy: phy@6501c000 {
+		#phy-cells = <0>;
+		compatible = "brcm,ns2-drd-phy";
+		reg = <0x66000000 0x1000>,
+	}
+
+	udc_dwc: usb@664e0000 {
+		compatible = "snps,dw-ahb-udc";
+		reg = <0x664e0000 0x2000>;
+		interrupts = <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>;
+		phys = <&usbdrd_phy>;
+		phy-names = "usb2drd";
+		extcon = <&usbdrd_phy>";
+	};
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver
From: Raviteja Garimella @ 2016-11-30  6:05 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Greg Kroah-Hartman, Felipe Balbi
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	linux-usb-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480485910-7797-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>

This is driver for Synopsys Designware Cores USB Device
Controller (UDC) Subsystem with the AMBA Advanced High-Performance
Bus (AHB). This driver works with Synopsys UDC20 products.

Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
 drivers/usb/gadget/udc/Kconfig    |   12 +
 drivers/usb/gadget/udc/Makefile   |    1 +
 drivers/usb/gadget/udc/snps_udc.c | 1751 +++++++++++++++++++++++++++++++++++++
 drivers/usb/gadget/udc/snps_udc.h | 1071 +++++++++++++++++++++++
 4 files changed, 2835 insertions(+)
 create mode 100644 drivers/usb/gadget/udc/snps_udc.c
 create mode 100644 drivers/usb/gadget/udc/snps_udc.h

diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index 658b8da..28cd679 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -239,6 +239,18 @@ config USB_MV_U3D
 	  MARVELL PXA2128 Processor series include a super speed USB3.0 device
 	  controller, which support super speed USB peripheral.
 
+config USB_SNP_UDC
+	tristate "Synopsys USB 2.0 Device controller"
+	select USB_GADGET_DUALSPEED
+	depends on (ARM || ARM64) && USB_GADGET
+	default ARCH_BCM_IPROC
+	help
+	  This adds Device support for Synopsys Designware core
+	  AHB subsystem USB2.0 Device Controller(UDC) .
+
+	  This driver works with Synopsys UDC20 products.
+	  If unsure, say N.
+
 #
 # Controllers available in both integrated and discrete versions
 #
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
index 98e74ed..2b63a2b 100644
--- a/drivers/usb/gadget/udc/Makefile
+++ b/drivers/usb/gadget/udc/Makefile
@@ -36,4 +36,5 @@ obj-$(CONFIG_USB_FOTG210_UDC)	+= fotg210-udc.o
 obj-$(CONFIG_USB_MV_U3D)	+= mv_u3d_core.o
 obj-$(CONFIG_USB_GR_UDC)	+= gr_udc.o
 obj-$(CONFIG_USB_GADGET_XILINX)	+= udc-xilinx.o
+obj-$(CONFIG_USB_SNP_UDC)	+= snps_udc.o
 obj-$(CONFIG_USB_BDC_UDC)	+= bdc/
diff --git a/drivers/usb/gadget/udc/snps_udc.c b/drivers/usb/gadget/udc/snps_udc.c
new file mode 100644
index 0000000..d8c46ce
--- /dev/null
+++ b/drivers/usb/gadget/udc/snps_udc.c
@@ -0,0 +1,1751 @@
+/*
+ * snps_udc.c - Synopsys USB 2.0 Device Controller driver
+ *
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/extcon.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/proc_fs.h>
+#include <linux/types.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/version.h>
+#include "snps_udc.h"
+
+#define DRIVER_DESC "Driver for Synopsys Designware core UDC"
+
+static void ep0_setup_init(struct snps_udc_ep *ep, int status)
+{
+	struct snps_udc *udc = ep->udc;
+
+	ep->dma.virt->setup.status = DMA_STS_BUF_HOST_READY;
+	ep->dirn = USB_DIR_OUT;
+	ep->stopped = 0;
+
+	if (!status) {
+		clear_ep_nak(udc->regs, ep->num, USB_DIR_OUT);
+		clear_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+	} else {
+		enable_ep_stall(udc->regs, ep->num, USB_DIR_IN);
+		enable_ep_stall(udc->regs, ep->num, USB_DIR_OUT);
+	}
+
+	enable_udc_ep_irq(udc->regs, ep->num, USB_DIR_OUT);
+	enable_ep_dma(udc->regs, ep->num, USB_DIR_OUT);
+
+	dev_dbg(udc->dev, "%s setup buffer initialized\n", ep->name);
+}
+
+static void ep_dma_init(struct snps_udc_ep *ep)
+{
+	struct snps_udc *udc = ep->udc;
+	u32 desc_cnt = (DESC_CNT - 1);
+	u32 i;
+
+	ep->dma.virt = &ep->udc->dma.virt->ep[ep->num];
+	ep->dma.phys = &ep->udc->dma.phys->ep[ep->num];
+
+	ep->dma.virt->setup.status = DMA_STS_BUF_HOST_BUSY;
+	set_setup_buf_ptr(udc->regs, ep->num, USB_DIR_OUT,
+			  &ep->dma.phys->setup);
+
+	for (i = 0; i < DESC_CNT; i++) {
+		ep->dma.virt->desc[i].status = DMA_STS_BUF_HOST_BUSY;
+		ep->dma.virt->desc[i].next_desc_addr =
+				(dma_addr_t)&ep->dma.phys->desc[i + 1];
+	}
+	ep->dma.virt->desc[desc_cnt].next_desc_addr =
+				(dma_addr_t)&ep->dma.phys->desc[0];
+
+	set_data_desc_ptr(udc->regs, ep->num, USB_DIR_OUT,
+			  &ep->dma.phys->desc[0]);
+	set_data_desc_ptr(udc->regs, ep->num, USB_DIR_IN,
+			  &ep->dma.phys->desc[0]);
+
+	dev_dbg(udc->dev, " %s dma initialized\n", ep->name);
+}
+
+static void ep_data_dma_init(struct snps_udc_ep *ep)
+{
+	struct ep_xfer_req *ep_req;
+
+	dev_dbg(ep->udc->dev, "enter: %s\n", __func__);
+
+	ep_req = list_first_entry(&ep->queue, struct ep_xfer_req, queue);
+
+	if (ep_req->dma_aligned) {
+		ep_req->dma_addr_orig = ep_req->usb_req.dma;
+		ep_req->usb_req.dma = ep->dma.aligned_addr;
+		if (ep->dirn == USB_DIR_IN)
+			memcpy(ep->dma.aligned_buf, ep_req->usb_req.buf,
+			       ep_req->usb_req.length);
+	}
+
+	ep->dma.done = 0;
+	ep->dma.len_done = 0;
+	ep->dma.len_rem = ep->dma.usb_req->length;
+	ep->dma.buf_addr = ep->dma.usb_req->dma;
+	ep->dma.status = DMA_STS_RX_SUCCESS;
+
+	if ((ep->dirn == USB_DIR_IN) &&
+	    (ep->type != USB_ENDPOINT_XFER_ISOC)) {
+		if (in_bf_mode)
+			ep->dma.len_max = ep->dma.usb_req->length;
+		else
+			ep->dma.len_max = ep->usb_ep.maxpacket;
+	} else {
+		if (out_bf_mode)
+			ep->dma.len_max = ep->dma.usb_req->length;
+		else
+			ep->dma.len_max = ep->usb_ep.maxpacket;
+	}
+
+	dma_desc_chain_reset(ep);
+}
+
+static void ep_data_dma_finish(struct snps_udc_ep *ep)
+{
+	struct snps_udc *udc = ep->udc;
+	struct ep_xfer_req *ep_req;
+
+	disable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+	disable_ep_dma(udc->regs, ep->num, ep->dirn);
+
+	ep_req = list_first_entry(&ep->queue, struct ep_xfer_req, queue);
+
+	if (ep_req->dma_aligned) {
+		if (ep->dirn == USB_DIR_OUT)
+			memcpy(ep_req->usb_req.buf,
+			       ep->dma.aligned_buf, ep_req->usb_req.length);
+		ep_req->usb_req.dma = ep_req->dma_addr_orig;
+	}
+	dev_dbg(udc->dev, "%s dma finished\n", ep->name);
+}
+
+static void ep_data_dma_add(struct snps_udc_ep *ep)
+{
+	struct data_desc *desc = NULL;
+	u32 status;
+	u32 len;
+
+	if (!ep->dma.len_rem)
+		ep->dma.usb_req->zero = 1;
+
+	ep->dma.last = ep->dma.usb_req->zero;
+
+	while (!dma_desc_chain_is_full(ep) &&
+	       (ep->dma.len_rem || ep->dma.usb_req->zero)) {
+		desc = dma_desc_chain_alloc(ep);
+		len = (ep->dma.len_rem < ep->dma.len_max) ?
+			ep->dma.len_rem : ep->dma.len_max;
+		ep->dma.len_rem -= len;
+		status = 0;
+
+		if (len <= ep->dma.len_max ||
+		    (out_bf_mode && (len <= ep->dma.len_max))) {
+			if (in_bf_mode ||
+			    !((ep->dirn == USB_DIR_IN) &&
+			      (ep->type == USB_ENDPOINT_XFER_BULK) &&
+			      (len != 0) &&
+			      (len % ep->usb_ep.maxpacket == 0)))
+				ep->dma.usb_req->zero = 0;
+		}
+
+		if ((ep->dirn == USB_DIR_IN) &&
+		    (ep->type == USB_ENDPOINT_XFER_ISOC)) {
+			ep->dma.frame_num += ep->dma.frame_incr;
+			dev_dbg(ep->udc->dev, "%s: DMA started: frame_num=%d.%d\n",
+				ep->name, (ep->dma.frame_num >> 3),
+				(ep->dma.frame_num & 0x7));
+			status |= ((ep->dma.frame_num <<
+				  DMA_STS_FRAME_NUM_SHIFT)
+				  & DMA_STS_FRAME_NUM_MASK);
+		}
+
+		desc->buf_addr = ep->dma.buf_addr;
+		status |= (len << DMA_STS_BYTE_CNT_SHIFT);
+		desc->status = status | DMA_STS_BUF_HOST_READY;
+		/* Ensure all writes are done before going for next descriptor*/
+		wmb();
+		ep->dma.buf_addr += len;
+
+		if ((ep->dirn == USB_DIR_IN) &&
+		    (ep->type == USB_ENDPOINT_XFER_ISOC))
+			break;
+	}
+
+	if (desc)
+		desc->status |= DMA_STS_LAST_DESC;
+
+	dev_dbg(ep->udc->dev, "%s dma data added\n", ep->name);
+}
+
+static void ep_data_dma_remove(struct snps_udc_ep *ep)
+{
+	struct data_desc *desc;
+	u32 status;
+	u32 len = 0;
+
+	while (!dma_desc_chain_is_empty(ep)) {
+		desc = dma_desc_chain_head(ep);
+		status = desc->status;
+		desc->status = DMA_STS_BUF_HOST_BUSY;
+		/* Ensure all writes are done before going for next descriptor*/
+		wmb();
+		len = (status & DMA_STS_NISO_BYTE_CNT_MASK) >>
+			DMA_STS_NISO_BYTE_CNT_SHIFT;
+
+		if ((ep->dirn == USB_DIR_IN) || (status &
+					DMA_STS_LAST_DESC)) {
+			ep->dma.len_done += len;
+			ep->dma.usb_req->actual += len;
+		}
+
+		if ((status & DMA_STS_RX_MASK) != DMA_STS_RX_SUCCESS) {
+			ep->dma.status = status & DMA_STS_RX_MASK;
+			ep->dma.usb_req->status = -EIO;
+			dev_warn(ep->udc->dev, "%s: DMA error\n", ep->name);
+		}
+
+		if ((ep->dirn == USB_DIR_IN) &&
+		    (ep->type == USB_ENDPOINT_XFER_ISOC)) {
+			if (ep->dma.usb_req->actual ==
+					ep->dma.usb_req->length)
+				ep->dma.usb_req->status = 0;
+			dma_desc_chain_reset(ep);
+		} else {
+			dma_desc_chain_free(ep);
+		}
+	}
+
+	if ((!ep->dma.len_rem || (len < ep->usb_ep.maxpacket)) &&
+	    (ep->dma.usb_req->status == -EINPROGRESS))
+		ep->dma.usb_req->status = 0;
+
+	dev_dbg(ep->udc->dev, "%s dma data removed\n", ep->name);
+}
+
+static int fifo_ram_alloc(struct snps_udc_ep *ep, u32 max_pkt_size)
+{
+	u32 rx_cnt;
+	u32 tx_cnt;
+
+	switch (EP_DIRN_TYPE(ep->dirn, ep->type)) {
+	case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_BULK):
+	case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_INT):
+	case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_ISOC):
+		rx_cnt = FIFO_SZ_U8(max_pkt_size);
+		tx_cnt = 0;
+		break;
+
+	case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_BULK):
+	case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_INT):
+		rx_cnt = 0;
+		tx_cnt = FIFO_SZ_U8(max_pkt_size);
+		break;
+
+	case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_ISOC):
+		rx_cnt = 0;
+		tx_cnt = 2 * FIFO_SZ_U8(max_pkt_size);
+		break;
+
+	case EP_DIRN_TYPE(USB_DIR_IN,  USB_ENDPOINT_XFER_CONTROL):
+	case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_CONTROL):
+		rx_cnt = FIFO_SZ_U8(max_pkt_size);
+		tx_cnt = rx_cnt;
+		break;
+
+	default:
+		dev_err(ep->udc->dev, "%s: invalid EP attributes\n", ep->name);
+		return -ENODEV;
+	}
+
+	dev_dbg(ep->udc->dev, "rx req=%u free=%u: tx req=%u free=%u\n",
+		rx_cnt, ep->udc->rx_fifo_space, tx_cnt, ep->udc->tx_fifo_space);
+
+	if ((ep->udc->rx_fifo_space < rx_cnt) ||
+	    (ep->udc->tx_fifo_space < tx_cnt)) {
+		dev_err(ep->udc->dev, "%s: fifo alloc failed\n", ep->name);
+		return -ENOSPC;
+	}
+
+	ep->rx_fifo_size = rx_cnt;
+	ep->tx_fifo_size = tx_cnt;
+
+	if (mrx_fifo)
+		ep->udc->rx_fifo_space -= rx_cnt;
+
+	ep->udc->tx_fifo_space -= tx_cnt;
+
+	return 0;
+}
+
+static void fifo_ram_free(struct snps_udc_ep *ep)
+{
+	if (mrx_fifo)
+		ep->udc->rx_fifo_space += ep->rx_fifo_size;
+
+	ep->udc->tx_fifo_space += ep->tx_fifo_size;
+
+	ep->rx_fifo_size = 0;
+	ep->tx_fifo_size = 0;
+}
+
+static int ep_cfg(struct snps_udc_ep *ep, u32 type,
+		  u32 max_pkt_size)
+{
+	struct snps_udc *udc = ep->udc;
+
+	ep->type = type;
+	if (fifo_ram_alloc(ep, max_pkt_size) != 0)
+		return -ENOSPC;
+
+	ep->type = type;
+	ep->usb_ep.maxpacket = max_pkt_size;
+
+	if (ep->udc->conn_type)
+		init_ep_reg(udc->regs, ep->num, ep->type, ep->dirn,
+			    max_pkt_size);
+	dev_dbg(udc->dev, "ep_cfg: %s: type=%u dirn=0x%x pkt=%u\n",
+		ep->usb_ep.name, type, ep->dirn, max_pkt_size);
+
+	return 0;
+}
+
+static void epreq_xfer_done(struct snps_udc_ep *ep,
+			    struct ep_xfer_req *ep_req, int status)
+{
+	struct snps_udc *udc = ep->udc;
+	u32 stopped;
+
+	list_del_init(&ep_req->queue);
+
+	if (ep_req->usb_req.status == -EINPROGRESS)
+		ep_req->usb_req.status = status;
+
+	if (ep_req->dma_aligned) {
+		ep_req->dma_aligned = 0;
+	} else if (ep_req->dma_mapped) {
+		dma_unmap_single(ep->udc->gadget.dev.parent,
+				 ep_req->usb_req.dma,
+				 (ep_req->usb_req.length ?
+				 ep_req->usb_req.length : 1),
+				 (ep->dirn == USB_DIR_IN ? DMA_TO_DEVICE :
+				 DMA_FROM_DEVICE));
+		ep_req->dma_mapped = 0;
+		ep_req->usb_req.dma = DMA_ADDR_INVALID;
+	}
+
+	dev_dbg(udc->dev, "%s xfer done req=0x%p buf=0x%p len=%d actual=%d\n",
+		ep->name, &ep_req->usb_req, ep_req->usb_req.buf,
+		ep_req->usb_req.length, ep_req->usb_req.actual);
+
+	stopped = ep->stopped;
+	ep->stopped = 1;
+	spin_unlock(&ep->udc->lock);
+	ep_req->usb_req.complete(&ep->usb_ep, &ep_req->usb_req);
+	spin_lock(&ep->udc->lock);
+	ep->stopped = stopped;
+}
+
+static void epreq_xfer_process(struct snps_udc_ep *ep)
+{
+	struct snps_udc *udc = ep->udc;
+	struct ep_xfer_req *ep_req;
+
+	dev_dbg(udc->dev, "%s: xfer request\n", ep->name);
+
+	if (!ep->dma.usb_req) {
+		dev_dbg(udc->dev, "%s: No dma usb request\n", ep->name);
+		return;
+	}
+
+	disable_ep_dma(udc->regs, ep->num, ep->dirn);
+	ep_data_dma_remove(ep);
+
+	if (ep->dma.usb_req->status != -EINPROGRESS) {
+		ep_data_dma_finish(ep);
+
+		if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+		    (ep->dirn == USB_DIR_IN) &&
+		    (ep->dma.usb_req->status == 0)) {
+			ep->dirn = USB_DIR_OUT;
+			ep->b_ep_addr = ep->num | ep->dirn;
+			ep->dma.usb_req->status = -EINPROGRESS;
+			ep->dma.usb_req->actual = 0;
+			ep->dma.usb_req->length = 0;
+			ep_data_dma_init(ep);
+		} else {
+			if (in_bf_mode && is_ep_in() && is_ep_bulk() &&
+			    (ep->dma.usb_req->length != 0) &&
+			    (ep->dma.usb_req->length %
+			    ep->usb_ep.maxpacket == 0) &&
+			    (ep->dma.last)) {
+				ep->dma.usb_req->status = -EINPROGRESS;
+				ep->dma.usb_req->actual = 0;
+				ep->dma.usb_req->length = 0;
+			} else if (!list_empty(&ep->queue))
+				epreq_xfer_done(ep,
+						list_first_entry(&ep->queue,
+								 struct
+								 ep_xfer_req,
+								 queue), 0);
+
+			if (ep->type == USB_ENDPOINT_XFER_CONTROL)
+				ep0_setup_init(ep, 0);
+
+			if (is_ep_in() && is_ep_bulk() &&
+			    !list_empty(&ep->queue)) {
+				ep->in_xfer_done = true;
+				clear_ep_nak(udc->regs, ep->num, ep->dirn);
+				enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+				return;
+			}
+
+			if (list_empty(&ep->queue)) {
+				ep->dma.usb_req = NULL;
+			} else {
+				ep_req = list_first_entry(&ep->queue,
+							  struct ep_xfer_req,
+							  queue);
+				ep->dma.usb_req = &ep_req->usb_req;
+				ep_data_dma_init(ep);
+			}
+		}
+	}
+
+	if (ep->dma.usb_req) {
+		ep_data_dma_add(ep);
+		enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+		clear_ep_nak(udc->regs, ep->num, ep->dirn);
+		enable_ep_dma(udc->regs, ep->num, ep->dirn);
+	}
+}
+
+static void epreq_xfer_error(struct snps_udc_ep *ep, int status)
+{
+	if (!ep->dma.usb_req) {
+		dev_err(ep->udc->dev, "%s: No DMA usb request\n", ep->name);
+		return;
+	}
+
+	ep->dma.usb_req->status = status;
+	epreq_xfer_process(ep);
+}
+
+static void epreq_xfer_add(struct snps_udc_ep *ep,
+			   struct ep_xfer_req *ep_req)
+{
+	struct snps_udc *udc = ep->udc;
+
+	list_add_tail(&ep_req->queue, &ep->queue);
+	if (ep->stopped)
+		return;
+
+	if ((ep->dirn == USB_DIR_IN) &&
+	    (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+	    (ep->dma.usb_req) &&
+	    (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+		ep_data_dma_finish(ep);
+		ep->dma.usb_req = NULL;
+		epreq_xfer_done(ep,
+				list_first_entry(&ep->queue,
+						 struct ep_xfer_req,
+						 queue),
+				-EREMOTEIO);
+	}
+
+	if (ep->dma.usb_req) {
+		dev_dbg(udc->dev, "%s: busy\n", ep->name);
+	} else if (!in_isoc_delay_disabled && (ep->dirn == USB_DIR_IN) &&
+		   (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+		   (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+		dev_dbg(udc->dev, "%s: ISOC delay xfer start\n", ep->name);
+		ep->dma.usb_req = &(list_first_entry(&ep->queue,
+				struct ep_xfer_req, queue))->usb_req;
+		ep_data_dma_init(ep);
+		clear_ep_nak(udc->regs, ep->num, ep->dirn);
+		enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+
+	} else {
+		if (in_isoc_delay_disabled && (ep->dirn == USB_DIR_IN) &&
+		    (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+		    (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+			ep->dma.frame_num = get_last_rx_frnum(udc->regs);
+		}
+
+		if (is_ep_in() && is_ep_bulk() && !ep->dma.usb_req) {
+			ep->in_xfer_done = true;
+			clear_ep_nak(udc->regs, ep->num, ep->dirn);
+			enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+			return;
+		}
+
+		ep_req = list_first_entry(&ep->queue,
+					  struct ep_xfer_req, queue);
+		ep->dma.usb_req = &ep_req->usb_req;
+		ep_data_dma_init(ep);
+		ep_data_dma_add(ep);
+		enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+		clear_ep_nak(udc->regs, ep->num, ep->dirn);
+		enable_ep_dma(udc->regs, ep->num, ep->dirn);
+	}
+
+	dev_dbg(udc->dev, "%s: xfer add ep request\n", ep->name);
+}
+
+static void epreq_queue_flush(struct snps_udc_ep *ep, int status)
+{
+	struct snps_udc *udc = ep->udc;
+	struct ep_xfer_req *ep_req;
+
+	ep->stopped = 1;
+
+	while (!list_empty(&ep->queue)) {
+		ep_req = list_first_entry(&ep->queue,
+					  struct ep_xfer_req, queue);
+		epreq_xfer_done(ep, ep_req, status);
+	}
+
+	ep->dma.usb_req = NULL;
+	if ((is_ep_in() && is_ep_bulk()) || !ep->num) {
+		set_ep_fifo_flush(udc->regs, ep->num, ep->dirn);
+		clear_ep_fifo_flush(udc->regs, ep->num, ep->dirn);
+	}
+
+	dev_dbg(udc->dev, "%s: EP queue flushed\n", ep->usb_ep.name);
+}
+
+static void ep0_setup_rx(struct snps_udc_ep *ep,
+			 struct usb_ctrlrequest *setup)
+{
+	struct snps_udc *udc = ep->udc;
+	int status;
+	u32 val;
+	u32 idx;
+	u32 len;
+
+	val = le16_to_cpu(setup->wValue);
+	idx = le16_to_cpu(setup->wIndex);
+	len = le16_to_cpu(setup->wLength);
+
+	ep->dirn = setup->bRequestType & USB_ENDPOINT_DIR_MASK;
+
+	dev_dbg(udc->dev, "%s: SETUP %02x.%02x v%04x i%04x l %04x\n",
+		ep->name, setup->bRequestType, setup->bRequest,
+		val, idx, len);
+
+	if (ep->num != 0) {
+		status = -EOPNOTSUPP;
+	} else {
+		spin_unlock(&udc->lock);
+		status = udc->gadget_driver->setup(&udc->gadget, setup);
+		spin_lock(&udc->lock);
+	}
+
+	if (status < 0)
+		ep0_setup_init(ep, status);
+	else if (len == 0)
+		ep0_setup_init(ep, 0);
+}
+
+static void irq_ep_out_setup(struct snps_udc_ep *ep)
+{
+	struct setup_desc *desc = &ep->dma.virt->setup;
+	u32 status = desc->status;
+
+	dev_dbg(ep->udc->dev, "irq set up %s desc status: 0x%x\n",
+		ep->name, status);
+
+	if ((status & DMA_STS_BUF_MASK) != DMA_STS_BUF_DMA_DONE) {
+		ep0_setup_init(ep, 0);
+	} else if ((status & DMA_STS_RX_MASK) != DMA_STS_RX_SUCCESS) {
+		ep0_setup_init(ep, 0);
+	} else {
+		desc->status = (status & ~DMA_STS_BUF_MASK)
+					| DMA_STS_BUF_HOST_BUSY;
+		ep0_setup_rx(ep, (struct usb_ctrlrequest *)&desc->data1);
+	}
+}
+
+static void irq_process_epout(struct snps_udc_ep *ep)
+{
+	struct snps_udc *udc = ep->udc;
+	u32 status;
+
+	status = get_ep_status(udc->regs, ep->num, USB_DIR_OUT);
+	clear_ep_status(udc->regs, ep->num, USB_DIR_OUT, status);
+
+	status &= EP_STS_ALL;
+
+	if (!status)
+		return;
+
+	if ((ep->dirn != USB_DIR_OUT) &&
+	    (ep->type != USB_ENDPOINT_XFER_CONTROL)) {
+		dev_err(udc->dev, "%s: unexpected interrupt\n", ep->name);
+		return;
+	}
+
+	if (status & OUT_DMA_DATA_DONE) {
+		status &= ~OUT_DMA_DATA_DONE;
+		epreq_xfer_process(ep);
+	}
+
+	if (status & OUT_DMA_SETUP_DONE) {
+		status &= ~OUT_DMA_SETUP_DONE;
+		irq_ep_out_setup(ep);
+	}
+
+	if (status & DMA_BUF_NOT_AVAIL) {
+		status &= ~DMA_BUF_NOT_AVAIL;
+		dev_dbg(udc->dev, "%s: DMA BUF NOT AVAIL\n", ep->name);
+		epreq_xfer_process(ep);
+	}
+
+	if (status & DMA_ERROR) {
+		status &= ~DMA_ERROR;
+		dev_err(udc->dev, "%s: DMA ERROR\n", ep->usb_ep.name);
+		epreq_xfer_error(ep, -EIO);
+	}
+
+	if (status)
+		dev_err(udc->dev, "%s: unknown status=0x%x\n",
+			ep->name, status);
+}
+
+static void irq_process_epin(struct snps_udc_ep *ep)
+{
+	struct snps_udc *udc = ep->udc;
+	struct ep_xfer_req *ep_req;
+	u32 status;
+
+	status = get_ep_status(udc->regs, ep->num, USB_DIR_IN);
+	clear_ep_status(udc->regs, ep->num, USB_DIR_IN, status);
+
+	if (!status)
+		return;
+
+	if (ep->dirn != USB_DIR_IN) {
+		dev_err(udc->dev, "%s: unexpected OUT endpoint\n", ep->name);
+		return;
+	}
+
+	if ((ep->type == USB_ENDPOINT_XFER_ISOC) &&
+	    (status & (IN_XFER_DONE | DMA_BUF_NOT_AVAIL))) {
+		dev_warn(ep->udc->dev, "%s: ISOC IN unexpected status=0x%x\n",
+			 ep->name, status);
+	}
+
+	if (status & IN_TOKEN_RX) {
+		status &= ~IN_TOKEN_RX;
+		if (!ep->dma.usb_req && list_empty(&ep->queue))
+			enable_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+
+		if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+			ep->dma.frame_num = get_frnum_last_rx(udc->regs);
+			dev_dbg(udc->dev, "%s: ISOC IN\n", ep->name);
+			if (ep->dma.usb_req) {
+				ep->dma.usb_req->status = -EREMOTEIO;
+				epreq_xfer_process(ep);
+			}
+		}
+	}
+
+	if (is_ep_bulk() && !list_empty(&ep->queue) &&
+	    ep->in_xfer_done) {
+		ep->in_xfer_done = false;
+		ep_req = list_first_entry(&ep->queue,
+					  struct ep_xfer_req, queue);
+		ep->dma.usb_req = &ep_req->usb_req;
+
+		ep_data_dma_init(ep);
+		ep_data_dma_add(ep);
+		clear_ep_nak(udc->regs, ep->num, ep->dirn);
+		enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+		enable_ep_dma(udc->regs, ep->num, ep->dirn);
+	}
+
+	if (status & IN_DMA_DONE) {
+		status &= ~IN_DMA_DONE;
+		clear_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+
+		if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+			dev_dbg(udc->dev, "%s: ISOC IN\n", ep->usb_ep.name);
+			epreq_xfer_process(ep);
+		} else if (ep->dma.done & IN_XFER_DONE) {
+			dev_dbg(udc->dev, "%s: late IN DMA done rec'd\n",
+				ep->name);
+			epreq_xfer_process(ep);
+		} else {
+			ep->dma.done = IN_DMA_DONE;
+		}
+	}
+
+	if (status & IN_XFER_DONE) {
+		status &= ~(IN_XFER_DONE);
+		status &= ~(IN_FIFO_EMPTY);
+
+		if (ep->dma.done & IN_DMA_DONE)
+			epreq_xfer_process(ep);
+		else
+			ep->dma.done = IN_XFER_DONE;
+	}
+
+	status &= ~(IN_FIFO_EMPTY);
+
+	if (status & DMA_BUF_NOT_AVAIL) {
+		dev_err(udc->dev, "%s: DMA BUF NOT AVAIL\n", ep->name);
+		status &= ~(DMA_BUF_NOT_AVAIL);
+		epreq_xfer_process(ep);
+	}
+
+	if (status & DMA_ERROR) {
+		status &= ~DMA_ERROR;
+		dev_err(udc->dev, "%s: DMA ERROR\n", ep->name);
+		epreq_xfer_error(ep, -EIO);
+	}
+
+	if (status)
+		dev_err(udc->dev, "%s: unknown status=0x%x\n",
+			ep->name, status);
+}
+
+static void ep_irq_process(struct snps_udc *udc, u32 irq_in, u32 irq_out)
+{
+	u32 mask = 1;
+	u32 num;
+
+	for (num = 0; num < UDC_MAX_EP; num++) {
+		if (irq_in & mask)
+			irq_process_epin(&udc->ep[num]);
+
+		if (irq_out & mask)
+			irq_process_epout(&udc->ep[num]);
+
+		mask <<= 1;
+	}
+}
+
+static void irq_process_set_intf(struct snps_udc *udc)
+{
+	struct usb_ctrlrequest setup;
+	u32 ep_num;
+	u16 intf;
+	u16 alt;
+
+	intf = (uint16_t)get_intf_num(udc->regs);
+	alt =  (uint16_t)get_alt_num(udc->regs);
+
+	setup.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD
+					 | USB_RECIP_INTERFACE;
+	setup.bRequest = USB_REQ_SET_INTERFACE;
+	setup.wValue = cpu_to_le16(alt);
+	setup.wIndex = cpu_to_le16(intf);
+	setup.wLength = 0;
+
+	for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++) {
+		set_ep_alt_num(udc->regs, ep_num, alt);
+		set_ep_intf_num(udc->regs, ep_num, intf);
+	}
+	dev_info(udc->dev, "SET INTF=%d ALT=%d\n", intf, alt);
+
+	ep0_setup_rx(&udc->ep[0], &setup);
+	set_setup_done(udc->regs);
+}
+
+static void irq_process_set_cfg(struct snps_udc *udc)
+{
+	struct usb_ctrlrequest setup;
+	u32 ep_num;
+	u16 cfg;
+
+	cfg = (u16)get_cfg_num(udc->regs);
+
+	setup.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD
+					 | USB_RECIP_DEVICE;
+	setup.bRequest = USB_REQ_SET_CONFIGURATION;
+	setup.wValue = cpu_to_le16(cfg);
+	setup.wIndex = 0;
+	setup.wLength = 0;
+
+	for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++)
+		set_epcfg_reg(udc->regs, ep_num, cfg);
+
+	dev_info(udc->dev, "SET CFG=%d\n", cfg);
+
+	ep0_setup_rx(&udc->ep[0], &setup);
+	set_setup_done(udc->regs);
+}
+
+static void irq_process_speed_enum(struct snps_udc *udc)
+{
+	u32 speed = udc->gadget.speed;
+
+	switch (get_enum_speed(udc->regs)) {
+	case SPEED_HIGH:
+		dev_info(udc->dev, "HIGH SPEED\n");
+		udc->gadget.speed = USB_SPEED_HIGH;
+		break;
+	case SPEED_FULL:
+		dev_info(udc->dev, "FULL SPEED\n");
+		udc->gadget.speed = USB_SPEED_FULL;
+		break;
+	case SPEED_LOW:
+		dev_warn(udc->dev, "LOW SPEED not supported\n");
+		udc->gadget.speed = USB_SPEED_LOW;
+		break;
+	default:
+		dev_err(udc->dev, "Unknown SPEED = 0x%x\n",
+			get_enum_speed(udc->regs));
+		break;
+	}
+
+	if ((speed == USB_SPEED_UNKNOWN) &&
+	    (udc->gadget.speed != USB_SPEED_UNKNOWN)) {
+		ep0_setup_init(&udc->ep[0], 0);
+		clear_devnak(udc->regs);
+	}
+}
+
+static void irq_process_bus_idle(struct snps_udc *udc)
+{
+	int num;
+
+	for (num = 0; num < UDC_MAX_EP; num++) {
+		set_ep_fifo_flush(udc->regs, num, EP_DIRN_IN);
+		clear_ep_fifo_flush(udc->regs, num, EP_DIRN_IN);
+	}
+}
+
+static void dev_irq_process(struct snps_udc *udc, u32 irq)
+{
+	if (irq & IRQ_BUS_RESET)
+		dev_info(udc->dev, "BUS RESET\n");
+
+	if (irq & IRQ_BUS_SUSPEND)
+		dev_dbg(udc->dev, "BUS SUSPEND\n");
+
+	if (irq & IRQ_BUS_IDLE) {
+		dev_dbg(udc->dev, "BUS IDLE\n");
+		irq_process_bus_idle(udc);
+	}
+
+	if (irq & IRQ_SPEED_ENUM_DONE) {
+		dev_dbg(udc->dev, "BUS speed enum done\n");
+		irq_process_speed_enum(udc);
+	}
+
+	if (irq & IRQ_SET_CFG) {
+		dev_dbg(udc->dev, "SET CFG\n");
+		irq_process_set_cfg(udc);
+	}
+
+	if (irq & IRQ_SET_INTF) {
+		dev_dbg(udc->dev, "SET INTF\n");
+		irq_process_set_intf(udc);
+	}
+}
+
+static irqreturn_t snps_udc_irq(int irq, void *dev)
+{
+	struct snps_udc *udc = (struct snps_udc *)dev;
+	u32 devintr, epin_intr, epout_intr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&udc->lock, flags);
+
+	devintr = get_irq_active(udc->regs);
+	epin_intr = get_ep_irq_active(udc->regs, USB_DIR_IN);
+	epout_intr = get_ep_irq_active(udc->regs, USB_DIR_OUT);
+
+	clear_udc_dev_irq(udc->regs, devintr);
+	clear_udc_ep_irq_list(udc->regs, USB_DIR_IN, epin_intr);
+	clear_udc_ep_irq_list(udc->regs, USB_DIR_OUT, epout_intr);
+
+	if (!udc->gadget_driver) {
+		spin_unlock_irqrestore(&udc->lock, flags);
+		return IRQ_NONE;
+	}
+
+	/* SET_CFG and SET_INTF interrupts are handled last */
+	dev_irq_process(udc, devintr & ~(IRQ_SET_CFG | IRQ_SET_INTF));
+	ep_irq_process(udc, epin_intr, epout_intr);
+	dev_irq_process(udc, devintr & (IRQ_SET_CFG | IRQ_SET_INTF));
+
+	spin_unlock_irqrestore(&udc->lock, flags);
+	dev_dbg(udc->dev, "UDC interrupts: Dev=0x%x EpIn=0x%x EpOut=0x%x\n",
+		devintr, epin_intr, epout_intr);
+
+	return IRQ_HANDLED;
+}
+
+static int snps_ep_enable(struct usb_ep *usb_ep,
+			  const struct usb_endpoint_descriptor *desc)
+{
+	struct snps_udc_ep *ep;
+	struct snps_udc *udc;
+	unsigned long flags;
+	u32 max_pkt_size;
+	u32 xfertype;
+
+	ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+	udc = ep->udc;
+
+	if (!usb_ep || (ep->b_ep_addr != desc->bEndpointAddress)) {
+		dev_err(udc->dev, "invalid endpoint (%p)\n", usb_ep);
+		return -EINVAL;
+	}
+
+	if (!desc || (desc->bDescriptorType != USB_DT_ENDPOINT)) {
+		dev_err(udc->dev, "ep%d: invalid descriptor=%p\n",
+			ep->num, desc);
+		return -EINVAL;
+	}
+
+	if (desc == ep->desc) {
+		dev_err(udc->dev, "ep%d: already enabled\n", ep->num);
+		return -EEXIST;
+	}
+
+	if (ep->desc) {
+		dev_err(udc->dev, "ep%d:already enabled wth other descr\n",
+			ep->num);
+		return -EBUSY;
+	}
+
+	if (!udc->gadget_driver) {
+		dev_warn(udc->dev, "%s: invalid device state\n", ep->name);
+		return -ESHUTDOWN;
+	}
+
+	xfertype = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+	max_pkt_size = le16_to_cpu(desc->wMaxPacketSize) & 0x7FF;
+
+	if (!max_pkt_size || (max_pkt_size > ep->max_pkt_size)) {
+		dev_err(udc->dev, "%s: invalid max pkt size\n", ep->name);
+		return -ERANGE;
+	}
+
+	if ((ep->dirn == USB_DIR_IN) &&
+	    (xfertype == USB_ENDPOINT_XFER_ISOC)) {
+		if ((desc->bInterval < 1) || (desc->bInterval > 16)) {
+			dev_err(udc->dev, "%s: invalid binterval\n", ep->name);
+			return -ERANGE;
+		}
+		ep->dma.frame_num = FRAME_NUM_INVALID;
+		ep->dma.frame_incr = 1 << (desc->bInterval - 1);
+	}
+
+	spin_lock_irqsave(&udc->lock, flags);
+
+	if (ep_cfg(ep, xfertype, max_pkt_size) != 0) {
+		spin_unlock_irqrestore(&udc->lock, flags);
+		dev_err(udc->dev, "%s: not enough FIFO space\n", ep->name);
+		return -ENOSPC;
+	}
+
+	set_epcfg_reg(udc->regs, ep->num, get_cfg_num(udc->regs));
+
+	ep->desc = desc;
+	ep->stopped = 0;
+	ep->usb_ep.maxpacket = max_pkt_size;
+
+	spin_unlock_irqrestore(&udc->lock, flags);
+
+	dev_dbg(udc->dev, "%s: enabled: type: 0x%x, max_pkt_size: %d\n",
+		ep->name, xfertype, max_pkt_size);
+
+	return 0;
+}
+
+static int snps_ep_disable(struct usb_ep *usb_ep)
+{
+	struct snps_udc_ep *ep;
+	struct snps_udc *udc;
+	unsigned long flags;
+
+	ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+	udc = ep->udc;
+
+	if (!usb_ep || !ep->desc) {
+		dev_err(udc->dev, "%s: invalid endpoint\n", ep->usb_ep.name);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&udc->lock, flags);
+
+	epreq_queue_flush(ep, -ESHUTDOWN);
+	ep->desc = NULL;
+	ep->usb_ep.maxpacket = ep->max_pkt_size;
+	fifo_ram_free(ep);
+
+	spin_unlock_irqrestore(&udc->lock, flags);
+
+	return 0;
+}
+
+static struct usb_request *
+snps_ep_alloc_request(struct usb_ep *usb_ep, gfp_t gfp_flags)
+{
+	struct ep_xfer_req *ep_req;
+
+	if (!usb_ep)
+		return NULL;
+
+	ep_req = kzalloc(sizeof(*ep_req), gfp_flags);
+	if (ep_req) {
+		INIT_LIST_HEAD(&ep_req->queue);
+		ep_req->usb_req.dma = DMA_ADDR_INVALID;
+		pr_debug("%s: ep alloc req\n", usb_ep->name);
+		return &ep_req->usb_req;
+	}
+
+	return NULL;
+}
+
+static void snps_ep_free_request(struct usb_ep *usb_ep,
+				 struct usb_request *usb_req)
+{
+	struct ep_xfer_req *ep_req;
+
+	ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+	if (usb_req) {
+		pr_debug("%s: freed\n", usb_ep->name);
+		kfree(ep_req);
+	}
+}
+
+static int snps_ep_queue(struct usb_ep *usb_ep,
+			 struct usb_request *usb_req, gfp_t gfp_flags)
+{
+	struct ep_xfer_req *ep_req;
+	struct snps_udc_ep *ep;
+	struct snps_udc *udc;
+	unsigned long flags;
+
+	ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+	ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+	dev_dbg(ep->udc->dev, "%s: %s\n", __func__, ep->usb_ep.name);
+	if (!usb_ep || !usb_req || !ep_req->usb_req.complete ||
+	    !ep_req->usb_req.buf || !list_empty(&ep_req->queue)) {
+		dev_dbg(ep->udc->dev, "%s:invalid queue request\n", ep->name);
+		return -EINVAL;
+	}
+
+	if (!ep->desc && (ep->num != 0)) {
+		dev_err(ep->udc->dev, "%s: invalid EP state\n", ep->name);
+		return -EFAULT;
+	}
+
+	if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+	    !list_empty(&ep->queue)) {
+		dev_err(ep->udc->dev, "%s: EP queue not empty\n", ep->name);
+		return -EPERM;
+	}
+
+	if (usb_req->length > 0xffff) {
+		dev_err(ep->udc->dev, "%s: request too big\n", ep->name);
+		return -E2BIG;
+	}
+
+	if ((ep->type == USB_ENDPOINT_XFER_ISOC) &&
+	    (ep->dirn == USB_DIR_IN) &&
+	    (usb_req->length > ep->usb_ep.maxpacket)) {
+		dev_err(ep->udc->dev, "%s: request > scheduled bandwidth, length=%u\n",
+			ep->name, usb_req->length);
+		return -EFBIG;
+	}
+
+	udc = ep->udc;
+	if (!udc->gadget_driver) {
+		dev_err(udc->dev, "%s: invalid device state\n", ep->name);
+		return -ESHUTDOWN;
+	}
+
+	if (((unsigned long)ep_req->usb_req.buf) & 0x3UL) {
+		dev_dbg(udc->dev, "%s: invalid buffer alignment: addr=0x%p\n",
+			ep->usb_ep.name, ep_req->usb_req.buf);
+
+		if ((ep->dma.aligned_buf) &&
+		    (ep->dma.aligned_len < ep_req->usb_req.length)) {
+			dma_free_coherent(NULL, ep->dma.aligned_len,
+					  ep->dma.aligned_buf,
+					  ep->dma.aligned_addr);
+			ep->dma.aligned_buf = NULL;
+		}
+
+		if (!ep->dma.aligned_buf) {
+			ep->dma.aligned_len = ep_req->usb_req.length;
+			ep->dma.aligned_buf = dma_alloc_coherent(NULL,
+				ep->dma.aligned_len, &ep->dma.aligned_addr,
+				GFP_ATOMIC);
+		}
+
+		if (!ep->dma.aligned_buf) {
+			dev_err(udc->dev, "%s: ep dma alloc failed\n",
+				ep->name);
+			return -ENOMEM;
+		}
+
+		ep_req->dma_aligned = 1;
+	} else if ((ep_req->usb_req.dma == DMA_ADDR_INVALID) ||
+		   (ep_req->usb_req.dma == 0)) {
+		ep_req->dma_mapped = 1;
+		ep_req->usb_req.dma = dma_map_single(
+					ep->udc->gadget.dev.parent,
+					ep_req->usb_req.buf,
+					(ep_req->usb_req.length ?
+					ep_req->usb_req.length : 1),
+		(ep->dirn == USB_DIR_IN ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+		if (dma_mapping_error(ep->udc->gadget.dev.parent,
+				      ep_req->usb_req.dma)) {
+			dev_err(ep->udc->gadget.dev.parent,
+				"failed to map buffer\n");
+			return -EFAULT;
+		}
+	}
+
+	spin_lock_irqsave(&udc->lock, flags);
+
+	ep_req->usb_req.status = -EINPROGRESS;
+	ep_req->usb_req.actual = 0;
+
+	if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+	    (ep->dirn == USB_DIR_OUT) &&
+	    (ep_req->usb_req.length == 0)) {
+		epreq_xfer_done(ep, ep_req, 0);
+	} else {
+		if (ep_req->usb_req.length == 0)
+			ep_req->usb_req.zero = 1;
+
+		epreq_xfer_add(ep, ep_req);
+	}
+
+	spin_unlock_irqrestore(&udc->lock, flags);
+
+	return 0;
+}
+
+static int snps_ep_dequeue(struct usb_ep *usb_ep,
+			   struct usb_request *usb_req)
+{
+	struct ep_xfer_req *ep_req;
+	struct snps_udc_ep *ep;
+	unsigned long flags;
+
+	ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+	ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+	if (!usb_ep || !usb_req) {
+		dev_err(ep->udc->dev, "%s: invalid dequeue request\n",
+			ep->name);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&ep->udc->lock, flags);
+
+	list_for_each_entry(ep_req, &ep->queue, queue) {
+		if (&ep_req->usb_req == usb_req)
+			break;
+	}
+
+	if (&ep_req->usb_req != usb_req) {
+		spin_unlock_irqrestore(&ep->udc->lock, flags);
+		dev_err(ep->udc->dev, "%s: request not queued\n", ep->name);
+		return -ENOLINK;
+	}
+
+	epreq_xfer_done(ep, ep_req, -ECONNRESET);
+	spin_unlock_irqrestore(&ep->udc->lock, flags);
+
+	dev_dbg(ep->udc->dev, "%s: req=0x%p\n", ep->name, usb_req);
+	return 0;
+}
+
+static int snps_ep_set_halt(struct usb_ep *usb_ep, int halt)
+{
+	struct snps_udc_ep *ep;
+	unsigned long flags;
+	struct snps_udc *udc;
+
+	ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+	udc = ep->udc;
+	if (!usb_ep) {
+		dev_err(udc->dev, "%s: invalid halt request\n", ep->name);
+		return -EINVAL;
+	}
+
+	if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+		dev_err(udc->dev, "%s: unsupported halt req\n", ep->name);
+		return -EOPNOTSUPP;
+	}
+
+	if (halt && (ep->dirn == USB_DIR_IN) &&
+	    !list_empty(&ep->queue)) {
+		dev_err(udc->dev, "%s: EP IN queue not empty\n", ep->name);
+		return -EAGAIN;
+	}
+
+	if (!halt && (ep->type == USB_ENDPOINT_XFER_CONTROL)) {
+		dev_err(udc->dev, "%s: CTRL HALT clear\n", ep->name);
+		return -EPROTO;
+	}
+
+	spin_lock_irqsave(&ep->udc->lock, flags);
+
+	if (!halt) {
+		disable_ep_stall(udc->regs, ep->num, ep->dirn);
+	} else if (ep->type != USB_ENDPOINT_XFER_CONTROL) {
+		enable_ep_stall(udc->regs, ep->num, ep->dirn);
+	} else {
+		enable_ep_stall(udc->regs, ep->num, USB_DIR_IN);
+		enable_ep_stall(udc->regs, ep->num, USB_DIR_OUT);
+	}
+
+	spin_unlock_irqrestore(&ep->udc->lock, flags);
+
+	dev_dbg(udc->dev, "%s: HALT %s done\n", ep->name,
+		halt ? "SET" : "CLR");
+
+	return 0;
+}
+
+static struct usb_ep_ops snps_ep_ops = {
+	.enable		= snps_ep_enable,
+	.disable	= snps_ep_disable,
+
+	.alloc_request	= snps_ep_alloc_request,
+	.free_request	= snps_ep_free_request,
+
+	.queue		= snps_ep_queue,
+	.dequeue	= snps_ep_dequeue,
+
+	.set_halt	= snps_ep_set_halt,
+};
+
+static int eps_init(struct snps_udc *udc)
+{
+	struct snps_udc_ep *ep;
+	int i, ret;
+
+	/* Initialize Endpoint 0 */
+	ep = &udc->ep[0];
+	ep->udc = udc;
+	ep->num = 0;
+	ep->in_xfer_done = true;
+	ep->dirn = USB_DIR_OUT;
+	ep->b_ep_addr = ep->num | ep->dirn;
+	strncpy(ep->name, "ep0", sizeof(ep->name));
+	ep->usb_ep.name = ep->name;
+	ep->max_pkt_size = EP_CTRL_MAX_PKT_SIZE;
+	usb_ep_set_maxpacket_limit(&ep->usb_ep, EP_CTRL_MAX_PKT_SIZE);
+	ep->usb_ep.ops = &snps_ep_ops;
+	ep->stopped = 0;
+	ep->usb_ep.caps.type_control = true;
+	ep->usb_ep.caps.dir_in = true;
+	ep->usb_ep.caps.dir_out = true;
+	INIT_LIST_HEAD(&ep->queue);
+	ep->type = USB_ENDPOINT_XFER_CONTROL;
+	ep->usb_ep.maxpacket = EP_CTRL_MAX_PKT_SIZE;
+
+	if (udc->conn_type)
+		ep_dma_init(ep);
+
+	dev_dbg(udc->dev, "%s: type: 0x%x, Dir:0x%x, Max Size: %d\n",
+		ep->name, ep->type, ep->dirn, ep->max_pkt_size);
+
+	/* Initialize remaining endpoints */
+	for (i = 1; i < UDC_MAX_EP; i++) {
+		ep = &udc->ep[i];
+		ep->udc = udc;
+		ep->max_pkt_size = EP_MAX_PKT_SIZE;
+		usb_ep_set_maxpacket_limit(&ep->usb_ep, EP_MAX_PKT_SIZE);
+		ep->usb_ep.ops = &snps_ep_ops;
+		ep->in_xfer_done = true;
+		ep->num = i;
+		if (i % 2) {
+			snprintf(ep->name, sizeof(ep->name), "ep%din", i);
+			ep->dirn = EP_DIRN_IN;
+			ep->usb_ep.caps.dir_in = true;
+		} else {
+			snprintf(ep->name, sizeof(ep->name), "ep%dout", i);
+			ep->dirn = EP_DIRN_OUT;
+			ep->usb_ep.caps.dir_out = true;
+		}
+		ep->usb_ep.name = ep->name;
+		ep->b_ep_addr = ep->num | ep->dirn;
+
+		ep->usb_ep.caps.type_iso = true;
+		ep->usb_ep.caps.type_bulk = true;
+		ep->usb_ep.caps.type_int = true;
+		ep->stopped = 0;
+		ep->usb_ep.maxpacket = EP_MAX_PKT_SIZE;
+
+		INIT_LIST_HEAD(&ep->queue);
+		if (udc->conn_type)
+			ep_dma_init(ep);
+
+		dev_dbg(udc->dev, "%s: type: 0x%x, Dir: 0x%x, Max Size: %d\n",
+			ep->name, ep->type, ep->dirn, ep->max_pkt_size);
+	}
+
+	udc->rx_fifo_space = OUT_RX_FIFO_MEM_SIZE;
+	udc->tx_fifo_space = IN_TX_FIFO_MEM_SIZE;
+	ret = ep_cfg(&udc->ep[0], USB_ENDPOINT_XFER_CONTROL,
+		     EP_CTRL_MAX_PKT_SIZE);
+	if (ret) {
+		dev_err(udc->dev, "Synopsys-UDC: error configuring endpoints\n");
+		return ret;
+	}
+
+	dev_dbg(udc->dev, "Synopsys UDC Endpoints initialized\n");
+	return 0;
+}
+
+static void start_udc(struct snps_udc *udc)
+{
+	int i;
+
+	init_udc_reg(udc->regs);
+
+	udc->rx_fifo_space = OUT_RX_FIFO_MEM_SIZE;
+	udc->tx_fifo_space = IN_TX_FIFO_MEM_SIZE;
+
+	eps_init(udc);
+	enable_self_pwr(udc->regs);
+
+	enable_udc_dev_irq(udc->regs, IRQ_SPEED_ENUM_DONE | IRQ_BUS_SUSPEND |
+			   IRQ_BUS_IDLE | IRQ_BUS_RESET | IRQ_SET_INTF |
+			   IRQ_SET_CFG);
+
+	for (i = 0; i < UDC_MAX_EP; ++i) {
+		if (udc->ep[i].usb_ep.name) {
+			enable_udc_ep_irq(udc->regs,
+					  udc->ep[i].num, USB_DIR_OUT);
+			enable_udc_ep_irq(udc->regs,
+					  udc->ep[i].num, USB_DIR_IN);
+		}
+	}
+
+	clear_devnak(udc->regs);
+	enable_ctrl_dma(udc->regs);
+	bus_connect(udc->regs);
+
+	dev_dbg(udc->dev, "Synopsys UDC started\n");
+}
+
+static void stop_udc(struct snps_udc *udc)
+{
+	finish_udc(udc->regs);
+
+	udc->gadget.speed = USB_SPEED_UNKNOWN;
+	epreq_queue_flush(&udc->ep[0], -ESHUTDOWN);
+	udc->ep[0].desc = NULL;
+
+	bus_disconnect(udc->regs);
+
+	if (udc->gadget_driver && udc->gadget_driver->disconnect) {
+		spin_unlock(&udc->lock);
+		udc->gadget_driver->disconnect(&udc->gadget);
+		spin_lock(&udc->lock);
+	}
+
+	dev_dbg(udc->dev, "Synopsys UDC stopped\n");
+}
+
+static int snps_gadget_pullup(struct usb_gadget *gadget, int is_on)
+{
+	struct snps_udc *udc;
+	unsigned long flags;
+
+	udc = container_of(gadget, struct snps_udc, gadget);
+
+	spin_lock_irqsave(&udc->lock, flags);
+
+	if (!udc->gadget_driver) {
+		spin_unlock_irqrestore(&udc->lock, flags);
+		return 0;
+	}
+
+	if (is_on && udc->pullup_on) {
+		start_udc(udc);
+		udc->ep[0].stopped = 0;
+		dev_info(udc->dev, "Synopsys UDC device connected\n");
+	} else if (!is_on && !udc->pullup_on) {
+		stop_udc(udc);
+		udc->ep[0].stopped = 1;
+		dev_info(udc->dev, "Synopsys UDC device Disconnected\n");
+	}
+
+	spin_unlock_irqrestore(&udc->lock, flags);
+
+	return 0;
+}
+
+static int snps_gadget_start(struct usb_gadget *gadget,
+			     struct usb_gadget_driver *driver)
+{
+	struct snps_udc *udc;
+	unsigned long flags;
+
+	udc = container_of(gadget, struct snps_udc, gadget);
+
+	if (udc->gadget_driver)
+		return -EBUSY;
+
+	spin_lock_irqsave(&udc->lock, flags);
+
+	driver->driver.bus = NULL;
+	udc->gadget_driver = driver;
+	udc->gadget.dev.driver = &driver->driver;
+	udc->ep[0].stopped = 0;
+
+	spin_unlock_irqrestore(&udc->lock, flags);
+
+	/* when cable is connected at boot time */
+	if (udc->conn_type)
+		schedule_delayed_work(&udc->drd_work, USBD_WQ_DELAY_MS);
+	dev_dbg(udc->dev, "%s: Done\n", __func__);
+
+	return 0;
+}
+
+static int snps_gadget_stop(struct usb_gadget *gadget)
+{
+	struct snps_udc_ep *ep;
+	struct snps_udc *udc;
+	unsigned long flags;
+
+	udc = container_of(gadget, struct snps_udc, gadget);
+
+	spin_lock_irqsave(&udc->lock, flags);
+	stop_udc(udc);
+	udc->gadget.dev.driver = NULL;
+	udc->gadget_driver = NULL;
+
+	list_for_each_entry(ep, &udc->gadget.ep_list, usb_ep.ep_list) {
+		epreq_queue_flush(ep, -ESHUTDOWN);
+		if (ep->desc)
+			ep->desc = NULL;
+	}
+
+	spin_unlock_irqrestore(&udc->lock, flags);
+
+	dev_dbg(udc->dev, "%s: Done\n", __func__);
+
+	return 0;
+}
+
+static struct usb_gadget_ops snps_gadget_ops = {
+	.pullup		= snps_gadget_pullup,
+	.udc_start	= snps_gadget_start,
+	.udc_stop	= snps_gadget_stop,
+};
+
+void snps_udc_drd_work(struct work_struct *work)
+{
+	struct snps_udc *udc;
+
+	udc = container_of(to_delayed_work(work),
+			   struct snps_udc, drd_work);
+
+	if (udc->conn_type) {
+		dev_dbg(udc->dev, "idle -> device\n");
+		if (udc->gadget_driver) {
+			udc->pullup_on = 1;
+			snps_gadget_pullup(&udc->gadget, 1);
+		}
+	} else {
+		dev_dbg(udc->dev, "device -> idle\n");
+		udc->pullup_on = 0;
+		snps_gadget_pullup(&udc->gadget, 0);
+	}
+}
+
+static int usbd_connect_notify(struct notifier_block *self,
+			       unsigned long event, void *ptr)
+{
+	struct snps_udc *udc = container_of(self, struct snps_udc, nb);
+
+	dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event);
+
+	udc->conn_type = event;
+
+	schedule_delayed_work(&udc->drd_work, USBD_WQ_DELAY_MS);
+
+	return NOTIFY_OK;
+}
+
+static void free_udc_dma(struct platform_device *pdev, struct snps_udc *udc)
+{
+	u32 num;
+
+	dma_free_coherent(&pdev->dev, sizeof(struct ep_desc_array),
+			  udc->dma.virt, (dma_addr_t)udc->dma.phys);
+
+	for (num = 0; num < UDC_MAX_EP; num++) {
+		if (udc->ep[num].dma.aligned_buf) {
+			dma_free_coherent(NULL, udc->ep[num].dma.aligned_len,
+					  udc->ep[num].dma.aligned_buf,
+					  udc->ep[num].dma.aligned_addr);
+			udc->ep[num].dma.aligned_buf = NULL;
+		}
+	}
+}
+
+static int snps_udc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct snps_udc *udc;
+	int i, ret;
+
+	udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL);
+	if (!udc)
+		return -ENOMEM;
+
+	spin_lock_init(&udc->lock);
+	udc->dev = dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	udc->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(udc->regs))
+		return PTR_ERR(udc->regs);
+
+	udc->irq = irq_of_parse_and_map(dev->of_node, 0);
+	if (udc->irq <= 0) {
+		dev_err(dev, "Can't parse and map interrupt\n");
+		return -EINVAL;
+	}
+
+	udc->udc_phy = devm_phy_get(dev, "usb2drd");
+	if (IS_ERR(udc->udc_phy)) {
+		dev_err(dev, "Failed to obtain phy from device tree\n");
+		return PTR_ERR(udc->udc_phy);
+	}
+
+	ret = phy_init(udc->udc_phy);
+	if (ret) {
+		dev_err(dev, "UDC phy init failed");
+		return ret;
+	}
+
+	ret = phy_power_on(udc->udc_phy);
+	if (ret) {
+		dev_err(dev, "UDC phy power on failed");
+		phy_exit(udc->udc_phy);
+		return ret;
+	}
+
+	udc->edev = extcon_get_edev_by_phandle(dev, 0);
+	if (IS_ERR(udc->edev)) {
+		if (PTR_ERR(udc->edev) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+		dev_err(dev, "Invalid or missing extcon\n");
+		ret = PTR_ERR(udc->edev);
+		goto exit_phy;
+	}
+
+	udc->nb.notifier_call = usbd_connect_notify;
+	ret = extcon_register_notifier(udc->edev, EXTCON_USB, &udc->nb);
+	if (ret < 0) {
+		dev_err(dev, "Can't register extcon device\n");
+		goto exit_phy;
+	}
+
+	ret = extcon_get_cable_state_(udc->edev, EXTCON_USB);
+	if (ret < 0) {
+		dev_err(dev, "Can't get cable state\n");
+		goto exit_extcon;
+	} else if (ret) {
+		udc->conn_type = ret;
+	}
+
+	udc->dma.virt = dma_alloc_coherent(&pdev->dev,
+				sizeof(struct ep_desc_array),
+				(dma_addr_t *)&udc->dma.phys,
+				 GFP_KERNEL);
+	if (!udc->dma.virt) {
+		dev_err(dev, "Failed to allocate memory for ep\n");
+		ret = -ENOMEM;
+		goto exit_extcon;
+	}
+
+	INIT_DELAYED_WORK(&udc->drd_work, snps_udc_drd_work);
+
+	ret = devm_request_irq(dev, udc->irq, snps_udc_irq, IRQF_SHARED,
+			       "snps-udc", udc);
+	if (ret < 0) {
+		dev_err(dev, "Request irq %d failed for UDC\n", udc->irq);
+		goto exit_dma;
+	}
+
+	/* Gagdet structure init */
+	udc->gadget.name	= "snps-udc";
+	udc->gadget.speed	= USB_SPEED_UNKNOWN;
+	udc->gadget.max_speed	= USB_SPEED_HIGH;
+	udc->gadget.ops		= &snps_gadget_ops;
+	udc->gadget.ep0		= &udc->ep[0].usb_ep;
+	INIT_LIST_HEAD(&udc->gadget.ep_list);
+
+	eps_init(udc);
+	for (i = 1; i < UDC_MAX_EP; i++) {
+		list_add_tail(&udc->ep[i].usb_ep.ep_list,
+			      &udc->gadget.ep_list);
+	}
+
+	ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
+	if (ret) {
+		dev_err(dev, "Error adding gadget udc: %d\n", ret);
+		goto exit_dma;
+	}
+
+	platform_set_drvdata(pdev, udc);
+	dev_info(dev, "Synopsys UDC driver probe successful\n");
+
+	return 0;
+exit_dma:
+	free_udc_dma(pdev, udc);
+exit_extcon:
+	extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
+exit_phy:
+	phy_power_off(udc->udc_phy);
+	phy_exit(udc->udc_phy);
+
+	return ret;
+}
+
+static int snps_udc_remove(struct platform_device *pdev)
+{
+	struct snps_udc *udc;
+
+	udc = platform_get_drvdata(pdev);
+
+	usb_del_gadget_udc(&udc->gadget);
+
+	platform_set_drvdata(pdev, NULL);
+
+	if (udc->drd_wq) {
+		flush_workqueue(udc->drd_wq);
+		destroy_workqueue(udc->drd_wq);
+	}
+
+	free_udc_dma(pdev, udc);
+	phy_power_off(udc->udc_phy);
+	phy_exit(udc->udc_phy);
+	extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
+
+	dev_info(&pdev->dev, "Synopsys UDC driver removed\n");
+
+	return 0;
+}
+
+static void snps_udc_shutdown(struct platform_device *pdev)
+{
+	struct snps_udc *udc = platform_get_drvdata(pdev);
+
+	snps_gadget_stop(&udc->gadget);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int snps_udc_suspend(struct device *dev)
+{
+	struct snps_udc *udc;
+
+	udc = dev_get_drvdata(dev);
+
+	if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
+		dev_dbg(udc->dev, "device -> idle\n");
+		snps_gadget_pullup(&udc->gadget, 0);
+	}
+	phy_power_off(udc->udc_phy);
+	phy_exit(udc->udc_phy);
+
+	return 0;
+}
+
+static int snps_udc_resume(struct device *dev)
+{
+	struct snps_udc *udc;
+	int ret;
+
+	udc = dev_get_drvdata(dev);
+
+	ret = phy_init(udc->udc_phy);
+	if (ret) {
+		dev_err(udc->dev, "UDC phy init failure");
+		return ret;
+	}
+
+	ret = phy_power_on(udc->udc_phy);
+	if (ret) {
+		dev_err(udc->dev, "UDC phy power on failure");
+		phy_exit(udc->udc_phy);
+		return ret;
+	}
+
+	if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
+		dev_dbg(udc->dev, "idle -> device\n");
+		snps_gadget_pullup(&udc->gadget, 1);
+	}
+
+	return 0;
+}
+
+static const struct dev_pm_ops snps_udc_pm_ops = {
+	.suspend	= snps_udc_suspend,
+	.resume		= snps_udc_resume,
+};
+#endif
+
+static const struct of_device_id of_udc_match[] = {
+	{ .compatible = "snps,dw-ahb-udc", },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, of_udc_match);
+
+static struct platform_driver snps_udc_driver = {
+	.probe		= snps_udc_probe,
+	.remove		= snps_udc_remove,
+	.shutdown	= snps_udc_shutdown,
+	.driver		= {
+		.name	= "snps-udc",
+		.of_match_table = of_match_ptr(of_udc_match),
+#ifdef CONFIG_PM_SLEEP
+		.pm	= &snps_udc_pm_ops,
+#endif
+	},
+};
+
+module_platform_driver(snps_udc_driver);
+
+MODULE_ALIAS("platform:snps-udc");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/gadget/udc/snps_udc.h b/drivers/usb/gadget/udc/snps_udc.h
new file mode 100644
index 0000000..0355d59
--- /dev/null
+++ b/drivers/usb/gadget/udc/snps_udc.h
@@ -0,0 +1,1071 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SNPS_UDC_H
+#define __SNPS_UDC_H
+
+/* UDC speeds */
+#define SPEED_UNKNOWN			(0)
+#define SPEED_LOW			(1)
+#define SPEED_FULL			(2)
+#define SPEED_HIGH			(3)
+
+/* Endpoint directions */
+#define EP_DIRN_IN			(0x80)
+#define EP_DIRN_OUT			(0x00)
+#define EP_DIRN_MASK			(0x80)
+
+/* Endpoint types */
+#define EP_TYPE_CTRL			(0)
+#define EP_TYPE_ISOC			(1)
+#define EP_TYPE_BULK			(2)
+#define EP_TYPE_INTR			(3)
+#define EP_TYPE_MASK			(0x03)
+
+/* Max supported endpoints */
+#define UDC_MAX_EP			(10)
+
+#define EP_MAX_PKT_SIZE			512
+#define EP_CTRL_MAX_PKT_SIZE		64
+#define OUT_RX_FIFO_MEM_SIZE		4096
+#define IN_TX_FIFO_MEM_SIZE		4096
+
+#define is_ep_in()			((ep->dirn) == USB_DIR_IN)
+#define is_ep_out()			((ep->dirn) == USB_DIR_OUT)
+#define is_ep_bulk()			((ep->type) == USB_ENDPOINT_XFER_BULK)
+
+#define DESC_CNT (1)
+
+#define EP_DMA_DESC_IDX_MASK		(DESC_CNT - 1)
+#define EP_DMA_DESC_IDX(num)		((num) & EP_DMA_DESC_IDX_MASK)
+
+#define USB_MODE_IDLE			(1)
+#define USB_MODE_DEVICE			(2)
+
+#define FIFO_SZ_U32(pkt_sz)		(((pkt_sz) + 3) / sizeof(u32))
+#define FIFO_SZ_U8(sz)			(FIFO_SZ_U32(sz) * sizeof(u32))
+#define USBD_WQ_DELAY_MS		msecs_to_jiffies(100)
+/* Register Masks and definitions */
+
+/* Endpoint Control Registers*/
+#define EP_CTRL_OUT_FLUSH_ENABLE	BIT(12)
+#define EP_CTRL_OUT_CLOSE_DESC		BIT(11)
+#define EP_CTRL_IN_SEND_NULL		BIT(10)
+#define EP_CTRL_OUT_DMA_ENABLE		BIT(9)
+#define EP_CTRL_NAK_CLEAR		BIT(8)
+#define EP_CTRL_NAK_SET			BIT(7)
+#define EP_CTRL_NAK_IN_PROGRESS		BIT(6)
+#define EP_CTRL_TYPE_SHIFT		(4)
+#define EP_CTRL_TYPE_MASK		(3 << EP_CTRL_TYPE_SHIFT)
+#define EP_CTRL_IN_DMA_ENABLE		BIT(3)
+#define EP_CTRL_SNOOP_ENABLE		BIT(2)
+#define EP_CTRL_IN_FLUSH_ENABLE		BIT(1)
+#define EP_CTRL_STALL_ENABLE		BIT(0)
+
+/* Endpoint Status Registers */
+#define EP_STS_CLOSE_DESC_CLEAR		BIT(28)
+#define EP_STS_IN_XFER_DONE		BIT(27)
+#define EP_STS_STALL_SET_RX		BIT(26)
+#define EP_STS_STALL_CLEAR_RX		BIT(25)
+#define EP_STS_IN_FIFO_EMPTY		BIT(24)
+#define EP_STS_IN_DMA_DONE		BIT(10)
+#define EP_STS_AHB_BUS_ERROR		BIT(9)
+#define EP_STS_OUT_FIFO_EMPTY		BIT(8)
+#define EP_STS_DMA_BUF_NOT_AVAIL	BIT(7)
+#define EP_STS_IN_TOKEN_RX		BIT(6)
+#define EP_STS_OUT_DMA_SETUP_DONE	BIT(5)
+#define EP_STS_OUT_DMA_DATA_DONE	BIT(4)
+
+/* Buffer Regs for EP In, Receive Packet Frame Num Regs for EP Out */
+#define EP_REG2_OUT_ISOC_PID_SHIFT	(16)
+#define EP_REG2_OUT_ISOC_PID_MASK	(3 << EP_REG2_OUT_ISOC_PID_SHIFT)
+#define EP_REG2_IN_DEPTH_SHIFT		(0)
+#define EP_REG2_IN_DEPTH_MASK		(0xffff << EP_REG2_IN_DEPTH_SHIFT)
+#define EP_REG2_OUT_FRAME_NUM_SHIFT	EP_REG2_IN_DEPTH_SHIFT
+#define EP_REG2_OUT_FRAME_NUM_MASK	EP_REG2_IN_DEPTH_MASK
+
+/* Max Packet Size Regs for EP In, Buffer Size Regs for EP Out */
+#define EP_REG3_OUT_DEPTH_SHIFT		(16)
+#define EP_REG3_OUT_DEPTH_MASK		(0xffff << EP_REG3_OUT_DEPTH_SHIFT)
+#define EP_REG3_PKT_MAX_SHIFT		(0)
+#define EP_REG3_PKT_MAX_MASK		(0xffff << EP_REG3_PKT_MAX_SHIFT)
+
+/* Endpoint Config Registers */
+#define EP_CFG_DIRN_IN			BIT(4)
+#define EP_CFG_DIRN_OUT			(0)
+#define EP_CFG_PKT_MAX_SHIFT		(19)
+#define EP_CFG_PKT_MAX_MASK		(0x7ff << EP_CFG_PKT_MAX_SHIFT)
+#define EP_CFG_ALT_NUM_SHIFT		(15)
+#define EP_CFG_ALT_NUM_MASK		(0xf << EP_CFG_ALT_NUM_SHIFT)
+#define EP_CFG_INTF_NUM_SHIFT		(11)
+#define EP_CFG_INTF_NUM_MASK		(0xf << EP_CFG_INTF_NUM_SHIFT)
+#define EP_CFG_CFG_NUM_SHIFT		(7)
+#define EP_CFG_CFG_NUM_MASK		(0xf << EP_CFG_CFG_NUM_SHIFT)
+#define EP_CFG_TYPE_SHIFT		(5)
+#define EP_CFG_TYPE_MASK		(0x3 << EP_CFG_TYPE_SHIFT)
+#define EP_CFG_FIFO_NUM_SHIFT		(0)
+#define EP_CFG_FIFO_NUM_MASK		(0xf << EP_CFG_FIFO_NUM_SHIFT)
+
+/* Endpoint Interrupt Registers */
+#define EP_INTR_OUT_SHIFT		(16)
+#define EP_INTR_OUT_MASK		(0xffff << EP_INTR_OUT_SHIFT)
+#define EP_INTR_IN_SHIFT		(0)
+#define EP_INTR_IN_MASK			(0xffff << EP_INTR_IN_SHIFT)
+
+/* Device Config Register */
+#define CFG_ULPI_DDR_ENABLE		BIT(19)
+#define CFG_SET_DESCRIPTOR_ENABLE	BIT(18)
+#define CFG_CSR_PROGRAM_ENABLE		BIT(17)
+#define CFG_HALT_STALL_ENABLE		BIT(16)
+#define CFG_HS_TIMEOUT_CALIB_SHIFT	(13)
+#define CFG_HS_TIMEOUT_CALIB_MASK	(7 << CFG_HS_TIMEOUT_CALIB_SHIFT)
+#define CFG_FS_TIMEOUT_CALIB_SHIFT	(10)
+#define CFG_FS_TIMEOUT_CALIB_MASK	(7 << CFG_FS_TIMEOUT_CALIB_SHIFT)
+#define CFG_STS_1_ENABLE		BIT(8)
+#define CFG_STS_ENABLE			BIT(7)
+#define CFG_UTMI_BI_DIRN_ENABLE		BIT(6)
+#define CFG_UTMI_8BIT_ENABLE		BIT(5)
+#define CFG_SYNC_FRAME_ENABLE		BIT(4)
+#define CFG_SELF_PWR_ENABLE		BIT(3)
+#define CFG_REMOTE_WAKEUP_ENABLE	BIT(2)
+#define CFG_SPD_SHIFT			(0)
+#define CFG_SPD_MASK			(3 << CFG_SPD_SHIFT)
+#define CFG_SPD_HS			(0 << CFG_SPD_SHIFT)
+#define CFG_SPD_FS			BIT(0)
+#define CFG_SPD_LS			(2 << CFG_SPD_SHIFT)
+#define CFG_SPD_FS_48MHZ		(3 << CFG_SPD_SHIFT)
+
+/* Device Control Register*/
+#define CTRL_DMA_OUT_THRESH_LEN_SHIFT	(24)
+#define CTRL_DMA_OUT_THRESH_LEN_MASK	(0xff << CTRL_DMA_OUT_THRESH_LEN_SHIFT)
+#define CTRL_DMA_BURST_LEN_SHIFT	(16)
+#define CTRL_DMA_BURST_LEN_MASK		(0xff << CTRL_DMA_BURST_LEN_SHIFT)
+#define CTRL_OUT_FIFO_FLUSH_ENABLE	BIT(14)
+#define CTRL_CSR_DONE			BIT(13)
+#define CTRL_OUT_ALL_NAK		BIT(12)
+#define CTRL_DISCONNECT_ENABLE		BIT(10)
+#define CTRL_DMA_MODE_ENABLE		BIT(9)
+#define CTRL_DMA_BURST_ENABLE		BIT(8)
+#define CTRL_DMA_OUT_THRESH_ENABLE	BIT(7)
+#define CTRL_DMA_BUFF_FILL_MODE_ENABLE	BIT(6)
+#define CTRL_ENDIAN_BIG_ENABLE		BIT(5)
+#define CTRL_DMA_DESC_UPDATE_ENABLE	BIT(4)
+#define CTRL_DMA_IN_ENABLE		BIT(3)
+#define CTRL_DMA_OUT_ENABLE		BIT(2)
+#define CTRL_RESUME_SIGNAL_ENABLE	BIT(0)
+#define CTRL_LE_ENABLE			(0)
+
+/* Device Status Register */
+#define STS_SOF_FRAME_NUM_SHIFT		(18)
+#define STS_SOF_FRAME_NUM_MASK		(0x3ffff << STS_SOF_FRAME_NUM_SHIFT)
+#define STS_REMOTE_WAKEUP_ALLOWED	BIT(17)
+#define STS_PHY_ERROR			BIT(16)
+#define STS_OUT_FIFO_EMPTY		BIT(15)
+#define STS_SPD_SHIFT			(13)
+#define STS_SPD_MASK			(3 << STS_SPD_SHIFT)
+#define STS_SPD_HS			(0 << STS_SPD_SHIFT)
+#define STS_SPD_FS			BIT(13)
+#define STS_SPD_LS			(2 << STS_SPD_SHIFT)
+#define STS_SPD_FS_48MHZ		(3 << STS_SPD_SHIFT)
+#define STS_BUS_SUSPENDED		BIT(12)
+#define STS_ALT_NUM_SHIFT		(8)
+#define STS_ALT_NUM_MASK		(0xf << STS_SPD_SHIFT)
+#define STS_INTF_NUM_SHIFT		(4)
+#define STS_INTF_NUM_MASK		(0xf << STS_INTF_NUM_SHIFT)
+#define STS_CFG_NUM_SHIFT		(0)
+#define STS_CFG_NUM_MASK		(0xf << STS_CFG_NUM_SHIFT)
+
+/* Device Interrupt Register */
+#define INTR_REMOTE_WAKEUP_DELTA	BIT(7)
+#define INTR_SPD_ENUM_DONE		BIT(6)
+#define INTR_SOF_RX			BIT(5)
+#define INTR_BUS_SUSPEND		BIT(4)
+#define INTR_BUS_RESET			BIT(3)
+#define INTR_BUS_IDLE			BIT(2)
+#define INTR_SET_INTF_RX		BIT(1)
+#define INTR_SET_CFG_RX			BIT(0)
+
+#define DMA_STS_BUF_SHIFT		(30)
+#define DMA_STS_BUF_HOST_READY		(0 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_DMA_BUSY		BIT(30)
+#define DMA_STS_BUF_DMA_DONE		(2 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_HOST_BUSY		(3 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_MASK		(3 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_RX_SHIFT		(28)
+#define DMA_STS_RX_SUCCESS		(0 << DMA_STS_RX_SHIFT)
+#define DMA_STS_RX_ERR_DESC		BIT(28)
+#define DMA_STS_RX_ERR_BUF		(3 << DMA_STS_RX_SHIFT)
+#define DMA_STS_RX_MASK			(3 << DMA_STS_RX_SHIFT)
+#define DMA_STS_CFG_NUM_SHIFT		(24)
+#define DMA_STS_CFG_NUM_MASK		(0xf << DMA_STS_CFG_NUM_SHIFT)
+#define DMA_STS_INTF_NUM_SHIFT		(20)
+#define DMA_STS_INTF_NUM_MASK		(0xf << DMA_STS_INTF_NUM_SHIFT)
+#define DMA_STS_LAST_DESC		BIT(27)
+#define DMA_STS_FRAME_NUM_SHIFT		(16)
+#define DMA_STS_FRAME_NUM_MASK		(0x7ff << DMA_STS_FRAME_NUM_SHIFT)
+#define DMA_STS_BYTE_CNT_SHIFT		(0)
+#define DMA_STS_ISO_PID_SHIFT		(14)
+#define DMA_STS_ISO_PID_MASK		(0x3 << DMA_STS_ISO_PID_SHIFT)
+#define DMA_STS_ISO_BYTE_CNT_SHIFT	(DMA_STS_BYTE_CNT_SHIFT)
+#define DMA_STS_ISO_BYTE_CNT_MASK	(0x3fff << DMA_STS_ISO_BYTE_CNT_SHIFT)
+#define DMA_STS_NISO_BYTE_CNT_SHIFT	(DMA_STS_BYTE_CNT_SHIFT)
+#define DMA_STS_NISO_BYTE_CNT_MASK	(0xffff << DMA_STS_NISO_BYTE_CNT_SHIFT)
+
+/* UDC Interrupts */
+#define UDC_IRQ_ALL			(IRQ_REMOTEWAKEUP_DELTA | \
+					IRQ_SPEED_ENUM_DONE | \
+					IRQ_BUS_SUSPEND | \
+					IRQ_BUS_RESET | \
+					IRQ_BUS_IDLE | \
+					IRQ_SET_INTF | \
+					IRQ_SET_CFG)
+#define IRQ_REMOTEWAKEUP_DELTA		INTR_REMOTE_WAKEUP_DELTA
+#define IRQ_SPEED_ENUM_DONE		INTR_SPD_ENUM_DONE
+#define IRQ_SOF_DETECTED		INTR_SOF_RX
+#define IRQ_BUS_SUSPEND			INTR_BUS_SUSPEND
+#define IRQ_BUS_RESET			INTR_BUS_RESET
+#define IRQ_BUS_IDLE			INTR_BUS_IDLE
+#define IRQ_SET_INTF			INTR_SET_INTF_RX
+#define IRQ_SET_CFG			INTR_SET_CFG_RX
+
+/* Endpoint status */
+#define EP_STS_ALL			(DMA_ERROR | \
+					DMA_BUF_NOT_AVAIL | \
+					IN_TOKEN_RX | \
+					IN_DMA_DONE | \
+					IN_XFER_DONE | \
+					OUT_DMA_DATA_DONE | \
+					OUT_DMA_SETUP_DONE)
+
+#define DMA_ERROR			EP_STS_AHB_BUS_ERROR
+#define DMA_BUF_NOT_AVAIL		EP_STS_DMA_BUF_NOT_AVAIL
+#define IN_TOKEN_RX			EP_STS_IN_TOKEN_RX
+#define IN_DMA_DONE			EP_STS_IN_DMA_DONE
+#define IN_FIFO_EMPTY			EP_STS_IN_FIFO_EMPTY
+#define IN_XFER_DONE			EP_STS_IN_XFER_DONE
+#define OUT_DMA_DATA_DONE		EP_STS_OUT_DMA_DATA_DONE
+#define OUT_DMA_SETUP_DONE		EP_STS_OUT_DMA_SETUP_DONE
+
+#define DMA_ADDR_INVALID	(~(dma_addr_t)0)
+#define DIRN_STR(dirn)		((dirn) == USB_DIR_IN ? "IN" : "OUT")
+#define EP_DIRN_TYPE(d, t)	(((d) << 8) | (t))
+
+/* Used for ISOC IN transfers for frame alignment. */
+#define FRAME_NUM_INVALID	(~(u32)0)
+
+/* UDC config parameters */
+
+/* If multiple RX FIFO controllers are implemented for
+ * OUT Endpoints, MRX_FIFO is enabled.
+ * Multi RX FIFO controllers are not implemented in RTL.
+ */
+#define MRX_FIFO 0
+#if MRX_FIFO
+static bool mrx_fifo = true;
+#else
+static bool mrx_fifo;
+#endif
+
+/* Buffer Fill mode is enabled for IN transfers,
+ * disabled for OUT transfers.
+ */
+#define IN_DMA_BUF_FILL_EN 1
+#if IN_DMA_BUF_FILL_EN
+static bool in_bf_mode = true;
+#else
+static bool in_bf_mode;
+#endif
+
+#define OUT_DMA_BUF_FILL_EN 0
+#if OUT_DMA_BUF_FILL_EN
+static bool out_bf_mode = true;
+#else
+static bool out_bf_mode;
+#endif
+/*
+ * If it desired that frames start being DMA'd w/o frame
+ * alignment, define ISOC_IN_XFER_DELAY_DISABLE.
+ * If frame alignment is used, this delay is not disabled.
+ */
+#define ISOC_IN_XFER_DELAY_DISABLE 0
+#if ISOC_IN_XFER_DELAY_DISABLE
+static bool in_isoc_delay_disabled = true;
+#else
+static bool in_isoc_delay_disabled;
+#endif
+
+/* Endpoint IN/OUT registers
+ * Register space is reserved for 16 endpoints, but the controller
+ * actually supports 10 endpoints only.
+ */
+#define EP_CNT	(16)
+struct snps_ep_regs {
+	u32 ctrl;	/* EP control */
+	u32 status;	/* EP status */
+	u32 epreg2;	/* Buffer for IN, Rec Pkt Frame num for OUT */
+	u32 epreg3;	/* Max pkt size for IN, Buf size for OUT */
+	u32 setupbuf;	/* Rsvd for IN, EP setup buffer ptr for OUT */
+	u32 datadesc;	/* EP data descriptor pointer */
+	u32 rsvd[2];
+};
+
+/* UDC registers */
+struct snps_udc_regs {
+	struct snps_ep_regs ep_in[EP_CNT];
+	struct snps_ep_regs ep_out[EP_CNT];
+	u32 devcfg;
+	u32 devctrl;
+	u32 devstatus;
+	u32 devintrstat;
+	u32 devintrmask;
+	u32 epintrstat;
+	u32 epintrmask;
+	u32 testmode;
+	u32 releasenum;
+	u32 rsvd[56];
+	u32 epcfg[EP_CNT];
+	u32 rsvd1[175];
+	u32 rx_fifo[256];
+	u32 tx_fifo[256];
+	u32 strap;
+};
+
+/* Endpoint SETUP buffer */
+struct setup_desc {
+	u32 status;
+	u32 reserved;
+	u32 data1;
+	u32 data2;
+};
+
+/* Endpoint In/Out data descriptor */
+struct data_desc {
+	u32 status;
+	u32 reserved;
+	u32 buf_addr;
+	u32 next_desc_addr;
+};
+
+/* Endpoint descriptor layout. */
+struct ep_dma_desc {
+	struct setup_desc setup;
+	struct data_desc  desc[DESC_CNT];
+};
+
+/* Endpoint descriptor array for Synopsys UDC */
+struct ep_desc_array {
+	struct ep_dma_desc ep[UDC_MAX_EP];
+};
+
+struct snps_udc;
+
+/* Endpoint data structure (for each endpoint) */
+struct snps_udc_ep {
+	struct usb_ep usb_ep;
+	const struct usb_endpoint_descriptor *desc;
+	struct list_head queue;
+	struct snps_udc *udc;
+	char name[14];
+	bool in_xfer_done;
+	u32 num;
+	u32 dirn;
+	u32 type;			/* USB_ENDPOINT_XFER_xxx */
+	u32 b_ep_addr;			/* dirn | type */
+	u32 max_pkt_size;
+	u32 rx_fifo_size;		/* Rx FIFO ram allocated */
+	u32 tx_fifo_size;		/* Tx FIFO ram allocated */
+	u32 stopped:1;
+	struct {
+		struct ep_dma_desc *virt;
+		struct ep_dma_desc *phys;
+		struct usb_request *usb_req;/* Current request being DMA'd */
+		u32 len_max;		/* to use with a descriptor */
+		u32 len_done;		/* Length of request DMA'd so far */
+		u32 len_rem;		/* Length of request left to DMA */
+		u32 add_idx;		/* descriptor chain index */
+		u32 remove_idx;		/* descriptor chain index */
+		u32 buf_addr;		/* Location in request to DMA */
+		u32 frame_num;		/* Frame number for ISOC transfers */
+		u32 frame_incr;		/* Frame number increment (period) */
+		u32 status;
+		u32 done;		/* DMA/USB xfer completion indication */
+		void *aligned_buf;	/* used if usb_req buf not aligned */
+		dma_addr_t aligned_addr;/* Aligned buffer physical address */
+		u32 aligned_len;	/* Aligned buffer length */
+		u32 last;
+	} dma;
+};
+
+/* Endpoint xfer request structure */
+struct ep_xfer_req {
+	struct usb_request	usb_req;
+	struct list_head	queue;
+	dma_addr_t		dma_addr_orig;
+	u32			dma_mapped:1;
+	u32			dma_aligned:1;
+};
+
+/* Controller data structure */
+struct snps_udc {
+	struct usb_gadget		gadget;
+	struct usb_gadget_driver	*gadget_driver;
+	struct device			*dev;
+	void __iomem			*regs;
+	int				irq;
+	struct completion		*dev_release;
+	spinlock_t			lock; /* UDC spin lock variable */
+	u32				rx_fifo_space;
+	u32				tx_fifo_space;
+	struct snps_udc_ep		ep[UDC_MAX_EP];
+	struct {
+		struct ep_desc_array	*virt;
+		struct ep_desc_array	*phys;
+	} dma;
+	struct gpio_desc		*vbus_gpiod;
+	u32				vbus_active:1;
+	u32				pullup_on:1;
+	struct phy			*udc_phy;
+	u32				mode;
+	struct extcon_dev		*edev;
+	struct extcon_specific_cable_nb	extcon_nb;
+	struct notifier_block		nb;
+	struct delayed_work		drd_work;
+	struct workqueue_struct		*drd_wq;
+	u32				conn_type;
+};
+
+#define REG_WR(reg, val)		writel(val, &reg)
+#define REG_MOD_AND(reg, val)		writel(val & readl(&reg), &reg)
+#define REG_MOD_OR(reg, val)		writel(val | readl(&reg), &reg)
+#define REG_MOD_MASK(reg, mask, val)	writel(val | (mask & readl(&reg)), &reg)
+#define REG_RD(reg)			readl(&reg)
+
+static inline void dump_regs(struct snps_udc_regs *regs)
+{
+	pr_debug("DEVCFG: 0x%x\n", REG_RD(regs->devcfg));
+	pr_debug("DEVCTRL: 0x%x\n", REG_RD(regs->devctrl));
+	pr_debug("DEVSTS: 0x%x\n", REG_RD(regs->devstatus));
+	pr_debug("DEVINTRMASK: 0x%x\n", REG_RD(regs->devintrmask));
+	pr_debug("DEVINTRSTS: 0x%x\n", REG_RD(regs->devintrstat));
+	pr_debug("EPINTRMASK: 0x%x\n", REG_RD(regs->epintrmask));
+	pr_debug("EPINTRSTS: 0x%x\n", REG_RD(regs->epintrstat));
+}
+
+static inline void bus_connect(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devctrl, ~CTRL_DISCONNECT_ENABLE);
+}
+
+static inline void bus_disconnect(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devctrl, CTRL_DISCONNECT_ENABLE);
+}
+
+static inline bool is_bus_suspend(struct snps_udc_regs *regs)
+{
+	return REG_RD(regs->devstatus) &
+		STS_BUS_SUSPENDED ? true : false;
+}
+
+static inline u32 get_alt_num(struct snps_udc_regs *regs)
+{
+	return (REG_RD(regs->devstatus) & STS_ALT_NUM_MASK)
+						>> STS_ALT_NUM_SHIFT;
+}
+
+static inline u32 get_cfg_num(struct snps_udc_regs *regs)
+{
+	return (REG_RD(regs->devstatus) & STS_CFG_NUM_MASK)
+						>> STS_CFG_NUM_SHIFT;
+}
+
+static inline u32 get_intf_num(struct snps_udc_regs *regs)
+{
+	return (REG_RD(regs->devstatus) & STS_INTF_NUM_MASK)
+						>> STS_INTF_NUM_SHIFT;
+}
+
+static inline void disable_ctrl_dma(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_IN_ENABLE |
+					     CTRL_DMA_OUT_ENABLE));
+}
+
+static inline void enable_ctrl_dma(struct snps_udc_regs *regs)
+{
+		REG_MOD_OR(regs->devctrl, (CTRL_DMA_IN_ENABLE |
+					   CTRL_DMA_OUT_ENABLE));
+}
+
+static inline bool is_ctrl_dma_enable(struct snps_udc_regs *regs)
+{
+	return REG_RD(regs->devctrl) &
+				CTRL_DMA_OUT_ENABLE ? true : false;
+}
+
+static inline void disable_epin_dma(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_IN_ENABLE));
+}
+
+static inline void enable_epin_dma(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devctrl, (CTRL_DMA_IN_ENABLE));
+}
+
+static inline bool is_epin_dma_enable(struct snps_udc_regs *regs)
+{
+	return REG_RD(regs->devctrl) &
+				CTRL_DMA_IN_ENABLE ? true : false;
+}
+
+static inline void disable_epout_dma(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_OUT_ENABLE));
+}
+
+static inline void enable_epout_dma(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devctrl, (CTRL_DMA_OUT_ENABLE));
+}
+
+static inline bool is_epout_dma_enable(struct snps_udc_regs *regs)
+{
+	return REG_RD(regs->devctrl) &
+				CTRL_DMA_OUT_ENABLE ? true : false;
+}
+
+static inline u32 get_frnum_last_rx(struct snps_udc_regs *regs)
+{
+	return (REG_RD(regs->devstatus) &
+		STS_SOF_FRAME_NUM_MASK) >> STS_SOF_FRAME_NUM_SHIFT;
+}
+
+static inline u32 get_irq_active(struct snps_udc_regs *regs)
+{
+	return REG_RD(regs->devintrstat);
+}
+
+static inline void clear_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+	REG_WR(regs->devintrstat, mask);
+}
+
+static inline void disable_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+	REG_MOD_OR(regs->devintrmask, mask);
+}
+
+static inline void enable_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+	REG_MOD_AND(regs->devintrmask, ~mask);
+}
+
+static inline u32 mask_irq(struct snps_udc_regs *regs)
+{
+	return (~REG_RD(regs->devintrmask)) & UDC_IRQ_ALL;
+}
+
+static inline void clear_devnak(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devctrl, ~CTRL_OUT_ALL_NAK);
+}
+
+static inline void set_devnak(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devctrl, CTRL_OUT_ALL_NAK);
+}
+
+static inline bool is_phy_error(struct snps_udc_regs *regs)
+{
+	return REG_RD(regs->devstatus) &
+		STS_PHY_ERROR ? true : false;
+}
+
+static inline bool is_rmtwkp(struct snps_udc_regs *regs)
+{
+	return REG_RD(regs->devstatus) &
+		STS_REMOTE_WAKEUP_ALLOWED ? true : false;
+}
+
+static inline void clear_rmtwkup(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devcfg, ~CFG_REMOTE_WAKEUP_ENABLE);
+}
+
+static inline void set_rmtwkp(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devcfg, CFG_REMOTE_WAKEUP_ENABLE);
+}
+
+static inline void start_rmtwkp(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devctrl, CTRL_RESUME_SIGNAL_ENABLE);
+}
+
+static inline void stop_rmtwkp(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devctrl, ~CTRL_RESUME_SIGNAL_ENABLE);
+}
+
+static inline void disable_self_pwr(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devcfg, ~CFG_SELF_PWR_ENABLE);
+}
+
+static inline void enable_self_pwr(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devcfg, CFG_SELF_PWR_ENABLE);
+}
+
+static inline void disable_set_desc(struct snps_udc_regs *regs)
+{
+	REG_MOD_AND(regs->devcfg, ~CFG_SET_DESCRIPTOR_ENABLE);
+}
+
+static inline void enable_set_desc(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devcfg, CFG_SET_DESCRIPTOR_ENABLE);
+}
+
+static inline void set_setup_done(struct snps_udc_regs *regs)
+{
+	REG_MOD_OR(regs->devctrl, CTRL_CSR_DONE);
+}
+
+static inline u32 get_enum_speed(struct snps_udc_regs *regs)
+{
+	switch (REG_RD(regs->devstatus) & STS_SPD_MASK) {
+	case STS_SPD_LS:
+		return SPEED_LOW;
+	case STS_SPD_HS:
+		return SPEED_HIGH;
+	case STS_SPD_FS:
+	case STS_SPD_FS_48MHZ:
+		return SPEED_FULL;
+	default:
+		return 0;
+	}
+}
+
+static inline void set_speed_requested(struct snps_udc_regs *regs, u32 speed)
+{
+	REG_MOD_AND(regs->devcfg, ~CFG_SPD_MASK);
+
+	switch (speed) {
+	case SPEED_LOW:
+		REG_MOD_OR(regs->devcfg, CFG_SPD_LS);
+		break;
+
+	case SPEED_HIGH:
+		REG_MOD_OR(regs->devcfg, CFG_SPD_HS);
+		break;
+
+	case SPEED_FULL:
+	default:
+		REG_MOD_OR(regs->devcfg, CFG_SPD_FS);
+		break;
+	}
+}
+
+static inline void init_ep_reg(struct snps_udc_regs *regs, u32 num, u32 type,
+			       u32 dirn, u32 max_pkt_size)
+{
+	if ((type == EP_TYPE_CTRL) || (dirn == EP_DIRN_OUT)) {
+		REG_WR(regs->ep_out[num].ctrl,
+		       (type << EP_CTRL_TYPE_SHIFT));
+		REG_WR(regs->ep_out[num].status,
+		       regs->ep_out[num].status);
+		REG_WR(regs->ep_out[num].epreg2,  0);
+		REG_WR(regs->ep_out[num].epreg3,
+		       ((max_pkt_size >> 2) << 16) | max_pkt_size);
+
+		if (mrx_fifo)
+			REG_MOD_OR(regs->ep_out[num].epreg3,
+				   (FIFO_SZ_U32(max_pkt_size) <<
+				   EP_REG3_OUT_DEPTH_SHIFT));
+	}
+	if ((type == EP_TYPE_CTRL) || (dirn == EP_DIRN_IN)) {
+		REG_WR(regs->ep_in[num].ctrl,
+		       (type << EP_CTRL_TYPE_SHIFT));
+		REG_WR(regs->ep_in[num].epreg3,
+		       (max_pkt_size << EP_REG3_PKT_MAX_SHIFT));
+		REG_WR(regs->ep_in[num].epreg2,
+		       (max_pkt_size >> 2));
+		REG_MOD_OR(regs->ep_in[num].ctrl,
+			   EP_CTRL_IN_FLUSH_ENABLE);
+		REG_MOD_AND(regs->ep_in[num].ctrl,
+			    ~EP_CTRL_IN_FLUSH_ENABLE);
+		REG_MOD_AND(regs->ep_in[num].ctrl,
+			    EP_CTRL_NAK_SET);
+	}
+	REG_WR(regs->epcfg[num],
+	       (num << EP_CFG_FIFO_NUM_SHIFT) |
+	       (type << EP_CFG_TYPE_SHIFT) |
+	       (max_pkt_size << EP_CFG_PKT_MAX_SHIFT) |
+	       ((dirn == EP_DIRN_OUT) ? EP_CFG_DIRN_OUT : EP_CFG_DIRN_IN));
+}
+
+static inline void set_ep_alt_num(struct snps_udc_regs *regs, u32 num, u32 alt)
+{
+	REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_ALT_NUM_MASK,
+		     (alt << EP_CFG_ALT_NUM_SHIFT));
+}
+
+static inline void set_epcfg_reg(struct snps_udc_regs *regs, u32 num, u32 cfg)
+{
+	REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_CFG_NUM_MASK,
+		     (cfg << EP_CFG_CFG_NUM_SHIFT));
+}
+
+static inline void set_ep_intf_num(struct snps_udc_regs *regs, u32 num,
+				   u32 intf)
+{
+	REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_INTF_NUM_MASK,
+		     (intf << EP_CFG_INTF_NUM_SHIFT));
+}
+
+static inline void disable_ep_dma(struct snps_udc_regs *regs, u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT) {
+		if (mrx_fifo)
+			REG_MOD_AND(regs->ep_out[num].ctrl,
+				    ~EP_CTRL_OUT_DMA_ENABLE);
+	} else {
+		REG_MOD_AND(regs->ep_in[num].ctrl,
+			    ~EP_CTRL_IN_DMA_ENABLE);
+	}
+}
+
+static inline void enable_ep_dma(struct snps_udc_regs *regs,
+				 u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT) {
+		if (mrx_fifo)
+			REG_MOD_OR(regs->ep_out[num].ctrl,
+				   EP_CTRL_OUT_DMA_ENABLE);
+		else
+			REG_MOD_OR(regs->devctrl,
+				   CTRL_DMA_OUT_ENABLE);
+	} else
+		REG_MOD_OR(regs->ep_in[num].ctrl,
+			   EP_CTRL_IN_DMA_ENABLE);
+}
+
+static inline void set_setup_buf_ptr(struct snps_udc_regs *regs,
+				     u32 num, u32 dirn, void *addr)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_WR(regs->ep_out[num].setupbuf, (dma_addr_t)addr);
+}
+
+static inline void set_data_desc_ptr(struct snps_udc_regs *regs,
+				     u32 num, u32 dirn, void *addr)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_WR(regs->ep_out[num].datadesc, (dma_addr_t)addr);
+	else
+		REG_WR(regs->ep_in[num].datadesc, (dma_addr_t)addr);
+}
+
+static inline bool is_ep_fifo_empty(struct snps_udc_regs *regs,
+				    u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT) {
+		if (mrx_fifo)
+			return REG_RD(regs->ep_out[num].status) &
+				EP_STS_OUT_FIFO_EMPTY ? true : false;
+		else
+			return REG_RD(regs->devstatus) &
+				STS_OUT_FIFO_EMPTY ? true : false;
+	}
+	return REG_RD(regs->ep_in[num].status) &
+			EP_STS_IN_FIFO_EMPTY ? true : false;
+}
+
+static inline void clear_ep_fifo_flush(struct snps_udc_regs *regs,
+				       u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT) {
+		if (mrx_fifo)
+			REG_MOD_AND(regs->ep_out[num].ctrl,
+				    ~EP_CTRL_OUT_FLUSH_ENABLE);
+		else
+			REG_MOD_AND(regs->devctrl,
+				    ~CTRL_OUT_FIFO_FLUSH_ENABLE);
+	} else {
+		REG_MOD_AND(regs->ep_in[num].ctrl,
+			    ~EP_CTRL_IN_FLUSH_ENABLE);
+	}
+}
+
+static inline void set_ep_fifo_flush(struct snps_udc_regs *regs,
+				     u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT) {
+		if (mrx_fifo)
+			REG_MOD_OR(regs->ep_out[num].ctrl,
+				   EP_CTRL_OUT_FLUSH_ENABLE);
+		else
+			REG_MOD_OR(regs->devctrl,
+				   CTRL_OUT_FIFO_FLUSH_ENABLE);
+	} else {
+		REG_MOD_OR(regs->ep_in[num].ctrl,
+			   EP_CTRL_IN_FLUSH_ENABLE);
+	}
+}
+
+static inline u32 get_ep_frnum(struct snps_udc_regs *regs,
+			       u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		return (regs->ep_out[num].epreg2 &
+				EP_REG2_OUT_FRAME_NUM_MASK) >>
+				EP_REG2_OUT_FRAME_NUM_SHIFT;
+	return 0;
+}
+
+static inline void clear_udc_ep_irq(struct snps_udc_regs *regs,
+				    u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_WR(regs->epintrstat, (1 << num) <<
+		       EP_INTR_OUT_SHIFT);
+	else
+		REG_WR(regs->epintrstat, (1 << num) <<
+		       EP_INTR_IN_SHIFT);
+}
+
+static inline void disable_udc_ep_irq(struct snps_udc_regs *regs,
+				      u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT) {
+		REG_MOD_OR(regs->epintrmask, ((1 << num) <<
+			   EP_INTR_OUT_SHIFT));
+	} else {
+		REG_MOD_OR(regs->epintrmask, ((1 << num) <<
+			   EP_INTR_IN_SHIFT));
+	}
+}
+
+static inline void enable_udc_ep_irq(struct snps_udc_regs *regs,
+				     u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT) {
+		REG_MOD_AND(regs->epintrmask, ~((1 << num) <<
+			    EP_INTR_OUT_SHIFT));
+	} else {
+		REG_MOD_AND(regs->epintrmask, ~((1 << num) <<
+			    EP_INTR_IN_SHIFT));
+	}
+}
+
+static inline u32 get_ep_irq_active(struct snps_udc_regs *regs, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		return (REG_RD(regs->epintrstat) & EP_INTR_OUT_MASK)
+				 >> EP_INTR_OUT_SHIFT;
+
+	return (REG_RD(regs->epintrstat) & EP_INTR_IN_MASK)
+				 >> EP_INTR_IN_SHIFT;
+}
+
+static inline void clear_udc_ep_irq_list(struct snps_udc_regs *regs,
+					 u32 dirn, u32 mask)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_WR(regs->epintrstat, (mask << EP_INTR_OUT_SHIFT));
+	else
+		REG_WR(regs->epintrstat, (mask << EP_INTR_IN_SHIFT));
+}
+
+static inline u32 get_ep_status(struct snps_udc_regs *regs, u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		return REG_RD(regs->ep_out[num].status);
+
+	return REG_RD(regs->ep_in[num].status);
+}
+
+static inline void clear_ep_status(struct snps_udc_regs *regs,
+				   u32 num, u32 dirn, u32 mask)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_WR(regs->ep_out[num].status, mask);
+	else
+		REG_WR(regs->ep_in[num].status, mask);
+}
+
+static inline void clear_ep_nak(struct snps_udc_regs *regs,
+				u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_MOD_OR(regs->ep_out[num].ctrl, EP_CTRL_NAK_CLEAR);
+	else
+		REG_MOD_OR(regs->ep_in[num].ctrl, EP_CTRL_NAK_CLEAR);
+}
+
+static inline void enable_ep_nak(struct snps_udc_regs *regs,
+				 u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_MOD_OR(regs->ep_out[num].ctrl, EP_CTRL_NAK_SET);
+	else
+		REG_MOD_OR(regs->ep_in[num].ctrl, EP_CTRL_NAK_SET);
+}
+
+static inline void disable_ep_nak(struct snps_udc_regs *regs,
+				  u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_MOD_AND(regs->ep_out[num].ctrl, ~EP_CTRL_NAK_SET);
+	else
+		REG_MOD_AND(regs->ep_in[num].ctrl, ~EP_CTRL_NAK_SET);
+}
+
+static inline bool is_ep_nak_inprog(struct snps_udc_regs *regs,
+				    u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		return REG_RD(regs->ep_out[num].ctrl) &
+			EP_CTRL_NAK_IN_PROGRESS ? true : false;
+
+	return REG_RD(regs->ep_in[num].ctrl) &
+			EP_CTRL_NAK_IN_PROGRESS ? true : false;
+}
+
+static inline void disable_ep_stall(struct snps_udc_regs *regs,
+				    u32 num, u32 dirn)
+{
+	if (dirn == EP_DIRN_OUT)
+		REG_MOD_AND(regs->ep_out[num].ctrl,
+			    ~EP_CTRL_STALL_ENABLE);
+	else
+		REG_MOD_AND(regs->ep_in[num].ctrl,
+			    ~EP_CTRL_STALL_ENABLE);
+}
+
+static inline void enable_ep_stall(struct snps_udc_regs *regs,
+				   u32 num, u32 dirn)
+{
+	if (mrx_fifo && !(REG_RD(regs->ep_out[num].status) &
+			EP_STS_OUT_FIFO_EMPTY))
+		return;
+	else if (!mrx_fifo && !(REG_RD(regs->devstatus) &
+			STS_OUT_FIFO_EMPTY))
+		return;
+
+	if (dirn == EP_DIRN_OUT)
+		REG_MOD_OR(regs->ep_out[num].ctrl,
+			   EP_CTRL_STALL_ENABLE);
+	else
+		REG_MOD_OR(regs->ep_in[num].ctrl,
+			   EP_CTRL_STALL_ENABLE);
+}
+
+static inline u32 get_last_rx_frnum(struct snps_udc_regs *regs)
+{
+	return (REG_RD(regs->devstatus) & STS_SOF_FRAME_NUM_MASK)
+			>> STS_SOF_FRAME_NUM_SHIFT;
+}
+
+static inline void finish_udc(struct snps_udc_regs *regs)
+{
+	u32 ep_num;
+
+	disable_ctrl_dma(regs);
+	disable_udc_dev_irq(regs, UDC_IRQ_ALL);
+	clear_udc_dev_irq(regs, UDC_IRQ_ALL);
+
+	for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++) {
+		disable_udc_ep_irq(regs, ep_num, EP_DIRN_IN);
+		clear_udc_ep_irq(regs, ep_num, EP_DIRN_IN);
+		clear_ep_status(regs, ep_num, EP_DIRN_IN,
+				get_ep_status(regs, ep_num,
+					      EP_DIRN_IN));
+
+		disable_udc_ep_irq(regs, ep_num, EP_DIRN_OUT);
+		clear_udc_ep_irq(regs, ep_num, EP_DIRN_OUT);
+		clear_ep_status(regs, ep_num, EP_DIRN_OUT,
+				get_ep_status(regs, ep_num,
+					      EP_DIRN_OUT));
+	}
+}
+
+static inline void init_udc_reg(struct snps_udc_regs *regs)
+{
+	finish_udc(regs);
+	REG_WR(regs->devcfg, CFG_SET_DESCRIPTOR_ENABLE
+					| CFG_UTMI_8BIT_ENABLE
+					| CFG_CSR_PROGRAM_ENABLE
+					| CFG_SPD_HS);
+	REG_WR(regs->devctrl, CTRL_LE_ENABLE
+					| CTRL_DISCONNECT_ENABLE
+					| CTRL_DMA_MODE_ENABLE
+					| CTRL_DMA_DESC_UPDATE_ENABLE
+					| CTRL_OUT_ALL_NAK
+					| CTRL_DMA_OUT_THRESH_LEN_MASK
+					| CTRL_DMA_BURST_LEN_MASK
+					| CTRL_DMA_BURST_ENABLE
+					| CTRL_OUT_FIFO_FLUSH_ENABLE
+			);
+
+	if (mrx_fifo)
+		REG_MOD_AND(regs->devctrl, ~CTRL_OUT_FIFO_FLUSH_ENABLE);
+
+	if (out_bf_mode)
+		REG_MOD_OR(regs->devctrl, CTRL_DMA_BUFF_FILL_MODE_ENABLE);
+
+	REG_WR(regs->devintrmask, IRQ_BUS_IDLE | IRQ_SOF_DETECTED);
+	REG_WR(regs->epintrmask, 0);
+}
+
+static inline struct data_desc *dma_desc_chain_alloc(struct snps_udc_ep *ep)
+{
+	u32 idx;
+
+	idx = ep->dma.add_idx++;
+
+	return &ep->dma.virt->desc[EP_DMA_DESC_IDX(idx)];
+}
+
+static inline int dma_desc_chain_is_empty(struct snps_udc_ep *ep)
+{
+	return ep->dma.add_idx == ep->dma.remove_idx;
+}
+
+static inline void dma_desc_chain_free(struct snps_udc_ep *ep)
+{
+	ep->dma.remove_idx++;
+}
+
+static inline int dma_desc_chain_is_full(struct snps_udc_ep *ep)
+{
+	return !dma_desc_chain_is_empty(ep) &&
+		(EP_DMA_DESC_IDX(ep->dma.add_idx) ==
+		 EP_DMA_DESC_IDX(ep->dma.remove_idx));
+}
+
+static inline struct data_desc *dma_desc_chain_head(struct snps_udc_ep *ep)
+{
+	u32 index = EP_DMA_DESC_IDX(ep->dma.remove_idx);
+
+	return &ep->dma.virt->desc[index];
+}
+
+static inline void dma_desc_chain_reset(struct snps_udc_ep *ep)
+{
+	ep->dma.add_idx = 0;
+	ep->dma.remove_idx = 0;
+}
+#endif
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* Re: [PATCH V2 09/10] thermal: da9062/61: Thermal junction temperature monitoring driver
From: Eduardo Valentin @ 2016-11-30  6:09 UTC (permalink / raw)
  To: Steve Twiss
  Cc: LINUX-KERNEL, LINUX-PM, Zhang Rui, DEVICETREE, Dmitry Torokhov,
	Guenter Roeck, LINUX-INPUT, LINUX-WATCHDOG, Lee Jones,
	Liam Girdwood, Mark Brown, Mark Rutland, Rob Herring,
	Support Opensource, Wim Van Sebroeck
In-Reply-To: <6ED8E3B22081A4459DAC7699F3695FB7018CCEC2F8@SW-EX-MBX02.diasemi.com>

Hey Steve,

On Tue, Nov 29, 2016 at 11:11:59AM +0000, Steve Twiss wrote:
> Hi Eduardo,
> 
> Thanks for your response.
> 
> On 29 November 2016 01:24, Eduardo Valentin, wrote:
> 
> > On Wed, Oct 26, 2016 at 05:56:39PM +0100, Steve Twiss wrote:
> > > +config DA9062_THERMAL
> > > +	tristate "DA9062/DA9061 Dialog Semiconductor thermal driver"
> > > +	depends on MFD_DA9062
> > > +	depends on OF
> > > +	help
> > > +	  Enable this for the Dialog Semiconductor thermal sensor driver.
> > > +	  This will report PMIC junction over-temperature for one thermal trip
> > > +	  zone.
> > > +	  Compatible with the DA9062 and DA9061 PMICs.
> > 
> > Is there any hardware documentation available for this chip that can be
> > pointed out?
> 
> As part of this patch set, I added a link to the the datasheet into the top-level MFD
> component of the device tree binding: https://patchwork.kernel.org/patch/9426791/
> You can get all the information for DA9062 and DA9061 from the patch update in that
> link.

Thanks for getting me up to speed.

> 
> [...]

<cut>

> > Does this mean that the chip temperature is above or equal to 125C, is
> > this really a safe temperature to keep it running?
> 
> There is more information in the datasheet under the section titles "Junction
> temperature supervision". The value of 125 degC comes from the "warning
> temperature threshold" and the event is triggered when "the junction temperature
> rises above the first threshold (TEMP_WARN), the event E_TEMP is asserted".
> 
> This suggests strictly greater than 125. However, there is no way for the chip to
> know the exact temperature. The mechanism is interrupt based and triggering
> only happens when the temperature rises above the threshold level.

Understood. My original concern was if the driver would be too nice with
the event. But given that the hardware has its own shutdown protection,
looks like we are OK here.

> 
> [...]
> > > +static irqreturn_t da9062_thermal_irq_handler(int irq, void *data)
> > > +{
> > > +	struct da9062_thermal *thermal = data;
> > > +
> > > +	disable_irq_nosync(thermal->irq);
> > > +	schedule_delayed_work(&thermal->work, 0);
> > 
> > Can you please elaborate a little on why you have an one shot threaded IRQ
> > handler that is only disabling the IRQ then, scheduling a work with delay of 0?
> > 
> > To my understanding, this is exactly what you get when you run your
> > threaded IRQ handler, when you configure it as one shot.
> > 
> > Have you tried simply handling the same work done in your workqueue
> > handler in your threaded IRQ? That should simplify your code and get the
> > same result you are expecting.
> > 
> > If you are not getting the IRQ disabled on the threaded IRQ when
> > configured as one shot, something else seams to be broken.
> 
> Over-temperature triggering is event based: an interrupt happens when the
> temperature rises above 125 degC. However, this event based system changes into
> a polling operation to detect when the temperature falls below the threshold
> level again. This asymmetry in the chip's behaviour is the reasoning behind
> why I am not using the thermal core's built-in polling functionality.
> 
> When over-temperature is reached, the interrupt from the DA9061/2 will be
> repeatedly triggered. The IRQ is disabled when the first over-temperature event
> happens and the status bit is polled using the work-queue until it becomes false.
> After that, the IRQ is re-enabled again so a new critical temperature event can
> be detected.
> 
> After the interrupt has happened, event bit for the interrupt switches into a status
> bit and is used for polling and detecting when the temperature drops below the
> threshold.

OK. got it. Then, I am assuming your strategy here is to keep periodically issuing uevents
(HOT trip point) to userland, hoping that something would change the
system power consumption, then, relying on the hardware shutdown protection
if nothing happens.

I would say, your above explanation, and the uevent based strategy,
deserves to be at least in the commit message, preferably in the driver
documentation, so people know what to expect from the driver.

Now, if my understand is correct, would it make sense to have still a
failsafe mechanism in the driver? Maybe having a max number of polling?

The data sheet does not mention anything, but does one have any silicon
lifetime implication if one leaves the PMIC running for very long time
in a temperature between Twarn and Tcrit?

> 
> https://lkml.org/lkml/2016/10/20/372
> https://lkml.org/lkml/2016/10/20/433
> 
> [...]
> > > +	thermal->zone = thermal_zone_device_register(thermal->config->name,
> > > +					1, 0, thermal,
> > > +					&da9062_thermal_ops, NULL, 0,
> > > +					0);
> > 
> > Did you try using of-thermal?
> > 
> > So you would avoid having specific DT properties for something that
> > there is already a defined property?
> 
> In an earlier RFC, I examined a work-around by hijacking and toggling the
> thermal core's built-in polling function when I needed to poll the temperature
> through get_temp(). However I thought this was quite dangerous, since it would
> not be using a formal thermal core interface.
> 
> https://patchwork.kernel.org/patch/9387241/
> 

my point was more under the lines of having this driver using the
of-thermal DT support to get the polling value, instead of you having a
manufacturer specific property:
Documentation/devicetree/bindings/thermal/thermal.txt

But given that your case is more specific, I start to see why you
avoided using it. But still, you could probably get the polling
values from of-thermal, populated in the tz struct, then using them from
the tz, when handling the IRQ event.

Probably your regular polling should always be set to 0, and the passive
polling to the value you want.

then again, this comment is more from a DT perspective than from the
driver code itself. Just trying to avoid specific properties that may
get described by what is already defined.

> [...]
> > > +	ret = request_threaded_irq(thermal->irq, NULL,
> > > +				   da9062_thermal_irq_handler,
> > > +				   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> > > +				   "THERMAL", thermal);
> > 
> > How about using the devm_ version?
> 
> I wanted to explicitly free_irq() before calling thermal_zone_device_unregister()
> in the remove function.
> 

Ok

> Regards,
> Steve

^ permalink raw reply

* Re: [PATCH V8 2/6] thermal: bcm2835: add thermal driver for bcm2835 soc
From: Eduardo Valentin @ 2016-11-30  6:39 UTC (permalink / raw)
  To: Eric Anholt
  Cc: Martin Sperl, Zhang Rui, Rob Herring, Pawel Moll, Mark Rutland,
	Stephen Warren, Lee Jones, Russell King, Florian Fainelli,
	Catalin Marinas, Will Deacon, linux-pm, devicetree,
	linux-rpi-kernel, linux-arm-kernel
In-Reply-To: <87lgw2lyu9.fsf@eliezer.anholt.net>

Hello,

On Tue, Nov 29, 2016 at 02:12:46PM -0800, Eric Anholt wrote:
> Eduardo Valentin <edubezval@gmail.com> writes:
> 
> > Hello Eric, Martin,
> >
> > On Mon, Nov 28, 2016 at 12:30:38PM -0800, Eric Anholt wrote:
> >> Either the device was initialized by the firmware before handing off to
> >> ARM (today's firmware) or it never will be (potential future firmware).
> >
> > And do you have a way to check if the firmware has the initialization
> > code or not? By firmware version, for example. Or even, chip version,
> > maybe?
> 
> We would know if it's not present because the register would be in its
> power-on reset state, which is what the code is checking for.

This just looks odd for a driver, in its probe, to check if device is in
reset state, only then initializes it. if not, it assumes everything is fine.

Besides that, as described in the code, sounds like a quirk.

+        * right now the FW does set up the HW-block, so we are not
+        * touching the configuration registers.
+        * But if the HW is not enabled, then set it up
+        * using "sane" values used by the firmware right now.


And based on what you are describing now, it is not a quirk, but it is by  design (??)

Again, does it hurt to always initialize the device to a *sane* config?

BR,


^ permalink raw reply

* [PATCH] arm64: dts: add zx296718's topcrm node
From: Baoyou Xie @ 2016-11-30  7:33 UTC (permalink / raw)
  To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	catalin.marinas-5wv7dgnIgG8, will.deacon-5wv7dgnIgG8,
	shawnguo-DgEjT+Ai2ygdnm+yROfE0A, jun.nie-QSEj5FYQhm4dnm+yROfE0A
  Cc: baoyou.xie-QSEj5FYQhm4dnm+yROfE0A,
	xie.baoyou-Th6q7B73Y6EnDS1+zs4M5A,
	chen.chaokai-Th6q7B73Y6EnDS1+zs4M5A,
	wang.qiang01-Th6q7B73Y6EnDS1+zs4M5A,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

Enable topcrm clock node for zx296718, which is used for
CPU's frequency change.

Furthermore, this patch adds the CPU clock phandle in CPU's node
and uses operating-points-v2 to register operating points.

So it can be used by cpufreq-dt driver.

Signed-off-by: Baoyou Xie <baoyou.xie-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 arch/arm64/boot/dts/zte/zx296718.dtsi | 48 +++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/arch/arm64/boot/dts/zte/zx296718.dtsi b/arch/arm64/boot/dts/zte/zx296718.dtsi
index 6b239a3..f9eb37d 100644
--- a/arch/arm64/boot/dts/zte/zx296718.dtsi
+++ b/arch/arm64/boot/dts/zte/zx296718.dtsi
@@ -44,6 +44,7 @@
 #include <dt-bindings/input/input.h>
 #include <dt-bindings/interrupt-controller/arm-gic.h>
 #include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/clock/zx296718-clock.h>
 
 / {
 	compatible = "zte,zx296718";
@@ -81,6 +82,8 @@
 			compatible = "arm,cortex-a53","arm,armv8";
 			reg = <0x0 0x0>;
 			enable-method = "psci";
+			clocks = <&topcrm A53_GATE>;
+			operating-points-v2 = <&cluster0_opp>;
 		};
 
 		cpu1: cpu@1 {
@@ -88,6 +91,7 @@
 			compatible = "arm,cortex-a53","arm,armv8";
 			reg = <0x0 0x1>;
 			enable-method = "psci";
+			operating-points-v2 = <&cluster0_opp>;
 		};
 
 		cpu2: cpu@2 {
@@ -95,6 +99,7 @@
 			compatible = "arm,cortex-a53","arm,armv8";
 			reg = <0x0 0x2>;
 			enable-method = "psci";
+			operating-points-v2 = <&cluster0_opp>;
 		};
 
 		cpu3: cpu@3 {
@@ -102,6 +107,43 @@
 			compatible = "arm,cortex-a53","arm,armv8";
 			reg = <0x0 0x3>;
 			enable-method = "psci";
+			operating-points-v2 = <&cluster0_opp>;
+		};
+	};
+
+	cluster0_opp: opp_table0 {
+		compatible = "operating-points-v2";
+		opp-shared;
+
+		opp@1000000000 {
+			opp-hz = /bits/ 64 <500000000>;
+			opp-microvolt = <857000>;
+			clock-latency-ns = <500000>;
+		};
+		opp@1100000000 {
+			opp-hz = /bits/ 64 <648000000>;
+			opp-microvolt = <857000>;
+			clock-latency-ns = <500000>;
+		};
+		opp@1200000000 {
+			opp-hz = /bits/ 64 <800000000>;
+			opp-microvolt = <882000>;
+			clock-latency-ns = <500000>;
+		};
+		opp@1300000000 {
+			opp-hz = /bits/ 64 <1000000000>;
+			opp-microvolt = <892000>;
+			clock-latency-ns = <500000>;
+		};
+		opp@1400000000 {
+			opp-hz = /bits/ 64 <1188000000>;
+			opp-microvolt = <1009000>;
+			clock-latency-ns = <500000>;
+		};
+		opp@1500000000 {
+			opp-hz = /bits/ 64 <1312000000>;
+			opp-microvolt = <1052000>;
+			clock-latency-ns = <500000>;
 		};
 	};
 
@@ -279,6 +321,12 @@
 			dma-requests = <32>;
 		};
 
+		topcrm: clock-controller@01461000 {
+			compatible = "zte,zx296718-topcrm";
+			reg = <0x01461000 0x1000>;
+			#clock-cells = <1>;
+		};
+
 		sysctrl: sysctrl@1463000 {
 			compatible = "zte,zx296718-sysctrl", "syscon";
 			reg = <0x1463000 0x1000>;
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* Re: [PATCH 00/39] mtd: nand: denali: 2nd round of Denali NAND IP patch bomb
From: Masahiro Yamada @ 2016-11-30  8:02 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Linux Kernel Mailing List,
	Marek Vasut, Brian Norris, Richard Weinberger, David Woodhouse,
	Cyrille Pitchen, Rob Herring, Mark Rutland, Andy Shevchenko
In-Reply-To: <20161127160459.5894be93@bbrezillon>

Hi.

2016-11-28 0:04 GMT+09:00 Boris Brezillon <boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>:
> +Andy
>
> Hi Masahiro,
>
> On Sun, 27 Nov 2016 03:05:46 +0900
> Masahiro Yamada <yamada.masahiro-uWyLwvC0a2jby3iVrkZq2A@public.gmane.org> wrote:
>
>> As I said in the 1st round series, I am tackling on this driver
>> to use it for my SoCs.
>>
>> The previous series was just cosmetic things, but this series
>> includes *real* changes.
>>
>> After some more cleanups, I will start to add changes that
>> are really necessary.
>> One of the biggest problems I want to solve is a bunch of
>> hard-coded parameters that prevent me from using this driver for
>> my SoCs.
>>
>> I will introduce capability flags that are associated with DT
>> compatible and make platform-dependent parameters overridable.
>>
>> I still have lots of reworks to get done (so probably 3rd round
>> series will come), but I hope it is getting better and
>> I am showing a big picture now.
>>
>
> Thanks for posting this 2nd round of patches, I know have a clearer
> view of what you're trying to achieve.
> Could you be a bit more specific about the remaining rework (your 3rd
> round)?


[1]
I want to remove
get_samsung_nand_para()
get_onfi_nand_para()

The driver should not hard-code timing parameters of Samsung specific
chips.  For ONFI, it is duplicating effort of the core framework.

I am thinking if it would be possible to implement
chip->setup_data_interface() in order to set up
timings in a generic way.

[2]
Remove driver-internal bounce buffer.
The current Denali driver allocate DMA_BIDIRECTIONAL buffer
to use it as a driver-internal bounce buffer.

The hardware transfer page data into the bounce buffer,
then CPU copies from the bounce buffer to a given buf (and oob_poi).
This is not efficient.

So, I want to set NAND_USE_BOUNCE_BUFFER flag
and do dma_map_single directly for a given buffer.

[3]
Fix raw and oob callbacks.

I asked in another thread,
the current driver just puts the physically accessed OOB data
into oob_poi, which is not a collection of ECC data.
Raw write/read() are wrong as well.

After fixing those, enable BBT scan by removing the following flag:
    /* skip the scan for now until we have OOB read and write support */
    chip->options |= NAND_SKIP_BBTSCAN;



> Also, if you don't mind, I'd like to have reviews and testing from intel
> users before applying the series. Can you Cc Andy (and possibly other
> intel maintainers) for the next round.

Sure.

Anyway, this series already missed the pull-req for 4.10-rc1,
we have plenty of time until 4.11-rc1.

Review/test from Intel engineers are very appreciated
because I have no access to their boards.


-- 
Best Regards
Masahiro Yamada
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH v7 0/8] drm: sun8i: Add DE2 HDMI video support
From: Laurent Pinchart @ 2016-11-30  8:08 UTC (permalink / raw)
  To: Jernej Skrabec
  Cc: linux-sunxi, moinejf-GANU6spQydw, airlied-cv59FeDIM0c,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
	kieran.bingham-ryLnwIuWjnjg/C1BVhZhaw
In-Reply-To: <239b60f3-3ea7-4aa7-8e8d-353e1a1d4ee5-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org>

Hi Jernej,

On Tuesday 29 Nov 2016 15:24:25 Jernej Skrabec wrote:
> Dne torek, 29. november 2016 23.56.31 UTC+1 je oseba Laurent Pinchart
> napisala:
> > On Tuesday 29 Nov 2016 14:47:20 Jernej Skrabec wrote:
> >> Dne torek, 29. november 2016 22.37.03 UTC+1 je oseba Maxime Ripard
> > napisala:
> >>> On Tue, Nov 29, 2016 at 11:18:35AM +0100, Jean-Francois Moine wrote:
> >>>> This patchset series adds HDMI video support to the Allwinner
> >>>> sun8i SoCs which include the display engine 2 (DE2).
> >>>> The driver contains the code for the A83T and H3 SoCs, and
> >>>> some H3 boards, but it could be used/extended for other SoCs
> >>>> (A64, H2, H5) and boards (Banana PIs, Orange PIs).
> >>> 
> >>> Honestly, I'm getting a bit worried by the fact that you ignore
> >>> reviews.
> >>> 
> >>> On the important reviews that you got that are to be seen as major
> >>> issues that block the inclusion, we have:
> >>>   - The fact that the HDMI driver is actually just a designware IP,
> >>>     and while you should use the driver that already exists, you just
> >>>     duplicated all that code.
> >> 
> >> That might be hard thing to do. A83T fits perfectly, but H3 and newer
> >> SoCs do not. They are using completely different HDMI phy. Decoupling
> >> controller and phy code means rewritting a good portion of the code,
> >> unless some tricks are applied, like calling phy function pointers, if
> >> they are defined.
> > 
> > Same HDMI TX but different HDMI TX PHY ? Kieran is working on decoupling
> > the PHY configuration code for a Renesas SoC, that might be of interest to
> > you.
> 
> Exactly. I'm developing only U-Boot driver, but Jean-Francois will probably
> have more interest in this.

We'll post patches as soon as they're ready.

By the way, do you know if the H3 and newer SoCs use a different PHY from 
Synopsys, or a custom PHY developed by Allwinner ?

> >> Register addresses also differ, but that can be easily solved by using
> >> undocumented magic value to restore them.
> > 
> > I love that :-)
> 
> Is it allowed to use magic number which was found in binary blob? I'm new in
> all this.

I don't really see a problem with that, we have many drivers in the kernel 
that have been developed through reverse-engineering. You should not include 
large pieces of code that have been obtained through decompilation of a 
proprietary binary blob as those could be protected by copyright, but writing 
to undocumented registers based on information found through usage of a binary 
driver isn't a problem. (Please remember that I'm not a lawyer though)

> >>>   - The fact that you ignored Rob (v6) and I (v5) comment on using OF
> >>>     graph to model the connection between the display engine and the
> >>>     TCON. Something that Laurent also pointed out in this version.
> >>>   
> >>>   - The fact that you ignored that you needed an HDMI connector node
> >>>     as a child of the HDMI controller. This has been reported by Rob
> >>>     (v6) and yet again in this version by Laurent.
> >>>   
> >>>   - And finally the fact that we can't have several display engine in
> >>>     parallel, if needs be. This has happened in the past already on
> >>>     Allwinner SoCs, so it's definitely something we should consider in
> >>>     the DT bindings, since we can't break them.
> >>> 
> >>> Until those are fixed, I cannot see how this driver can be merged,
> >>> unfortunately.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply

* Re: [PATCH v7 4/8] drm/sunxi: Add DT bindings documentation of Allwinner HDMI
From: Jean-Francois Moine @ 2016-11-30  8:12 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Dave Airlie,
	Maxime Ripard, Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <4502748.8rUF7ESxa4@avalon>

On Tue, 29 Nov 2016 22:10:01 +0200
Laurent Pinchart <laurent.pinchart-ryLnwIuWjnjg/C1BVhZhaw@public.gmane.org> wrote:

> Hi Jean-François,
> 
> On Tuesday 29 Nov 2016 21:04:55 Jean-Francois Moine wrote:
> > On Tue, 29 Nov 2016 21:33 +0200 Laurent Pinchart wrote:
> > >>> You need a third port for the HDMI encoder output, connected to an
> > >>> HDMI connector DT node.
> > >> 
> > >> I don't see what you mean. The HDMI device is both the encoder
> > >> and connector (as the TDA998x):
> > >
> > > The driver might create both an encoder and a connector, but I very much
> > > doubt that the "allwinner,sun8i-a83t-hdmi" hardware contains a connector,
> > > unless the SoC package has an HDMI connector coming out of it :-)
> > > 
> > >> plane -> DE2 mixer ---> TCON -----> HDMI -----> display device
> > >> ----- plane ------    - CRTC -   - encoder  \
> > >>                                    connector -- (HDMI cable)
> > >>          audio-controller -   - audio-codec /
> > 
> > The schema is the same as the Dove Cubox: the TDA998x is just a chip
> > with some wires going out and the physical connector is supposed to be
> > at the end of the wires.
> 
> I've missed the Dove Cubox DT bindings when they were submitted. Fortunately 
> (or unfortunately for you, depending on how you look at it ;-)) I've paid more 
> attention this time.
> 
> > Here, the HDMI pins of the SoC go to a pure hardware chip and then to
> > the physical connector. Which software entity do you want to add?
> 
> I don't want to add a software entity, I just want to model the connector in 
> DT as it's present in the system. Even though that's more common for other bus 
> types than HDMI (LVDS for instance) it wouldn't be inconceivable to connect 
> the HDMI signals to an on-board chim instead of an HDMI connector, so the HDMI 
> encoder output should be modelled by a port and connected to a connector DT 
> node in this case.

Well, I don't see what this connector can be.
May you give me a DT example?

-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox