Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 00/10] AXP803 PMIC support for Pine64
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel

The Pine64 (including Pine64+) boards have an AXP803 PMIC, which is a PMIC
similar to AXP288, but tweaked to use with Allwinner SoCs rather than Intel
tablets (with DCIN and Vbus re-splitted like other AXP PMICs, and RSB bus
support added).

This patchset adds support for it and enabled it in Pine64 device tree.

The basical part of AXP803 MFD driver is already applied, according to Lee.

Thus this patchset is now still two parts, but a bit different to older
revisions:

- Part1: from PATCH 1/10 to PATCH 5/10, which focus on enabling AXP803 in
  the device tree: the RSB bus, the R_INTC interrupt controller (for the
  NMI line, which is connected to AXP803 on Pine64), and finally the basical
  AXP803 node.
- Part2: from PATCH 5/10 to PATCH 10/10, which are enabling the regulator
  function of the AXP803 PMIC. Finally Wi-Fi function is added
  as a usage of regulators function.

PATCH 1 adds RSB device nodes, which is used for the communication between
A64 and AXP803.

PATCH 2 adds device tree binding of A64 R_INTC.

PATCH 3 really adds support for A64 R_INTC in NMI driver.

PATCH 4 adds R_INTC node in A64 device tree.

PATCH 5 adds AXP803 node to the Pine64 device tree by using already
applied drivers/bindings.

PATCH 6 adds support for AXP803 regulators in AXP20x regulatoe driver.
(The binding is already applied)

PATCH 7 enables the AXP803 regulator cell in MFD driver.

PATCH 8 adds a DTSI file for AXP803, like other older AXP PMICs.

PATCH 9 enables AXP803 regulators in Pine64 device tree.

PATCH 10 enables Wi-Fi for Pine64.

Icenowy Zheng (10):
  arm64: allwinner: a64: enable RSB on A64
  irqchip/sunxi-nmi: add A64 R_INTC to the binding doc
  irqchip/sunxi-nmi: add support for the NMI in A64 R_INTC
  arm64: allwinner: a64: add NMI (R_INTC) controller on A64
  arm64: allwinner: a64: add AXP803 node to Pine64 device tree
  regulator: axp20x-regulator: add support for AXP803
  mfd: axp20x: add axp20x-regulator cell for AXP803
  arm64: allwinner: a64: add DTSI file for AXP803 PMIC
  arm64: allwinner: a64: enable AXP803 regulators for Pine64
  arm64: allwinner: a64: enable Wi-Fi for Pine64

 .../interrupt-controller/allwinner,sunxi-nmi.txt   |   7 +-
 arch/arm64/boot/dts/allwinner/axp803.dtsi          | 150 ++++++++++++++++++++
 .../arm64/boot/dts/allwinner/sun50i-a64-pine64.dts | 136 ++++++++++++++++++
 arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi      |  27 ++++
 drivers/irqchip/irq-sunxi-nmi.c                    |  13 ++
 drivers/mfd/axp20x.c                               |   3 +-
 drivers/regulator/axp20x-regulator.c               | 153 ++++++++++++++++++---
 include/linux/mfd/axp20x.h                         |  37 +++++
 8 files changed, 501 insertions(+), 25 deletions(-)
 create mode 100644 arch/arm64/boot/dts/allwinner/axp803.dtsi

-- 
2.12.2

^ permalink raw reply

* [PATCH v5 01/10] arm64: allwinner: a64: enable RSB on A64
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

Allwinner A64 have a RSB controller like the one on A23/A33 SoCs.

Add it and its pinmux.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
Acked-by: Chen-Yu Tsai <wens@csie.org>
---
Changes in v2:
- Removed bonus properties in pio node.
- Added Chen-Yu's ACK.

 arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
index c7f669f5884f..05ec9fc5e81f 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
@@ -422,6 +422,25 @@
 			#gpio-cells = <3>;
 			interrupt-controller;
 			#interrupt-cells = <3>;
+
+			r_rsb_pins: rsb at 0 {
+				pins = "PL0", "PL1";
+				function = "s_rsb";
+			};
+		};
+
+		r_rsb: rsb at 1f03400 {
+			compatible = "allwinner,sun8i-a23-rsb";
+			reg = <0x01f03400 0x400>;
+			interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&r_ccu 6>;
+			clock-frequency = <3000000>;
+			resets = <&r_ccu 2>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&r_rsb_pins>;
+			status = "disabled";
+			#address-cells = <1>;
+			#size-cells = <0>;
 		};
 	};
 };
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 02/10] irqchip/sunxi-nmi: add A64 R_INTC to the binding doc
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

The A31 NMI driver seems to be using wrong base address.

As we're going to convert to use a correct NMI base address (and
correctly name it to R_INTC as the datasheet suggests), add a new
compatible string for the "correct" R_INTC, which we will use for A64
SoC.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
 .../bindings/interrupt-controller/allwinner,sunxi-nmi.txt          | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt
index 81cd3692405e..fea0c6a6211f 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt
+++ b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt
@@ -3,8 +3,11 @@ Allwinner Sunxi NMI Controller
 
 Required properties:
 
-- compatible : should be "allwinner,sun7i-a20-sc-nmi" or
-  "allwinner,sun6i-a31-sc-nmi" or "allwinner,sun9i-a80-nmi"
+- compatible : should be one of:
+	"allwinner,sun6i-a31-sc-nmi"
+	"allwinner,sun7i-a20-sc-nmi"
+	"allwinner,sun9i-a80-nmi"
+	"allwinner,sun50i-a64-r-intc"
 - reg : Specifies base physical address and size of the registers.
 - interrupt-controller : Identifies the node as an interrupt controller
 - #interrupt-cells : Specifies the number of cells needed to encode an
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 03/10] irqchip/sunxi-nmi: add support for the NMI in A64 R_INTC
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

Add support for the newly imported compatible for the A64 R_INTC in
irq-sunxi-nmi driver.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
Changes in v5:
- Fix A64 R_INTC compatible.

 drivers/irqchip/irq-sunxi-nmi.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/irqchip/irq-sunxi-nmi.c b/drivers/irqchip/irq-sunxi-nmi.c
index 668730c5cb66..5559c1d593bf 100644
--- a/drivers/irqchip/irq-sunxi-nmi.c
+++ b/drivers/irqchip/irq-sunxi-nmi.c
@@ -56,6 +56,12 @@ static struct sunxi_sc_nmi_reg_offs sun9i_reg_offs = {
 	.enable	= 0x04,
 };
 
+static struct sunxi_sc_nmi_reg_offs sun50i_reg_offs = {
+	.ctrl	= 0x0c,
+	.pend	= 0x10,
+	.enable	= 0x40,
+};
+
 static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off,
 				      u32 val)
 {
@@ -220,3 +226,10 @@ static int __init sun9i_nmi_irq_init(struct device_node *node,
 	return sunxi_sc_nmi_irq_init(node, &sun9i_reg_offs);
 }
 IRQCHIP_DECLARE(sun9i_nmi, "allwinner,sun9i-a80-nmi", sun9i_nmi_irq_init);
+
+static int __init sun50i_nmi_irq_init(struct device_node *node,
+				     struct device_node *parent)
+{
+	return sunxi_sc_nmi_irq_init(node, &sun50i_reg_offs);
+}
+IRQCHIP_DECLARE(sun50i_nmi, "allwinner,sun50i-a64-r-intc", sun50i_nmi_irq_init);
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 04/10] arm64: allwinner: a64: add NMI (R_INTC) controller on A64
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

Allwinner A64 SoC features a R_INTC controller, which controls the NMI
line, and this interrupt line is usually connected to the AXP PMIC.

Add support for it.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
Changes in v4:
- Changes it to use R_INTC binding and change node label to r_intc.
- Fixed MMIO region.
- Dropped Chen-Yu's ACK due to big change.
Changes in v2:
- Added Chen-Yu's ACK.

 arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
index 05ec9fc5e81f..a6566d292934 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
@@ -403,6 +403,14 @@
 				     <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		r_intc: interrupt-controller at 1f00c00 {
+			compatible = "allwinner,sun50i-a64-r-intc";
+			interrupt-controller;
+			#interrupt-cells = <2>;
+			reg = <0x01f00c00 0x400>;
+			interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
+		};
+
 		r_ccu: clock at 1f01400 {
 			compatible = "allwinner,sun50i-a64-r-ccu";
 			reg = <0x01f01400 0x100>;
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 05/10] arm64: allwinner: a64: add AXP803 node to Pine64 device tree
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

The Pine64 (including Pine64+) boards have an AXP803 as its main PMIC.

Add its device node.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
 arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
index c680ed385da3..3e1b44292534 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
@@ -95,6 +95,17 @@
 	status = "okay";
 };
 
+&r_rsb {
+	status = "okay";
+
+	axp803: pmic at 3a3 {
+		compatible = "x-powers,axp803";
+		reg = <0x3a3>;
+		interrupt-parent = <&r_intc>;
+		interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
 &uart0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart0_pins_a>;
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 06/10] regulator: axp20x-regulator: add support for AXP803
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

AXP803 PMIC also have a series of regulators (DCDCs and LDOs)
controllable via I2C/RSB bus.

Add support for them.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
Acked-by: Chen-Yu Tsai <wens@csie.org>
---
Changes in v4:
- Fixed somewhere which mention AXP806 before 803.
Changes in v2:
- Place AXP803 codes before AXP806/809 ones.
- Fixed some errors in regulator description.
- Reuse AXP803 DLDO2 range for AXP806 CLDO2 & AXP809 DLDO1.

 drivers/regulator/axp20x-regulator.c | 153 ++++++++++++++++++++++++++++++-----
 include/linux/mfd/axp20x.h           |  37 +++++++++
 2 files changed, 168 insertions(+), 22 deletions(-)

diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c
index 0b9d4e3e52c7..e2608fe770b9 100644
--- a/drivers/regulator/axp20x-regulator.c
+++ b/drivers/regulator/axp20x-regulator.c
@@ -244,6 +244,82 @@ static const struct regulator_desc axp22x_drivevbus_regulator = {
 	.ops		= &axp20x_ops_sw,
 };
 
+static const struct regulator_linear_range axp803_dcdc234_ranges[] = {
+	REGULATOR_LINEAR_RANGE(500000, 0x0, 0x46, 10000),
+	REGULATOR_LINEAR_RANGE(1220000, 0x47, 0x4b, 20000),
+};
+
+static const struct regulator_linear_range axp803_dcdc5_ranges[] = {
+	REGULATOR_LINEAR_RANGE(800000, 0x0, 0x20, 10000),
+	REGULATOR_LINEAR_RANGE(1140000, 0x21, 0x44, 20000),
+};
+
+static const struct regulator_linear_range axp803_dcdc6_ranges[] = {
+	REGULATOR_LINEAR_RANGE(600000, 0x0, 0x32, 10000),
+	REGULATOR_LINEAR_RANGE(1120000, 0x33, 0x47, 20000),
+};
+
+/* AXP806's CLDO2 and AXP809's DLDO1 shares the same range */
+static const struct regulator_linear_range axp803_dldo2_ranges[] = {
+	REGULATOR_LINEAR_RANGE(700000, 0x0, 0x1a, 100000),
+	REGULATOR_LINEAR_RANGE(3400000, 0x1b, 0x1f, 200000),
+};
+
+static const struct regulator_desc axp803_regulators[] = {
+	AXP_DESC(AXP803, DCDC1, "dcdc1", "vin1", 1600, 3400, 100,
+		 AXP803_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(0)),
+	AXP_DESC_RANGES(AXP803, DCDC2, "dcdc2", "vin2", axp803_dcdc234_ranges,
+			76, AXP803_DCDC2_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1,
+			BIT(1)),
+	AXP_DESC_RANGES(AXP803, DCDC3, "dcdc3", "vin3", axp803_dcdc234_ranges,
+			76, AXP803_DCDC3_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1,
+			BIT(2)),
+	AXP_DESC_RANGES(AXP803, DCDC4, "dcdc4", "vin4", axp803_dcdc234_ranges,
+			76, AXP803_DCDC4_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1,
+			BIT(3)),
+	AXP_DESC_RANGES(AXP803, DCDC5, "dcdc5", "vin5", axp803_dcdc5_ranges,
+			68, AXP803_DCDC5_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1,
+			BIT(4)),
+	AXP_DESC_RANGES(AXP803, DCDC6, "dcdc6", "vin6", axp803_dcdc6_ranges,
+			72, AXP803_DCDC6_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1,
+			BIT(5)),
+	/* secondary switchable output of DCDC1 */
+	AXP_DESC_SW(AXP803, DC1SW, "dc1sw", NULL, AXP22X_PWR_OUT_CTRL2,
+		    BIT(7)),
+	AXP_DESC(AXP803, ALDO1, "aldo1", "aldoin", 700, 3300, 100,
+		 AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(5)),
+	AXP_DESC(AXP803, ALDO2, "aldo2", "aldoin", 700, 3300, 100,
+		 AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(6)),
+	AXP_DESC(AXP803, ALDO3, "aldo3", "aldoin", 700, 3300, 100,
+		 AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(7)),
+	AXP_DESC(AXP803, DLDO1, "dldo1", "dldoin", 700, 3300, 100,
+		 AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(3)),
+	AXP_DESC_RANGES(AXP803, DLDO2, "dldo2", "dldoin", axp803_dldo2_ranges,
+			32, AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2,
+			BIT(4)),
+	AXP_DESC(AXP803, DLDO3, "dldo3", "dldoin", 700, 3300, 100,
+		 AXP22X_DLDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)),
+	AXP_DESC(AXP803, DLDO4, "dldo4", "dldoin", 700, 3300, 100,
+		 AXP22X_DLDO4_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(6)),
+	AXP_DESC(AXP803, ELDO1, "eldo1", "eldoin", 700, 1900, 50,
+		 AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)),
+	AXP_DESC(AXP803, ELDO2, "eldo2", "eldoin", 700, 1900, 50,
+		 AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)),
+	AXP_DESC(AXP803, ELDO3, "eldo3", "eldoin", 700, 1900, 50,
+		 AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)),
+	AXP_DESC(AXP803, FLDO1, "fldo1", "fldoin", 700, 1450, 50,
+		 AXP803_FLDO1_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(2)),
+	AXP_DESC(AXP803, FLDO2, "fldo2", "fldoin", 700, 1450, 50,
+		 AXP803_FLDO2_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(3)),
+	AXP_DESC_IO(AXP803, LDO_IO0, "ldo-io0", "ips", 700, 3300, 100,
+		    AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07,
+		    AXP22X_IO_ENABLED, AXP22X_IO_DISABLED),
+	AXP_DESC_IO(AXP803, LDO_IO1, "ldo-io1", "ips", 700, 3300, 100,
+		    AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07,
+		    AXP22X_IO_ENABLED, AXP22X_IO_DISABLED),
+	AXP_DESC_FIXED(AXP803, RTC_LDO, "rtc-ldo", "ips", 3000),
+};
+
 static const struct regulator_linear_range axp806_dcdca_ranges[] = {
 	REGULATOR_LINEAR_RANGE(600000, 0x0, 0x32, 10000),
 	REGULATOR_LINEAR_RANGE(1120000, 0x33, 0x47, 20000),
@@ -254,11 +330,6 @@ static const struct regulator_linear_range axp806_dcdcd_ranges[] = {
 	REGULATOR_LINEAR_RANGE(1600000, 0x2e, 0x3f, 100000),
 };
 
-static const struct regulator_linear_range axp806_cldo2_ranges[] = {
-	REGULATOR_LINEAR_RANGE(700000, 0x0, 0x1a, 100000),
-	REGULATOR_LINEAR_RANGE(3400000, 0x1b, 0x1f, 200000),
-};
-
 static const struct regulator_desc axp806_regulators[] = {
 	AXP_DESC_RANGES(AXP806, DCDCA, "dcdca", "vina", axp806_dcdca_ranges,
 			72, AXP806_DCDCA_V_CTRL, 0x7f, AXP806_PWR_OUT_CTRL1,
@@ -289,7 +360,7 @@ static const struct regulator_desc axp806_regulators[] = {
 		 AXP806_BLDO4_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(3)),
 	AXP_DESC(AXP806, CLDO1, "cldo1", "cldoin", 700, 3300, 100,
 		 AXP806_CLDO1_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2, BIT(4)),
-	AXP_DESC_RANGES(AXP806, CLDO2, "cldo2", "cldoin", axp806_cldo2_ranges,
+	AXP_DESC_RANGES(AXP806, CLDO2, "cldo2", "cldoin", axp803_dldo2_ranges,
 			32, AXP806_CLDO2_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2,
 			BIT(5)),
 	AXP_DESC(AXP806, CLDO3, "cldo3", "cldoin", 700, 3300, 100,
@@ -326,7 +397,7 @@ static const struct regulator_desc axp809_regulators[] = {
 		 AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(7)),
 	AXP_DESC(AXP809, ALDO3, "aldo3", "aldoin", 700, 3300, 100,
 		 AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)),
-	AXP_DESC_RANGES(AXP809, DLDO1, "dldo1", "dldoin", axp806_cldo2_ranges,
+	AXP_DESC_RANGES(AXP809, DLDO1, "dldo1", "dldoin", axp803_dldo2_ranges,
 			32, AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2,
 			BIT(3)),
 	AXP_DESC(AXP809, DLDO2, "dldo2", "dldoin", 700, 3300, 100,
@@ -369,14 +440,21 @@ static int axp20x_set_dcdc_freq(struct platform_device *pdev, u32 dcdcfreq)
 		def = 1500;
 		step = 75;
 		break;
-	case AXP806_ID:
+	case AXP803_ID:
 		/*
-		 * AXP806 DCDC work frequency setting has the same range and
+		 * AXP803 DCDC work frequency setting has the same range and
 		 * step as AXP22X, but at a different register.
 		 * Fall through to the check below.
 		 * (See include/linux/mfd/axp20x.h)
 		 */
-		reg = AXP806_DCDC_FREQ_CTRL;
+		reg = AXP803_DCDC_FREQ_CTRL;
+	case AXP806_ID:
+		/*
+		 * AXP806 also have DCDC work frequency setting register at a
+		 * different position.
+		 */
+		if (axp20x->variant == AXP806_ID)
+			reg = AXP806_DCDC_FREQ_CTRL;
 	case AXP221_ID:
 	case AXP223_ID:
 	case AXP809_ID:
@@ -475,6 +553,14 @@ static int axp20x_set_dcdc_workmode(struct regulator_dev *rdev, int id, u32 work
 		workmode <<= id - AXP22X_DCDC1;
 		break;
 
+	case AXP803_ID:
+		if (id < AXP803_DCDC1 || id > AXP803_DCDC6)
+			return -EINVAL;
+
+		mask = AXP22X_WORKMODE_DCDCX_MASK(id - AXP803_DCDC1);
+		workmode <<= id - AXP803_DCDC1;
+		break;
+
 	default:
 		/* should not happen */
 		WARN_ON(1);
@@ -492,20 +578,38 @@ static bool axp20x_is_polyphase_slave(struct axp20x_dev *axp20x, int id)
 {
 	u32 reg = 0;
 
-	/* Only AXP806 has poly-phase outputs */
-	if (axp20x->variant != AXP806_ID)
-		return false;
+	/*
+	 * Currently in our supported AXP variants, only AXP803 and AXP806
+	 * have polyphase regulators.
+	 */
+	switch (axp20x->variant) {
+	case AXP803_ID:
+		regmap_read(axp20x->regmap, AXP803_POLYPHASE_CTRL, &reg);
+
+		switch (id) {
+		case AXP803_DCDC3:
+			return !!(reg & BIT(6));
+		case AXP803_DCDC6:
+			return !!(reg & BIT(7));
+		}
+		break;
 
-	regmap_read(axp20x->regmap, AXP806_DCDC_MODE_CTRL2, &reg);
+	case AXP806_ID:
+		regmap_read(axp20x->regmap, AXP806_DCDC_MODE_CTRL2, &reg);
+
+		switch (id) {
+		case AXP806_DCDCB:
+			return (((reg & GENMASK(7, 6)) == BIT(6)) ||
+				((reg & GENMASK(7, 6)) == BIT(7)));
+		case AXP806_DCDCC:
+			return ((reg & GENMASK(7, 6)) == BIT(7));
+		case AXP806_DCDCE:
+			return !!(reg & BIT(5));
+		}
+		break;
 
-	switch (id) {
-	case AXP806_DCDCB:
-		return (((reg & GENMASK(7, 6)) == BIT(6)) ||
-			((reg & GENMASK(7, 6)) == BIT(7)));
-	case AXP806_DCDCC:
-		return ((reg & GENMASK(7, 6)) == BIT(7));
-	case AXP806_DCDCE:
-		return !!(reg & BIT(5));
+	default:
+		return false;
 	}
 
 	return false;
@@ -540,6 +644,10 @@ static int axp20x_regulator_probe(struct platform_device *pdev)
 		drivevbus = of_property_read_bool(pdev->dev.parent->of_node,
 						  "x-powers,drive-vbus-en");
 		break;
+	case AXP803_ID:
+		regulators = axp803_regulators;
+		nregulators = AXP803_REG_ID_MAX;
+		break;
 	case AXP806_ID:
 		regulators = axp806_regulators;
 		nregulators = AXP806_REG_ID_MAX;
@@ -579,6 +687,7 @@ static int axp20x_regulator_probe(struct platform_device *pdev)
 		 * name.
 		 */
 		if ((regulators == axp22x_regulators && i == AXP22X_DC1SW) ||
+		    (regulators == axp803_regulators && i == AXP803_DC1SW) ||
 		    (regulators == axp809_regulators && i == AXP809_DC1SW)) {
 			new_desc = devm_kzalloc(&pdev->dev, sizeof(*desc),
 						GFP_KERNEL);
diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h
index cde56cfe8446..965b027e31b3 100644
--- a/include/linux/mfd/axp20x.h
+++ b/include/linux/mfd/axp20x.h
@@ -119,6 +119,17 @@ enum axp20x_variants {
 #define AXP806_BUS_ADDR_EXT		0xfe
 #define AXP806_REG_ADDR_EXT		0xff
 
+#define AXP803_POLYPHASE_CTRL		0x14
+#define AXP803_FLDO1_V_OUT		0x1c
+#define AXP803_FLDO2_V_OUT		0x1d
+#define AXP803_DCDC1_V_OUT		0x20
+#define AXP803_DCDC2_V_OUT		0x21
+#define AXP803_DCDC3_V_OUT		0x22
+#define AXP803_DCDC4_V_OUT		0x23
+#define AXP803_DCDC5_V_OUT		0x24
+#define AXP803_DCDC6_V_OUT		0x25
+#define AXP803_DCDC_FREQ_CTRL		0x3b
+
 /* Interrupt */
 #define AXP152_IRQ1_EN			0x40
 #define AXP152_IRQ2_EN			0x41
@@ -350,6 +361,32 @@ enum {
 	AXP809_REG_ID_MAX,
 };
 
+enum {
+	AXP803_DCDC1 = 0,
+	AXP803_DCDC2,
+	AXP803_DCDC3,
+	AXP803_DCDC4,
+	AXP803_DCDC5,
+	AXP803_DCDC6,
+	AXP803_DC1SW,
+	AXP803_ALDO1,
+	AXP803_ALDO2,
+	AXP803_ALDO3,
+	AXP803_DLDO1,
+	AXP803_DLDO2,
+	AXP803_DLDO3,
+	AXP803_DLDO4,
+	AXP803_ELDO1,
+	AXP803_ELDO2,
+	AXP803_ELDO3,
+	AXP803_FLDO1,
+	AXP803_FLDO2,
+	AXP803_RTC_LDO,
+	AXP803_LDO_IO0,
+	AXP803_LDO_IO1,
+	AXP803_REG_ID_MAX,
+};
+
 /* IRQs */
 enum {
 	AXP152_IRQ_LDO0IN_CONNECT = 1,
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 07/10] mfd: axp20x: add axp20x-regulator cell for AXP803
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

As axp20x-regulator now supports AXP803, add a cell for it.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
Acked-by: Chen-Yu Tsai <wens@csie.org>
---
Changes in v5:
- Removed wrong snippet.
Changes in v4:
- Added a trailing comma for new cell, for easier further cell addition.
Changes in v3:
- Make the new cell one-liner.

 drivers/mfd/axp20x.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index 1dc6235778eb..917b6ddc4f15 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -848,7 +848,8 @@ static struct mfd_cell axp803_cells[] = {
 		.name			= "axp20x-pek",
 		.num_resources		= ARRAY_SIZE(axp803_pek_resources),
 		.resources		= axp803_pek_resources,
-	}
+	},
+	{	.name			= "axp20x-regulator" },
 };
 
 static struct mfd_cell axp806_cells[] = {
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 08/10] arm64: allwinner: a64: add DTSI file for AXP803 PMIC
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

As nearly all A64 boards are using AXP803 PMIC, add a DTSI file for it,
like the old DTSI files for AXP20x/22x, for the common parts of the
PMIC.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
Acked-by: Mark Brown <broonie@kernel.org>
---
Changes in v5:
- Added Mark Brown's ACK.
Changes in v4:
- Re-sorted the nodes.

 arch/arm64/boot/dts/allwinner/axp803.dtsi | 150 ++++++++++++++++++++++++++++++
 1 file changed, 150 insertions(+)
 create mode 100644 arch/arm64/boot/dts/allwinner/axp803.dtsi

diff --git a/arch/arm64/boot/dts/allwinner/axp803.dtsi b/arch/arm64/boot/dts/allwinner/axp803.dtsi
new file mode 100644
index 000000000000..ff8af52743ff
--- /dev/null
+++ b/arch/arm64/boot/dts/allwinner/axp803.dtsi
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2017 Icenowy Zheng <icenowy@aosc.xyz>
+ *
+ * This file is dual-licensed: you can use it either under the terms
+ * of the GPL or the X11 license, at your option. Note that this dual
+ * licensing only applies to this file, and not this project as a
+ * whole.
+ *
+ *  a) This file 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; either version 2 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This file is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ * Or, alternatively,
+ *
+ *  b) Permission is hereby granted, free of charge, to any person
+ *     obtaining a copy of this software and associated documentation
+ *     files (the "Software"), to deal in the Software without
+ *     restriction, including without limitation the rights to use,
+ *     copy, modify, merge, publish, distribute, sublicense, and/or
+ *     sell copies of the Software, and to permit persons to whom the
+ *     Software is furnished to do so, subject to the following
+ *     conditions:
+ *
+ *     The above copyright notice and this permission notice shall be
+ *     included in all copies or substantial portions of the Software.
+ *
+ *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *     OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * AXP803 Integrated Power Management Chip
+ * http://files.pine64.org/doc/datasheet/pine64/AXP803_Datasheet_V1.0.pdf
+ */
+
+&axp803 {
+	interrupt-controller;
+	#interrupt-cells = <1>;
+
+	regulators {
+		/* Default work frequency for buck regulators */
+		x-powers,dcdc-freq = <3000>;
+
+		reg_aldo1: aldo1 {
+			regulator-name = "aldo1";
+		};
+
+		reg_aldo2: aldo2 {
+			regulator-name = "aldo2";
+		};
+
+		reg_aldo3: aldo3 {
+			regulator-name = "aldo3";
+		};
+
+		reg_dc1sw: dc1sw {
+			regulator-name = "dc1sw";
+		};
+
+		reg_dcdc1: dcdc1 {
+			regulator-name = "dcdc1";
+		};
+
+		reg_dcdc2: dcdc2 {
+			regulator-name = "dcdc2";
+		};
+
+		reg_dcdc3: dcdc3 {
+			regulator-name = "dcdc3";
+		};
+
+		reg_dcdc4: dcdc4 {
+			regulator-name = "dcdc4";
+		};
+
+		reg_dcdc5: dcdc5 {
+			regulator-name = "dcdc5";
+		};
+
+		reg_dcdc6: dcdc6 {
+			regulator-name = "dcdc6";
+		};
+
+		reg_dldo1: dldo1 {
+			regulator-name = "dldo1";
+		};
+
+		reg_dldo2: dldo2 {
+			regulator-name = "dldo2";
+		};
+
+		reg_dldo3: dldo3 {
+			regulator-name = "dldo3";
+		};
+
+		reg_dldo4: dldo4 {
+			regulator-name = "dldo4";
+		};
+
+		reg_eldo1: eldo1 {
+			regulator-name = "eldo1";
+		};
+
+		reg_eldo2: eldo2 {
+			regulator-name = "eldo2";
+		};
+
+		reg_eldo3: eldo3 {
+			regulator-name = "eldo3";
+		};
+
+		reg_fldo1: fldo1 {
+			regulator-name = "fldo1";
+		};
+
+		reg_fldo2: fldo2 {
+			regulator-name = "fldo2";
+		};
+
+		reg_ldo_io0: ldo-io0 {
+			regulator-name = "ldo-io0";
+			status = "disabled";
+		};
+
+		reg_ldo_io1: ldo-io1 {
+			regulator-name = "ldo-io1";
+			status = "disabled";
+		};
+
+		reg_rtc_ldo: rtc-ldo {
+			/* RTC_LDO is a fixed, always-on regulator */
+			regulator-always-on;
+			regulator-min-microvolt = <3000000>;
+			regulator-max-microvolt = <3000000>;
+			regulator-name = "rtc-ldo";
+		};
+	};
+};
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 09/10] arm64: allwinner: a64: enable AXP803 regulators for Pine64
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

Add support of AXP803 regulators in the Pine64 device tree, in order to
enable many future functionalities, e.g. Wi-Fi.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
 .../arm64/boot/dts/allwinner/sun50i-a64-pine64.dts | 109 +++++++++++++++++++++
 1 file changed, 109 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
index 3e1b44292534..abc1879e91f2 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
@@ -106,6 +106,115 @@
 	};
 };
 
+#include "axp803.dtsi"
+
+&reg_aldo1 {
+	regulator-min-microvolt = <2800000>;
+	regulator-max-microvolt = <2800000>;
+	regulator-name = "vcc-csi";
+};
+
+&reg_aldo2 {
+	regulator-always-on;
+	regulator-min-microvolt = <1800000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-pl";
+};
+
+&reg_aldo3 {
+	regulator-always-on;
+	regulator-min-microvolt = <2700000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-pll-avcc";
+};
+
+&reg_dc1sw {
+	regulator-name = "vcc-phy";
+};
+
+&reg_dcdc1 {
+	regulator-always-on;
+	regulator-min-microvolt = <3300000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-3v3";
+};
+
+&reg_dcdc2 {
+	regulator-always-on;
+	regulator-min-microvolt = <1000000>;
+	regulator-max-microvolt = <1300000>;
+	regulator-name = "vdd-cpux";
+};
+
+/* DCDC3 is polyphased with DCDC2 */
+
+&reg_dcdc5 {
+	regulator-always-on;
+	regulator-min-microvolt = <1500000>;
+	regulator-max-microvolt = <1500000>;
+	regulator-name = "vcc-dram";
+};
+
+&reg_dcdc6 {
+	regulator-always-on;
+	regulator-min-microvolt = <1100000>;
+	regulator-max-microvolt = <1100000>;
+	regulator-name = "vdd-sys";
+};
+
+&reg_dldo1 {
+	regulator-min-microvolt = <3300000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-hdmi";
+};
+
+&reg_dldo2 {
+	regulator-min-microvolt = <3300000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-mipi";
+};
+
+&reg_dldo3 {
+	regulator-min-microvolt = <3300000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "avdd-csi";
+};
+
+&reg_dldo4 {
+	regulator-min-microvolt = <3300000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-wifi";
+};
+
+&reg_eldo1 {
+	regulator-min-microvolt = <1800000>;
+	regulator-max-microvolt = <1800000>;
+	regulator-name = "cpvdd";
+};
+
+&reg_eldo3 {
+	regulator-min-microvolt = <1800000>;
+	regulator-max-microvolt = <1800000>;
+	regulator-name = "vdd-1v8-csi";
+};
+
+&reg_fldo1 {
+	regulator-min-microvolt = <1200000>;
+	regulator-max-microvolt = <1200000>;
+	regulator-name = "vcc-1v2-hsic";
+};
+
+&reg_fldo2 {
+	regulator-always-on;
+	regulator-min-microvolt = <1100000>;
+	regulator-max-microvolt = <1100000>;
+	regulator-name = "vdd-cpus";
+};
+
+&reg_rtc_ldo {
+	regulator-name = "vcc-rtc";
+};
+
 &uart0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart0_pins_a>;
-- 
2.12.2

^ permalink raw reply related

* [PATCH v5 10/10] arm64: allwinner: a64: enable Wi-Fi for Pine64
From: Icenowy Zheng @ 2017-04-26 15:20 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426152023.41567-1-icenowy@aosc.io>

The Wi-Fi module of Pine64 is powered via DLDO4 and ELDO1 (the latter
one provides I/O voltage).

Add device node for it.

Although the Wi-Fi module is an external module which should be inserted
to a header, according to my personal talk with TL Lim, he does not want
this header to be used as GPIO (so it's with 2.0mm pitch, not 2.54mm as
other GPIO headers).

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
 arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
index abc1879e91f2..2e4f44800162 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
@@ -64,6 +64,11 @@
 		regulator-min-microvolt = <3300000>;
 		regulator-max-microvolt = <3300000>;
 	};
+
+	wifi_pwrseq: wifi_pwrseq {
+		compatible = "mmc-pwrseq-simple";
+		reset-gpios = <&r_pio 0 2 GPIO_ACTIVE_LOW>; /* PL2 */
+	};
 };
 
 &ehci1 {
@@ -91,6 +96,17 @@
 	status = "okay";
 };
 
+&mmc1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&mmc1_pins>;
+	vmmc-supply = <&reg_dldo4>;
+	vqmmc-supply = <&reg_eldo1>;
+	mmc-pwrseq = <&wifi_pwrseq>;
+	non-removable;
+	bus-width = <4>;
+	status = "okay";
+};
+
 &ohci1 {
 	status = "okay";
 };
-- 
2.12.2

^ permalink raw reply related

* [patch] autogain support for bayer10 format (was Re: [patch] propagating controls in libv4l2)
From: Ivaylo Dimitrov @ 2017-04-26 15:43 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426132337.GA6482@amd>

Hi,

On 26.04.2017 16:23, Pavel Machek wrote:
> Hi!
>
>>>> I don't see why it would be hard to open files or have threads inside
>>>> a library. There are several libraries that do that already, specially
>>>> the ones designed to be used on multimidia apps.
>>>
>>> Well, This is what the libv4l2 says:
>>>
>>>    This file implements libv4l2, which offers v4l2_ prefixed versions
>>>    of
>>>       open/close/etc. The API is 100% the same as directly opening
>>>    /dev/videoX
>>>       using regular open/close/etc, the big difference is that format
>>>    conversion
>>>
>>> but if I open additional files in v4l2_open(), API is no longer the
>>> same, as unix open() is defined to open just one file descriptor.
>>>
>>> Now. There is autogain support in libv4lconvert, but it expects to use
>>> same fd for camera and for the gain... which does not work with
>>> subdevs.
>>>
>>> Of course, opening subdevs by name like this is not really
>>> acceptable. But can you suggest a method that is?
>>
>> There are two separate things here:
>>
>> 1) Autofoucs for a device that doesn't use subdev API
>> 2) libv4l2 support for devices that require MC and subdev API
>
> Actually there are three: 0) autogain. Unfortunately, I need autogain
> first before autofocus has a chance...
>
> And that means... bayer10 support for autogain.
>
> Plus, I changed avg_lum to long long. Quick calculation tells me int
> could overflow with few megapixel sensor.
>
> Oh, btw http://ytse.tricolour.net/docs/LowLightOptimization.html no
> longer works.
>
> Regards,
> 								Pavel
>
> diff --git a/lib/libv4lconvert/processing/autogain.c b/lib/libv4lconvert/processing/autogain.c
> index c6866d6..0b52d0f 100644
> --- a/lib/libv4lconvert/processing/autogain.c
> +++ b/lib/libv4lconvert/processing/autogain.c
> @@ -68,6 +71,41 @@ static void autogain_adjust(struct v4l2_queryctrl *ctrl, int *value,
>  	}
>  }
>
> +static int get_luminosity_bayer10(uint16_t *buf, const struct v4l2_format *fmt)
> +{
> +	long long avg_lum = 0;
> +	int x, y;
> +	
> +	buf += fmt->fmt.pix.height * fmt->fmt.pix.bytesperline / 4 +
> +		fmt->fmt.pix.width / 4;
> +
> +	for (y = 0; y < fmt->fmt.pix.height / 2; y++) {
> +		for (x = 0; x < fmt->fmt.pix.width / 2; x++)

That would take some time :). AIUI, we have NEON support in ARM kernels 
(CONFIG_KERNEL_MODE_NEON), I wonder if it makes sense (me) to convert 
the above loop to NEON-optimized when it comes to it? Are there any 
drawbacks in using NEON code in kernel?

> +			avg_lum += *buf++;
> +		buf += fmt->fmt.pix.bytesperline - fmt->fmt.pix.width / 2;
> +	}
> +	avg_lum /= fmt->fmt.pix.height * fmt->fmt.pix.width / 4;
> +	avg_lum /= 4;
> +	return avg_lum;
> +}
> +
> +static int get_luminosity_bayer8(unsigned char *buf, const struct v4l2_format *fmt)
> +{
> +	long long avg_lum = 0;
> +	int x, y;
> +	
> +	buf += fmt->fmt.pix.height * fmt->fmt.pix.bytesperline / 4 +
> +		fmt->fmt.pix.width / 4;
> +
> +	for (y = 0; y < fmt->fmt.pix.height / 2; y++) {
> +		for (x = 0; x < fmt->fmt.pix.width / 2; x++)

ditto.

> +			avg_lum += *buf++;
> +		buf += fmt->fmt.pix.bytesperline - fmt->fmt.pix.width / 2;
> +	}
> +	avg_lum /= fmt->fmt.pix.height * fmt->fmt.pix.width / 4;
> +	return avg_lum;
> +}
> +
>  /* auto gain and exposure algorithm based on the knee algorithm described here:
>  http://ytse.tricolour.net/docs/LowLightOptimization.html */
>  static int autogain_calculate_lookup_tables(
> @@ -100,17 +142,16 @@ static int autogain_calculate_lookup_tables(
>  	switch (fmt->fmt.pix.pixelformat) {
> +	case V4L2_PIX_FMT_SGBRG10:
> +	case V4L2_PIX_FMT_SGRBG10:
> +	case V4L2_PIX_FMT_SBGGR10:
> +	case V4L2_PIX_FMT_SRGGB10:
> +		avg_lum = get_luminosity_bayer10((void *) buf, fmt);
> +		break;
> +
>  	case V4L2_PIX_FMT_SGBRG8:
>  	case V4L2_PIX_FMT_SGRBG8:
>  	case V4L2_PIX_FMT_SBGGR8:
>  	case V4L2_PIX_FMT_SRGGB8:
> -		buf += fmt->fmt.pix.height * fmt->fmt.pix.bytesperline / 4 +
> -			fmt->fmt.pix.width / 4;
> -
> -		for (y = 0; y < fmt->fmt.pix.height / 2; y++) {
> -			for (x = 0; x < fmt->fmt.pix.width / 2; x++)
> -				avg_lum += *buf++;
> -			buf += fmt->fmt.pix.bytesperline - fmt->fmt.pix.width / 2;
> -		}
> -		avg_lum /= fmt->fmt.pix.height * fmt->fmt.pix.width / 4;
> +		avg_lum = get_luminosity_bayer8(buf, fmt);
>  		break;
>
>  	case V4L2_PIX_FMT_RGB24:
> diff --git a/lib/libv4lconvert/processing/libv4lprocessing.c b/lib/libv4lconvert/processing/libv4lprocessing.c
> index b061f50..b98d024 100644
> --- a/lib/libv4lconvert/processing/libv4lprocessing.c
> +++ b/lib/libv4lconvert/processing/libv4lprocessing.c
> @@ -164,6 +165,10 @@ void v4lprocessing_processing(struct v4lprocessing_data *data,
>  	case V4L2_PIX_FMT_SGRBG8:
>  	case V4L2_PIX_FMT_SBGGR8:
>  	case V4L2_PIX_FMT_SRGGB8:
> +	case V4L2_PIX_FMT_SGBRG10:
> +	case V4L2_PIX_FMT_SGRBG10:
> +	case V4L2_PIX_FMT_SBGGR10:
> +	case V4L2_PIX_FMT_SRGGB10:
>  	case V4L2_PIX_FMT_RGB24:
>  	case V4L2_PIX_FMT_BGR24:
>  		break;
>
>
>

^ permalink raw reply

* [PATCH v5 01/22] KVM: arm/arm64: Add ITS save/restore API documentation
From: Auger Eric @ 2017-04-26 15:48 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426123120.GL4104@cbox>

Hi Christoffer,

On 26/04/2017 14:31, Christoffer Dall wrote:
> On Fri, Apr 14, 2017 at 12:15:13PM +0200, Eric Auger wrote:
>> Add description for how to access ITS registers and how to save/restore
>> ITS tables into/from memory.
>>
>> Signed-off-by: Eric Auger <eric.auger@redhat.com>
>>
>> ---
>> v4 -> v5:
>> - take into account Christoffer's comments
>> - pending table save on GICV3 side now
>>
>> v3 -> v4:
>> - take into account Peter's comments:
>>   - typos
>>   - KVM_DEV_ARM_VGIC_GRP_ITS_TABLES kvm_device_attr = 0
>>   - add a validity bit in DTE
>>   - document all fields in CTE and ITE
>>   - document ABI revision
>> - take into account Andre's comments:
>>   - document restrictions about GITS_CREADR writing and GITS_IIDR
>>   - document -EBUSY error if one or more VCPUS are runnning
>>   - document 64b registers only can be accessed with 64b access
>> - itt_addr field matches bits [51:8] of the itt_addr
>>
>> v1 -> v2:
>> - DTE and ITE now are 8 bytes
>> - DTE and ITE now indexed by deviceid/eventid
>> - use ITE name instead of ITTE
>> - mentions ITT_addr matches bits [51:8] of the actual address
>> - mentions LE layout
>> ---
>>  Documentation/virtual/kvm/devices/arm-vgic-its.txt | 99 ++++++++++++++++++++++
>>  1 file changed, 99 insertions(+)
>>
>> diff --git a/Documentation/virtual/kvm/devices/arm-vgic-its.txt b/Documentation/virtual/kvm/devices/arm-vgic-its.txt
>> index 6081a5b..b5f010d 100644
>> --- a/Documentation/virtual/kvm/devices/arm-vgic-its.txt
>> +++ b/Documentation/virtual/kvm/devices/arm-vgic-its.txt
>> @@ -32,7 +32,106 @@ Groups:
>>      KVM_DEV_ARM_VGIC_CTRL_INIT
>>        request the initialization of the ITS, no additional parameter in
>>        kvm_device_attr.addr.
>> +
>> +    KVM_DEV_ARM_ITS_SAVE_TABLES
>> +      save the ITS table data into guest RAM, at the location provisioned
>> +      by the guest in corresponding registers/table entries.
>> +
>> +      The layout of the tables in guest memory defines an ABI. The entries
>> +      are laid out in little endian format as described in the last paragraph.
>> +
>> +    KVM_DEV_ARM_ITS_RESTORE_TABLES
>> +      restore the ITS tables from guest RAM to ITS internal structures.
>> +
>> +      The GICV3 must be restored before the ITS and all ITS registers but
>> +      the GITS_CTLR must be restored before restoring the ITS tables.
>> +
>> +      The GITS_IIDR read-only register must also be restored before
>> +      the table restore as the IIDR revision field encodes the ABI revision.
>> +
> 
> what is the expected sequence of operations.  For example, to restore
> the ITS, do I call KVM_DEV_ARM_VGIC_CTRL_INIT first, then restore all
> the memory and registers, and finally call KVM_DEV_ARM_ITS_RESTORE_TABLES?
Yes KVM_DEV_ARM_VGIC_CTRL_INIT comes first, then restore all registers
except GITS_CTLR, then table restore, then GITS_CTLR
> 
> Is there any interaction between when you call KVM_DEV_ARM_ITS_RESTORE_TABLES
> and restore GITS_CTLR (which enables the ITS)?

Yep, when GITS_CTLR is set, LPIs may be enabled and this on that event
that the pending table is read. But the whole pending table is not read
as we only iterate on registered LPIs. So the ITT must have been
restored previously.

I became aware that the pending table sync is done twice, once in the
pending table restore,  and once in the GITS_CTLR restore. So if we
leave this order specification, I should be able to remove the sync on
table restore. This was the original reason why GITS_CTLR restore has
been done at the very end.
> 
>>    Errors:
>>      -ENXIO:  ITS not properly configured as required prior to setting
>>               this attribute
>>      -ENOMEM: Memory shortage when allocating ITS internal data
>> +    -EINVAL: Inconsistent restored data
>> +    -EFAULT: Invalid guest ram access
>> +    -EBUSY:  One or more VCPUS are running
>> +
>> +  KVM_DEV_ARM_VGIC_GRP_ITS_REGS
>> +  Attributes:
>> +      The attr field of kvm_device_attr encodes the offset of the
>> +      ITS register, relative to the ITS control frame base address
>> +      (ITS_base).
>> +
>> +      kvm_device_attr.addr points to a __u64 value whatever the width
>> +      of the addressed register (32/64 bits). 64 bit registers can only
>> +      be accessed with full length.
>> +
>> +      Writes to read-only registers are ignored by the kernel except for:
>> +      - GITS_READR. It needs to be restored otherwise commands in the queue
>> +        will be re-executed after restoring CWRITER. GITS_READR must be restored
>> +        before restoring the GITS_CTLR which is likely to enable the ITS.
>> +        Also it needs to be restored after GITS_CBASER since a write to
>> +        GITS_CBASER resets GITS_CREADR.
>> +      - GITS_IIDR. Its Revision field encodes the table layout ABI revision.
>> +        In the future we might implement direct injection of virtual LPIS.
>> +        This will require an upgrade of the table layout and an evolution of
>> +        the ABI. GITS_IIDR must be restored before the table restoration.
>> +
>> +      For other registers, getting or setting a register has the same
>> +      effect as reading/writing the register on real hardware.
>> +  Errors:
>> +    -ENXIO: Offset does not correspond to any supported register
>> +    -EFAULT: Invalid user pointer for attr->addr
>> +    -EINVAL: Offset is not 64-bit aligned
>> +    -EBUSY: one or more VCPUS are running
> 
> 
> It may be helpful to state the ordering requirements somewhere:
> 
> Restoring the ITS:
> ------------------
> Restoring the ITS requires certain things to happen in order.
> Specifically:
>  1. Initialize the ITS (KVM_DEV_ARM_VGIC_CTRL_INIT)
>  2. Restore GITS_IIDR
>  3. Restore GITS_CBASER
>  4. Restore GITS_READR
>  5. Restore remainin registers except GITS_CTLR
>  6. Make sure all guest memory is restored
>  7. Load the ITS table data (KVM_DEV_ARM_ITS_RESTORE_TABLES)

OK I will try to fit that description somewhere.

> 
> 
>> +
>> + ITS Table ABI REV0:
>> + -------------------
>> +
>> + Revision 0 of the ABI only supports physical LPIs.
>> +
>> + The device table and ITT are indexed by the deviceid and eventid,
>> + respectively. The collection table is not indexed by collectionid:
>> + CTE are written in the table in the order of collection creation. All
>> + entries are 8 bytes.
>> +
>> + Device Table Entry (DTE):
>> +
>> + bits:     | 63| 62 ... 49 | 48 ... 5 | 4 ... 0 |
>> + values:   | V |   next    | ITT_addr |  Size   |
>> +
>> + where;
>> + - V indicates whether the entry is valid. If not, other fields
>> +   are not meaningful.
>> + - next: equals to 0 if this entry is the last one; otherwise it
>> +   corresponds to the deviceid offset to the next DTE, capped by
>> +   2^14 -1.
>> + - ITT_addr matches bits [51:8] of the ITT address (256B aligned).
> 
> I assume the B here is bytes.  Where does this requirement come from?
Yes this is 256 byte aligned. I guess this is to save memory as the ITT
is devised to encode a small range of eventids.
> 
>> + - Size specifies the supported number of bits for the deviceid,
>> +   minus one
> 
> deviceid or eventid?
oups eventid thanks.
> 
>> +
>> + Collection Table Entry (CTE):
>> +
>> + bits:     | 63| 62 ..  52  | 51 ... 16 | 15  ...   0 |
>> + values:   | V |    RES0    |  RDBase   |    ICID     |
>> +
>> + where:
>> + - V indicates whether the entry is valid. If not, other fields are
>> +   not meaningful.
>> + - RES0: reserved field with Should-Be-Zero-or-Preserved behavior.
>> + - RDBase is the PE number (GICR_TYPER.Processor_Number semantic),
>> + - ICID is the collection ID
>> +
>> + Interrupt Translation Entry (ITE):
>> +
>> + bits:     | 63 ... 48 | 47 ... 16 | 15 ... 0 |
>> + values:   |    next   |   pINTID  |  ICID    |
>> +
>> + where:
>> + - next: equals to 0 if this entry is the last one; otherwise it corresponds
>> +   to the eventid offset to the next ITE capped by 2^16 -1.
>> + - pINTID is the physical LPI ID; if zero, it means the entry is not valid
>> +   and other fields are not meaningful.
>> + - ICID is the collection ID
>> +
>> -- 
>> 2.5.5
>>
> 
> Besides the minor suggestions above:
> 
> Reviewed-by: Christoffer Dall <cdall@linaro.org>
Thanks

Eric
> 

^ permalink raw reply

* Applied "ASoC: stm32: add SAI driver" to the asoc tree
From: Mark Brown @ 2017-04-26 15:50 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1491837596-2924-3-git-send-email-olivier.moysan@st.com>

The patch

   ASoC: stm32: add SAI driver

has been applied to the asoc tree at

   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

>From 3e086edfe0c73daaabd929b926bbe26536272d9a Mon Sep 17 00:00:00 2001
From: olivier moysan <olivier.moysan@st.com>
Date: Mon, 10 Apr 2017 17:19:56 +0200
Subject: [PATCH] ASoC: stm32: add SAI driver

This patch implements SAI ASoC driver for STM32.

Signed-off-by: olivier moysan <olivier.moysan@st.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/Kconfig             |   1 +
 sound/soc/Makefile            |   1 +
 sound/soc/stm/Kconfig         |   8 +
 sound/soc/stm/Makefile        |   6 +
 sound/soc/stm/stm32_sai.c     | 115 ++++++
 sound/soc/stm/stm32_sai.h     | 200 ++++++++++
 sound/soc/stm/stm32_sai_sub.c | 884 ++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1215 insertions(+)
 create mode 100644 sound/soc/stm/Kconfig
 create mode 100644 sound/soc/stm/Makefile
 create mode 100644 sound/soc/stm/stm32_sai.c
 create mode 100644 sound/soc/stm/stm32_sai.h
 create mode 100644 sound/soc/stm/stm32_sai_sub.c

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 182d92efc7c8..3836ebe8938f 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -63,6 +63,7 @@ source "sound/soc/sh/Kconfig"
 source "sound/soc/sirf/Kconfig"
 source "sound/soc/spear/Kconfig"
 source "sound/soc/sti/Kconfig"
+source "sound/soc/stm/Kconfig"
 source "sound/soc/sunxi/Kconfig"
 source "sound/soc/tegra/Kconfig"
 source "sound/soc/txx9/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 9a30f21d16ee..5440cf77c39a 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_SND_SOC)	+= sh/
 obj-$(CONFIG_SND_SOC)	+= sirf/
 obj-$(CONFIG_SND_SOC)	+= spear/
 obj-$(CONFIG_SND_SOC)	+= sti/
+obj-$(CONFIG_SND_SOC)	+= stm/
 obj-$(CONFIG_SND_SOC)	+= sunxi/
 obj-$(CONFIG_SND_SOC)	+= tegra/
 obj-$(CONFIG_SND_SOC)	+= txx9/
diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig
new file mode 100644
index 000000000000..972970f0890a
--- /dev/null
+++ b/sound/soc/stm/Kconfig
@@ -0,0 +1,8 @@
+menuconfig SND_SOC_STM32
+	tristate "STMicroelectronics STM32 SOC audio support"
+	depends on ARCH_STM32 || COMPILE_TEST
+	depends on SND_SOC
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+	select REGMAP_MMIO
+	help
+	  Say Y if you want to enable ASoC-support for STM32
diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile
new file mode 100644
index 000000000000..e466a4759698
--- /dev/null
+++ b/sound/soc/stm/Makefile
@@ -0,0 +1,6 @@
+# SAI
+snd-soc-stm32-sai-sub-objs := stm32_sai_sub.o
+obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai-sub.o
+
+snd-soc-stm32-sai-objs := stm32_sai.o
+obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai.o
diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c
new file mode 100644
index 000000000000..2a27a26bf7a1
--- /dev/null
+++ b/sound/soc/stm/stm32_sai.c
@@ -0,0 +1,115 @@
+/*
+ * STM32 ALSA SoC Digital Audio Interface (SAI) driver.
+ *
+ * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
+ * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/reset.h>
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/core.h>
+
+#include "stm32_sai.h"
+
+static const struct of_device_id stm32_sai_ids[] = {
+	{ .compatible = "st,stm32f4-sai", .data = (void *)SAI_STM32F4 },
+	{}
+};
+
+static int stm32_sai_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct stm32_sai_data *sai;
+	struct reset_control *rst;
+	struct resource *res;
+	void __iomem *base;
+	const struct of_device_id *of_id;
+
+	sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+	if (!sai)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	of_id = of_match_device(stm32_sai_ids, &pdev->dev);
+	if (of_id)
+		sai->version = (enum stm32_sai_version)of_id->data;
+	else
+		return -EINVAL;
+
+	sai->clk_x8k = devm_clk_get(&pdev->dev, "x8k");
+	if (IS_ERR(sai->clk_x8k)) {
+		dev_err(&pdev->dev, "missing x8k parent clock\n");
+		return PTR_ERR(sai->clk_x8k);
+	}
+
+	sai->clk_x11k = devm_clk_get(&pdev->dev, "x11k");
+	if (IS_ERR(sai->clk_x11k)) {
+		dev_err(&pdev->dev, "missing x11k parent clock\n");
+		return PTR_ERR(sai->clk_x11k);
+	}
+
+	/* init irqs */
+	sai->irq = platform_get_irq(pdev, 0);
+	if (sai->irq < 0) {
+		dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+		return sai->irq;
+	}
+
+	/* reset */
+	rst = reset_control_get(&pdev->dev, NULL);
+	if (!IS_ERR(rst)) {
+		reset_control_assert(rst);
+		udelay(2);
+		reset_control_deassert(rst);
+	}
+
+	sai->pdev = pdev;
+	platform_set_drvdata(pdev, sai);
+
+	return of_platform_populate(np, NULL, NULL, &pdev->dev);
+}
+
+static int stm32_sai_remove(struct platform_device *pdev)
+{
+	of_platform_depopulate(&pdev->dev);
+
+	return 0;
+}
+
+MODULE_DEVICE_TABLE(of, stm32_sai_ids);
+
+static struct platform_driver stm32_sai_driver = {
+	.driver = {
+		.name = "st,stm32-sai",
+		.of_match_table = stm32_sai_ids,
+	},
+	.probe = stm32_sai_probe,
+	.remove = stm32_sai_remove,
+};
+
+module_platform_driver(stm32_sai_driver);
+
+MODULE_DESCRIPTION("STM32 Soc SAI Interface");
+MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>");
+MODULE_ALIAS("platform:st,stm32-sai");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h
new file mode 100644
index 000000000000..a801fda5066f
--- /dev/null
+++ b/sound/soc/stm/stm32_sai.h
@@ -0,0 +1,200 @@
+/*
+ * STM32 ALSA SoC Digital Audio Interface (SAI) driver.
+ *
+ * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
+ * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ */
+
+/******************** SAI Register Map **************************************/
+
+/* common register */
+#define STM_SAI_GCR		0x00
+
+/* Sub-block A&B registers offsets, relative to A&B sub-block addresses */
+#define STM_SAI_CR1_REGX	0x00	/* A offset: 0x04. B offset: 0x24 */
+#define STM_SAI_CR2_REGX	0x04
+#define STM_SAI_FRCR_REGX	0x08
+#define STM_SAI_SLOTR_REGX	0x0C
+#define STM_SAI_IMR_REGX	0x10
+#define STM_SAI_SR_REGX		0x14
+#define STM_SAI_CLRFR_REGX	0x18
+#define STM_SAI_DR_REGX		0x1C
+
+/******************** Bit definition for SAI_GCR register *******************/
+#define SAI_GCR_SYNCIN_SHIFT	0
+#define SAI_GCR_SYNCIN_MASK	GENMASK(1, SAI_GCR_SYNCIN_SHIFT)
+#define SAI_GCR_SYNCIN_SET(x)	((x) << SAI_GCR_SYNCIN_SHIFT)
+
+#define SAI_GCR_SYNCOUT_SHIFT	4
+#define SAI_GCR_SYNCOUT_MASK	GENMASK(5, SAI_GCR_SYNCOUT_SHIFT)
+#define SAI_GCR_SYNCOUT_SET(x)	((x) << SAI_GCR_SYNCOUT_SHIFT)
+
+/******************* Bit definition for SAI_XCR1 register *******************/
+#define SAI_XCR1_RX_TX_SHIFT	0
+#define SAI_XCR1_RX_TX		BIT(SAI_XCR1_RX_TX_SHIFT)
+#define SAI_XCR1_SLAVE_SHIFT	1
+#define SAI_XCR1_SLAVE		BIT(SAI_XCR1_SLAVE_SHIFT)
+
+#define SAI_XCR1_PRTCFG_SHIFT	2
+#define SAI_XCR1_PRTCFG_MASK	GENMASK(3, SAI_XCR1_PRTCFG_SHIFT)
+#define SAI_XCR1_PRTCFG_SET(x)	((x) << SAI_XCR1_PRTCFG_SHIFT)
+
+#define SAI_XCR1_DS_SHIFT	5
+#define SAI_XCR1_DS_MASK	GENMASK(7, SAI_XCR1_DS_SHIFT)
+#define SAI_XCR1_DS_SET(x)	((x) << SAI_XCR1_DS_SHIFT)
+
+#define SAI_XCR1_LSBFIRST_SHIFT	8
+#define SAI_XCR1_LSBFIRST	BIT(SAI_XCR1_LSBFIRST_SHIFT)
+#define SAI_XCR1_CKSTR_SHIFT	9
+#define SAI_XCR1_CKSTR		BIT(SAI_XCR1_CKSTR_SHIFT)
+
+#define SAI_XCR1_SYNCEN_SHIFT	10
+#define SAI_XCR1_SYNCEN_MASK	GENMASK(11, SAI_XCR1_SYNCEN_SHIFT)
+#define SAI_XCR1_SYNCEN_SET(x)	((x) << SAI_XCR1_SYNCEN_SHIFT)
+
+#define SAI_XCR1_MONO_SHIFT	12
+#define SAI_XCR1_MONO		BIT(SAI_XCR1_MONO_SHIFT)
+#define SAI_XCR1_OUTDRIV_SHIFT	13
+#define SAI_XCR1_OUTDRIV	BIT(SAI_XCR1_OUTDRIV_SHIFT)
+#define SAI_XCR1_SAIEN_SHIFT	16
+#define SAI_XCR1_SAIEN		BIT(SAI_XCR1_SAIEN_SHIFT)
+#define SAI_XCR1_DMAEN_SHIFT	17
+#define SAI_XCR1_DMAEN		BIT(SAI_XCR1_DMAEN_SHIFT)
+#define SAI_XCR1_NODIV_SHIFT	19
+#define SAI_XCR1_NODIV		BIT(SAI_XCR1_NODIV_SHIFT)
+
+#define SAI_XCR1_MCKDIV_SHIFT	20
+#define SAI_XCR1_MCKDIV_WIDTH	4
+#define SAI_XCR1_MCKDIV_MASK	GENMASK(24, SAI_XCR1_MCKDIV_SHIFT)
+#define SAI_XCR1_MCKDIV_SET(x)	((x) << SAI_XCR1_MCKDIV_SHIFT)
+#define SAI_XCR1_MCKDIV_MAX	((1 << SAI_XCR1_MCKDIV_WIDTH) - 1)
+
+#define SAI_XCR1_OSR_SHIFT	26
+#define SAI_XCR1_OSR		BIT(SAI_XCR1_OSR_SHIFT)
+
+/******************* Bit definition for SAI_XCR2 register *******************/
+#define SAI_XCR2_FTH_SHIFT	0
+#define SAI_XCR2_FTH_MASK	GENMASK(2, SAI_XCR2_FTH_SHIFT)
+#define SAI_XCR2_FTH_SET(x)	((x) << SAI_XCR2_FTH_SHIFT)
+
+#define SAI_XCR2_FFLUSH_SHIFT	3
+#define SAI_XCR2_FFLUSH		BIT(SAI_XCR2_FFLUSH_SHIFT)
+#define SAI_XCR2_TRIS_SHIFT	4
+#define SAI_XCR2_TRIS		BIT(SAI_XCR2_TRIS_SHIFT)
+#define SAI_XCR2_MUTE_SHIFT	5
+#define SAI_XCR2_MUTE		BIT(SAI_XCR2_MUTE_SHIFT)
+#define SAI_XCR2_MUTEVAL_SHIFT	6
+#define SAI_XCR2_MUTEVAL	BIT(SAI_XCR2_MUTEVAL_SHIFT)
+
+#define SAI_XCR2_MUTECNT_SHIFT	7
+#define SAI_XCR2_MUTECNT_MASK	GENMASK(12, SAI_XCR2_MUTECNT_SHIFT)
+#define SAI_XCR2_MUTECNT_SET(x)	((x) << SAI_XCR2_MUTECNT_SHIFT)
+
+#define SAI_XCR2_CPL_SHIFT	13
+#define SAI_XCR2_CPL		BIT(SAI_XCR2_CPL_SHIFT)
+
+#define SAI_XCR2_COMP_SHIFT	14
+#define SAI_XCR2_COMP_MASK	GENMASK(15, SAI_XCR2_COMP_SHIFT)
+#define SAI_XCR2_COMP_SET(x)	((x) << SAI_XCR2_COMP_SHIFT)
+
+/****************** Bit definition for SAI_XFRCR register *******************/
+#define SAI_XFRCR_FRL_SHIFT	0
+#define SAI_XFRCR_FRL_MASK	GENMASK(7, SAI_XFRCR_FRL_SHIFT)
+#define SAI_XFRCR_FRL_SET(x)	((x) << SAI_XFRCR_FRL_SHIFT)
+
+#define SAI_XFRCR_FSALL_SHIFT	8
+#define SAI_XFRCR_FSALL_MASK	GENMASK(14, SAI_XFRCR_FSALL_SHIFT)
+#define SAI_XFRCR_FSALL_SET(x)	((x) << SAI_XFRCR_FSALL_SHIFT)
+
+#define SAI_XFRCR_FSDEF_SHIFT	16
+#define SAI_XFRCR_FSDEF		BIT(SAI_XFRCR_FSDEF_SHIFT)
+#define SAI_XFRCR_FSPOL_SHIFT	17
+#define SAI_XFRCR_FSPOL		BIT(SAI_XFRCR_FSPOL_SHIFT)
+#define SAI_XFRCR_FSOFF_SHIFT	18
+#define SAI_XFRCR_FSOFF		BIT(SAI_XFRCR_FSOFF_SHIFT)
+
+/****************** Bit definition for SAI_XSLOTR register ******************/
+
+#define SAI_XSLOTR_FBOFF_SHIFT	0
+#define SAI_XSLOTR_FBOFF_MASK	GENMASK(4, SAI_XSLOTR_FBOFF_SHIFT)
+#define SAI_XSLOTR_FBOFF_SET(x)	((x) << SAI_XSLOTR_FBOFF_SHIFT)
+
+#define SAI_XSLOTR_SLOTSZ_SHIFT	6
+#define SAI_XSLOTR_SLOTSZ_MASK	GENMASK(7, SAI_XSLOTR_SLOTSZ_SHIFT)
+#define SAI_XSLOTR_SLOTSZ_SET(x)	((x) << SAI_XSLOTR_SLOTSZ_SHIFT)
+
+#define SAI_XSLOTR_NBSLOT_SHIFT 8
+#define SAI_XSLOTR_NBSLOT_MASK	GENMASK(11, SAI_XSLOTR_NBSLOT_SHIFT)
+#define SAI_XSLOTR_NBSLOT_SET(x) ((x) << SAI_XSLOTR_NBSLOT_SHIFT)
+
+#define SAI_XSLOTR_SLOTEN_SHIFT	16
+#define SAI_XSLOTR_SLOTEN_WIDTH	16
+#define SAI_XSLOTR_SLOTEN_MASK	GENMASK(31, SAI_XSLOTR_SLOTEN_SHIFT)
+#define SAI_XSLOTR_SLOTEN_SET(x) ((x) << SAI_XSLOTR_SLOTEN_SHIFT)
+
+/******************* Bit definition for SAI_XIMR register *******************/
+#define SAI_XIMR_OVRUDRIE	BIT(0)
+#define SAI_XIMR_MUTEDETIE	BIT(1)
+#define SAI_XIMR_WCKCFGIE	BIT(2)
+#define SAI_XIMR_FREQIE		BIT(3)
+#define SAI_XIMR_CNRDYIE	BIT(4)
+#define SAI_XIMR_AFSDETIE	BIT(5)
+#define SAI_XIMR_LFSDETIE	BIT(6)
+
+#define SAI_XIMR_SHIFT	0
+#define SAI_XIMR_MASK		GENMASK(6, SAI_XIMR_SHIFT)
+
+/******************** Bit definition for SAI_XSR register *******************/
+#define SAI_XSR_OVRUDR		BIT(0)
+#define SAI_XSR_MUTEDET		BIT(1)
+#define SAI_XSR_WCKCFG		BIT(2)
+#define SAI_XSR_FREQ		BIT(3)
+#define SAI_XSR_CNRDY		BIT(4)
+#define SAI_XSR_AFSDET		BIT(5)
+#define SAI_XSR_LFSDET		BIT(6)
+
+#define SAI_XSR_SHIFT	0
+#define SAI_XSR_MASK		GENMASK(6, SAI_XSR_SHIFT)
+
+/****************** Bit definition for SAI_XCLRFR register ******************/
+#define SAI_XCLRFR_COVRUDR	BIT(0)
+#define SAI_XCLRFR_CMUTEDET	BIT(1)
+#define SAI_XCLRFR_CWCKCFG	BIT(2)
+#define SAI_XCLRFR_CFREQ	BIT(3)
+#define SAI_XCLRFR_CCNRDY	BIT(4)
+#define SAI_XCLRFR_CAFSDET	BIT(5)
+#define SAI_XCLRFR_CLFSDET	BIT(6)
+
+#define SAI_XCLRFR_SHIFT	0
+#define SAI_XCLRFR_MASK		GENMASK(6, SAI_XCLRFR_SHIFT)
+
+enum stm32_sai_version {
+	SAI_STM32F4
+};
+
+/**
+ * struct stm32_sai_data - private data of SAI instance driver
+ * @pdev: device data pointer
+ * @clk_x8k: SAI parent clock for sampling frequencies multiple of 8kHz
+ * @clk_x11k: SAI parent clock for sampling frequencies multiple of 11kHz
+ * @version: SOC version
+ * @irq: SAI interrupt line
+ */
+struct stm32_sai_data {
+	struct platform_device *pdev;
+	struct clk *clk_x8k;
+	struct clk *clk_x11k;
+	int version;
+	int irq;
+};
diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c
new file mode 100644
index 000000000000..ae4706ca265b
--- /dev/null
+++ b/sound/soc/stm/stm32_sai_sub.c
@@ -0,0 +1,884 @@
+/*
+ * STM32 ALSA SoC Digital Audio Interface (SAI) driver.
+ *
+ * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
+ * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "stm32_sai.h"
+
+#define SAI_FREE_PROTOCOL	0x0
+
+#define SAI_SLOT_SIZE_AUTO	0x0
+#define SAI_SLOT_SIZE_16	0x1
+#define SAI_SLOT_SIZE_32	0x2
+
+#define SAI_DATASIZE_8		0x2
+#define SAI_DATASIZE_10		0x3
+#define SAI_DATASIZE_16		0x4
+#define SAI_DATASIZE_20		0x5
+#define SAI_DATASIZE_24		0x6
+#define SAI_DATASIZE_32		0x7
+
+#define STM_SAI_FIFO_SIZE	8
+#define STM_SAI_DAI_NAME_SIZE	15
+
+#define STM_SAI_IS_PLAYBACK(ip)	((ip)->dir == SNDRV_PCM_STREAM_PLAYBACK)
+#define STM_SAI_IS_CAPTURE(ip)	((ip)->dir == SNDRV_PCM_STREAM_CAPTURE)
+
+#define STM_SAI_A_ID		0x0
+#define STM_SAI_B_ID		0x1
+
+#define STM_SAI_BLOCK_NAME(x)	(((x)->id == STM_SAI_A_ID) ? "A" : "B")
+
+/**
+ * struct stm32_sai_sub_data - private data of SAI sub block (block A or B)
+ * @pdev: device data pointer
+ * @regmap: SAI register map pointer
+ * @dma_params: dma configuration data for rx or tx channel
+ * @cpu_dai_drv: DAI driver data pointer
+ * @cpu_dai: DAI runtime data pointer
+ * @substream: PCM substream data pointer
+ * @pdata: SAI block parent data pointer
+ * @sai_ck: kernel clock feeding the SAI clock generator
+ * @phys_addr: SAI registers physical base address
+ * @mclk_rate: SAI block master clock frequency (Hz). set at init
+ * @id: SAI sub block id corresponding to sub-block A or B
+ * @dir: SAI block direction (playback or capture). set at init
+ * @master: SAI block mode flag. (true=master, false=slave) set at init
+ * @fmt: SAI block format. relevant only for custom protocols. set at init
+ * @sync: SAI block synchronization mode. (none, internal or external)
+ * @fs_length: frame synchronization length. depends on protocol settings
+ * @slots: rx or tx slot number
+ * @slot_width: rx or tx slot width in bits
+ * @slot_mask: rx or tx active slots mask. set at init or at runtime
+ * @data_size: PCM data width. corresponds to PCM substream width.
+ */
+struct stm32_sai_sub_data {
+	struct platform_device *pdev;
+	struct regmap *regmap;
+	struct snd_dmaengine_dai_dma_data dma_params;
+	struct snd_soc_dai_driver *cpu_dai_drv;
+	struct snd_soc_dai *cpu_dai;
+	struct snd_pcm_substream *substream;
+	struct stm32_sai_data *pdata;
+	struct clk *sai_ck;
+	dma_addr_t phys_addr;
+	unsigned int mclk_rate;
+	unsigned int id;
+	int dir;
+	bool master;
+	int fmt;
+	int sync;
+	int fs_length;
+	int slots;
+	int slot_width;
+	int slot_mask;
+	int data_size;
+};
+
+enum stm32_sai_fifo_th {
+	STM_SAI_FIFO_TH_EMPTY,
+	STM_SAI_FIFO_TH_QUARTER,
+	STM_SAI_FIFO_TH_HALF,
+	STM_SAI_FIFO_TH_3_QUARTER,
+	STM_SAI_FIFO_TH_FULL,
+};
+
+static bool stm32_sai_sub_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case STM_SAI_CR1_REGX:
+	case STM_SAI_CR2_REGX:
+	case STM_SAI_FRCR_REGX:
+	case STM_SAI_SLOTR_REGX:
+	case STM_SAI_IMR_REGX:
+	case STM_SAI_SR_REGX:
+	case STM_SAI_CLRFR_REGX:
+	case STM_SAI_DR_REGX:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case STM_SAI_DR_REGX:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case STM_SAI_CR1_REGX:
+	case STM_SAI_CR2_REGX:
+	case STM_SAI_FRCR_REGX:
+	case STM_SAI_SLOTR_REGX:
+	case STM_SAI_IMR_REGX:
+	case STM_SAI_SR_REGX:
+	case STM_SAI_CLRFR_REGX:
+	case STM_SAI_DR_REGX:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config stm32_sai_sub_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = STM_SAI_DR_REGX,
+	.readable_reg = stm32_sai_sub_readable_reg,
+	.volatile_reg = stm32_sai_sub_volatile_reg,
+	.writeable_reg = stm32_sai_sub_writeable_reg,
+	.fast_io = true,
+};
+
+static irqreturn_t stm32_sai_isr(int irq, void *devid)
+{
+	struct stm32_sai_sub_data *sai = (struct stm32_sai_sub_data *)devid;
+	struct snd_pcm_substream *substream = sai->substream;
+	struct platform_device *pdev = sai->pdev;
+	unsigned int sr, imr, flags;
+	snd_pcm_state_t status = SNDRV_PCM_STATE_RUNNING;
+
+	regmap_read(sai->regmap, STM_SAI_IMR_REGX, &imr);
+	regmap_read(sai->regmap, STM_SAI_SR_REGX, &sr);
+
+	flags = sr & imr;
+	if (!flags)
+		return IRQ_NONE;
+
+	regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK,
+			   SAI_XCLRFR_MASK);
+
+	if (flags & SAI_XIMR_OVRUDRIE) {
+		dev_err(&pdev->dev, "IT %s\n",
+			STM_SAI_IS_PLAYBACK(sai) ? "underrun" : "overrun");
+		status = SNDRV_PCM_STATE_XRUN;
+	}
+
+	if (flags & SAI_XIMR_MUTEDETIE)
+		dev_dbg(&pdev->dev, "IT mute detected\n");
+
+	if (flags & SAI_XIMR_WCKCFGIE) {
+		dev_err(&pdev->dev, "IT wrong clock configuration\n");
+		status = SNDRV_PCM_STATE_DISCONNECTED;
+	}
+
+	if (flags & SAI_XIMR_CNRDYIE)
+		dev_warn(&pdev->dev, "IT Codec not ready\n");
+
+	if (flags & SAI_XIMR_AFSDETIE) {
+		dev_warn(&pdev->dev, "IT Anticipated frame synchro\n");
+		status = SNDRV_PCM_STATE_XRUN;
+	}
+
+	if (flags & SAI_XIMR_LFSDETIE) {
+		dev_warn(&pdev->dev, "IT Late frame synchro\n");
+		status = SNDRV_PCM_STATE_XRUN;
+	}
+
+	if (status != SNDRV_PCM_STATE_RUNNING) {
+		snd_pcm_stream_lock(substream);
+		snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+		snd_pcm_stream_unlock(substream);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai,
+				int clk_id, unsigned int freq, int dir)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+
+	if ((dir == SND_SOC_CLOCK_OUT) && sai->master) {
+		sai->mclk_rate = freq;
+		dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq);
+	}
+
+	return 0;
+}
+
+static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
+				      u32 rx_mask, int slots, int slot_width)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int slotr, slotr_mask, slot_size;
+
+	dev_dbg(cpu_dai->dev, "masks tx/rx:%#x/%#x, slots:%d, width:%d\n",
+		tx_mask, rx_mask, slots, slot_width);
+
+	switch (slot_width) {
+	case 16:
+		slot_size = SAI_SLOT_SIZE_16;
+		break;
+	case 32:
+		slot_size = SAI_SLOT_SIZE_32;
+		break;
+	default:
+		slot_size = SAI_SLOT_SIZE_AUTO;
+		break;
+	}
+
+	slotr = SAI_XSLOTR_SLOTSZ_SET(slot_size) |
+		SAI_XSLOTR_NBSLOT_SET(slots - 1);
+	slotr_mask = SAI_XSLOTR_SLOTSZ_MASK | SAI_XSLOTR_NBSLOT_MASK;
+
+	/* tx/rx mask set in machine init, if slot number defined in DT */
+	if (STM_SAI_IS_PLAYBACK(sai)) {
+		sai->slot_mask = tx_mask;
+		slotr |= SAI_XSLOTR_SLOTEN_SET(tx_mask);
+	}
+
+	if (STM_SAI_IS_CAPTURE(sai)) {
+		sai->slot_mask = rx_mask;
+		slotr |= SAI_XSLOTR_SLOTEN_SET(rx_mask);
+	}
+
+	slotr_mask |= SAI_XSLOTR_SLOTEN_MASK;
+
+	regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, slotr_mask, slotr);
+
+	sai->slot_width = slot_width;
+	sai->slots = slots;
+
+	return 0;
+}
+
+static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int cr1 = 0, frcr = 0;
+	int cr1_mask = 0, frcr_mask = 0;
+	int ret;
+
+	dev_dbg(cpu_dai->dev, "fmt %x\n", fmt);
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	/* SCK active high for all protocols */
+	case SND_SOC_DAIFMT_I2S:
+		cr1 |= SAI_XCR1_CKSTR;
+		frcr |= SAI_XFRCR_FSOFF | SAI_XFRCR_FSDEF;
+		break;
+	/* Left justified */
+	case SND_SOC_DAIFMT_MSB:
+		frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF;
+		break;
+	/* Right justified */
+	case SND_SOC_DAIFMT_LSB:
+		frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		frcr |= SAI_XFRCR_FSPOL;
+		break;
+	default:
+		dev_err(cpu_dai->dev, "Unsupported protocol %#x\n",
+			fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+
+	cr1_mask |= SAI_XCR1_PRTCFG_MASK | SAI_XCR1_CKSTR;
+	frcr_mask |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF |
+		     SAI_XFRCR_FSDEF;
+
+	/* DAI clock strobing. Invert setting previously set */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		cr1 ^= SAI_XCR1_CKSTR;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		frcr ^= SAI_XFRCR_FSPOL;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		/* Invert fs & sck */
+		cr1 ^= SAI_XCR1_CKSTR;
+		frcr ^= SAI_XFRCR_FSPOL;
+		break;
+	default:
+		dev_err(cpu_dai->dev, "Unsupported strobing %#x\n",
+			fmt & SND_SOC_DAIFMT_INV_MASK);
+		return -EINVAL;
+	}
+	cr1_mask |= SAI_XCR1_CKSTR;
+	frcr_mask |= SAI_XFRCR_FSPOL;
+
+	regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
+
+	/* DAI clock master masks */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* codec is master */
+		cr1 |= SAI_XCR1_SLAVE;
+		sai->master = false;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		sai->master = true;
+		break;
+	default:
+		dev_err(cpu_dai->dev, "Unsupported mode %#x\n",
+			fmt & SND_SOC_DAIFMT_MASTER_MASK);
+		return -EINVAL;
+	}
+	cr1_mask |= SAI_XCR1_SLAVE;
+
+	ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
+	if (ret < 0) {
+		dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+		return ret;
+	}
+
+	sai->fmt = fmt;
+
+	return 0;
+}
+
+static int stm32_sai_startup(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *cpu_dai)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int imr, cr2, ret;
+
+	sai->substream = substream;
+
+	ret = clk_prepare_enable(sai->sai_ck);
+	if (ret < 0) {
+		dev_err(cpu_dai->dev, "failed to enable clock: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable ITs */
+	regmap_update_bits(sai->regmap, STM_SAI_SR_REGX,
+			   SAI_XSR_MASK, (unsigned int)~SAI_XSR_MASK);
+
+	regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX,
+			   SAI_XCLRFR_MASK, SAI_XCLRFR_MASK);
+
+	imr = SAI_XIMR_OVRUDRIE;
+	if (STM_SAI_IS_CAPTURE(sai)) {
+		regmap_read(sai->regmap, STM_SAI_CR2_REGX, &cr2);
+		if (cr2 & SAI_XCR2_MUTECNT_MASK)
+			imr |= SAI_XIMR_MUTEDETIE;
+	}
+
+	if (sai->master)
+		imr |= SAI_XIMR_WCKCFGIE;
+	else
+		imr |= SAI_XIMR_AFSDETIE | SAI_XIMR_LFSDETIE;
+
+	regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX,
+			   SAI_XIMR_MASK, imr);
+
+	return 0;
+}
+
+static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai,
+				struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int cr1, cr1_mask, ret;
+	int fth = STM_SAI_FIFO_TH_HALF;
+
+	/* FIFO config */
+	regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX,
+			   SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK,
+			   SAI_XCR2_FFLUSH | SAI_XCR2_FTH_SET(fth));
+
+	/* Mode, data format and channel config */
+	cr1 = SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL);
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_8);
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_16);
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_32);
+		break;
+	default:
+		dev_err(cpu_dai->dev, "Data format not supported");
+		return -EINVAL;
+	}
+	cr1_mask = SAI_XCR1_DS_MASK | SAI_XCR1_PRTCFG_MASK;
+
+	cr1_mask |= SAI_XCR1_RX_TX;
+	if (STM_SAI_IS_CAPTURE(sai))
+		cr1 |= SAI_XCR1_RX_TX;
+
+	cr1_mask |= SAI_XCR1_MONO;
+	if ((sai->slots == 2) && (params_channels(params) == 1))
+		cr1 |= SAI_XCR1_MONO;
+
+	ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
+	if (ret < 0) {
+		dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+		return ret;
+	}
+
+	/* DMA config */
+	sai->dma_params.maxburst = STM_SAI_FIFO_SIZE * fth / sizeof(u32);
+	snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)&sai->dma_params);
+
+	return 0;
+}
+
+static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int slotr, slot_sz;
+
+	regmap_read(sai->regmap, STM_SAI_SLOTR_REGX, &slotr);
+
+	/*
+	 * If SLOTSZ is set to auto in SLOTR, align slot width on data size
+	 * By default slot width = data size, if not forced from DT
+	 */
+	slot_sz = slotr & SAI_XSLOTR_SLOTSZ_MASK;
+	if (slot_sz == SAI_XSLOTR_SLOTSZ_SET(SAI_SLOT_SIZE_AUTO))
+		sai->slot_width = sai->data_size;
+
+	if (sai->slot_width < sai->data_size) {
+		dev_err(cpu_dai->dev,
+			"Data size %d larger than slot width\n",
+			sai->data_size);
+		return -EINVAL;
+	}
+
+	/* Slot number is set to 2, if not specified in DT */
+	if (!sai->slots)
+		sai->slots = 2;
+
+	/* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/
+	regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
+			   SAI_XSLOTR_NBSLOT_MASK,
+			   SAI_XSLOTR_NBSLOT_SET((sai->slots - 1)));
+
+	/* Set default slots mask if not already set from DT */
+	if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) {
+		sai->slot_mask = (1 << sai->slots) - 1;
+		regmap_update_bits(sai->regmap,
+				   STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK,
+				   SAI_XSLOTR_SLOTEN_SET(sai->slot_mask));
+	}
+
+	dev_dbg(cpu_dai->dev, "slots %d, slot width %d\n",
+		sai->slots, sai->slot_width);
+
+	return 0;
+}
+
+static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int fs_active, offset, format;
+	int frcr, frcr_mask;
+
+	format = sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+	sai->fs_length = sai->slot_width * sai->slots;
+
+	fs_active = sai->fs_length / 2;
+	if ((format == SND_SOC_DAIFMT_DSP_A) ||
+	    (format == SND_SOC_DAIFMT_DSP_B))
+		fs_active = 1;
+
+	frcr = SAI_XFRCR_FRL_SET((sai->fs_length - 1));
+	frcr |= SAI_XFRCR_FSALL_SET((fs_active - 1));
+	frcr_mask = SAI_XFRCR_FRL_MASK | SAI_XFRCR_FSALL_MASK;
+
+	dev_dbg(cpu_dai->dev, "frame length %d, frame active %d\n",
+		sai->fs_length, fs_active);
+
+	regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
+
+	if ((sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_LSB) {
+		offset = sai->slot_width - sai->data_size;
+
+		regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
+				   SAI_XSLOTR_FBOFF_MASK,
+				   SAI_XSLOTR_FBOFF_SET(offset));
+	}
+}
+
+static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai,
+				     struct snd_pcm_hw_params *params)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int cr1, mask, div = 0;
+	int sai_clk_rate, ret;
+
+	if (!sai->mclk_rate) {
+		dev_err(cpu_dai->dev, "Mclk rate is null\n");
+		return -EINVAL;
+	}
+
+	if (!(params_rate(params) % 11025))
+		clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k);
+	else
+		clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k);
+	sai_clk_rate = clk_get_rate(sai->sai_ck);
+
+	/*
+	 * mclk_rate = 256 * fs
+	 * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate
+	 * MCKDIV = sai_ck / (2 * mclk_rate) otherwise
+	 */
+	if (2 * sai_clk_rate >= 3 * sai->mclk_rate)
+		div = DIV_ROUND_CLOSEST(sai_clk_rate, 2 * sai->mclk_rate);
+
+	if (div > SAI_XCR1_MCKDIV_MAX) {
+		dev_err(cpu_dai->dev, "Divider %d out of range\n", div);
+		return -EINVAL;
+	}
+	dev_dbg(cpu_dai->dev, "SAI clock %d, divider %d\n", sai_clk_rate, div);
+
+	mask = SAI_XCR1_MCKDIV_MASK;
+	cr1 = SAI_XCR1_MCKDIV_SET(div);
+	ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1);
+	if (ret < 0) {
+		dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int stm32_sai_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *params,
+			       struct snd_soc_dai *cpu_dai)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int ret;
+
+	sai->data_size = params_width(params);
+
+	ret = stm32_sai_set_slots(cpu_dai);
+	if (ret < 0)
+		return ret;
+	stm32_sai_set_frame(cpu_dai);
+
+	ret = stm32_sai_set_config(cpu_dai, substream, params);
+	if (ret)
+		return ret;
+
+	if (sai->master)
+		ret = stm32_sai_configure_clock(cpu_dai, params);
+
+	return ret;
+}
+
+static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd,
+			     struct snd_soc_dai *cpu_dai)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	int ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n");
+
+		regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+				   SAI_XCR1_DMAEN, SAI_XCR1_DMAEN);
+
+		/* Enable SAI */
+		ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+					 SAI_XCR1_SAIEN, SAI_XCR1_SAIEN);
+		if (ret < 0)
+			dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_STOP:
+		dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n");
+
+		regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+				   SAI_XCR1_DMAEN,
+				   (unsigned int)~SAI_XCR1_DMAEN);
+
+		ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+					 SAI_XCR1_SAIEN,
+					 (unsigned int)~SAI_XCR1_SAIEN);
+		if (ret < 0)
+			dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static void stm32_sai_shutdown(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *cpu_dai)
+{
+	struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+
+	regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0);
+
+	clk_disable_unprepare(sai->sai_ck);
+	sai->substream = NULL;
+}
+
+static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+	struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev);
+
+	sai->dma_params.addr = (dma_addr_t)(sai->phys_addr + STM_SAI_DR_REGX);
+	sai->dma_params.maxburst = 1;
+	/* Buswidth will be set by framework at runtime */
+	sai->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+
+	if (STM_SAI_IS_PLAYBACK(sai))
+		snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params, NULL);
+	else
+		snd_soc_dai_init_dma_data(cpu_dai, NULL, &sai->dma_params);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = {
+	.set_sysclk	= stm32_sai_set_sysclk,
+	.set_fmt	= stm32_sai_set_dai_fmt,
+	.set_tdm_slot	= stm32_sai_set_dai_tdm_slot,
+	.startup	= stm32_sai_startup,
+	.hw_params	= stm32_sai_hw_params,
+	.trigger	= stm32_sai_trigger,
+	.shutdown	= stm32_sai_shutdown,
+};
+
+static const struct snd_pcm_hardware stm32_sai_pcm_hw = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP,
+	.buffer_bytes_max = 8 * PAGE_SIZE,
+	.period_bytes_min = 1024, /* 5ms at 48kHz */
+	.period_bytes_max = PAGE_SIZE,
+	.periods_min = 2,
+	.periods_max = 8,
+};
+
+static struct snd_soc_dai_driver stm32_sai_playback_dai[] = {
+{
+		.probe = stm32_sai_dai_probe,
+		.id = 1, /* avoid call to fmt_single_name() */
+		.playback = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rate_min = 8000,
+			.rate_max = 192000,
+			.rates = SNDRV_PCM_RATE_CONTINUOUS,
+			/* DMA does not support 24 bits transfers */
+			.formats =
+				SNDRV_PCM_FMTBIT_S8 |
+				SNDRV_PCM_FMTBIT_S16_LE |
+				SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &stm32_sai_pcm_dai_ops,
+	}
+};
+
+static struct snd_soc_dai_driver stm32_sai_capture_dai[] = {
+{
+		.probe = stm32_sai_dai_probe,
+		.id = 1, /* avoid call to fmt_single_name() */
+		.capture = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rate_min = 8000,
+			.rate_max = 192000,
+			.rates = SNDRV_PCM_RATE_CONTINUOUS,
+			/* DMA does not support 24 bits transfers */
+			.formats =
+				SNDRV_PCM_FMTBIT_S8 |
+				SNDRV_PCM_FMTBIT_S16_LE |
+				SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &stm32_sai_pcm_dai_ops,
+	}
+};
+
+static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = {
+	.pcm_hardware	= &stm32_sai_pcm_hw,
+	.prepare_slave_config	= snd_dmaengine_pcm_prepare_slave_config,
+};
+
+static const struct snd_soc_component_driver stm32_component = {
+	.name = "stm32-sai",
+};
+
+static const struct of_device_id stm32_sai_sub_ids[] = {
+	{ .compatible = "st,stm32-sai-sub-a",
+	  .data = (void *)STM_SAI_A_ID},
+	{ .compatible = "st,stm32-sai-sub-b",
+	  .data = (void *)STM_SAI_B_ID},
+	{}
+};
+MODULE_DEVICE_TABLE(of, stm32_sai_sub_ids);
+
+static int stm32_sai_sub_parse_of(struct platform_device *pdev,
+				  struct stm32_sai_sub_data *sai)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct resource *res;
+	void __iomem *base;
+
+	if (!np)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	dev_err(&pdev->dev, "res %pr\n", res);
+
+	base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	sai->phys_addr = res->start;
+	sai->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+					    &stm32_sai_sub_regmap_config);
+
+	/* Get direction property */
+	if (of_property_match_string(np, "dma-names", "tx") >= 0) {
+		sai->dir = SNDRV_PCM_STREAM_PLAYBACK;
+	} else if (of_property_match_string(np, "dma-names", "rx") >= 0) {
+		sai->dir = SNDRV_PCM_STREAM_CAPTURE;
+	} else {
+		dev_err(&pdev->dev, "Unsupported direction\n");
+		return -EINVAL;
+	}
+
+	sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck");
+	if (IS_ERR(sai->sai_ck)) {
+		dev_err(&pdev->dev, "missing kernel clock sai_ck\n");
+		return PTR_ERR(sai->sai_ck);
+	}
+
+	return 0;
+}
+
+static int stm32_sai_sub_dais_init(struct platform_device *pdev,
+				   struct stm32_sai_sub_data *sai)
+{
+	sai->cpu_dai_drv = devm_kzalloc(&pdev->dev,
+					sizeof(struct snd_soc_dai_driver),
+					GFP_KERNEL);
+	if (!sai->cpu_dai_drv)
+		return -ENOMEM;
+
+	sai->cpu_dai_drv->name = dev_name(&pdev->dev);
+	if (STM_SAI_IS_PLAYBACK(sai)) {
+		memcpy(sai->cpu_dai_drv, &stm32_sai_playback_dai,
+		       sizeof(stm32_sai_playback_dai));
+		sai->cpu_dai_drv->playback.stream_name = sai->cpu_dai_drv->name;
+	} else {
+		memcpy(sai->cpu_dai_drv, &stm32_sai_capture_dai,
+		       sizeof(stm32_sai_capture_dai));
+		sai->cpu_dai_drv->capture.stream_name = sai->cpu_dai_drv->name;
+	}
+
+	return 0;
+}
+
+static int stm32_sai_sub_probe(struct platform_device *pdev)
+{
+	struct stm32_sai_sub_data *sai;
+	const struct of_device_id *of_id;
+	int ret;
+
+	sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+	if (!sai)
+		return -ENOMEM;
+
+	of_id = of_match_device(stm32_sai_sub_ids, &pdev->dev);
+	if (!of_id)
+		return -EINVAL;
+	sai->id = (uintptr_t)of_id->data;
+
+	sai->pdev = pdev;
+	platform_set_drvdata(pdev, sai);
+
+	sai->pdata = dev_get_drvdata(pdev->dev.parent);
+	if (!sai->pdata) {
+		dev_err(&pdev->dev, "Parent device data not available\n");
+		return -EINVAL;
+	}
+
+	ret = stm32_sai_sub_parse_of(pdev, sai);
+	if (ret)
+		return ret;
+
+	ret = stm32_sai_sub_dais_init(pdev, sai);
+	if (ret)
+		return ret;
+
+	ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr,
+			       IRQF_SHARED, dev_name(&pdev->dev), sai);
+	if (ret) {
+		dev_err(&pdev->dev, "irq request returned %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &stm32_component,
+					      sai->cpu_dai_drv, 1);
+	if (ret)
+		return ret;
+
+	ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
+					      &stm32_sai_pcm_config, 0);
+	if (ret) {
+		dev_err(&pdev->dev, "could not register pcm dma\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver stm32_sai_sub_driver = {
+	.driver = {
+		.name = "st,stm32-sai-sub",
+		.of_match_table = stm32_sai_sub_ids,
+	},
+	.probe = stm32_sai_sub_probe,
+};
+
+module_platform_driver(stm32_sai_sub_driver);
+
+MODULE_DESCRIPTION("STM32 Soc SAI sub-block Interface");
+MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>");
+MODULE_ALIAS("platform:st,stm32-sai-sub");
+MODULE_LICENSE("GPL v2");
-- 
2.11.0

^ permalink raw reply related

* Applied "ASoC: stm32: add bindings for SAI" to the asoc tree
From: Mark Brown @ 2017-04-26 15:50 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1491837596-2924-2-git-send-email-olivier.moysan@st.com>

The patch

   ASoC: stm32: add bindings for SAI

has been applied to the asoc tree at

   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

>From b45c4f117b002a902e8b9f52eaf542e3e82c36a8 Mon Sep 17 00:00:00 2001
From: olivier moysan <olivier.moysan@st.com>
Date: Mon, 10 Apr 2017 17:19:55 +0200
Subject: [PATCH] ASoC: stm32: add bindings for SAI

This patch adds documentation of device tree bindings for the
STM32 SAI ASoC driver.

Signed-off-by: olivier moysan <olivier.moysan@st.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 .../devicetree/bindings/sound/st,stm32-sai.txt     | 89 ++++++++++++++++++++++
 1 file changed, 89 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-sai.txt

diff --git a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt
new file mode 100644
index 000000000000..c59a3d779e06
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt
@@ -0,0 +1,89 @@
+STMicroelectronics STM32 Serial Audio Interface (SAI).
+
+The SAI interface (Serial Audio Interface) offers a wide set of audio protocols
+as I2S standards, LSB or MSB-justified, PCM/DSP, TDM, and AC'97.
+The SAI contains two independent audio sub-blocks. Each sub-block has
+its own clock generator and I/O lines controller.
+
+Required properties:
+  - compatible: Should be "st,stm32f4-sai"
+  - reg: Base address and size of SAI common register set.
+  - clocks: Must contain phandle and clock specifier pairs for each entry
+	in clock-names.
+  - clock-names: Must contain "x8k" and "x11k"
+	"x8k": SAI parent clock for sampling rates multiple of 8kHz.
+	"x11k": SAI parent clock for sampling rates multiple of 11.025kHz.
+  - interrupts: cpu DAI interrupt line shared by SAI sub-blocks
+
+Optional properties:
+  - resets: Reference to a reset controller asserting the SAI
+
+SAI subnodes:
+Two subnodes corresponding to SAI sub-block instances A et B can be defined.
+Subnode can be omitted for unsused sub-block.
+
+SAI subnodes required properties:
+  - compatible: Should be "st,stm32-sai-sub-a" or "st,stm32-sai-sub-b"
+	for SAI sub-block A or B respectively.
+  - reg: Base address and size of SAI sub-block register set.
+  - clocks: Must contain one phandle and clock specifier pair
+	for sai_ck which feeds the internal clock generator.
+  - clock-names: Must contain "sai_ck".
+  - dmas: see Documentation/devicetree/bindings/dma/stm32-dma.txt
+  - dma-names: identifier string for each DMA request line
+	"tx": if sai sub-block is configured as playback DAI
+	"rx": if sai sub-block is configured as capture DAI
+  - pinctrl-names: should contain only value "default"
+  - pinctrl-0: see Documentation/devicetree/bindings/pinctrl/pinctrl-stm32.txt
+
+Example:
+sound_card {
+	compatible = "audio-graph-card";
+	dais = <&sai1b_port>;
+};
+
+sai1: sai1 at 40015800 {
+	compatible = "st,stm32f4-sai";
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges;
+	reg = <0x40015800 0x4>;
+	clocks = <&rcc 1 CLK_SAIQ_PDIV>, <&rcc 1 CLK_I2SQ_PDIV>;
+	clock-names = "x8k", "x11k";
+	interrupts = <87>;
+
+	sai1b: audio-controller at 40015824 {
+		#sound-dai-cells = <0>;
+		compatible = "st,stm32-sai-sub-b";
+		reg = <0x40015824 0x1C>;
+		clocks = <&rcc 1 CLK_SAI2>;
+		clock-names = "sai_ck";
+		dmas = <&dma2 5 0 0x400 0x0>;
+		dma-names = "tx";
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_sai1b>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sai1b_port: port at 0 {
+				reg = <0>;
+				cpu_endpoint: endpoint {
+					remote-endpoint = <&codec_endpoint>;
+					audio-graph-card,format = "i2s";
+					audio-graph-card,bitclock-master = <&codec_endpoint>;
+					audio-graph-card,frame-master = <&codec_endpoint>;
+				};
+			};
+		};
+	};
+};
+
+audio-codec {
+	codec_port: port {
+		codec_endpoint: endpoint {
+			remote-endpoint = <&cpu_endpoint>;
+		};
+	};
+};
-- 
2.11.0

^ permalink raw reply related

* [PATCH] iommu: Print a message with the default domain type created
From: Joerg Roedel @ 2017-04-26 15:50 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426143813.21225-1-rrichter@cavium.com>

Hi Robert,

On Wed, Apr 26, 2017 at 04:38:13PM +0200, Robert Richter wrote:
> -	pr_info("Adding device %s to group %d\n", dev_name(dev), group->id);
> +	pr_info("Adding device %s to group %d, default domain type %d\n",
> +		dev_name(dev), group->id,
> +		group->default_domain ? group->default_domain->type : -1);

Printing just a number is not very user-friendly. We have only two
default domain-types right now, so it is sufficient to just print
additional information if a device is identity-mapped. Something like

	"Adding device %s to group %d - using identity mapping"


Regards,

	Joerg

^ permalink raw reply

* [PATCH 1/2] kvm: Fix mmu_notifier release race
From: Suzuki K Poulose @ 2017-04-26 16:03 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170425184904.GI5713@potion>

On 25/04/17 19:49, Radim Kr?m?? wrote:
> 2017-04-24 11:10+0100, Suzuki K Poulose:
>> The KVM uses mmu_notifier (wherever available) to keep track
>> of the changes to the mm of the guest. The guest shadow page
>> tables are released when the VM exits via mmu_notifier->ops.release().
>> There is a rare chance that the mmu_notifier->release could be
>> called more than once via two different paths, which could end
>> up in use-after-free of kvm instance (such as [0]).
>>
>> e.g:
>>
>> thread A                                        thread B
>> -------                                         --------------
>>
>>  get_signal->                                   kvm_destroy_vm()->
>>  do_exit->                                        mmu_notifier_unregister->
>>  exit_mm->                                        kvm_arch_flush_shadow_all()->
>>  exit_mmap->                                      spin_lock(&kvm->mmu_lock)
>>  mmu_notifier_release->                           ....
>>   kvm_arch_flush_shadow_all()->                   .....
>>   ... spin_lock(&kvm->mmu_lock)                   .....
>>                                                   spin_unlock(&kvm->mmu_lock)
>>                                                 kvm_arch_free_kvm()
>>    *** use after free of kvm ***
>
> I don't understand this race ...
> a piece of code in mmu_notifier_unregister() says:
>
>   	/*
>   	 * Wait for any running method to finish, of course including
>   	 * ->release if it was run by mmu_notifier_release instead of us.
>   	 */
>   	synchronize_srcu(&srcu);
>
> and code before that removes the notifier from the list, so it cannot be
> called after we pass this point.  mmu_notifier_release() does roughly
> the same and explains it as:
>
>   	/*
>   	 * synchronize_srcu here prevents mmu_notifier_release from returning to
>   	 * exit_mmap (which would proceed with freeing all pages in the mm)
>   	 * until the ->release method returns, if it was invoked by
>   	 * mmu_notifier_unregister.
>   	 *
>   	 * The mmu_notifier_mm can't go away from under us because one mm_count
>   	 * is held by exit_mmap.
>   	 */
>   	synchronize_srcu(&srcu);
>
> The call of mmu_notifier->release is protected by srcu in both cases and
> while it seems possible that mmu_notifier->release would be called
> twice, I don't see a combination that could result in use-after-free
> from mmu_notifier_release after mmu_notifier_unregister() has returned.

Thanks for bringing it up. Even I am wondering why this is triggered ! (But it
does get triggered for sure !!)

The only difference I can spot with _unregister & _release paths are the way
we use src_read_lock across the deletion of the entry from the list.

In mmu_notifier_unregister() we do :

                 id = srcu_read_lock(&srcu);
                 /*
                  * exit_mmap will block in mmu_notifier_release to guarantee
                  * that ->release is called before freeing the pages.
                  */
                 if (mn->ops->release)
                         mn->ops->release(mn, mm);
                 srcu_read_unlock(&srcu, id);

## Releases the srcu lock here and then goes on to grab the spin_lock.

                 spin_lock(&mm->mmu_notifier_mm->lock);
                 /*
                  * Can not use list_del_rcu() since __mmu_notifier_release
                  * can delete it before we hold the lock.
                  */
                 hlist_del_init_rcu(&mn->hlist);
                 spin_unlock(&mm->mmu_notifier_mm->lock);

While in mmu_notifier_release() we hold it until the node(s) are deleted from the
list :
         /*
          * SRCU here will block mmu_notifier_unregister until
          * ->release returns.
          */
         id = srcu_read_lock(&srcu);
         hlist_for_each_entry_rcu(mn, &mm->mmu_notifier_mm->list, hlist)
                 /*
                  * If ->release runs before mmu_notifier_unregister it must be
                  * handled, as it's the only way for the driver to flush all
                  * existing sptes and stop the driver from establishing any more
                  * sptes before all the pages in the mm are freed.
                  */
                 if (mn->ops->release)
                         mn->ops->release(mn, mm);

         spin_lock(&mm->mmu_notifier_mm->lock);
         while (unlikely(!hlist_empty(&mm->mmu_notifier_mm->list))) {
                 mn = hlist_entry(mm->mmu_notifier_mm->list.first,
                                  struct mmu_notifier,
                                  hlist);
                 /*
                  * We arrived before mmu_notifier_unregister so
                  * mmu_notifier_unregister will do nothing other than to wait
                  * for ->release to finish and for mmu_notifier_unregister to
                  * return.
                  */
                 hlist_del_init_rcu(&mn->hlist);
         }
         spin_unlock(&mm->mmu_notifier_mm->lock);
         srcu_read_unlock(&srcu, id);

## The lock is release only after the deletion of the node.

Both are followed by a synchronize_srcu(). Now, I am wondering if the unregister path
could potentially miss SRCU read lock held in _release() path and go onto finish the
synchronize_srcu before the item is deleted ? May be we should do the read_unlock
after the deletion of the node in _unregister (like we do in the _release()) ?

>
> Doesn't [2/2] solve the exact same issue (that the release method cannot
> be called twice in parallel)?

Not really. This could be a race between a release() and one of the other notifier
callbacks. e.g, In [0], we were hitting a use-after-free in kvm_unmap_hva() where,
the unregister could have succeeded and released the KVM.


[0] http://lkml.kernel.org/r/febea966-3767-21ff-3c40-1a76d1399138 at suse.de

In effect this all could be due to the same reason, the synchronize in unregister
missing another reader.

Suzuki

>
> Thanks.
>

^ permalink raw reply

* [PATCH 1/3] ARM: at91: pm: Add sama5d2 backup mode
From: Alexandre Belloni @ 2017-04-26 16:04 UTC (permalink / raw)
  To: linux-arm-kernel

The sama5d2 has a mode were it is possible to cut power to the SoC while
keeping the RAM in self refresh.
Resuming from that mode needs support in the firmware/bootloader.

Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
---
 arch/arm/mach-at91/Makefile          |   4 ++
 arch/arm/mach-at91/generic.h         |   2 +
 arch/arm/mach-at91/pm.c              | 103 ++++++++++++++++++++++++++++++++++-
 arch/arm/mach-at91/pm.h              |   4 ++
 arch/arm/mach-at91/pm_data-offsets.c |   3 +
 arch/arm/mach-at91/pm_suspend.S      |  86 ++++++++++++++++++++++-------
 arch/arm/mach-at91/sama5.c           |  19 ++++++-
 7 files changed, 198 insertions(+), 23 deletions(-)

diff --git a/arch/arm/mach-at91/Makefile b/arch/arm/mach-at91/Makefile
index cfd8f60a9268..87fe17dbdb56 100644
--- a/arch/arm/mach-at91/Makefile
+++ b/arch/arm/mach-at91/Makefile
@@ -14,6 +14,10 @@ obj-$(CONFIG_PM)		+= pm_suspend.o
 ifeq ($(CONFIG_CPU_V7),y)
 AFLAGS_pm_suspend.o := -march=armv7-a
 endif
+# Backup mode will not compile for ARMv5 because of movt
+ifeq ($(CONFIG_SOC_SAMA5D2),y)
+AFLAGS_pm_suspend.o += -DBACKUP_MODE
+endif
 ifeq ($(CONFIG_PM_DEBUG),y)
 CFLAGS_pm.o += -DDEBUG
 endif
diff --git a/arch/arm/mach-at91/generic.h b/arch/arm/mach-at91/generic.h
index f1ead0f13c19..e2bd17237964 100644
--- a/arch/arm/mach-at91/generic.h
+++ b/arch/arm/mach-at91/generic.h
@@ -15,10 +15,12 @@
 extern void __init at91rm9200_pm_init(void);
 extern void __init at91sam9_pm_init(void);
 extern void __init sama5_pm_init(void);
+extern void __init sama5d2_pm_init(void);
 #else
 static inline void __init at91rm9200_pm_init(void) { }
 static inline void __init at91sam9_pm_init(void) { }
 static inline void __init sama5_pm_init(void) { }
+static inline void __init sama5d2_pm_init(void) { }
 #endif
 
 #endif /* _AT91_GENERIC_H */
diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c
index 2cd27c830ab6..1e03f1277f14 100644
--- a/arch/arm/mach-at91/pm.c
+++ b/arch/arm/mach-at91/pm.c
@@ -22,6 +22,7 @@
 #include <asm/cacheflush.h>
 #include <asm/fncpy.h>
 #include <asm/system_misc.h>
+#include <asm/suspend.h>
 
 #include "generic.h"
 #include "pm.h"
@@ -58,6 +59,14 @@ static int at91_pm_valid_state(suspend_state_t state)
 	}
 }
 
+static int canary = 0xA5A5A5A5;
+
+static struct at91_pm_bu {
+	int suspended;
+	unsigned long reserved;
+	phys_addr_t canary;
+	phys_addr_t resume;
+} *pm_bu;
 
 static suspend_state_t target_state;
 
@@ -123,15 +132,39 @@ static void (*at91_suspend_sram_fn)(struct at91_pm_data *);
 extern void at91_pm_suspend_in_sram(struct at91_pm_data *pm_data);
 extern u32 at91_pm_suspend_in_sram_sz;
 
-static void at91_pm_suspend(suspend_state_t state)
+static int at91_suspend_finish(unsigned long val)
 {
-	pm_data.mode = (state == PM_SUSPEND_MEM) ? AT91_PM_SLOW_CLOCK : 0;
-
 	flush_cache_all();
 	outer_disable();
 
 	at91_suspend_sram_fn(&pm_data);
 
+	return 0;
+}
+
+static void at91_pm_suspend(suspend_state_t state)
+{
+	if (pm_data.deepest_state == AT91_PM_BACKUP)
+		if (state == PM_SUSPEND_MEM)
+			pm_data.mode = AT91_PM_BACKUP;
+		else
+			pm_data.mode = AT91_PM_SLOW_CLOCK;
+	else
+		pm_data.mode = (state == PM_SUSPEND_MEM) ? AT91_PM_SLOW_CLOCK : 0;
+
+	if (pm_data.mode == AT91_PM_BACKUP) {
+		pm_bu->suspended = 1;
+
+		cpu_suspend(0, at91_suspend_finish);
+
+		/* The SRAM is lost between suspend cycles */
+		at91_suspend_sram_fn = fncpy(at91_suspend_sram_fn,
+					     &at91_pm_suspend_in_sram,
+					     at91_pm_suspend_in_sram_sz);
+	} else {
+		at91_suspend_finish(0);
+	}
+
 	outer_resume();
 }
 
@@ -375,6 +408,25 @@ static __init void at91_dt_ramc(void)
 	at91_cpuidle_device.dev.platform_data = standby;
 }
 
+static __init void at91_dt_shdwc(void)
+{
+	struct device_node *np;
+
+	np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-shdwc");
+	if (!np)
+		return;
+
+	pm_data.shdwc = of_iomap(np, 0);
+	of_node_put(np);
+
+	np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-sfrbu");
+	if (!np)
+		return;
+
+	pm_data.sfrbu = of_iomap(np, 0);
+	of_node_put(np);
+}
+
 static void at91rm9200_idle(void)
 {
 	/*
@@ -436,6 +488,44 @@ static void __init at91_pm_sram_init(void)
 			&at91_pm_suspend_in_sram, at91_pm_suspend_in_sram_sz);
 }
 
+static void __init at91_pm_bu_sram_init(void)
+{
+	struct gen_pool *sram_pool;
+	struct device_node *node;
+	struct platform_device *pdev = NULL;
+
+	pm_bu = NULL;
+
+	for_each_compatible_node(node, NULL, "atmel,sama5d2-securam") {
+		pdev = of_find_device_by_node(node);
+		if (pdev) {
+			of_node_put(node);
+			break;
+		}
+	}
+
+	if (!pdev) {
+		pr_warn("%s: failed to find securam device!\n", __func__);
+		return;
+	}
+
+	sram_pool = gen_pool_get(&pdev->dev, NULL);
+	if (!sram_pool) {
+		pr_warn("%s: securam pool unavailable!\n", __func__);
+		return;
+	}
+
+	pm_bu = (void *)gen_pool_alloc(sram_pool, sizeof(struct at91_pm_bu));
+	if (!pm_bu) {
+		pr_warn("%s: unable to alloc securam!\n", __func__);
+		return;
+	}
+
+	pm_bu->suspended = 0;
+	pm_bu->canary = virt_to_phys(&canary);
+	pm_bu->resume = virt_to_phys(cpu_resume);
+}
+
 struct pmc_info {
 	unsigned long uhp_udp_mask;
 };
@@ -510,3 +600,10 @@ void __init sama5_pm_init(void)
 	at91_dt_ramc();
 	at91_pm_init(NULL);
 }
+
+void __init sama5d2_pm_init(void)
+{
+	at91_dt_shdwc();
+	at91_pm_bu_sram_init();
+	sama5_pm_init();
+}
diff --git a/arch/arm/mach-at91/pm.h b/arch/arm/mach-at91/pm.h
index fc0f7d048187..d9c6612ef62f 100644
--- a/arch/arm/mach-at91/pm.h
+++ b/arch/arm/mach-at91/pm.h
@@ -22,6 +22,7 @@
 #define AT91_MEMCTRL_DDRSDR	2
 
 #define	AT91_PM_SLOW_CLOCK	0x01
+#define	AT91_PM_BACKUP		0x02
 
 #ifndef __ASSEMBLY__
 struct at91_pm_data {
@@ -30,6 +31,9 @@ struct at91_pm_data {
 	unsigned long uhp_udp_mask;
 	unsigned int memctrl;
 	unsigned int mode;
+	void __iomem *shdwc;
+	void __iomem *sfrbu;
+	unsigned int deepest_state;
 };
 #endif
 
diff --git a/arch/arm/mach-at91/pm_data-offsets.c b/arch/arm/mach-at91/pm_data-offsets.c
index 30302cb16df0..c0a73e62b725 100644
--- a/arch/arm/mach-at91/pm_data-offsets.c
+++ b/arch/arm/mach-at91/pm_data-offsets.c
@@ -9,5 +9,8 @@ int main(void)
 	DEFINE(PM_DATA_RAMC1,		offsetof(struct at91_pm_data, ramc[1]));
 	DEFINE(PM_DATA_MEMCTRL,	offsetof(struct at91_pm_data, memctrl));
 	DEFINE(PM_DATA_MODE,		offsetof(struct at91_pm_data, mode));
+	DEFINE(PM_DATA_SHDWC,		offsetof(struct at91_pm_data, shdwc));
+	DEFINE(PM_DATA_SFRBU,		offsetof(struct at91_pm_data, sfrbu));
+
 	return 0;
 }
diff --git a/arch/arm/mach-at91/pm_suspend.S b/arch/arm/mach-at91/pm_suspend.S
index 96781daa671a..b5ffa8e1f203 100644
--- a/arch/arm/mach-at91/pm_suspend.S
+++ b/arch/arm/mach-at91/pm_suspend.S
@@ -97,15 +97,74 @@ ENTRY(at91_pm_suspend_in_sram)
 	str	tmp1, .memtype
 	ldr	tmp1, [r0, #PM_DATA_MODE]
 	str	tmp1, .pm_mode
+	ldr	tmp1, [r0, #PM_DATA_SHDWC]
+#if defined(BACKUP_MODE)
+	str	tmp1, .shdwc
+	cmp	tmp1, #0
+	ldrne	tmp2, [tmp1, #0]
+	ldr	tmp1, [r0, #PM_DATA_SFRBU]
+	str	tmp1, .sfr
+	cmp	tmp1, #0
+	ldrne	tmp2, [tmp1, #0x10]
+#endif
 
 	/* Active the self-refresh mode */
 	mov	r0, #SRAMC_SELF_FRESH_ACTIVE
 	bl	at91_sramc_self_refresh
 
 	ldr	r0, .pm_mode
-	tst	r0, #AT91_PM_SLOW_CLOCK
-	beq	skip_disable_main_clock
+	cmp	r0, #AT91_PM_SLOW_CLOCK
+	beq	slow_clock
+#if defined(BACKUP_MODE)
+	cmp	r0, #AT91_PM_BACKUP
+	beq	backup_mode
+#endif
 
+	/* Wait for interrupt */
+	ldr	pmc, .pmc_base
+	at91_cpu_idle
+	b	exit_suspend
+
+slow_clock:
+	bl	at91_slowck_mode
+	b	exit_suspend
+#if defined(BACKUP_MODE)
+backup_mode:
+	bl	at91_backup_mode
+	b	exit_suspend
+#endif
+
+exit_suspend:
+	/* Exit the self-refresh mode */
+	mov	r0, #SRAMC_SELF_FRESH_EXIT
+	bl	at91_sramc_self_refresh
+
+	/* Restore registers, and return */
+	ldmfd	sp!, {r4 - r12, pc}
+ENDPROC(at91_pm_suspend_in_sram)
+
+#if defined(BACKUP_MODE)
+ENTRY(at91_backup_mode)
+	#if 0
+	/* Read LPR */
+	ldr	r2, .sramc_base
+	ldr	r3, [r2, #AT91_DDRSDRC_LPR]
+	#endif
+
+	/*BUMEN*/
+	ldr	r0, .sfr
+	mov	tmp1, #(0x1)
+	str	tmp1, [r0, #0x10]
+
+	/* Shutdown */
+	ldr	r0, .shdwc
+	movw    tmp1, #0x1
+	movt    tmp1, #0xA500
+	str	tmp1, [r0, #0]
+ENDPROC(at91_backup_mode)
+#endif
+
+ENTRY(at91_slowck_mode)
 	ldr	pmc, .pmc_base
 
 	/* Save Master clock setting */
@@ -134,18 +193,9 @@ ENTRY(at91_pm_suspend_in_sram)
 	orr	tmp1, tmp1, #AT91_PMC_KEY
 	str	tmp1, [pmc, #AT91_CKGR_MOR]
 
-skip_disable_main_clock:
-	ldr	pmc, .pmc_base
-
 	/* Wait for interrupt */
 	at91_cpu_idle
 
-	ldr	r0, .pm_mode
-	tst	r0, #AT91_PM_SLOW_CLOCK
-	beq	skip_enable_main_clock
-
-	ldr	pmc, .pmc_base
-
 	/* Turn on the main oscillator */
 	ldr	tmp1, [pmc, #AT91_CKGR_MOR]
 	orr	tmp1, tmp1, #AT91_PMC_MOSCEN
@@ -174,14 +224,8 @@ skip_disable_main_clock:
 
 	wait_mckrdy
 
-skip_enable_main_clock:
-	/* Exit the self-refresh mode */
-	mov	r0, #SRAMC_SELF_FRESH_EXIT
-	bl	at91_sramc_self_refresh
-
-	/* Restore registers, and return */
-	ldmfd	sp!, {r4 - r12, pc}
-ENDPROC(at91_pm_suspend_in_sram)
+	mov	pc, lr
+ENDPROC(at91_slowck_mode)
 
 /*
  * void at91_sramc_self_refresh(unsigned int is_active)
@@ -314,6 +358,10 @@ ENDPROC(at91_sramc_self_refresh)
 	.word 0
 .sramc1_base:
 	.word 0
+.shdwc:
+	.word 0
+.sfr:
+	.word 0
 .memtype:
 	.word 0
 .pm_mode:
diff --git a/arch/arm/mach-at91/sama5.c b/arch/arm/mach-at91/sama5.c
index 6d157d0ead8e..3d0bf95a56ae 100644
--- a/arch/arm/mach-at91/sama5.c
+++ b/arch/arm/mach-at91/sama5.c
@@ -34,7 +34,6 @@ DT_MACHINE_START(sama5_dt, "Atmel SAMA5")
 MACHINE_END
 
 static const char *const sama5_alt_dt_board_compat[] __initconst = {
-	"atmel,sama5d2",
 	"atmel,sama5d4",
 	NULL
 };
@@ -45,3 +44,21 @@ DT_MACHINE_START(sama5_alt_dt, "Atmel SAMA5")
 	.dt_compat	= sama5_alt_dt_board_compat,
 	.l2c_aux_mask	= ~0UL,
 MACHINE_END
+
+static void __init sama5d2_init(void)
+{
+	of_platform_default_populate(NULL, NULL, NULL);
+	sama5d2_pm_init();
+}
+
+static const char *const sama5d2_compat[] __initconst = {
+	"atmel,sama5d2",
+	NULL
+};
+
+DT_MACHINE_START(sama5d2, "Atmel SAMA5")
+	/* Maintainer: Atmel */
+	.init_machine	= sama5d2_init,
+	.dt_compat	= sama5d2_compat,
+	.l2c_aux_mask	= ~0UL,
+MACHINE_END
-- 
2.11.0

^ permalink raw reply related

* [PATCH 2/3] ARM: at91: pm: allow selecting standby and suspend modes
From: Alexandre Belloni @ 2017-04-26 16:04 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426160419.22401-1-alexandre.belloni@free-electrons.com>

While we can only select between "standby" and "mem" states for power
management, the atmel platforms can actually support more modes.

For both standby and mem, allow selecting which mode will be used using the
atmel.pm_modes kernel parameter.
By default, keep the current modes.

Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
---
 arch/arm/mach-at91/pm.c | 110 +++++++++++++++++++++++++++++++++---------------
 arch/arm/mach-at91/pm.h |   3 +-
 2 files changed, 78 insertions(+), 35 deletions(-)

diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c
index 1e03f1277f14..d08f032f9d94 100644
--- a/arch/arm/mach-at91/pm.c
+++ b/arch/arm/mach-at91/pm.c
@@ -15,6 +15,7 @@
 #include <linux/of_address.h>
 #include <linux/of.h>
 #include <linux/of_platform.h>
+#include <linux/parser.h>
 #include <linux/suspend.h>
 
 #include <linux/clk/at91_pmc.h>
@@ -38,7 +39,17 @@ extern void at91_pinctrl_gpio_suspend(void);
 extern void at91_pinctrl_gpio_resume(void);
 #endif
 
-static struct at91_pm_data pm_data;
+static const match_table_t pm_modes __initconst = {
+	{ 0, "standby" },
+	{ AT91_PM_SLOW_CLOCK, "ulp0" },
+	{ AT91_PM_BACKUP, "backup" },
+	{ -1, NULL },
+};
+
+static struct at91_pm_data pm_data = {
+	.standby_mode = 0,
+	.suspend_mode = AT91_PM_SLOW_CLOCK,
+};
 
 #define at91_ramc_read(id, field) \
 	__raw_readl(pm_data.ramc[id] + field)
@@ -68,14 +79,24 @@ static struct at91_pm_bu {
 	phys_addr_t resume;
 } *pm_bu;
 
-static suspend_state_t target_state;
-
 /*
  * Called after processes are frozen, but before we shutdown devices.
  */
 static int at91_pm_begin(suspend_state_t state)
 {
-	target_state = state;
+	switch (state) {
+	case PM_SUSPEND_MEM:
+		pm_data.mode = pm_data.suspend_mode;
+		break;
+
+	case PM_SUSPEND_STANDBY:
+		pm_data.mode = pm_data.standby_mode;
+		break;
+
+	default:
+		pm_data.mode = -1;
+	}
+
 	return 0;
 }
 
@@ -124,7 +145,7 @@ static int at91_pm_verify_clocks(void)
  */
 int at91_suspend_entering_slow_clock(void)
 {
-	return (target_state == PM_SUSPEND_MEM);
+	return (pm_data.mode >= AT91_PM_SLOW_CLOCK);
 }
 EXPORT_SYMBOL(at91_suspend_entering_slow_clock);
 
@@ -144,14 +165,6 @@ static int at91_suspend_finish(unsigned long val)
 
 static void at91_pm_suspend(suspend_state_t state)
 {
-	if (pm_data.deepest_state == AT91_PM_BACKUP)
-		if (state == PM_SUSPEND_MEM)
-			pm_data.mode = AT91_PM_BACKUP;
-		else
-			pm_data.mode = AT91_PM_SLOW_CLOCK;
-	else
-		pm_data.mode = (state == PM_SUSPEND_MEM) ? AT91_PM_SLOW_CLOCK : 0;
-
 	if (pm_data.mode == AT91_PM_BACKUP) {
 		pm_bu->suspended = 1;
 
@@ -168,38 +181,37 @@ static void at91_pm_suspend(suspend_state_t state)
 	outer_resume();
 }
 
+/*
+ * STANDBY mode has *all* drivers suspended; ignores irqs not marked as 'wakeup'
+ * event sources; and reduces DRAM power.  But otherwise it's identical to
+ * PM_SUSPEND_ON: cpu idle, and nothing fancy done with main or cpu clocks.
+ *
+ * AT91_PM_SLOW_CLOCK is like STANDBY plus slow clock mode, so drivers must
+ * suspend more deeply, the master clock switches to the clk32k and turns off
+ * the main oscillator
+ *
+ * AT91_PM_BACKUP turns off the whole SoC after placing the DDR in self refresh
+ */
 static int at91_pm_enter(suspend_state_t state)
 {
 #ifdef CONFIG_PINCTRL_AT91
 	at91_pinctrl_gpio_suspend();
 #endif
+
 	switch (state) {
-	/*
-	 * Suspend-to-RAM is like STANDBY plus slow clock mode, so
-	 * drivers must suspend more deeply, the master clock switches
-	 * to the clk32k and turns off the main oscillator
-	 */
 	case PM_SUSPEND_MEM:
+	case PM_SUSPEND_STANDBY:
 		/*
 		 * Ensure that clocks are in a valid state.
 		 */
-		if (!at91_pm_verify_clocks())
+		if ((pm_data.mode >= AT91_PM_SLOW_CLOCK) &&
+		    !at91_pm_verify_clocks())
 			goto error;
 
 		at91_pm_suspend(state);
 
 		break;
 
-	/*
-	 * STANDBY mode has *all* drivers suspended; ignores irqs not
-	 * marked as 'wakeup' event sources; and reduces DRAM power.
-	 * But otherwise it's identical to PM_SUSPEND_ON: cpu idle, and
-	 * nothing fancy done with main or cpu clocks.
-	 */
-	case PM_SUSPEND_STANDBY:
-		at91_pm_suspend(state);
-		break;
-
 	case PM_SUSPEND_ON:
 		cpu_do_idle();
 		break;
@@ -210,8 +222,6 @@ static int at91_pm_enter(suspend_state_t state)
 	}
 
 error:
-	target_state = PM_SUSPEND_ON;
-
 #ifdef CONFIG_PINCTRL_AT91
 	at91_pinctrl_gpio_resume();
 #endif
@@ -223,7 +233,6 @@ static int at91_pm_enter(suspend_state_t state)
  */
 static void at91_pm_end(void)
 {
-	target_state = PM_SUSPEND_ON;
 }
 
 
@@ -494,6 +503,10 @@ static void __init at91_pm_bu_sram_init(void)
 	struct device_node *node;
 	struct platform_device *pdev = NULL;
 
+	if ((pm_data.standby_mode != AT91_PM_BACKUP) &&
+	    (pm_data.suspend_mode != AT91_PM_BACKUP))
+		return;
+
 	pm_bu = NULL;
 
 	for_each_compatible_node(node, NULL, "atmel,sama5d2-securam") {
@@ -571,10 +584,14 @@ static void __init at91_pm_init(void (*pm_idle)(void))
 
 	at91_pm_sram_init();
 
-	if (at91_suspend_sram_fn)
+	if (at91_suspend_sram_fn) {
 		suspend_set_ops(&at91_pm_ops);
-	else
+		pr_info("AT91: PM: standby: %s, suspend: %s\n",
+			pm_modes[pm_data.standby_mode].pattern,
+			pm_modes[pm_data.suspend_mode].pattern);
+	} else {
 		pr_info("AT91: PM not supported, due to no SRAM allocated\n");
+	}
 }
 
 void __init at91rm9200_pm_init(void)
@@ -607,3 +624,28 @@ void __init sama5d2_pm_init(void)
 	at91_pm_bu_sram_init();
 	sama5_pm_init();
 }
+
+static int __init at91_pm_modes_select(char *str)
+{
+	char *s;
+	substring_t args[MAX_OPT_ARGS];
+	int standby, suspend;
+
+	if (!str)
+		return 0;
+
+	s = strsep(&str, ",");
+	standby = match_token(s, pm_modes, args);
+	if (standby < 0)
+		return 0;
+
+	suspend = match_token(str, pm_modes, args);
+	if (suspend < 0)
+		return 0;
+
+	pm_data.standby_mode = standby;
+	pm_data.suspend_mode = suspend;
+
+	return 0;
+}
+early_param("atmel.pm_modes", at91_pm_modes_select);
diff --git a/arch/arm/mach-at91/pm.h b/arch/arm/mach-at91/pm.h
index d9c6612ef62f..f95d31496f08 100644
--- a/arch/arm/mach-at91/pm.h
+++ b/arch/arm/mach-at91/pm.h
@@ -33,7 +33,8 @@ struct at91_pm_data {
 	unsigned int mode;
 	void __iomem *shdwc;
 	void __iomem *sfrbu;
-	unsigned int deepest_state;
+	unsigned int standby_mode;
+	unsigned int suspend_mode;
 };
 #endif
 
-- 
2.11.0

^ permalink raw reply related

* [PATCH 3/3] ARM: at91: pm: fallback to slowclock when backup mode fails
From: Alexandre Belloni @ 2017-04-26 16:04 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426160419.22401-1-alexandre.belloni@free-electrons.com>

If the backup sram allocation fails, ensure we can suspend by falling back
to the usual slow clock mode.

Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
---
 arch/arm/mach-at91/pm.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c
index d08f032f9d94..02823d8f3ada 100644
--- a/arch/arm/mach-at91/pm.c
+++ b/arch/arm/mach-at91/pm.c
@@ -519,24 +519,30 @@ static void __init at91_pm_bu_sram_init(void)
 
 	if (!pdev) {
 		pr_warn("%s: failed to find securam device!\n", __func__);
-		return;
+		goto fallback;
 	}
 
 	sram_pool = gen_pool_get(&pdev->dev, NULL);
 	if (!sram_pool) {
 		pr_warn("%s: securam pool unavailable!\n", __func__);
-		return;
+		goto fallback;
 	}
 
 	pm_bu = (void *)gen_pool_alloc(sram_pool, sizeof(struct at91_pm_bu));
 	if (!pm_bu) {
 		pr_warn("%s: unable to alloc securam!\n", __func__);
-		return;
+		goto fallback;
 	}
 
 	pm_bu->suspended = 0;
 	pm_bu->canary = virt_to_phys(&canary);
 	pm_bu->resume = virt_to_phys(cpu_resume);
+
+fallback:
+	if (pm_data.standby_mode == AT91_PM_BACKUP)
+		pm_data.standby_mode = AT91_PM_SLOW_CLOCK;
+	if (pm_data.suspend_mode != AT91_PM_BACKUP)
+		pm_data.suspend_mode = AT91_PM_SLOW_CLOCK;
 }
 
 struct pmc_info {
-- 
2.11.0

^ permalink raw reply related

* [RFC/RFT PATCH 07/18] ARM: PCI: orion5x: Convert PCI scan API to pci_scan_root_bus_bridge()
From: Lorenzo Pieralisi @ 2017-04-26 16:13 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170426151257.GA4489@lunn.ch>

On Wed, Apr 26, 2017 at 05:12:57PM +0200, Andrew Lunn wrote:
> On Wed, Apr 26, 2017 at 12:17:58PM +0100, Lorenzo Pieralisi wrote:
> > The introduction of pci_scan_root_bus_bridge() provides a PCI core
> > API to scan a PCI root bus backed by an already initialized
> > struct pci_host_bridge object, which simplifies the bus scan
> > interface and makes the PCI scan root bus interface easier to
> > generalize as members are added to the struct pci_host_bridge().
> > 
> > Convert ARM orion5x platform code to pci_scan_root_bus_bridge() to
> > improve the PCI root bus scanning interface.
> 
> Hi Lorenzo
> 
> Maybe there is something not right here.
> 
> With plain 4.11-rc7 i get:
> 
> root at orion5x:~# lspci -v
> 0000:00:00.0 Memory controller: Marvell Technology Group Ltd. 88f5182 [Orion-NAS] ARM SoC (rev 02)
>         Subsystem: Marvell Technology Group Ltd. Device 11ab
>         Flags: bus master, fast devsel, latency 0, IRQ 12
>         Memory at <ignored> (64-bit, prefetchable)
>         Capabilities: [40] Power Management version 2
>         Capabilities: [50] MSI: Enable- Count=1/1 Maskable- 64bit+
>         Capabilities: [60] Express Root Port (Slot-), MSI 00
> 
> 0001:01:00.0 Memory controller: Marvell Technology Group Ltd. 88f5182 [Orion-NAS] ARM SoC (rev 02)
>         Flags: bus master, fast Back2Back, 66MHz, medium devsel, latency 0
>         BIST result: 00
>         Memory at <unassigned> (64-bit, prefetchable)
>         Memory at <ignored> (64-bit, prefetchable)
>         Memory at <ignored> (64-bit, non-prefetchable)
>         Expansion ROM at <ignored> [disabled]
>         Capabilities: [40] Power Management version 2
>         Capabilities: [48] Vital Product Data
>         Capabilities: [50] MSI: Enable- Count=1/1 Maskable- 64bit+
>         Capabilities: [60] PCI-X non-bridge device
>         Capabilities: [68] CompactPCI hot-swap <?>
> 
> However, with your patches applied i get:
> 
> 0000:00:00.0 Memory controller: Marvell Technology Group Ltd. 88f5182 [Orion-NAS] ARM SoC (rev 02)
>         Subsystem: Marvell Technology Group Ltd. Device 11ab
>         Flags: bus master, fast devsel, latency 0
>         Memory at <ignored> (64-bit, prefetchable)
>         Capabilities: [40] Power Management version 2
>         Capabilities: [50] MSI: Enable- Count=1/1 Maskable- 64bit+
>         Capabilities: [60] Express Root Port (Slot-), MSI 00
> 
> 0001:01:00.0 Memory controller: Marvell Technology Group Ltd. 88f5182 [Orion-NAS] ARM SoC (rev 02)
>         Flags: bus master, fast Back2Back, 66MHz, medium devsel, latency 0
>         BIST result: 00
>         Memory at <unassigned> (64-bit, prefetchable)
>         Memory at <ignored> (64-bit, prefetchable)
>         Memory at <ignored> (64-bit, non-prefetchable)
>         Expansion ROM at <ignored> [disabled]
>         Capabilities: [40] Power Management version 2
>         Capabilities: [48] Vital Product Data
>         Capabilities: [50] MSI: Enable- Count=1/1 Maskable- 64bit+
>         Capabilities: [60] PCI-X non-bridge device
>         Capabilities: [68] CompactPCI hot-swap <?>
> 
> Note that IRQ 12 has disappeared from Flags: on 0000:00:00.0.

Thank you for testing it, that's exactly what we need.

To check if the plumbing is working (ie to check I have not messed
up the bus scan API rework), mind testing the patch below please
and report back (it applies on top of this series) ?

Thanks !
Lorenzo

-- >8 --
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index 4632fa4..afa7271 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -400,8 +400,6 @@ static int pci_device_probe(struct device *dev)
 	struct pci_dev *pci_dev = to_pci_dev(dev);
 	struct pci_driver *drv = to_pci_driver(dev->driver);
 
-	pci_assign_irq(pci_dev);
-
 	error = pcibios_alloc_irq(pci_dev);
 	if (error < 0)
 		return error;
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index de259f3..dc9a1b6 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1967,6 +1967,8 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
 	ret = pcibios_add_device(dev);
 	WARN_ON(ret < 0);
 
+	pci_assign_irq(dev);
+
 	/* Setup MSI irq domain */
 	pci_set_msi_domain(dev);
 

^ permalink raw reply related

* [PATCH 1/2] dt-bindings: Document STM32 I2S bindings
From: Mark Brown @ 2017-04-26 16:15 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <a87748e4-21e7-f65a-5be1-fe4f60bdfbef@st.com>

On Thu, Apr 13, 2017 at 08:01:34AM +0000, Olivier MOYSAN wrote:

> 1) 2 static dais NOT exclusive
> 	- dai tx
> 	- dai rx
> The IP exhibits a mode register, where you select mode TX, RX or FD.
> There are 2 two options to manage this register.
> option 1:
> 	start first channel with mode RX or TX
> 	when second channel is started, mode has to be changed to FD.
> 	Transfers have to be stopped before changing configuration
> 	registers, so this leads to cuts in audio stream.
> option 2:
> 	start a first channel with mode FD.
> 	In this case, we may have unpredictable behavior for the stream
> 	which is not already started. probably underrun/overrun.
> So, this solution rises problem for full-duplex management.

> 2) 3 static dais exclusive
> 	- dai tx
> 	- dai rx
> 	- dai rx-tx (fd)
> This is the current implementation.
> The choice of the dai is done at probe time. It is provided by DT 
> through sound-dai parameter.
> When dai fd is selected, after starting first stream, we assume that
> second stream will be started. In this case we wait for second stream
> to be available before enabling IP and starting transfers.

> 3) 1 dynamic dai
> 	- dai rx or tx or fd (according to dma conf in IP node)
> Here the driver exposes only a single dai constructed from dma 
> configuration provided by IP DT node.
> This allows to get ride of sound-dai parameter.

None of these options reflect how normal I2S controllers present
themselves in DT.  To repeat, you should present a single bidirectional
DAI for the single physical bidirectional I2S controller that your
hardware has.

If it's not possible to figure out a way to make the controller support
simultaneous playback and record with the two started independently then
the driver should just return an error if userspace tries to start the
second direction up.  This will severely limit the utility of the driver
as Linux generally treats playback and record independently but that's
going to apply just as much with any of the options involving multiple
DAIs or configuration in DT.  You might be able to do something with
feeding it dummy data I guess?
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170426/48459d98/attachment-0001.sig>

^ permalink raw reply

* [PATCH 1/2] kvm: Fix mmu_notifier release race
From: Paul E. McKenney @ 2017-04-26 16:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <c1232b1d-ad82-794b-1b86-4d0cc0d4cd7f@arm.com>

On Wed, Apr 26, 2017 at 05:03:44PM +0100, Suzuki K Poulose wrote:
> On 25/04/17 19:49, Radim Kr?m?? wrote:
> >2017-04-24 11:10+0100, Suzuki K Poulose:
> >>The KVM uses mmu_notifier (wherever available) to keep track
> >>of the changes to the mm of the guest. The guest shadow page
> >>tables are released when the VM exits via mmu_notifier->ops.release().
> >>There is a rare chance that the mmu_notifier->release could be
> >>called more than once via two different paths, which could end
> >>up in use-after-free of kvm instance (such as [0]).
> >>
> >>e.g:
> >>
> >>thread A                                        thread B
> >>-------                                         --------------
> >>
> >> get_signal->                                   kvm_destroy_vm()->
> >> do_exit->                                        mmu_notifier_unregister->
> >> exit_mm->                                        kvm_arch_flush_shadow_all()->
> >> exit_mmap->                                      spin_lock(&kvm->mmu_lock)
> >> mmu_notifier_release->                           ....
> >>  kvm_arch_flush_shadow_all()->                   .....
> >>  ... spin_lock(&kvm->mmu_lock)                   .....
> >>                                                  spin_unlock(&kvm->mmu_lock)
> >>                                                kvm_arch_free_kvm()
> >>   *** use after free of kvm ***
> >
> >I don't understand this race ...
> >a piece of code in mmu_notifier_unregister() says:
> >
> >  	/*
> >  	 * Wait for any running method to finish, of course including
> >  	 * ->release if it was run by mmu_notifier_release instead of us.
> >  	 */
> >  	synchronize_srcu(&srcu);
> >
> >and code before that removes the notifier from the list, so it cannot be
> >called after we pass this point.  mmu_notifier_release() does roughly
> >the same and explains it as:
> >
> >  	/*
> >  	 * synchronize_srcu here prevents mmu_notifier_release from returning to
> >  	 * exit_mmap (which would proceed with freeing all pages in the mm)
> >  	 * until the ->release method returns, if it was invoked by
> >  	 * mmu_notifier_unregister.
> >  	 *
> >  	 * The mmu_notifier_mm can't go away from under us because one mm_count
> >  	 * is held by exit_mmap.
> >  	 */
> >  	synchronize_srcu(&srcu);
> >
> >The call of mmu_notifier->release is protected by srcu in both cases and
> >while it seems possible that mmu_notifier->release would be called
> >twice, I don't see a combination that could result in use-after-free
> >from mmu_notifier_release after mmu_notifier_unregister() has returned.
> 
> Thanks for bringing it up. Even I am wondering why this is triggered ! (But it
> does get triggered for sure !!)
> 
> The only difference I can spot with _unregister & _release paths are the way
> we use src_read_lock across the deletion of the entry from the list.
> 
> In mmu_notifier_unregister() we do :
> 
>                 id = srcu_read_lock(&srcu);
>                 /*
>                  * exit_mmap will block in mmu_notifier_release to guarantee
>                  * that ->release is called before freeing the pages.
>                  */
>                 if (mn->ops->release)
>                         mn->ops->release(mn, mm);
>                 srcu_read_unlock(&srcu, id);
> 
> ## Releases the srcu lock here and then goes on to grab the spin_lock.
> 
>                 spin_lock(&mm->mmu_notifier_mm->lock);
>                 /*
>                  * Can not use list_del_rcu() since __mmu_notifier_release
>                  * can delete it before we hold the lock.
>                  */
>                 hlist_del_init_rcu(&mn->hlist);
>                 spin_unlock(&mm->mmu_notifier_mm->lock);
> 
> While in mmu_notifier_release() we hold it until the node(s) are deleted from the
> list :
>         /*
>          * SRCU here will block mmu_notifier_unregister until
>          * ->release returns.
>          */
>         id = srcu_read_lock(&srcu);
>         hlist_for_each_entry_rcu(mn, &mm->mmu_notifier_mm->list, hlist)
>                 /*
>                  * If ->release runs before mmu_notifier_unregister it must be
>                  * handled, as it's the only way for the driver to flush all
>                  * existing sptes and stop the driver from establishing any more
>                  * sptes before all the pages in the mm are freed.
>                  */
>                 if (mn->ops->release)
>                         mn->ops->release(mn, mm);
> 
>         spin_lock(&mm->mmu_notifier_mm->lock);
>         while (unlikely(!hlist_empty(&mm->mmu_notifier_mm->list))) {
>                 mn = hlist_entry(mm->mmu_notifier_mm->list.first,
>                                  struct mmu_notifier,
>                                  hlist);
>                 /*
>                  * We arrived before mmu_notifier_unregister so
>                  * mmu_notifier_unregister will do nothing other than to wait
>                  * for ->release to finish and for mmu_notifier_unregister to
>                  * return.
>                  */
>                 hlist_del_init_rcu(&mn->hlist);
>         }
>         spin_unlock(&mm->mmu_notifier_mm->lock);
>         srcu_read_unlock(&srcu, id);
> 
> ## The lock is release only after the deletion of the node.
> 
> Both are followed by a synchronize_srcu(). Now, I am wondering if the unregister path
> could potentially miss SRCU read lock held in _release() path and go onto finish the
> synchronize_srcu before the item is deleted ? May be we should do the read_unlock
> after the deletion of the node in _unregister (like we do in the _release()) ?
> 
> >
> >Doesn't [2/2] solve the exact same issue (that the release method cannot
> >be called twice in parallel)?
> 
> Not really. This could be a race between a release() and one of the other notifier
> callbacks. e.g, In [0], we were hitting a use-after-free in kvm_unmap_hva() where,
> the unregister could have succeeded and released the KVM.
> 
> 
> [0] http://lkml.kernel.org/r/febea966-3767-21ff-3c40-1a76d1399138 at suse.de
> 
> In effect this all could be due to the same reason, the synchronize in unregister
> missing another reader.

If this is at all reproducible, I suggest use of ftrace or event tracing
to work out exactly what is happening.

							Thanx, Paul

^ permalink raw reply

* [PATCH net-next 1/4] ixgbe: sparc: rename the ARCH_WANT_RELAX_ORDER to IXGBE_ALLOW_RELAXED_ORDER
From: Alexander Duyck @ 2017-04-26 16:18 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <a2239141-a185-1bb8-0abd-7a05b9fde015@huawei.com>

On Wed, Apr 26, 2017 at 2:26 AM, Ding Tianhong <dingtianhong@huawei.com> wrote:
> Hi Amir:
>
> It is really glad to hear that the mlx5 will support RO mode this year, if so, do you agree that enable it dynamic by ethtool -s xxx,
> we have try it several month ago but there was only one drivers would use it at that time so the maintainer against it, it mlx5 would support RO,
> we could try to restart this solution, what do you think about it. :)
>
> Thanks
> Ding

Hi Ding,

Enabing relaxed ordering really doesn't have any place in ethtool. It
is a PCIe attribute that you are essentially wanting to enable.

It might be worth while to take a look at updating the PCIe code path
to handle this. Really what we should probably do is guarantee that
the architectures that need relaxed ordering are setting it in the
PCIe Device Control register and that the ones that don't are clearing
the bit. It's possible that this is already occurring, but I don't
know the state of handling those bits is in the kernel. Once we can
guarantee that we could use that to have the drivers determine their
behavior in regards to relaxed ordering. For example in the case of
igb/ixgbe we could probably change the behavior so that it will bey
default try to use relaxed ordering but if it is not enabled in PCIe
Device Control register the hardware should not request to use it. It
would simplify things in the drivers and allow for each architecture
to control things as needed in their PCIe code.

- Alex

^ permalink raw reply

* [PATCH] soc: mediatek: Use devm_kcalloc() in init_scp()
From: SF Markus Elfring @ 2017-04-26 16:52 UTC (permalink / raw)
  To: linux-arm-kernel

From: Markus Elfring <elfring@users.sourceforge.net>
Date: Wed, 26 Apr 2017 18:33:15 +0200

Multiplications for the size determination of memory allocations
indicated that array data structures should be processed.
Thus use the corresponding function "devm_kcalloc".

This issue was detected by using the Coccinelle software.

Signed-off-by: Markus Elfring <elfring@users.sourceforge.net>
---
 drivers/soc/mediatek/mtk-scpsys.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/soc/mediatek/mtk-scpsys.c b/drivers/soc/mediatek/mtk-scpsys.c
index beb79162369a..9abf2b067ec6 100644
--- a/drivers/soc/mediatek/mtk-scpsys.c
+++ b/drivers/soc/mediatek/mtk-scpsys.c
@@ -365,15 +365,14 @@ static struct scp *init_scp(struct platform_device *pdev,
 	if (IS_ERR(scp->base))
 		return ERR_CAST(scp->base);
 
-	scp->domains = devm_kzalloc(&pdev->dev,
-				sizeof(*scp->domains) * num, GFP_KERNEL);
+	scp->domains = devm_kcalloc(&pdev->dev, num, sizeof(*scp->domains),
+				    GFP_KERNEL);
 	if (!scp->domains)
 		return ERR_PTR(-ENOMEM);
 
 	pd_data = &scp->pd_data;
-
-	pd_data->domains = devm_kzalloc(&pdev->dev,
-			sizeof(*pd_data->domains) * num, GFP_KERNEL);
+	pd_data->domains = devm_kcalloc(&pdev->dev, num,
+					sizeof(*pd_data->domains), GFP_KERNEL);
 	if (!pd_data->domains)
 		return ERR_PTR(-ENOMEM);
 
-- 
2.12.2

^ permalink raw reply related


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