[alsa-devel] [PATCH v4 0/7] ASoC: add driver for Atmel I2S controller
This is a rework of Cyrille's patches named: [PATCH v3 0/2] ASoC: add driver for Atmel I2S controller https://lkml.org/lkml/2015/9/29/454
This is the version 4 of the series, and addresses the received feedback on the mailing lists.
This series applies on top of asoc-next branch of broonie/sound.git.
Based on received feedback, I created a mux clock driver to select the I2S clock source, that also includes proper devicetree bindings and nodes. Also, I added the I2S nodes in sama5d2's devicetree, with the missing pin muxing for the second I2S controller.
This series of patches adds support to the new Atmel I2S controller embedded on sama5d2 SoCs.
ChangeLog
v3 -> v4 - as suggested by Rob Herring: - added a clock mux driver for I2S's clock control bit; - more precise description of I2S's devicetree bindings; - removed SoC and internal detalls from bindings; - addressed comments from Mark Brown; - added devicetree nodes and pin muxing for I2S;
v2 -> v3 - fix the coding style, add some more comments and add a section dedicated to sama5d2 SoCs in the DT binding documentation as suggested by Mark Brown.
v1 -> v2 - initialize dev->dev before calling dev->caps->mck_init()
Codrin Ciubotariu (3): dt-bindings: clk: at91: add an I2S mux clock clk: at91: add I2S clock mux driver ARM: dts: at91: sama5d2: add I2S clock muxing nodes
Cyrille Pitchen (4): ASoC: atmel-i2s: dt-bindings: add DT bindings for I2S controller ASoC: atmel-i2s: add driver for the new Atmel I2S controller ARM: dts: at91: sama5d2: add nodes for I2S controllers ARM: dts: at91: sama5d2 Xplained: add pin muxing for I2S
.../devicetree/bindings/clock/at91-clock.txt | 34 + .../devicetree/bindings/sound/atmel-i2s.txt | 47 ++ arch/arm/boot/dts/at91-sama5d2_xplained.dts | 28 + arch/arm/boot/dts/sama5d2.dtsi | 52 ++ arch/arm/mach-at91/Kconfig | 4 + drivers/clk/at91/Makefile | 1 + drivers/clk/at91/clk-i2s-mux.c | 117 ++++ sound/soc/atmel/Kconfig | 9 + sound/soc/atmel/Makefile | 2 + sound/soc/atmel/atmel-i2s.c | 765 +++++++++++++++++++++ 10 files changed, 1059 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/atmel-i2s.txt create mode 100644 drivers/clk/at91/clk-i2s-mux.c create mode 100644 sound/soc/atmel/atmel-i2s.c
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com --- .../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock + "atmel,sama5d2-clk-i2s-mux": + at91 I2S clock source selection
Required properties for SCKC node: - reg : defines the IO memory reserved for the SCKC. @@ -507,3 +509,35 @@ For example: atmel,clk-output-range = <0 83000000>; }; }; + +Required properties for I2S mux clocks: +- #size-cells : shall be 0 (reg is used to encode I2S bus id). +- #address-cells : shall be 1 (reg is used to encode I2S bus id). +- name: device tree node describing a specific mux clock. + * #clock-cells : from common clock binding; shall be set to 0. + * clocks : shall be the mux clock parent phandles; shall be 2 phandles: + peripheral and generated clock; the first phandle shall belong to the + peripheral clock and the second one shall belong to the generated + clock; "clock-indices" property can be user to specify + the correct order. + * reg: I2S bus id of the corresponding mux clock. + e.g. reg = <0>; for i2s0, reg = <1>; for i2s1 + +For example: + i2s_clkmux { + compatible = "atmel,sama5d2-clk-i2s-mux"; + #address-cells = <1>; + #size-cells = <0>; + + i2s0muxck: i2s0_muxclk { + clocks = <&i2s0_clk>, <&i2s0_gclk>; + #clock-cells = <0>; + reg = <0>; + }; + + i2s1muxck: i2s1_muxclk { + clocks = <&i2s1_clk>, <&i2s1_gclk>; + #clock-cells = <0>; + reg = <1>; + }; + };
On Fri, May 25, 2018 at 03:34:22PM +0300, Codrin Ciubotariu wrote:
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
.../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock
- "atmel,sama5d2-clk-i2s-mux":
at91 I2S clock source selection
Is this boolean or takes some values. If latter, what are valid values?
Required properties for SCKC node:
- reg : defines the IO memory reserved for the SCKC.
@@ -507,3 +509,35 @@ For example: atmel,clk-output-range = <0 83000000>; }; };
+Required properties for I2S mux clocks: +- #size-cells : shall be 0 (reg is used to encode I2S bus id). +- #address-cells : shall be 1 (reg is used to encode I2S bus id). +- name: device tree node describing a specific mux clock.
- #clock-cells : from common clock binding; shall be set to 0.
- clocks : shall be the mux clock parent phandles; shall be 2 phandles:
peripheral and generated clock; the first phandle shall belong to the
peripheral clock and the second one shall belong to the generated
clock; "clock-indices" property can be user to specify
the correct order.
- reg: I2S bus id of the corresponding mux clock.
e.g. reg = <0>; for i2s0, reg = <1>; for i2s1
+For example:
- i2s_clkmux {
What is this a child of?
compatible = "atmel,sama5d2-clk-i2s-mux";
#address-cells = <1>;
#size-cells = <0>;
How do you address this block? My guess is you don't because it is just part of some other block and you are just creating this node to instantiate a driver. Just make the node for the actual h/w block a clock provider and define the clock ids (0 and 1).
i2s0muxck: i2s0_muxclk {
clocks = <&i2s0_clk>, <&i2s0_gclk>;
#clock-cells = <0>;
reg = <0>;
};
i2s1muxck: i2s1_muxclk {
clocks = <&i2s1_clk>, <&i2s1_gclk>;
#clock-cells = <0>;
reg = <1>;
};
- };
-- 2.7.4
On 31.05.2018 03:58, Rob Herring wrote:
On Fri, May 25, 2018 at 03:34:22PM +0300, Codrin Ciubotariu wrote:
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
.../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock
- "atmel,sama5d2-clk-i2s-mux":
at91 I2S clock source selection
Is this boolean or takes some values. If latter, what are valid values?
This is the compatible string of the clock driver.
Required properties for SCKC node:
- reg : defines the IO memory reserved for the SCKC.
@@ -507,3 +509,35 @@ For example: atmel,clk-output-range = <0 83000000>; }; };
+Required properties for I2S mux clocks: +- #size-cells : shall be 0 (reg is used to encode I2S bus id). +- #address-cells : shall be 1 (reg is used to encode I2S bus id). +- name: device tree node describing a specific mux clock.
- #clock-cells : from common clock binding; shall be set to 0.
- clocks : shall be the mux clock parent phandles; shall be 2 phandles:
peripheral and generated clock; the first phandle shall belong to the
peripheral clock and the second one shall belong to the generated
clock; "clock-indices" property can be user to specify
the correct order.
- reg: I2S bus id of the corresponding mux clock.
e.g. reg = <0>; for i2s0, reg = <1>; for i2s1
+For example:
- i2s_clkmux {
What is this a child of?
It is a child of PMC node, since both parent clocks are generated by PMC.
compatible = "atmel,sama5d2-clk-i2s-mux";
#address-cells = <1>;
#size-cells = <0>;
How do you address this block? My guess is you don't because it is just part of some other block and you are just creating this node to instantiate a driver. Just make the node for the actual h/w block a clock provider and define the clock ids (0 and 1).
This block is not addressed, but its children are. The register we access in this driver is not part of other block. It's a SFR register, accessed through syscon and it has nothing to do with the I2S IP (see SAMA5D2 DS, page 1256, fig. 44-1: I2SC Block Diagram) that is the consumer of this clock. Adding a clock-id property in the I2S node would be just like v3 of this series, with the difference that we use clock-id instead of alias id to set the clock parent, which is not how you suggested back then.
Thank you for your review.
Best regards, Codrin
[...]
On Thu, May 31, 2018 at 5:25 AM, Codrin Ciubotariu codrin.ciubotariu@microchip.com wrote:
On 31.05.2018 03:58, Rob Herring wrote:
On Fri, May 25, 2018 at 03:34:22PM +0300, Codrin Ciubotariu wrote:
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
.../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock
"atmel,sama5d2-clk-i2s-mux":
at91 I2S clock source selection
Is this boolean or takes some values. If latter, what are valid values?
This is the compatible string of the clock driver.
Ah, now I remember. AT91 uses fine grained clock nodes in DT. Is there still a plan to fix this?
Required properties for SCKC node:
- reg : defines the IO memory reserved for the SCKC.
@@ -507,3 +509,35 @@ For example: atmel,clk-output-range = <0 83000000>; }; };
+Required properties for I2S mux clocks: +- #size-cells : shall be 0 (reg is used to encode I2S bus id). +- #address-cells : shall be 1 (reg is used to encode I2S bus id). +- name: device tree node describing a specific mux clock.
* #clock-cells : from common clock binding; shall be set to 0.
* clocks : shall be the mux clock parent phandles; shall be 2
phandles:
peripheral and generated clock; the first phandle shall belong
to the
peripheral clock and the second one shall belong to the
generated
clock; "clock-indices" property can be user to specify
the correct order.
* reg: I2S bus id of the corresponding mux clock.
e.g. reg = <0>; for i2s0, reg = <1>; for i2s1
+For example:
i2s_clkmux {
What is this a child of?
It is a child of PMC node, since both parent clocks are generated by PMC.
compatible = "atmel,sama5d2-clk-i2s-mux";
#address-cells = <1>;
#size-cells = <0>;
How do you address this block? My guess is you don't because it is just part of some other block and you are just creating this node to instantiate a driver. Just make the node for the actual h/w block a clock provider and define the clock ids (0 and 1).
This block is not addressed, but its children are. The register we access in this driver is not part of other block. It's a SFR register, accessed through syscon and it has nothing to do with the I2S IP (see SAMA5D2 DS, page 1256, fig. 44-1: I2SC Block Diagram) that is the consumer of this clock. Adding a clock-id property in the I2S node would be just like v3 of this series, with the difference that we use clock-id instead of alias id to set the clock parent, which is not how you suggested back then.
I wasn't suggesting a clock-id property, but a clock specifier (i.e. make #clock-cells 1).
But AT91 clocks are all a mess, so I don't know what to tell you.
Rob
Quoting Rob Herring (2018-05-31 07:20:57)
On Thu, May 31, 2018 at 5:25 AM, Codrin Ciubotariu codrin.ciubotariu@microchip.com wrote:
On 31.05.2018 03:58, Rob Herring wrote:
On Fri, May 25, 2018 at 03:34:22PM +0300, Codrin Ciubotariu wrote:
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
.../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock
"atmel,sama5d2-clk-i2s-mux":
at91 I2S clock source selection
Is this boolean or takes some values. If latter, what are valid values?
This is the compatible string of the clock driver.
Ah, now I remember. AT91 uses fine grained clock nodes in DT. Is there still a plan to fix this?
I'm also interested in a plan.
compatible = "atmel,sama5d2-clk-i2s-mux";
#address-cells = <1>;
#size-cells = <0>;
How do you address this block? My guess is you don't because it is just part of some other block and you are just creating this node to instantiate a driver. Just make the node for the actual h/w block a clock provider and define the clock ids (0 and 1).
This block is not addressed, but its children are. The register we access in this driver is not part of other block. It's a SFR register, accessed through syscon and it has nothing to do with the I2S IP (see SAMA5D2 DS, page 1256, fig. 44-1: I2SC Block Diagram) that is the consumer of this clock. Adding a clock-id property in the I2S node would be just like v3 of this series, with the difference that we use clock-id instead of alias id to set the clock parent, which is not how you suggested back then.
I wasn't suggesting a clock-id property, but a clock specifier (i.e. make #clock-cells 1).
But AT91 clocks are all a mess, so I don't know what to tell you.
If #clock-cells of 1 works then we should go with that. It's still weird that we need random nodes to add more clks, but I guess that's how it's going to be for each at91 clk driver until it changes to be one big provider node.
On Thu, May 31, 2018 at 10:31 AM, Stephen Boyd sboyd@kernel.org wrote:
Quoting Rob Herring (2018-05-31 07:20:57)
On Thu, May 31, 2018 at 5:25 AM, Codrin Ciubotariu codrin.ciubotariu@microchip.com wrote:
On 31.05.2018 03:58, Rob Herring wrote:
On Fri, May 25, 2018 at 03:34:22PM +0300, Codrin Ciubotariu wrote:
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
.../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock
"atmel,sama5d2-clk-i2s-mux":
at91 I2S clock source selection
Is this boolean or takes some values. If latter, what are valid values?
This is the compatible string of the clock driver.
Ah, now I remember. AT91 uses fine grained clock nodes in DT. Is there still a plan to fix this?
I'm also interested in a plan.
compatible = "atmel,sama5d2-clk-i2s-mux";
#address-cells = <1>;
#size-cells = <0>;
How do you address this block? My guess is you don't because it is just part of some other block and you are just creating this node to instantiate a driver. Just make the node for the actual h/w block a clock provider and define the clock ids (0 and 1).
This block is not addressed, but its children are. The register we access in this driver is not part of other block. It's a SFR register, accessed through syscon and it has nothing to do with the I2S IP (see SAMA5D2 DS, page 1256, fig. 44-1: I2SC Block Diagram) that is the consumer of this clock. Adding a clock-id property in the I2S node would be just like v3 of this series, with the difference that we use clock-id instead of alias id to set the clock parent, which is not how you suggested back then.
I wasn't suggesting a clock-id property, but a clock specifier (i.e. make #clock-cells 1).
But AT91 clocks are all a mess, so I don't know what to tell you.
If #clock-cells of 1 works then we should go with that. It's still weird that we need random nodes to add more clks, but I guess that's how it's going to be for each at91 clk driver until it changes to be one big provider node.
Seems to me that clock additions could use a new binding and we start with a new driver that handles these few clocks initially. But I haven't looked whether both can coexist.
Rob
On 31.05.2018 18:56, Rob Herring wrote:
On Thu, May 31, 2018 at 10:31 AM, Stephen Boyd sboyd@kernel.org wrote:
Quoting Rob Herring (2018-05-31 07:20:57)
On Thu, May 31, 2018 at 5:25 AM, Codrin Ciubotariu codrin.ciubotariu@microchip.com wrote:
On 31.05.2018 03:58, Rob Herring wrote:
On Fri, May 25, 2018 at 03:34:22PM +0300, Codrin Ciubotariu wrote:
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
.../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock
"atmel,sama5d2-clk-i2s-mux":
at91 I2S clock source selection
Is this boolean or takes some values. If latter, what are valid values?
This is the compatible string of the clock driver.
Ah, now I remember. AT91 uses fine grained clock nodes in DT. Is there still a plan to fix this?
I'm also interested in a plan.
compatible = "atmel,sama5d2-clk-i2s-mux";
#address-cells = <1>;
#size-cells = <0>;
How do you address this block? My guess is you don't because it is just part of some other block and you are just creating this node to instantiate a driver. Just make the node for the actual h/w block a clock provider and define the clock ids (0 and 1).
This block is not addressed, but its children are. The register we access in this driver is not part of other block. It's a SFR register, accessed through syscon and it has nothing to do with the I2S IP (see SAMA5D2 DS, page 1256, fig. 44-1: I2SC Block Diagram) that is the consumer of this clock. Adding a clock-id property in the I2S node would be just like v3 of this series, with the difference that we use clock-id instead of alias id to set the clock parent, which is not how you suggested back then.
I wasn't suggesting a clock-id property, but a clock specifier (i.e. make #clock-cells 1).
But AT91 clocks are all a mess, so I don't know what to tell you.
If #clock-cells of 1 works then we should go with that. It's still weird that we need random nodes to add more clks, but I guess that's how it's going to be for each at91 clk driver until it changes to be one big provider node.
Seems to me that clock additions could use a new binding and we start with a new driver that handles these few clocks initially. But I haven't looked whether both can coexist.
Mark already applied to broonie/sound.git the I2S bindings that have a phandle to this clock. If I am to change #clock-cells to 1, I will have to change the bindings to include the clock-id. Which approach should I take now?
Thanks and best regards, Codrin
Quoting Codrin Ciubotariu (2018-06-07 03:30:14)
Seems to me that clock additions could use a new binding and we start with a new driver that handles these few clocks initially. But I haven't looked whether both can coexist.
Mark already applied to broonie/sound.git the I2S bindings that have a phandle to this clock. If I am to change #clock-cells to 1, I will have to change the bindings to include the clock-id. Which approach should I take now?
You're talking about changing the example in the binding doc? That doesn't really matter. Consumer side of the provider has to match the cell count from the provider so it doesn't really need to be 'fixed' in the example at all.
On 31/05/2018 09:20:57-0500, Rob Herring wrote:
On Thu, May 31, 2018 at 5:25 AM, Codrin Ciubotariu codrin.ciubotariu@microchip.com wrote:
On 31.05.2018 03:58, Rob Herring wrote:
On Fri, May 25, 2018 at 03:34:22PM +0300, Codrin Ciubotariu wrote:
The I2S mux clock can be used to select the I2S input clock. The available parents are the peripheral and the generated clocks.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
.../devicetree/bindings/clock/at91-clock.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt index 51c259a..1c46b3c 100644 --- a/Documentation/devicetree/bindings/clock/at91-clock.txt +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt @@ -90,6 +90,8 @@ Required properties: "atmel,sama5d2-clk-audio-pll-pmc" at91 audio pll output on AUDIOPLLCLK that feeds the PMC and can be used by peripheral clock or generic clock
"atmel,sama5d2-clk-i2s-mux":
at91 I2S clock source selection
Is this boolean or takes some values. If latter, what are valid values?
This is the compatible string of the clock driver.
Ah, now I remember. AT91 uses fine grained clock nodes in DT. Is there still a plan to fix this?
There is still a plan to do that, hopefully soon (I'd like to aim for the next release or the one after).
I think this one should go in as-is so it can be fixed with all the other one at once.
This driver is a simple muxing driver that controls the I2S's clock input by using syscon/regmap to change the parrent. The available inputs can be Peripheral clock and Generated clock.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com --- arch/arm/mach-at91/Kconfig | 4 ++ drivers/clk/at91/Makefile | 1 + drivers/clk/at91/clk-i2s-mux.c | 117 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 drivers/clk/at91/clk-i2s-mux.c
diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig index 1254bf9..903f23c 100644 --- a/arch/arm/mach-at91/Kconfig +++ b/arch/arm/mach-at91/Kconfig @@ -27,6 +27,7 @@ config SOC_SAMA5D2 select HAVE_AT91_H32MX select HAVE_AT91_GENERATED_CLK select HAVE_AT91_AUDIO_PLL + select HAVE_AT91_I2S_MUX_CLK select PINCTRL_AT91PIO4 help Select this if ou are using one of Microchip's SAMA5D2 family SoC. @@ -129,6 +130,9 @@ config HAVE_AT91_GENERATED_CLK config HAVE_AT91_AUDIO_PLL bool
+config HAVE_AT91_I2S_MUX_CLK + bool + config SOC_SAM_V4_V5 bool
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile index 082596f..facc169 100644 --- a/drivers/clk/at91/Makefile +++ b/drivers/clk/at91/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_HAVE_AT91_USB_CLK) += clk-usb.o obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o +obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK) += clk-i2s-mux.o diff --git a/drivers/clk/at91/clk-i2s-mux.c b/drivers/clk/at91/clk-i2s-mux.c new file mode 100644 index 0000000..2d56ded --- /dev/null +++ b/drivers/clk/at91/clk-i2s-mux.c @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Microchip Technology Inc, + * Codrin Ciubotariu codrin.ciubotariu@microchip.com + * + * + */ + +#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <soc/at91/atmel-sfr.h> + +#define I2S_BUS_NR 2 + +struct clk_i2s_mux { + struct clk_hw hw; + struct regmap *regmap; + u32 bus_id; +}; + +#define to_clk_i2s_mux(hw) container_of(hw, struct clk_i2s_mux, hw) + +static u8 clk_i2s_mux_get_parent(struct clk_hw *hw) +{ + struct clk_i2s_mux *mux = to_clk_i2s_mux(hw); + u32 val; + + regmap_read(mux->regmap, AT91_SFR_I2SCLKSEL, &val); + + return (val & BIT(mux->bus_id)) >> mux->bus_id; +} + +static int clk_i2s_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_i2s_mux *mux = to_clk_i2s_mux(hw); + + return regmap_update_bits(mux->regmap, AT91_SFR_I2SCLKSEL, + BIT(mux->bus_id), index << mux->bus_id); +} + +const struct clk_ops clk_i2s_mux_ops = { + .get_parent = clk_i2s_mux_get_parent, + .set_parent = clk_i2s_mux_set_parent, + .determine_rate = __clk_mux_determine_rate, +}; + +static struct clk_hw * __init +at91_clk_i2s_mux_register(struct regmap *regmap, const char *name, + const char * const *parent_names, + unsigned int num_parents, u32 bus_id) +{ + struct clk_init_data init = {}; + struct clk_i2s_mux *i2s_ck; + int ret; + + i2s_ck = kzalloc(sizeof(*i2s_ck), GFP_KERNEL); + if (!i2s_ck) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_i2s_mux_ops; + init.parent_names = parent_names; + init.num_parents = num_parents; + init.flags = CLK_IGNORE_UNUSED; + + i2s_ck->hw.init = &init; + i2s_ck->bus_id = bus_id; + i2s_ck->regmap = regmap; + + ret = clk_hw_register(NULL, &i2s_ck->hw); + if (ret) { + kfree(i2s_ck); + return ERR_PTR(ret); + } + + return &i2s_ck->hw; +} + +static void __init of_sama5d2_clk_i2s_mux_setup(struct device_node *np) +{ + struct regmap *regmap_sfr; + u32 bus_id; + const char *parent_names[2]; + struct device_node *i2s_mux_np; + struct clk_hw *hw; + int ret; + + regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr"); + if (IS_ERR(regmap_sfr)) + return; + + for_each_child_of_node(np, i2s_mux_np) { + if (of_property_read_u32(i2s_mux_np, "reg", &bus_id)) + continue; + + if (bus_id > I2S_BUS_NR) + continue; + + ret = of_clk_parent_fill(i2s_mux_np, parent_names, 2); + if (ret != 2) + continue; + + hw = at91_clk_i2s_mux_register(regmap_sfr, i2s_mux_np->name, + parent_names, 2, bus_id); + if (IS_ERR(hw)) + continue; + + of_clk_add_hw_provider(i2s_mux_np, of_clk_hw_simple_get, hw); + } +} + +CLK_OF_DECLARE(sama5d2_clk_i2s_mux, "atmel,sama5d2-clk-i2s-mux", + of_sama5d2_clk_i2s_mux_setup);
Quoting Codrin Ciubotariu (2018-05-25 05:34:23)
This driver is a simple muxing driver that controls the I2S's clock input by using syscon/regmap to change the parrent.
s/parrent/parent/
The available inputs can be Peripheral clock and Generated clock.
Why capitalize peripheral and generated?
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig index 1254bf9..903f23c 100644 --- a/arch/arm/mach-at91/Kconfig +++ b/arch/arm/mach-at91/Kconfig @@ -27,6 +27,7 @@ config SOC_SAMA5D2 select HAVE_AT91_H32MX select HAVE_AT91_GENERATED_CLK select HAVE_AT91_AUDIO_PLL
select HAVE_AT91_I2S_MUX_CLK select PINCTRL_AT91PIO4 help Select this if ou are using one of Microchip's SAMA5D2 family SoC.
@@ -129,6 +130,9 @@ config HAVE_AT91_GENERATED_CLK config HAVE_AT91_AUDIO_PLL bool
+config HAVE_AT91_I2S_MUX_CLK
bool
config SOC_SAM_V4_V5 bool
I guess this is OK.
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile index 082596f..facc169 100644 --- a/drivers/clk/at91/Makefile +++ b/drivers/clk/at91/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_HAVE_AT91_USB_CLK) += clk-usb.o obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o +obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK) += clk-i2s-mux.o diff --git a/drivers/clk/at91/clk-i2s-mux.c b/drivers/clk/at91/clk-i2s-mux.c new file mode 100644 index 0000000..2d56ded --- /dev/null +++ b/drivers/clk/at91/clk-i2s-mux.c @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- Copyright (C) 2018 Microchip Technology Inc,
Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
- */
+#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <soc/at91/atmel-sfr.h>
+#define I2S_BUS_NR 2
+struct clk_i2s_mux {
struct clk_hw hw;
struct regmap *regmap;
u32 bus_id;
Can be a u8?
+};
+#define to_clk_i2s_mux(hw) container_of(hw, struct clk_i2s_mux, hw)
+static u8 clk_i2s_mux_get_parent(struct clk_hw *hw) +{
struct clk_i2s_mux *mux = to_clk_i2s_mux(hw);
u32 val;
regmap_read(mux->regmap, AT91_SFR_I2SCLKSEL, &val);
return (val & BIT(mux->bus_id)) >> mux->bus_id;
+}
+static int clk_i2s_mux_set_parent(struct clk_hw *hw, u8 index) +{
struct clk_i2s_mux *mux = to_clk_i2s_mux(hw);
return regmap_update_bits(mux->regmap, AT91_SFR_I2SCLKSEL,
BIT(mux->bus_id), index << mux->bus_id);
+}
+const struct clk_ops clk_i2s_mux_ops = {
static?
.get_parent = clk_i2s_mux_get_parent,
.set_parent = clk_i2s_mux_set_parent,
.determine_rate = __clk_mux_determine_rate,
+};
+static struct clk_hw * __init +at91_clk_i2s_mux_register(struct regmap *regmap, const char *name,
const char * const *parent_names,
unsigned int num_parents, u32 bus_id)
+{
struct clk_init_data init = {};
struct clk_i2s_mux *i2s_ck;
int ret;
i2s_ck = kzalloc(sizeof(*i2s_ck), GFP_KERNEL);
if (!i2s_ck)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_i2s_mux_ops;
init.parent_names = parent_names;
init.num_parents = num_parents;
init.flags = CLK_IGNORE_UNUSED;
Really? Why?
i2s_ck->hw.init = &init;
i2s_ck->bus_id = bus_id;
i2s_ck->regmap = regmap;
ret = clk_hw_register(NULL, &i2s_ck->hw);
if (ret) {
kfree(i2s_ck);
return ERR_PTR(ret);
}
return &i2s_ck->hw;
+}
On 31.05.2018 18:26, Stephen Boyd wrote:
Quoting Codrin Ciubotariu (2018-05-25 05:34:23)
This driver is a simple muxing driver that controls the I2S's clock input by using syscon/regmap to change the parrent.
s/parrent/parent/
I will fix it.
The available inputs can be Peripheral clock and Generated clock.
Why capitalize peripheral and generated?
In DS, at I2S block these clocks appear defined with capital letters... I will fix it.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com
diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig index 1254bf9..903f23c 100644 --- a/arch/arm/mach-at91/Kconfig +++ b/arch/arm/mach-at91/Kconfig @@ -27,6 +27,7 @@ config SOC_SAMA5D2 select HAVE_AT91_H32MX select HAVE_AT91_GENERATED_CLK select HAVE_AT91_AUDIO_PLL
select HAVE_AT91_I2S_MUX_CLK select PINCTRL_AT91PIO4 help Select this if ou are using one of Microchip's SAMA5D2 family SoC.
@@ -129,6 +130,9 @@ config HAVE_AT91_GENERATED_CLK config HAVE_AT91_AUDIO_PLL bool
+config HAVE_AT91_I2S_MUX_CLK
bool
- config SOC_SAM_V4_V5 bool
I guess this is OK.
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile index 082596f..facc169 100644 --- a/drivers/clk/at91/Makefile +++ b/drivers/clk/at91/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_HAVE_AT91_USB_CLK) += clk-usb.o obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o +obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK) += clk-i2s-mux.o diff --git a/drivers/clk/at91/clk-i2s-mux.c b/drivers/clk/at91/clk-i2s-mux.c new file mode 100644 index 0000000..2d56ded --- /dev/null +++ b/drivers/clk/at91/clk-i2s-mux.c @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- Copyright (C) 2018 Microchip Technology Inc,
Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
- */
+#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <soc/at91/atmel-sfr.h>
+#define I2S_BUS_NR 2
+struct clk_i2s_mux {
struct clk_hw hw;
struct regmap *regmap;
u32 bus_id;
Can be a u8?
I think so, I will cast out_value parameter of of_property_read_u32() to u8.
+};
+#define to_clk_i2s_mux(hw) container_of(hw, struct clk_i2s_mux, hw)
+static u8 clk_i2s_mux_get_parent(struct clk_hw *hw) +{
struct clk_i2s_mux *mux = to_clk_i2s_mux(hw);
u32 val;
regmap_read(mux->regmap, AT91_SFR_I2SCLKSEL, &val);
return (val & BIT(mux->bus_id)) >> mux->bus_id;
+}
+static int clk_i2s_mux_set_parent(struct clk_hw *hw, u8 index) +{
struct clk_i2s_mux *mux = to_clk_i2s_mux(hw);
return regmap_update_bits(mux->regmap, AT91_SFR_I2SCLKSEL,
BIT(mux->bus_id), index << mux->bus_id);
+}
+const struct clk_ops clk_i2s_mux_ops = {
static?
Yes, I will fix it.
.get_parent = clk_i2s_mux_get_parent,
.set_parent = clk_i2s_mux_set_parent,
.determine_rate = __clk_mux_determine_rate,
+};
+static struct clk_hw * __init +at91_clk_i2s_mux_register(struct regmap *regmap, const char *name,
const char * const *parent_names,
unsigned int num_parents, u32 bus_id)
+{
struct clk_init_data init = {};
struct clk_i2s_mux *i2s_ck;
int ret;
i2s_ck = kzalloc(sizeof(*i2s_ck), GFP_KERNEL);
if (!i2s_ck)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_i2s_mux_ops;
init.parent_names = parent_names;
init.num_parents = num_parents;
init.flags = CLK_IGNORE_UNUSED;
Really? Why?
I am thinking that there is no need to gate this clock, since there is no way to gate this clock in HW.
i2s_ck->hw.init = &init;
i2s_ck->bus_id = bus_id;
i2s_ck->regmap = regmap;
ret = clk_hw_register(NULL, &i2s_ck->hw);
if (ret) {
kfree(i2s_ck);
return ERR_PTR(ret);
}
return &i2s_ck->hw;
+}
Thank you for your review. I will wait a few more days for more comments on this series and send a V5 afterwards.
Best regards, Codrin
Quoting Codrin Ciubotariu (2018-06-04 01:20:29)
On 31.05.2018 18:26, Stephen Boyd wrote:
Quoting Codrin Ciubotariu (2018-05-25 05:34:23)
.get_parent = clk_i2s_mux_get_parent,
.set_parent = clk_i2s_mux_set_parent,
.determine_rate = __clk_mux_determine_rate,
+};
+static struct clk_hw * __init +at91_clk_i2s_mux_register(struct regmap *regmap, const char *name,
const char * const *parent_names,
unsigned int num_parents, u32 bus_id)
+{
struct clk_init_data init = {};
struct clk_i2s_mux *i2s_ck;
int ret;
i2s_ck = kzalloc(sizeof(*i2s_ck), GFP_KERNEL);
if (!i2s_ck)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_i2s_mux_ops;
init.parent_names = parent_names;
init.num_parents = num_parents;
init.flags = CLK_IGNORE_UNUSED;
Really? Why?
I am thinking that there is no need to gate this clock, since there is no way to gate this clock in HW.
This flag is not necessary if the clk can't be gated via hardware control registers. Please remove the flag.
This patch adds two clock muxes for the two I2S buses present on sama5d2 platforms.
Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com --- arch/arm/boot/dts/sama5d2.dtsi | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d2.dtsi b/arch/arm/boot/dts/sama5d2.dtsi index 61f68e5..eeb6afa 100644 --- a/arch/arm/boot/dts/sama5d2.dtsi +++ b/arch/arm/boot/dts/sama5d2.dtsi @@ -992,6 +992,24 @@ atmel,clk-output-range = <0 100000000>; }; }; + + i2s_clkmux { + compatible = "atmel,sama5d2-clk-i2s-mux"; + #address-cells = <1>; + #size-cells = <0>; + + i2s0muxck: i2s0_muxclk { + clocks = <&i2s0_clk>, <&i2s0_gclk>; + #clock-cells = <0>; + reg = <0>; + }; + + i2s1muxck: i2s1_muxclk { + clocks = <&i2s1_clk>, <&i2s1_gclk>; + #clock-cells = <0>; + reg = <1>; + }; + }; };
qspi0: spi@f0020000 {
From: Cyrille Pitchen cyrille.pitchen@atmel.com
This patch adds DT bindings for the new Atmel I2S controller embedded inside sama5d2x SoCs.
Signed-off-by: Cyrille Pitchen cyrille.pitchen@atmel.com Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com ---
Changes in v4: - more specific description for dmas, dma-names, clocks, clock-names properties; - removed SoC and internal details of the block IP; - added description for the new clock mux phandle;
.../devicetree/bindings/sound/atmel-i2s.txt | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/atmel-i2s.txt
diff --git a/Documentation/devicetree/bindings/sound/atmel-i2s.txt b/Documentation/devicetree/bindings/sound/atmel-i2s.txt new file mode 100644 index 0000000..735368b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/atmel-i2s.txt @@ -0,0 +1,47 @@ +* Atmel I2S controller + +Required properties: +- compatible: Should be "atmel,sama5d2-i2s". +- reg: Should be the physical base address of the controller and the + length of memory mapped region. +- interrupts: Should contain the interrupt for the controller. +- dmas: Should be one per channel name listed in the dma-names property, + as described in atmel-dma.txt and dma.txt files. +- dma-names: Two dmas have to be defined, "tx" and "rx". + This IP also supports one shared channel for both rx and tx; + if this mode is used, one "rx-tx" name must be used. +- clocks: Must contain an entry for each entry in clock-names. + Please refer to clock-bindings.txt. +- clock-names: Should be one of each entry matching the clocks phandles list: + - "pclk" (peripheral clock) Required. + - "gclk" (generated clock) Optional (1). + - "aclk" (Audio PLL clock) Optional (1). + - "muxclk" (I2S mux clock) Optional (1). + +Optional properties: +- pinctrl-0: Should specify pin control groups used for this controller. +- princtrl-names: Should contain only one value - "default". + + +(1) : Only the peripheral clock is required. The generated clock, the Audio + PLL clock adn the I2S mux clock are optional and should only be set + together, when Master Mode is required. + +Example: + + i2s@f8050000 { + compatible = "atmel,sama5d2-i2s"; + reg = <0xf8050000 0x300>; + interrupts = <54 IRQ_TYPE_LEVEL_HIGH 7>; + dmas = <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(31))>, + <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(32))>; + dma-names = "tx", "rx"; + clocks = <&i2s0_clk>, <&i2s0_gclk>, <&audio_pll_pmc>, <&i2s0muxck>; + clock-names = "pclk", "gclk", "aclk", "muxclk"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2s0_default>; + };
The patch
ASoC: atmel-i2s: dt-bindings: add DT bindings for I2S controller
has been applied to the asoc tree at
https://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 969cc0528e0efdfdeaa5bbc0ab707d8a7819a8ec Mon Sep 17 00:00:00 2001
From: Cyrille Pitchen cyrille.pitchen@atmel.com Date: Fri, 25 May 2018 15:34:25 +0300 Subject: [PATCH] ASoC: atmel-i2s: dt-bindings: add DT bindings for I2S controller
This patch adds DT bindings for the new Atmel I2S controller embedded inside sama5d2x SoCs.
Signed-off-by: Cyrille Pitchen cyrille.pitchen@atmel.com Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com Signed-off-by: Mark Brown broonie@kernel.org --- .../devicetree/bindings/sound/atmel-i2s.txt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/atmel-i2s.txt
diff --git a/Documentation/devicetree/bindings/sound/atmel-i2s.txt b/Documentation/devicetree/bindings/sound/atmel-i2s.txt new file mode 100644 index 000000000000..735368b8a73f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/atmel-i2s.txt @@ -0,0 +1,47 @@ +* Atmel I2S controller + +Required properties: +- compatible: Should be "atmel,sama5d2-i2s". +- reg: Should be the physical base address of the controller and the + length of memory mapped region. +- interrupts: Should contain the interrupt for the controller. +- dmas: Should be one per channel name listed in the dma-names property, + as described in atmel-dma.txt and dma.txt files. +- dma-names: Two dmas have to be defined, "tx" and "rx". + This IP also supports one shared channel for both rx and tx; + if this mode is used, one "rx-tx" name must be used. +- clocks: Must contain an entry for each entry in clock-names. + Please refer to clock-bindings.txt. +- clock-names: Should be one of each entry matching the clocks phandles list: + - "pclk" (peripheral clock) Required. + - "gclk" (generated clock) Optional (1). + - "aclk" (Audio PLL clock) Optional (1). + - "muxclk" (I2S mux clock) Optional (1). + +Optional properties: +- pinctrl-0: Should specify pin control groups used for this controller. +- princtrl-names: Should contain only one value - "default". + + +(1) : Only the peripheral clock is required. The generated clock, the Audio + PLL clock adn the I2S mux clock are optional and should only be set + together, when Master Mode is required. + +Example: + + i2s@f8050000 { + compatible = "atmel,sama5d2-i2s"; + reg = <0xf8050000 0x300>; + interrupts = <54 IRQ_TYPE_LEVEL_HIGH 7>; + dmas = <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(31))>, + <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(32))>; + dma-names = "tx", "rx"; + clocks = <&i2s0_clk>, <&i2s0_gclk>, <&audio_pll_pmc>, <&i2s0muxck>; + clock-names = "pclk", "gclk", "aclk", "muxclk"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2s0_default>; + };
From: Cyrille Pitchen cyrille.pitchen@atmel.com
This patch adds support for the Atmel I2S controller embedded into sama5d2x SoCs.
Signed-off-by: Cyrille Pitchen cyrille.pitchen@atmel.com Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com ---
Changes in v4: - treated -EPROBE_DEFER on probe; - replaced ternary operator with if; - return -EINVAL if we don't support the number of audio channels.
sound/soc/atmel/Kconfig | 9 + sound/soc/atmel/Makefile | 2 + sound/soc/atmel/atmel-i2s.c | 765 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 776 insertions(+) create mode 100644 sound/soc/atmel/atmel-i2s.c
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index dcee145..64b784e 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -88,4 +88,13 @@ config SND_ATMEL_SOC_TSE850_PCM5142 help Say Y if you want to add support for the ASoC driver for the Axentia TSE-850 with a PCM5142 codec. + +config SND_ATMEL_SOC_I2S + tristate "Atmel ASoC driver for boards using I2S" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for Atmel ASoc driver for boards + using I2S. endif diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 4440646..cd87cb4 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -3,10 +3,12 @@ snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o +snd-soc-atmel-i2s-objs := atmel-i2s.o
obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o +obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o
# AT91 Machine Support snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c new file mode 100644 index 0000000..5d3b5af --- /dev/null +++ b/sound/soc/atmel/atmel-i2s.c @@ -0,0 +1,765 @@ +/* + * Driver for Atmel I2S controller + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Cyrille Pitchen cyrille.pitchen@atmel.com + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#define ATMEL_I2SC_MAX_TDM_CHANNELS 8 + +/* + * ---- I2S Controller Register map ---- + */ +#define ATMEL_I2SC_CR 0x0000 /* Control Register */ +#define ATMEL_I2SC_MR 0x0004 /* Mode Register */ +#define ATMEL_I2SC_SR 0x0008 /* Status Register */ +#define ATMEL_I2SC_SCR 0x000c /* Status Clear Register */ +#define ATMEL_I2SC_SSR 0x0010 /* Status Set Register */ +#define ATMEL_I2SC_IER 0x0014 /* Interrupt Enable Register */ +#define ATMEL_I2SC_IDR 0x0018 /* Interrupt Disable Register */ +#define ATMEL_I2SC_IMR 0x001c /* Interrupt Mask Register */ +#define ATMEL_I2SC_RHR 0x0020 /* Receiver Holding Register */ +#define ATMEL_I2SC_THR 0x0024 /* Transmitter Holding Register */ +#define ATMEL_I2SC_VERSION 0x0028 /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define ATMEL_I2SC_CR_RXEN BIT(0) /* Receiver Enable */ +#define ATMEL_I2SC_CR_RXDIS BIT(1) /* Receiver Disable */ +#define ATMEL_I2SC_CR_CKEN BIT(2) /* Clock Enable */ +#define ATMEL_I2SC_CR_CKDIS BIT(3) /* Clock Disable */ +#define ATMEL_I2SC_CR_TXEN BIT(4) /* Transmitter Enable */ +#define ATMEL_I2SC_CR_TXDIS BIT(5) /* Transmitter Disable */ +#define ATMEL_I2SC_CR_SWRST BIT(7) /* Software Reset */ + +/* + * ---- Mode Register (Read/Write) ---- + */ +#define ATMEL_I2SC_MR_MODE_MASK GENMASK(0, 0) +#define ATMEL_I2SC_MR_MODE_SLAVE (0 << 0) +#define ATMEL_I2SC_MR_MODE_MASTER (1 << 0) + +#define ATMEL_I2SC_MR_DATALENGTH_MASK GENMASK(4, 2) +#define ATMEL_I2SC_MR_DATALENGTH_32_BITS (0 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_24_BITS (1 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_20_BITS (2 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_18_BITS (3 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS (4 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS_COMPACT (5 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS (6 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS_COMPACT (7 << 2) + +#define ATMEL_I2SC_MR_FORMAT_MASK GENMASK(7, 6) +#define ATMEL_I2SC_MR_FORMAT_I2S (0 << 6) +#define ATMEL_I2SC_MR_FORMAT_LJ (1 << 6) /* Left Justified */ +#define ATMEL_I2SC_MR_FORMAT_TDM (2 << 6) +#define ATMEL_I2SC_MR_FORMAT_TDMLJ (3 << 6) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_RXMONO BIT(8) + +/* Receiver uses one DMA channel ... */ +#define ATMEL_I2SC_MR_RXDMA_MASK GENMASK(9, 9) +#define ATMEL_I2SC_MR_RXDMA_SINGLE (0 << 9) /* for all audio channels */ +#define ATMEL_I2SC_MR_RXDMA_MULTIPLE (1 << 9) /* per audio channel */ + +/* I2SDO output of I2SC is internally connected to I2SDI input */ +#define ATMEL_I2SC_MR_RXLOOP BIT(10) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_TXMONO BIT(12) + +/* Transmitter uses one DMA channel ... */ +#define ATMEL_I2SC_MR_TXDMA_MASK GENMASK(13, 13) +#define ATMEL_I2SC_MR_TXDMA_SINGLE (0 << 13) /* for all audio channels */ +#define ATMEL_I2SC_MR_TXDME_MULTIPLE (1 << 13) /* per audio channel */ + +/* x sample transmitted when underrun */ +#define ATMEL_I2SC_MR_TXSAME_MASK GENMASK(14, 14) +#define ATMEL_I2SC_MR_TXSAME_ZERO (0 << 14) /* Zero sample */ +#define ATMEL_I2SC_MR_TXSAME_PREVIOUS (1 << 14) /* Previous sample */ + +/* Audio Clock to I2SC Master Clock ratio */ +#define ATMEL_I2SC_MR_IMCKDIV_MASK GENMASK(21, 16) +#define ATMEL_I2SC_MR_IMCKDIV(div) \ + (((div) << 16) & ATMEL_I2SC_MR_IMCKDIV_MASK) + +/* Master Clock to fs ratio */ +#define ATMEL_I2SC_MR_IMCKFS_MASK GENMASK(29, 24) +#define ATMEL_I2SC_MR_IMCKFS(fs) \ + (((fs) << 24) & ATMEL_I2SC_MR_IMCKFS_MASK) + +/* Master Clock mode */ +#define ATMEL_I2SC_MR_IMCKMODE_MASK GENMASK(30, 30) +/* 0: No master clock generated (selected clock drives I2SCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SCK (0 << 30) +/* 1: master clock generated (internally generated clock drives I2SMCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SMCK (1 << 30) + +/* Slot Width */ +/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */ +/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */ +#define ATMEL_I2SC_MR_IWS BIT(31) + +/* + * ---- Status Registers ---- + */ +#define ATMEL_I2SC_SR_RXEN BIT(0) /* Receiver Enabled */ +#define ATMEL_I2SC_SR_RXRDY BIT(1) /* Receive Ready */ +#define ATMEL_I2SC_SR_RXOR BIT(2) /* Receive Overrun */ + +#define ATMEL_I2SC_SR_TXEN BIT(4) /* Transmitter Enabled */ +#define ATMEL_I2SC_SR_TXRDY BIT(5) /* Transmit Ready */ +#define ATMEL_I2SC_SR_TXUR BIT(6) /* Transmit Underrun */ + +/* Receive Overrun Channel */ +#define ATMEL_I2SC_SR_RXORCH_MASK GENMASK(15, 8) +#define ATMEL_I2SC_SR_RXORCH(ch) (1 << (((ch) & 0x7) + 8)) + +/* Transmit Underrun Channel */ +#define ATMEL_I2SC_SR_TXURCH_MASK GENMASK(27, 20) +#define ATMEL_I2SC_SR_TXURCH(ch) (1 << (((ch) & 0x7) + 20)) + +/* + * ---- Interrupt Enable/Disable/Mask Registers ---- + */ +#define ATMEL_I2SC_INT_RXRDY ATMEL_I2SC_SR_RXRDY +#define ATMEL_I2SC_INT_RXOR ATMEL_I2SC_SR_RXOR +#define ATMEL_I2SC_INT_TXRDY ATMEL_I2SC_SR_TXRDY +#define ATMEL_I2SC_INT_TXUR ATMEL_I2SC_SR_TXUR + +static const struct regmap_config atmel_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_I2SC_VERSION, +}; + +struct atmel_i2s_gck_param { + int fs; + unsigned long mck; + int imckdiv; + int imckfs; +}; + +#define I2S_MCK_12M288 12288000UL +#define I2S_MCK_11M2896 11289600UL + +/* mck = (32 * (imckfs+1) / (imckdiv+1)) * fs */ +static const struct atmel_i2s_gck_param gck_params[] = { + /* mck = 12.288MHz */ + { 8000, I2S_MCK_12M288, 0, 47}, /* mck = 1536 fs */ + { 16000, I2S_MCK_12M288, 1, 47}, /* mck = 768 fs */ + { 24000, I2S_MCK_12M288, 3, 63}, /* mck = 512 fs */ + { 32000, I2S_MCK_12M288, 3, 47}, /* mck = 384 fs */ + { 48000, I2S_MCK_12M288, 7, 63}, /* mck = 256 fs */ + { 64000, I2S_MCK_12M288, 7, 47}, /* mck = 192 fs */ + { 96000, I2S_MCK_12M288, 7, 31}, /* mck = 128 fs */ + {192000, I2S_MCK_12M288, 7, 15}, /* mck = 64 fs */ + + /* mck = 11.2896MHz */ + { 11025, I2S_MCK_11M2896, 1, 63}, /* mck = 1024 fs */ + { 22050, I2S_MCK_11M2896, 3, 63}, /* mck = 512 fs */ + { 44100, I2S_MCK_11M2896, 7, 63}, /* mck = 256 fs */ + { 88200, I2S_MCK_11M2896, 7, 31}, /* mck = 128 fs */ + {176400, I2S_MCK_11M2896, 7, 15}, /* mck = 64 fs */ +}; + +struct atmel_i2s_dev; + +struct atmel_i2s_caps { + int (*mck_init)(struct atmel_i2s_dev *, struct device_node *np); +}; + +struct atmel_i2s_dev { + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct clk *aclk; + struct snd_dmaengine_dai_dma_data playback; + struct snd_dmaengine_dai_dma_data capture; + unsigned int fmt; + const struct atmel_i2s_gck_param *gck_param; + const struct atmel_i2s_caps *caps; +}; + +static irqreturn_t atmel_i2s_interrupt(int irq, void *dev_id) +{ + struct atmel_i2s_dev *dev = dev_id; + unsigned int sr, imr, pending, ch, mask; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + regmap_read(dev->regmap, ATMEL_I2SC_IMR, &imr); + pending = sr & imr; + + if (!pending) + return IRQ_NONE; + + if (pending & ATMEL_I2SC_INT_RXOR) { + mask = ATMEL_I2SC_SR_RXOR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_RXORCH(ch)) { + mask |= ATMEL_I2SC_SR_RXORCH(ch); + dev_err(dev->dev, + "RX overrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + if (pending & ATMEL_I2SC_INT_TXUR) { + mask = ATMEL_I2SC_SR_TXUR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_TXURCH(ch)) { + mask |= ATMEL_I2SC_SR_TXURCH(ch); + dev_err(dev->dev, + "TX underrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + return ret; +} + +#define ATMEL_I2S_RATES SNDRV_PCM_RATE_8000_192000 + +#define ATMEL_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int atmel_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev->fmt = fmt; + return 0; +} + +static int atmel_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int rhr, sr = 0; + + if (is_playback) { + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + if (sr & ATMEL_I2SC_SR_RXRDY) { + /* + * The RX Ready flag should not be set. However if here, + * we flush (read) the Receive Holding Register to start + * from a clean state. + */ + dev_dbg(dev->dev, "RXRDY is set\n"); + regmap_read(dev->regmap, ATMEL_I2SC_RHR, &rhr); + } + } + + return 0; +} + +static int atmel_i2s_get_gck_param(struct atmel_i2s_dev *dev, int fs) +{ + int i, best; + + if (!dev->gclk || !dev->aclk) { + dev_err(dev->dev, "cannot generate the I2S Master Clock\n"); + return -EINVAL; + } + + /* + * Find the best possible settings to generate the I2S Master Clock + * from the PLL Audio. + */ + dev->gck_param = NULL; + best = INT_MAX; + for (i = 0; i < ARRAY_SIZE(gck_params); ++i) { + const struct atmel_i2s_gck_param *gck_param = &gck_params[i]; + int val = abs(fs - gck_param->fs); + + if (val < best) { + best = val; + dev->gck_param = gck_param; + } + } + + return 0; +} + +static int atmel_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int mr = 0; + int ret; + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mr |= ATMEL_I2SC_MR_FORMAT_I2S; + break; + + default: + dev_err(dev->dev, "unsupported bus format\n"); + return -EINVAL; + } + + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* codec is slave, so cpu is master */ + mr |= ATMEL_I2SC_MR_MODE_MASTER; + ret = atmel_i2s_get_gck_param(dev, params_rate(params)); + if (ret) + return ret; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master, so cpu is slave */ + mr |= ATMEL_I2SC_MR_MODE_SLAVE; + dev->gck_param = NULL; + break; + + default: + dev_err(dev->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + switch (params_channels(params)) { + case 1: + if (is_playback) + mr |= ATMEL_I2SC_MR_TXMONO; + else + mr |= ATMEL_I2SC_MR_RXMONO; + break; + case 2: + break; + default: + dev_err(dev->dev, "unsupported number of audio channels\n"); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mr |= ATMEL_I2SC_MR_DATALENGTH_8_BITS; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_16_BITS; + break; + + case SNDRV_PCM_FORMAT_S18_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_18_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S20_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_20_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_32_BITS; + break; + + default: + dev_err(dev->dev, "unsupported size/endianness for audio samples\n"); + return -EINVAL; + } + + return regmap_write(dev->regmap, ATMEL_I2SC_MR, mr); +} + +static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev, + bool enabled) +{ + unsigned int mr, mr_mask; + unsigned long aclk_rate; + int ret; + + mr = 0; + mr_mask = (ATMEL_I2SC_MR_IMCKDIV_MASK | + ATMEL_I2SC_MR_IMCKFS_MASK | + ATMEL_I2SC_MR_IMCKMODE_MASK); + + if (!enabled) { + /* Disable the I2S Master Clock generator. */ + ret = regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKDIS); + if (ret) + return ret; + + /* Reset the I2S Master Clock generator settings. */ + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, + mr_mask, mr); + if (ret) + return ret; + + /* Disable/unprepare the PMC generated clock. */ + clk_disable_unprepare(dev->gclk); + + /* Disable/unprepare the PLL audio clock. */ + clk_disable_unprepare(dev->aclk); + return 0; + } + + if (!dev->gck_param) + return -EINVAL; + + aclk_rate = dev->gck_param->mck * (dev->gck_param->imckdiv + 1); + + /* Fist change the PLL audio clock frequency ... */ + ret = clk_set_rate(dev->aclk, aclk_rate); + if (ret) + return ret; + + /* + * ... then set the PMC generated clock rate to the very same frequency + * to set the gclk parent to aclk. + */ + ret = clk_set_rate(dev->gclk, aclk_rate); + if (ret) + return ret; + + /* Prepare and enable the PLL audio clock first ... */ + ret = clk_prepare_enable(dev->aclk); + if (ret) + return ret; + + /* ... then prepare and enable the PMC generated clock. */ + ret = clk_prepare_enable(dev->gclk); + if (ret) + return ret; + + /* Update the Mode Register to generate the I2S Master Clock. */ + mr |= ATMEL_I2SC_MR_IMCKDIV(dev->gck_param->imckdiv); + mr |= ATMEL_I2SC_MR_IMCKFS(dev->gck_param->imckfs); + mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK; + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); + if (ret) + return ret; + + /* Finally enable the I2S Master Clock generator. */ + return regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKEN); +} + +static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + bool is_master, mck_enabled; + unsigned int cr, mr; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + cr = is_playback ? ATMEL_I2SC_CR_TXEN : ATMEL_I2SC_CR_RXEN; + mck_enabled = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + cr = is_playback ? ATMEL_I2SC_CR_TXDIS : ATMEL_I2SC_CR_RXDIS; + mck_enabled = false; + break; + default: + return -EINVAL; + } + + /* Read the Mode Register to retrieve the master/slave state. */ + err = regmap_read(dev->regmap, ATMEL_I2SC_MR, &mr); + if (err) + return err; + is_master = (mr & ATMEL_I2SC_MR_MODE_MASK) == ATMEL_I2SC_MR_MODE_MASTER; + + /* If master starts, enable the audio clock. */ + if (is_master && mck_enabled) + err = atmel_i2s_switch_mck_generator(dev, true); + if (err) + return err; + + err = regmap_write(dev->regmap, ATMEL_I2SC_CR, cr); + if (err) + return err; + + /* If master stops, disable the audio clock. */ + if (is_master && !mck_enabled) + err = atmel_i2s_switch_mck_generator(dev, false); + + return err; +} + +static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, +}; + +static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture); + return 0; +} + +static struct snd_soc_dai_driver atmel_i2s_dai = { + .probe = atmel_i2s_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .ops = &atmel_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver atmel_i2s_component = { + .name = "atmel-i2s", +}; + +static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev, + struct device_node *np) +{ + struct clk *muxclk; + int err; + + if (!dev->gclk) + return 0; + + /* muxclk is optional, so we return error for probe defer only */ + muxclk = devm_clk_get(dev->dev, "muxclk"); + if (IS_ERR(muxclk)) { + err = PTR_ERR(muxclk); + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(dev->dev, + "failed to get the I2S clock control: %d\n", err); + return 0; + } + + return clk_set_parent(muxclk, dev->gclk); +} + +static const struct atmel_i2s_caps atmel_i2s_sama5d2_caps = { + .mck_init = atmel_i2s_sama5d2_mck_init, +}; + +static const struct of_device_id atmel_i2s_dt_ids[] = { + { + .compatible = "atmel,sama5d2-i2s", + .data = (void *)&atmel_i2s_sama5d2_caps, + }, + + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_i2s_dt_ids); + +static int atmel_i2s_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + struct atmel_i2s_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + int irq; + int err = -ENXIO; + unsigned int pcm_flags = 0; + unsigned int version; + + /* Get memory for driver data. */ + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* Get hardware capabilities. */ + match = of_match_node(atmel_i2s_dt_ids, np); + if (match) + dev->caps = match->data; + + /* Map I/O registers. */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + &atmel_i2s_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Request IRQ. */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(&pdev->dev, irq, atmel_i2s_interrupt, 0, + dev_name(&pdev->dev), dev); + if (err) + return err; + + /* Get the peripheral clock. */ + dev->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(dev->pclk)) { + err = PTR_ERR(dev->pclk); + dev_err(&pdev->dev, + "failed to get the peripheral clock: %d\n", err); + return err; + } + + /* Get audio clocks to generate the I2S Master Clock (I2S_MCK) */ + dev->aclk = devm_clk_get(&pdev->dev, "aclk"); + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(dev->aclk) && IS_ERR(dev->gclk)) { + if (PTR_ERR(dev->aclk) == -EPROBE_DEFER || + PTR_ERR(dev->gclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + /* Master Mode not supported */ + dev->aclk = NULL; + dev->gclk = NULL; + } else if (IS_ERR(dev->gclk)) { + err = PTR_ERR(dev->gclk); + dev_err(&pdev->dev, + "failed to get the PMC generated clock: %d\n", err); + return err; + } else if (IS_ERR(dev->aclk)) { + err = PTR_ERR(dev->aclk); + dev_err(&pdev->dev, + "failed to get the PLL audio clock: %d\n", err); + return err; + } + + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + /* Do hardware specific settings to initialize I2S_MCK generator */ + if (dev->caps && dev->caps->mck_init) { + err = dev->caps->mck_init(dev, np); + if (err) + return err; + } + + /* Enable the peripheral clock. */ + err = clk_prepare_enable(dev->pclk); + if (err) + return err; + + /* Get IP version. */ + regmap_read(dev->regmap, ATMEL_I2SC_VERSION, &version); + dev_info(&pdev->dev, "hw version: %#x\n", version); + + /* Enable error interrupts. */ + regmap_write(dev->regmap, ATMEL_I2SC_IER, + ATMEL_I2SC_INT_RXOR | ATMEL_I2SC_INT_TXUR); + + err = devm_snd_soc_register_component(&pdev->dev, + &atmel_i2s_component, + &atmel_i2s_dai, 1); + if (err) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + /* Prepare DMA config. */ + dev->playback.addr = (dma_addr_t)mem->start + ATMEL_I2SC_THR; + dev->playback.maxburst = 1; + dev->capture.addr = (dma_addr_t)mem->start + ATMEL_I2SC_RHR; + dev->capture.maxburst = 1; + + if (of_property_match_string(np, "dma-names", "rx-tx") == 0) + pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags); + if (err) { + dev_err(&pdev->dev, "failed to register PCM: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + return 0; +} + +static int atmel_i2s_remove(struct platform_device *pdev) +{ + struct atmel_i2s_dev *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct platform_driver atmel_i2s_driver = { + .driver = { + .name = "atmel_i2s", + .of_match_table = of_match_ptr(atmel_i2s_dt_ids), + }, + .probe = atmel_i2s_probe, + .remove = atmel_i2s_remove, +}; +module_platform_driver(atmel_i2s_driver); + +MODULE_DESCRIPTION("Atmel I2S Controller driver"); +MODULE_AUTHOR("Cyrille Pitchen cyrille.pitchen@atmel.com"); +MODULE_LICENSE("GPL v2");
The patch
ASoC: atmel-i2s: add driver for the new Atmel I2S controller
has been applied to the asoc tree at
https://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 b543e467d1a9451013cd89ddcacde07dda9e7f32 Mon Sep 17 00:00:00 2001
From: Cyrille Pitchen cyrille.pitchen@atmel.com Date: Fri, 25 May 2018 15:34:26 +0300 Subject: [PATCH] ASoC: atmel-i2s: add driver for the new Atmel I2S controller
This patch adds support for the Atmel I2S controller embedded into sama5d2x SoCs.
Signed-off-by: Cyrille Pitchen cyrille.pitchen@atmel.com Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/atmel/Kconfig | 9 + sound/soc/atmel/Makefile | 2 + sound/soc/atmel/atmel-i2s.c | 765 ++++++++++++++++++++++++++++++++++++ 3 files changed, 776 insertions(+) create mode 100644 sound/soc/atmel/atmel-i2s.c
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index dcee145dd179..64b784e96f84 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -88,4 +88,13 @@ config SND_ATMEL_SOC_TSE850_PCM5142 help Say Y if you want to add support for the ASoC driver for the Axentia TSE-850 with a PCM5142 codec. + +config SND_ATMEL_SOC_I2S + tristate "Atmel ASoC driver for boards using I2S" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for Atmel ASoc driver for boards + using I2S. endif diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 4440646416e8..cd87cb4bcff5 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -3,10 +3,12 @@ snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o +snd-soc-atmel-i2s-objs := atmel-i2s.o
obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o +obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o
# AT91 Machine Support snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c new file mode 100644 index 000000000000..5d3b5af9fd92 --- /dev/null +++ b/sound/soc/atmel/atmel-i2s.c @@ -0,0 +1,765 @@ +/* + * Driver for Atmel I2S controller + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Cyrille Pitchen cyrille.pitchen@atmel.com + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#define ATMEL_I2SC_MAX_TDM_CHANNELS 8 + +/* + * ---- I2S Controller Register map ---- + */ +#define ATMEL_I2SC_CR 0x0000 /* Control Register */ +#define ATMEL_I2SC_MR 0x0004 /* Mode Register */ +#define ATMEL_I2SC_SR 0x0008 /* Status Register */ +#define ATMEL_I2SC_SCR 0x000c /* Status Clear Register */ +#define ATMEL_I2SC_SSR 0x0010 /* Status Set Register */ +#define ATMEL_I2SC_IER 0x0014 /* Interrupt Enable Register */ +#define ATMEL_I2SC_IDR 0x0018 /* Interrupt Disable Register */ +#define ATMEL_I2SC_IMR 0x001c /* Interrupt Mask Register */ +#define ATMEL_I2SC_RHR 0x0020 /* Receiver Holding Register */ +#define ATMEL_I2SC_THR 0x0024 /* Transmitter Holding Register */ +#define ATMEL_I2SC_VERSION 0x0028 /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define ATMEL_I2SC_CR_RXEN BIT(0) /* Receiver Enable */ +#define ATMEL_I2SC_CR_RXDIS BIT(1) /* Receiver Disable */ +#define ATMEL_I2SC_CR_CKEN BIT(2) /* Clock Enable */ +#define ATMEL_I2SC_CR_CKDIS BIT(3) /* Clock Disable */ +#define ATMEL_I2SC_CR_TXEN BIT(4) /* Transmitter Enable */ +#define ATMEL_I2SC_CR_TXDIS BIT(5) /* Transmitter Disable */ +#define ATMEL_I2SC_CR_SWRST BIT(7) /* Software Reset */ + +/* + * ---- Mode Register (Read/Write) ---- + */ +#define ATMEL_I2SC_MR_MODE_MASK GENMASK(0, 0) +#define ATMEL_I2SC_MR_MODE_SLAVE (0 << 0) +#define ATMEL_I2SC_MR_MODE_MASTER (1 << 0) + +#define ATMEL_I2SC_MR_DATALENGTH_MASK GENMASK(4, 2) +#define ATMEL_I2SC_MR_DATALENGTH_32_BITS (0 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_24_BITS (1 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_20_BITS (2 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_18_BITS (3 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS (4 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS_COMPACT (5 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS (6 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS_COMPACT (7 << 2) + +#define ATMEL_I2SC_MR_FORMAT_MASK GENMASK(7, 6) +#define ATMEL_I2SC_MR_FORMAT_I2S (0 << 6) +#define ATMEL_I2SC_MR_FORMAT_LJ (1 << 6) /* Left Justified */ +#define ATMEL_I2SC_MR_FORMAT_TDM (2 << 6) +#define ATMEL_I2SC_MR_FORMAT_TDMLJ (3 << 6) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_RXMONO BIT(8) + +/* Receiver uses one DMA channel ... */ +#define ATMEL_I2SC_MR_RXDMA_MASK GENMASK(9, 9) +#define ATMEL_I2SC_MR_RXDMA_SINGLE (0 << 9) /* for all audio channels */ +#define ATMEL_I2SC_MR_RXDMA_MULTIPLE (1 << 9) /* per audio channel */ + +/* I2SDO output of I2SC is internally connected to I2SDI input */ +#define ATMEL_I2SC_MR_RXLOOP BIT(10) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_TXMONO BIT(12) + +/* Transmitter uses one DMA channel ... */ +#define ATMEL_I2SC_MR_TXDMA_MASK GENMASK(13, 13) +#define ATMEL_I2SC_MR_TXDMA_SINGLE (0 << 13) /* for all audio channels */ +#define ATMEL_I2SC_MR_TXDME_MULTIPLE (1 << 13) /* per audio channel */ + +/* x sample transmitted when underrun */ +#define ATMEL_I2SC_MR_TXSAME_MASK GENMASK(14, 14) +#define ATMEL_I2SC_MR_TXSAME_ZERO (0 << 14) /* Zero sample */ +#define ATMEL_I2SC_MR_TXSAME_PREVIOUS (1 << 14) /* Previous sample */ + +/* Audio Clock to I2SC Master Clock ratio */ +#define ATMEL_I2SC_MR_IMCKDIV_MASK GENMASK(21, 16) +#define ATMEL_I2SC_MR_IMCKDIV(div) \ + (((div) << 16) & ATMEL_I2SC_MR_IMCKDIV_MASK) + +/* Master Clock to fs ratio */ +#define ATMEL_I2SC_MR_IMCKFS_MASK GENMASK(29, 24) +#define ATMEL_I2SC_MR_IMCKFS(fs) \ + (((fs) << 24) & ATMEL_I2SC_MR_IMCKFS_MASK) + +/* Master Clock mode */ +#define ATMEL_I2SC_MR_IMCKMODE_MASK GENMASK(30, 30) +/* 0: No master clock generated (selected clock drives I2SCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SCK (0 << 30) +/* 1: master clock generated (internally generated clock drives I2SMCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SMCK (1 << 30) + +/* Slot Width */ +/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */ +/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */ +#define ATMEL_I2SC_MR_IWS BIT(31) + +/* + * ---- Status Registers ---- + */ +#define ATMEL_I2SC_SR_RXEN BIT(0) /* Receiver Enabled */ +#define ATMEL_I2SC_SR_RXRDY BIT(1) /* Receive Ready */ +#define ATMEL_I2SC_SR_RXOR BIT(2) /* Receive Overrun */ + +#define ATMEL_I2SC_SR_TXEN BIT(4) /* Transmitter Enabled */ +#define ATMEL_I2SC_SR_TXRDY BIT(5) /* Transmit Ready */ +#define ATMEL_I2SC_SR_TXUR BIT(6) /* Transmit Underrun */ + +/* Receive Overrun Channel */ +#define ATMEL_I2SC_SR_RXORCH_MASK GENMASK(15, 8) +#define ATMEL_I2SC_SR_RXORCH(ch) (1 << (((ch) & 0x7) + 8)) + +/* Transmit Underrun Channel */ +#define ATMEL_I2SC_SR_TXURCH_MASK GENMASK(27, 20) +#define ATMEL_I2SC_SR_TXURCH(ch) (1 << (((ch) & 0x7) + 20)) + +/* + * ---- Interrupt Enable/Disable/Mask Registers ---- + */ +#define ATMEL_I2SC_INT_RXRDY ATMEL_I2SC_SR_RXRDY +#define ATMEL_I2SC_INT_RXOR ATMEL_I2SC_SR_RXOR +#define ATMEL_I2SC_INT_TXRDY ATMEL_I2SC_SR_TXRDY +#define ATMEL_I2SC_INT_TXUR ATMEL_I2SC_SR_TXUR + +static const struct regmap_config atmel_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_I2SC_VERSION, +}; + +struct atmel_i2s_gck_param { + int fs; + unsigned long mck; + int imckdiv; + int imckfs; +}; + +#define I2S_MCK_12M288 12288000UL +#define I2S_MCK_11M2896 11289600UL + +/* mck = (32 * (imckfs+1) / (imckdiv+1)) * fs */ +static const struct atmel_i2s_gck_param gck_params[] = { + /* mck = 12.288MHz */ + { 8000, I2S_MCK_12M288, 0, 47}, /* mck = 1536 fs */ + { 16000, I2S_MCK_12M288, 1, 47}, /* mck = 768 fs */ + { 24000, I2S_MCK_12M288, 3, 63}, /* mck = 512 fs */ + { 32000, I2S_MCK_12M288, 3, 47}, /* mck = 384 fs */ + { 48000, I2S_MCK_12M288, 7, 63}, /* mck = 256 fs */ + { 64000, I2S_MCK_12M288, 7, 47}, /* mck = 192 fs */ + { 96000, I2S_MCK_12M288, 7, 31}, /* mck = 128 fs */ + {192000, I2S_MCK_12M288, 7, 15}, /* mck = 64 fs */ + + /* mck = 11.2896MHz */ + { 11025, I2S_MCK_11M2896, 1, 63}, /* mck = 1024 fs */ + { 22050, I2S_MCK_11M2896, 3, 63}, /* mck = 512 fs */ + { 44100, I2S_MCK_11M2896, 7, 63}, /* mck = 256 fs */ + { 88200, I2S_MCK_11M2896, 7, 31}, /* mck = 128 fs */ + {176400, I2S_MCK_11M2896, 7, 15}, /* mck = 64 fs */ +}; + +struct atmel_i2s_dev; + +struct atmel_i2s_caps { + int (*mck_init)(struct atmel_i2s_dev *, struct device_node *np); +}; + +struct atmel_i2s_dev { + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct clk *aclk; + struct snd_dmaengine_dai_dma_data playback; + struct snd_dmaengine_dai_dma_data capture; + unsigned int fmt; + const struct atmel_i2s_gck_param *gck_param; + const struct atmel_i2s_caps *caps; +}; + +static irqreturn_t atmel_i2s_interrupt(int irq, void *dev_id) +{ + struct atmel_i2s_dev *dev = dev_id; + unsigned int sr, imr, pending, ch, mask; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + regmap_read(dev->regmap, ATMEL_I2SC_IMR, &imr); + pending = sr & imr; + + if (!pending) + return IRQ_NONE; + + if (pending & ATMEL_I2SC_INT_RXOR) { + mask = ATMEL_I2SC_SR_RXOR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_RXORCH(ch)) { + mask |= ATMEL_I2SC_SR_RXORCH(ch); + dev_err(dev->dev, + "RX overrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + if (pending & ATMEL_I2SC_INT_TXUR) { + mask = ATMEL_I2SC_SR_TXUR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_TXURCH(ch)) { + mask |= ATMEL_I2SC_SR_TXURCH(ch); + dev_err(dev->dev, + "TX underrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + return ret; +} + +#define ATMEL_I2S_RATES SNDRV_PCM_RATE_8000_192000 + +#define ATMEL_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int atmel_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev->fmt = fmt; + return 0; +} + +static int atmel_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int rhr, sr = 0; + + if (is_playback) { + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + if (sr & ATMEL_I2SC_SR_RXRDY) { + /* + * The RX Ready flag should not be set. However if here, + * we flush (read) the Receive Holding Register to start + * from a clean state. + */ + dev_dbg(dev->dev, "RXRDY is set\n"); + regmap_read(dev->regmap, ATMEL_I2SC_RHR, &rhr); + } + } + + return 0; +} + +static int atmel_i2s_get_gck_param(struct atmel_i2s_dev *dev, int fs) +{ + int i, best; + + if (!dev->gclk || !dev->aclk) { + dev_err(dev->dev, "cannot generate the I2S Master Clock\n"); + return -EINVAL; + } + + /* + * Find the best possible settings to generate the I2S Master Clock + * from the PLL Audio. + */ + dev->gck_param = NULL; + best = INT_MAX; + for (i = 0; i < ARRAY_SIZE(gck_params); ++i) { + const struct atmel_i2s_gck_param *gck_param = &gck_params[i]; + int val = abs(fs - gck_param->fs); + + if (val < best) { + best = val; + dev->gck_param = gck_param; + } + } + + return 0; +} + +static int atmel_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int mr = 0; + int ret; + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mr |= ATMEL_I2SC_MR_FORMAT_I2S; + break; + + default: + dev_err(dev->dev, "unsupported bus format\n"); + return -EINVAL; + } + + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* codec is slave, so cpu is master */ + mr |= ATMEL_I2SC_MR_MODE_MASTER; + ret = atmel_i2s_get_gck_param(dev, params_rate(params)); + if (ret) + return ret; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master, so cpu is slave */ + mr |= ATMEL_I2SC_MR_MODE_SLAVE; + dev->gck_param = NULL; + break; + + default: + dev_err(dev->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + switch (params_channels(params)) { + case 1: + if (is_playback) + mr |= ATMEL_I2SC_MR_TXMONO; + else + mr |= ATMEL_I2SC_MR_RXMONO; + break; + case 2: + break; + default: + dev_err(dev->dev, "unsupported number of audio channels\n"); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mr |= ATMEL_I2SC_MR_DATALENGTH_8_BITS; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_16_BITS; + break; + + case SNDRV_PCM_FORMAT_S18_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_18_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S20_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_20_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_32_BITS; + break; + + default: + dev_err(dev->dev, "unsupported size/endianness for audio samples\n"); + return -EINVAL; + } + + return regmap_write(dev->regmap, ATMEL_I2SC_MR, mr); +} + +static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev, + bool enabled) +{ + unsigned int mr, mr_mask; + unsigned long aclk_rate; + int ret; + + mr = 0; + mr_mask = (ATMEL_I2SC_MR_IMCKDIV_MASK | + ATMEL_I2SC_MR_IMCKFS_MASK | + ATMEL_I2SC_MR_IMCKMODE_MASK); + + if (!enabled) { + /* Disable the I2S Master Clock generator. */ + ret = regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKDIS); + if (ret) + return ret; + + /* Reset the I2S Master Clock generator settings. */ + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, + mr_mask, mr); + if (ret) + return ret; + + /* Disable/unprepare the PMC generated clock. */ + clk_disable_unprepare(dev->gclk); + + /* Disable/unprepare the PLL audio clock. */ + clk_disable_unprepare(dev->aclk); + return 0; + } + + if (!dev->gck_param) + return -EINVAL; + + aclk_rate = dev->gck_param->mck * (dev->gck_param->imckdiv + 1); + + /* Fist change the PLL audio clock frequency ... */ + ret = clk_set_rate(dev->aclk, aclk_rate); + if (ret) + return ret; + + /* + * ... then set the PMC generated clock rate to the very same frequency + * to set the gclk parent to aclk. + */ + ret = clk_set_rate(dev->gclk, aclk_rate); + if (ret) + return ret; + + /* Prepare and enable the PLL audio clock first ... */ + ret = clk_prepare_enable(dev->aclk); + if (ret) + return ret; + + /* ... then prepare and enable the PMC generated clock. */ + ret = clk_prepare_enable(dev->gclk); + if (ret) + return ret; + + /* Update the Mode Register to generate the I2S Master Clock. */ + mr |= ATMEL_I2SC_MR_IMCKDIV(dev->gck_param->imckdiv); + mr |= ATMEL_I2SC_MR_IMCKFS(dev->gck_param->imckfs); + mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK; + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); + if (ret) + return ret; + + /* Finally enable the I2S Master Clock generator. */ + return regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKEN); +} + +static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + bool is_master, mck_enabled; + unsigned int cr, mr; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + cr = is_playback ? ATMEL_I2SC_CR_TXEN : ATMEL_I2SC_CR_RXEN; + mck_enabled = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + cr = is_playback ? ATMEL_I2SC_CR_TXDIS : ATMEL_I2SC_CR_RXDIS; + mck_enabled = false; + break; + default: + return -EINVAL; + } + + /* Read the Mode Register to retrieve the master/slave state. */ + err = regmap_read(dev->regmap, ATMEL_I2SC_MR, &mr); + if (err) + return err; + is_master = (mr & ATMEL_I2SC_MR_MODE_MASK) == ATMEL_I2SC_MR_MODE_MASTER; + + /* If master starts, enable the audio clock. */ + if (is_master && mck_enabled) + err = atmel_i2s_switch_mck_generator(dev, true); + if (err) + return err; + + err = regmap_write(dev->regmap, ATMEL_I2SC_CR, cr); + if (err) + return err; + + /* If master stops, disable the audio clock. */ + if (is_master && !mck_enabled) + err = atmel_i2s_switch_mck_generator(dev, false); + + return err; +} + +static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, +}; + +static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture); + return 0; +} + +static struct snd_soc_dai_driver atmel_i2s_dai = { + .probe = atmel_i2s_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .ops = &atmel_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver atmel_i2s_component = { + .name = "atmel-i2s", +}; + +static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev, + struct device_node *np) +{ + struct clk *muxclk; + int err; + + if (!dev->gclk) + return 0; + + /* muxclk is optional, so we return error for probe defer only */ + muxclk = devm_clk_get(dev->dev, "muxclk"); + if (IS_ERR(muxclk)) { + err = PTR_ERR(muxclk); + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(dev->dev, + "failed to get the I2S clock control: %d\n", err); + return 0; + } + + return clk_set_parent(muxclk, dev->gclk); +} + +static const struct atmel_i2s_caps atmel_i2s_sama5d2_caps = { + .mck_init = atmel_i2s_sama5d2_mck_init, +}; + +static const struct of_device_id atmel_i2s_dt_ids[] = { + { + .compatible = "atmel,sama5d2-i2s", + .data = (void *)&atmel_i2s_sama5d2_caps, + }, + + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_i2s_dt_ids); + +static int atmel_i2s_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + struct atmel_i2s_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + int irq; + int err = -ENXIO; + unsigned int pcm_flags = 0; + unsigned int version; + + /* Get memory for driver data. */ + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* Get hardware capabilities. */ + match = of_match_node(atmel_i2s_dt_ids, np); + if (match) + dev->caps = match->data; + + /* Map I/O registers. */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + &atmel_i2s_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Request IRQ. */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(&pdev->dev, irq, atmel_i2s_interrupt, 0, + dev_name(&pdev->dev), dev); + if (err) + return err; + + /* Get the peripheral clock. */ + dev->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(dev->pclk)) { + err = PTR_ERR(dev->pclk); + dev_err(&pdev->dev, + "failed to get the peripheral clock: %d\n", err); + return err; + } + + /* Get audio clocks to generate the I2S Master Clock (I2S_MCK) */ + dev->aclk = devm_clk_get(&pdev->dev, "aclk"); + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(dev->aclk) && IS_ERR(dev->gclk)) { + if (PTR_ERR(dev->aclk) == -EPROBE_DEFER || + PTR_ERR(dev->gclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + /* Master Mode not supported */ + dev->aclk = NULL; + dev->gclk = NULL; + } else if (IS_ERR(dev->gclk)) { + err = PTR_ERR(dev->gclk); + dev_err(&pdev->dev, + "failed to get the PMC generated clock: %d\n", err); + return err; + } else if (IS_ERR(dev->aclk)) { + err = PTR_ERR(dev->aclk); + dev_err(&pdev->dev, + "failed to get the PLL audio clock: %d\n", err); + return err; + } + + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + /* Do hardware specific settings to initialize I2S_MCK generator */ + if (dev->caps && dev->caps->mck_init) { + err = dev->caps->mck_init(dev, np); + if (err) + return err; + } + + /* Enable the peripheral clock. */ + err = clk_prepare_enable(dev->pclk); + if (err) + return err; + + /* Get IP version. */ + regmap_read(dev->regmap, ATMEL_I2SC_VERSION, &version); + dev_info(&pdev->dev, "hw version: %#x\n", version); + + /* Enable error interrupts. */ + regmap_write(dev->regmap, ATMEL_I2SC_IER, + ATMEL_I2SC_INT_RXOR | ATMEL_I2SC_INT_TXUR); + + err = devm_snd_soc_register_component(&pdev->dev, + &atmel_i2s_component, + &atmel_i2s_dai, 1); + if (err) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + /* Prepare DMA config. */ + dev->playback.addr = (dma_addr_t)mem->start + ATMEL_I2SC_THR; + dev->playback.maxburst = 1; + dev->capture.addr = (dma_addr_t)mem->start + ATMEL_I2SC_RHR; + dev->capture.maxburst = 1; + + if (of_property_match_string(np, "dma-names", "rx-tx") == 0) + pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags); + if (err) { + dev_err(&pdev->dev, "failed to register PCM: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + return 0; +} + +static int atmel_i2s_remove(struct platform_device *pdev) +{ + struct atmel_i2s_dev *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct platform_driver atmel_i2s_driver = { + .driver = { + .name = "atmel_i2s", + .of_match_table = of_match_ptr(atmel_i2s_dt_ids), + }, + .probe = atmel_i2s_probe, + .remove = atmel_i2s_remove, +}; +module_platform_driver(atmel_i2s_driver); + +MODULE_DESCRIPTION("Atmel I2S Controller driver"); +MODULE_AUTHOR("Cyrille Pitchen cyrille.pitchen@atmel.com"); +MODULE_LICENSE("GPL v2");
From: Cyrille Pitchen cyrille.pitchen@atmel.com
This patch adds DT nodes for I2S0 and I2S1. It also adds an alias for each I2S node.
Signed-off-by: Cyrille Pitchen cyrille.pitchen@atmel.com [codrin.ciubotariu@microchip.com: added phandle to new mux clock] Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com --- arch/arm/boot/dts/sama5d2.dtsi | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d2.dtsi b/arch/arm/boot/dts/sama5d2.dtsi index eeb6afa..eca73ce 100644 --- a/arch/arm/boot/dts/sama5d2.dtsi +++ b/arch/arm/boot/dts/sama5d2.dtsi @@ -58,6 +58,8 @@ serial1 = &uart3; tcb0 = &tcb0; tcb1 = &tcb1; + i2s0 = &i2s0; + i2s1 = &i2s1; };
cpus { @@ -1313,6 +1315,22 @@ clocks = <&clk32k>; };
+ i2s0: i2s@f8050000 { + compatible = "atmel,sama5d2-i2s"; + reg = <0xf8050000 0x100>; + interrupts = <54 IRQ_TYPE_LEVEL_HIGH 7>; + dmas = <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(31))>, + <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(32))>; + dma-names = "tx", "rx"; + clocks = <&i2s0_clk>, <&i2s0_gclk>, <&audio_pll_pmc>, <&i2s0muxck>; + clock-names = "pclk", "gclk", "aclk", "muxclk"; + status = "disabled"; + }; + can0: can@f8054000 { compatible = "bosch,m_can"; reg = <0xf8054000 0x4000>, <0x210000 0x4000>; @@ -1506,6 +1524,22 @@ status = "disabled"; };
+ i2s1: i2s@fc04c000 { + compatible = "atmel,sama5d2-i2s"; + reg = <0xfc04c000 0x100>; + interrupts = <55 IRQ_TYPE_LEVEL_HIGH 7>; + dmas = <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(33))>, + <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(34))>; + dma-names = "tx", "rx"; + clocks = <&i2s1_clk>, <&i2s1_gclk>, <&audio_pll_pmc>, <&i2s1muxck>; + clock-names = "pclk", "gclk", "aclk", "muxclk"; + status = "disabled"; + }; + can1: can@fc050000 { compatible = "bosch,m_can"; reg = <0xfc050000 0x4000>, <0x210000 0x4000>;
From: Cyrille Pitchen cyrille.pitchen@atmel.com
This patch sets the pin muxing for the I2S controllers
Signed-off-by: Cyrille Pitchen cyrille.pitchen@atmel.com [codrin.ciubotariu@microchip.com: added pin muxing for the second controller] Signed-off-by: Codrin Ciubotariu codrin.ciubotariu@microchip.com --- arch/arm/boot/dts/at91-sama5d2_xplained.dts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/arch/arm/boot/dts/at91-sama5d2_xplained.dts b/arch/arm/boot/dts/at91-sama5d2_xplained.dts index e4bbb7e..0391731 100644 --- a/arch/arm/boot/dts/at91-sama5d2_xplained.dts +++ b/arch/arm/boot/dts/at91-sama5d2_xplained.dts @@ -281,6 +281,11 @@ status = "okay"; };
+ i2s0: i2s@f8050000 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2s0_default>; + }; + can0: can@f8054000 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_can0_default>; @@ -424,6 +429,24 @@ bias-disable; };
+ pinctrl_i2s0_default: i2s0_default { + pinmux = <PIN_PC1__I2SC0_CK>, + <PIN_PC2__I2SC0_MCK>, + <PIN_PC3__I2SC0_WS>, + <PIN_PC4__I2SC0_DI0>, + <PIN_PC5__I2SC0_DO0>; + bias-disable; + }; + + pinctrl_i2s1_default: i2s1_default { + pinmux = <PIN_PA15__I2SC1_CK>, + <PIN_PA14__I2SC1_MCK>, + <PIN_PA16__I2SC1_WS>, + <PIN_PA17__I2SC1_DI0>, + <PIN_PA18__I2SC1_DO0>; + bias-disable; + }; + pinctrl_key_gpio_default: key_gpio_default { pinmux = <PIN_PB9__GPIO>; bias-pull-up; @@ -546,6 +569,11 @@ status = "okay"; };
+ i2s1: i2s@fc04c000 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2s1_default>; + }; + can1: can@fc050000 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_can1_default>;
participants (5)
-
Alexandre Belloni
-
Codrin Ciubotariu
-
Mark Brown
-
Rob Herring
-
Stephen Boyd