mirror of https://gitee.com/openkylin/linux.git
Merge remote-tracking branch 'asoc/topic/atmel' into asoc-next
This commit is contained in:
commit
3bc3249226
|
@ -0,0 +1,15 @@
|
|||
* Atmel SSC driver.
|
||||
|
||||
Required properties:
|
||||
- compatible: "atmel,at91rm9200-ssc" or "atmel,at91sam9g45-ssc"
|
||||
- atmel,at91rm9200-ssc: support pdc transfer
|
||||
- atmel,at91sam9g45-ssc: support dma transfer
|
||||
- reg: Should contain SSC registers location and length
|
||||
- interrupts: Should contain SSC interrupt
|
||||
|
||||
Example:
|
||||
ssc0: ssc@fffbc000 {
|
||||
compatible = "atmel,at91rm9200-ssc";
|
||||
reg = <0xfffbc000 0x4000>;
|
||||
interrupts = <14 4 5>;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
* Atmel at91sam9g20ek wm8731 audio complex
|
||||
|
||||
Required properties:
|
||||
- compatible: "atmel,at91sam9g20ek-wm8731-audio"
|
||||
- atmel,model: The user-visible name of this sound complex.
|
||||
- atmel,audio-routing: A list of the connections between audio components.
|
||||
- atmel,ssc-controller: The phandle of the SSC controller
|
||||
- atmel,audio-codec: The phandle of the WM8731 audio codec
|
||||
Optional properties:
|
||||
- pinctrl-names, pinctrl-0: Please refer to pinctrl-bindings.txt
|
||||
|
||||
Example:
|
||||
sound {
|
||||
compatible = "atmel,at91sam9g20ek-wm8731-audio";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_pck0_as_mck>;
|
||||
|
||||
atmel,model = "wm8731 @ AT91SAMG20EK";
|
||||
|
||||
atmel,audio-routing =
|
||||
"Ext Spk", "LHPOUT",
|
||||
"Int MIC", "MICIN";
|
||||
|
||||
atmel,ssc-controller = <&ssc0>;
|
||||
atmel,audio-codec = <&wm8731>;
|
||||
};
|
|
@ -29,6 +29,7 @@ aliases {
|
|||
tcb0 = &tcb0;
|
||||
tcb1 = &tcb1;
|
||||
i2c0 = &i2c0;
|
||||
ssc0 = &ssc0;
|
||||
};
|
||||
cpus {
|
||||
cpu@0 {
|
||||
|
@ -212,6 +213,13 @@ i2c0: i2c@fffac000 {
|
|||
status = "disabled";
|
||||
};
|
||||
|
||||
ssc0: ssc@fffbc000 {
|
||||
compatible = "atmel,at91rm9200-ssc";
|
||||
reg = <0xfffbc000 0x4000>;
|
||||
interrupts = <14 4 5>;
|
||||
status = "disable";
|
||||
};
|
||||
|
||||
adc0: adc@fffe0000 {
|
||||
compatible = "atmel,at91sam9260-adc";
|
||||
reg = <0xfffe0000 0x100>;
|
||||
|
|
|
@ -25,6 +25,8 @@ aliases {
|
|||
gpio4 = &pioE;
|
||||
tcb0 = &tcb0;
|
||||
i2c0 = &i2c0;
|
||||
ssc0 = &ssc0;
|
||||
ssc1 = &ssc1;
|
||||
};
|
||||
cpus {
|
||||
cpu@0 {
|
||||
|
@ -173,6 +175,20 @@ usart2: serial@fff94000 {
|
|||
status = "disabled";
|
||||
};
|
||||
|
||||
ssc0: ssc@fff98000 {
|
||||
compatible = "atmel,at91rm9200-ssc";
|
||||
reg = <0xfff98000 0x4000>;
|
||||
interrupts = <16 4 5>;
|
||||
status = "disable";
|
||||
};
|
||||
|
||||
ssc1: ssc@fff9c000 {
|
||||
compatible = "atmel,at91rm9200-ssc";
|
||||
reg = <0xfff9c000 0x4000>;
|
||||
interrupts = <17 4 5>;
|
||||
status = "disable";
|
||||
};
|
||||
|
||||
macb0: ethernet@fffbc000 {
|
||||
compatible = "cdns,at32ap7000-macb", "cdns,macb";
|
||||
reg = <0xfffbc000 0x100>;
|
||||
|
|
|
@ -30,6 +30,16 @@ main_clock: clock@0 {
|
|||
|
||||
ahb {
|
||||
apb {
|
||||
pinctrl@fffff400 {
|
||||
board {
|
||||
pinctrl_pck0_as_mck: pck0_as_mck {
|
||||
atmel,pins =
|
||||
<2 1 0x2 0x0>; /* PC1 periph B */
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
dbgu: serial@fffff200 {
|
||||
status = "okay";
|
||||
};
|
||||
|
@ -51,6 +61,11 @@ usb1: gadget@fffa4000 {
|
|||
atmel,vbus-gpio = <&pioC 5 0>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
ssc0: ssc@fffbc000 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&pinctrl_ssc0_tx>;
|
||||
};
|
||||
};
|
||||
|
||||
nand0: nand@40000000 {
|
||||
|
@ -114,7 +129,7 @@ i2c@0 {
|
|||
reg = <0x50>;
|
||||
};
|
||||
|
||||
wm8731@1b {
|
||||
wm8731: wm8731@1b {
|
||||
compatible = "wm8731";
|
||||
reg = <0x1b>;
|
||||
};
|
||||
|
@ -139,4 +154,19 @@ btn4 {
|
|||
gpio-key,wakeup;
|
||||
};
|
||||
};
|
||||
|
||||
sound {
|
||||
compatible = "atmel,at91sam9g20ek-wm8731-audio";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_pck0_as_mck>;
|
||||
|
||||
atmel,model = "wm8731 @ AT91SAMG20EK";
|
||||
|
||||
atmel,audio-routing =
|
||||
"Ext Spk", "LHPOUT",
|
||||
"Int Mic", "MICIN";
|
||||
|
||||
atmel,ssc-controller = <&ssc0>;
|
||||
atmel,audio-codec = <&wm8731>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -31,6 +31,8 @@ aliases {
|
|||
tcb1 = &tcb1;
|
||||
i2c0 = &i2c0;
|
||||
i2c1 = &i2c1;
|
||||
ssc0 = &ssc0;
|
||||
ssc1 = &ssc1;
|
||||
};
|
||||
cpus {
|
||||
cpu@0 {
|
||||
|
@ -226,6 +228,20 @@ i2c1: i2c@fff88000 {
|
|||
status = "disabled";
|
||||
};
|
||||
|
||||
ssc0: ssc@fff9c000 {
|
||||
compatible = "atmel,at91sam9g45-ssc";
|
||||
reg = <0xfff9c000 0x4000>;
|
||||
interrupts = <16 4 5>;
|
||||
status = "disable";
|
||||
};
|
||||
|
||||
ssc1: ssc@fffa0000 {
|
||||
compatible = "atmel,at91sam9g45-ssc";
|
||||
reg = <0xfffa0000 0x4000>;
|
||||
interrupts = <17 4 5>;
|
||||
status = "disable";
|
||||
};
|
||||
|
||||
adc0: adc@fffb0000 {
|
||||
compatible = "atmel,at91sam9260-adc";
|
||||
reg = <0xfffb0000 0x100>;
|
||||
|
|
|
@ -30,6 +30,7 @@ aliases {
|
|||
i2c0 = &i2c0;
|
||||
i2c1 = &i2c1;
|
||||
i2c2 = &i2c2;
|
||||
ssc0 = &ssc0;
|
||||
};
|
||||
cpus {
|
||||
cpu@0 {
|
||||
|
@ -87,6 +88,13 @@ pit: timer@fffffe30 {
|
|||
interrupts = <1 4 7>;
|
||||
};
|
||||
|
||||
ssc0: ssc@f0010000 {
|
||||
compatible = "atmel,at91sam9g45-ssc";
|
||||
reg = <0xf0010000 0x4000>;
|
||||
interrupts = <28 4 5>;
|
||||
status = "disable";
|
||||
};
|
||||
|
||||
tcb0: timer@f8008000 {
|
||||
compatible = "atmel,at91sam9x5-tcb";
|
||||
reg = <0xf8008000 0x100>;
|
||||
|
|
|
@ -184,9 +184,12 @@ static struct clk_lookup periph_clocks_lookups[] = {
|
|||
CLKDEV_CON_DEV_ID("t0_clk", "atmel_tcb.1", &tc3_clk),
|
||||
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.1", &tc4_clk),
|
||||
CLKDEV_CON_DEV_ID("t2_clk", "atmel_tcb.1", &tc5_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.2", &ssc2_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.2", &ssc2_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffd0000.ssc", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffd4000.ssc", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffd8000.ssc", &ssc2_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91rm9200.0", &twi_clk),
|
||||
/* fake hclk clock */
|
||||
CLKDEV_CON_DEV_ID("hclk", "at91_ohci", &ohci_clk),
|
||||
|
|
|
@ -752,7 +752,7 @@ static struct resource ssc0_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91rm9200_ssc0_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 0,
|
||||
.dev = {
|
||||
.dma_mask = &ssc0_dmamask,
|
||||
|
@ -794,7 +794,7 @@ static struct resource ssc1_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91rm9200_ssc1_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 1,
|
||||
.dev = {
|
||||
.dma_mask = &ssc1_dmamask,
|
||||
|
@ -836,7 +836,7 @@ static struct resource ssc2_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91rm9200_ssc2_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 2,
|
||||
.dev = {
|
||||
.dma_mask = &ssc2_dmamask,
|
||||
|
|
|
@ -210,7 +210,8 @@ static struct clk_lookup periph_clocks_lookups[] = {
|
|||
CLKDEV_CON_DEV_ID("t0_clk", "atmel_tcb.1", &tc3_clk),
|
||||
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.1", &tc4_clk),
|
||||
CLKDEV_CON_DEV_ID("t2_clk", "atmel_tcb.1", &tc5_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffbc000.ssc", &ssc_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9260.0", &twi_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g20.0", &twi_clk),
|
||||
/* more usart lookup table for DT entries */
|
||||
|
|
|
@ -742,7 +742,7 @@ static struct resource ssc_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9260_ssc_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 0,
|
||||
.dev = {
|
||||
.dma_mask = &ssc_dmamask,
|
||||
|
|
|
@ -174,9 +174,12 @@ static struct clk_lookup periph_clocks_lookups[] = {
|
|||
CLKDEV_CON_DEV_ID("t0_clk", "atmel_tcb.0", &tc0_clk),
|
||||
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.0", &tc1_clk),
|
||||
CLKDEV_CON_DEV_ID("t2_clk", "atmel_tcb.0", &tc2_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.2", &ssc2_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.2", &ssc2_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffbc000.ssc", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffc0000.ssc", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffc4000.ssc", &ssc2_clk),
|
||||
CLKDEV_CON_DEV_ID("hclk", "at91_ohci", &hck0),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9261.0", &twi_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g10.0", &twi_clk),
|
||||
|
|
|
@ -706,7 +706,7 @@ static struct resource ssc0_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9261_ssc0_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 0,
|
||||
.dev = {
|
||||
.dma_mask = &ssc0_dmamask,
|
||||
|
@ -748,7 +748,7 @@ static struct resource ssc1_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9261_ssc1_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 1,
|
||||
.dev = {
|
||||
.dma_mask = &ssc1_dmamask,
|
||||
|
@ -790,7 +790,7 @@ static struct resource ssc2_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9261_ssc2_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 2,
|
||||
.dev = {
|
||||
.dma_mask = &ssc2_dmamask,
|
||||
|
|
|
@ -186,8 +186,10 @@ static struct clk *periph_clocks[] __initdata = {
|
|||
static struct clk_lookup periph_clocks_lookups[] = {
|
||||
/* One additional fake clock for macb_hclk */
|
||||
CLKDEV_CON_ID("hclk", &macb_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fff98000.ssc", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fff9c000.ssc", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("mci_clk", "atmel_mci.0", &mmc0_clk),
|
||||
CLKDEV_CON_DEV_ID("mci_clk", "atmel_mci.1", &mmc1_clk),
|
||||
CLKDEV_CON_DEV_ID("spi_clk", "atmel_spi.0", &spi0_clk),
|
||||
|
|
|
@ -1199,7 +1199,7 @@ static struct resource ssc0_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9263_ssc0_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 0,
|
||||
.dev = {
|
||||
.dma_mask = &ssc0_dmamask,
|
||||
|
@ -1241,7 +1241,7 @@ static struct resource ssc1_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9263_ssc1_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 1,
|
||||
.dev = {
|
||||
.dma_mask = &ssc1_dmamask,
|
||||
|
|
|
@ -239,8 +239,10 @@ static struct clk_lookup periph_clocks_lookups[] = {
|
|||
CLKDEV_CON_DEV_ID("t0_clk", "atmel_tcb.1", &tcb0_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g10.0", &twi0_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g10.1", &twi1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91sam9g45_ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91sam9g45_ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fff9c000.ssc", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffa0000.ssc", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "atmel-trng", &trng_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "atmel_sha", &aestdessha_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "atmel_tdes", &aestdessha_clk),
|
||||
|
|
|
@ -1459,7 +1459,7 @@ static struct resource ssc0_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9g45_ssc0_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91sam9g45_ssc",
|
||||
.id = 0,
|
||||
.dev = {
|
||||
.dma_mask = &ssc0_dmamask,
|
||||
|
@ -1501,7 +1501,7 @@ static struct resource ssc1_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9g45_ssc1_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91sam9g45_ssc",
|
||||
.id = 1,
|
||||
.dev = {
|
||||
.dma_mask = &ssc1_dmamask,
|
||||
|
|
|
@ -184,8 +184,10 @@ static struct clk_lookup periph_clocks_lookups[] = {
|
|||
CLKDEV_CON_DEV_ID("t0_clk", "atmel_tcb.0", &tc0_clk),
|
||||
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.0", &tc1_clk),
|
||||
CLKDEV_CON_DEV_ID("t2_clk", "atmel_tcb.0", &tc2_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.1", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffc0000.ssc", &ssc0_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "fffc4000.ssc", &ssc1_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g20.0", &twi0_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g20.1", &twi1_clk),
|
||||
CLKDEV_CON_ID("pioA", &pioA_clk),
|
||||
|
|
|
@ -832,7 +832,7 @@ static struct resource ssc0_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9rl_ssc0_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 0,
|
||||
.dev = {
|
||||
.dma_mask = &ssc0_dmamask,
|
||||
|
@ -874,7 +874,7 @@ static struct resource ssc1_resources[] = {
|
|||
};
|
||||
|
||||
static struct platform_device at91sam9rl_ssc1_device = {
|
||||
.name = "ssc",
|
||||
.name = "at91rm9200_ssc",
|
||||
.id = 1,
|
||||
.dev = {
|
||||
.dma_mask = &ssc1_dmamask,
|
||||
|
|
|
@ -231,6 +231,7 @@ static struct clk_lookup periph_clocks_lookups[] = {
|
|||
CLKDEV_CON_DEV_ID("t0_clk", "f800c000.timer", &tcb0_clk),
|
||||
CLKDEV_CON_DEV_ID("dma_clk", "ffffec00.dma-controller", &dma0_clk),
|
||||
CLKDEV_CON_DEV_ID("dma_clk", "ffffee00.dma-controller", &dma1_clk),
|
||||
CLKDEV_CON_DEV_ID("pclk", "f0010000.ssc", &ssc_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "f8010000.i2c", &twi0_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "f8014000.i2c", &twi1_clk),
|
||||
CLKDEV_CON_DEV_ID(NULL, "f8018000.i2c", &twi2_clk),
|
||||
|
|
|
@ -353,6 +353,16 @@ static struct i2c_board_info __initdata ek_i2c_devices[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static struct platform_device sam9g20ek_audio_device = {
|
||||
.name = "at91sam9g20ek-audio",
|
||||
.id = -1,
|
||||
};
|
||||
|
||||
static void __init ek_add_device_audio(void)
|
||||
{
|
||||
platform_device_register(&sam9g20ek_audio_device);
|
||||
}
|
||||
|
||||
|
||||
static void __init ek_board_init(void)
|
||||
{
|
||||
|
@ -394,6 +404,7 @@ static void __init ek_board_init(void)
|
|||
at91_set_B_periph(AT91_PIN_PC1, 0);
|
||||
/* SSC (for WM8731) */
|
||||
at91_add_device_ssc(AT91SAM9260_ID_SSC, ATMEL_SSC_TX);
|
||||
ek_add_device_audio();
|
||||
}
|
||||
|
||||
MACHINE_START(AT91SAM9G20EK, "Atmel AT91SAM9G20-EK")
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/of.h>
|
||||
|
||||
/* Serialize access to ssc_list and user count */
|
||||
static DEFINE_SPINLOCK(user_lock);
|
||||
static LIST_HEAD(ssc_list);
|
||||
|
@ -29,7 +31,13 @@ struct ssc_device *ssc_request(unsigned int ssc_num)
|
|||
|
||||
spin_lock(&user_lock);
|
||||
list_for_each_entry(ssc, &ssc_list, list) {
|
||||
if (ssc->pdev->id == ssc_num) {
|
||||
if (ssc->pdev->dev.of_node) {
|
||||
if (of_alias_get_id(ssc->pdev->dev.of_node, "ssc")
|
||||
== ssc_num) {
|
||||
ssc_valid = 1;
|
||||
break;
|
||||
}
|
||||
} else if (ssc->pdev->id == ssc_num) {
|
||||
ssc_valid = 1;
|
||||
break;
|
||||
}
|
||||
|
@ -68,39 +76,93 @@ void ssc_free(struct ssc_device *ssc)
|
|||
}
|
||||
EXPORT_SYMBOL(ssc_free);
|
||||
|
||||
static int __init ssc_probe(struct platform_device *pdev)
|
||||
static struct atmel_ssc_platform_data at91rm9200_config = {
|
||||
.use_dma = 0,
|
||||
};
|
||||
|
||||
static struct atmel_ssc_platform_data at91sam9g45_config = {
|
||||
.use_dma = 1,
|
||||
};
|
||||
|
||||
static const struct platform_device_id atmel_ssc_devtypes[] = {
|
||||
{
|
||||
.name = "at91rm9200_ssc",
|
||||
.driver_data = (unsigned long) &at91rm9200_config,
|
||||
}, {
|
||||
.name = "at91sam9g45_ssc",
|
||||
.driver_data = (unsigned long) &at91sam9g45_config,
|
||||
}, {
|
||||
/* sentinel */
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id atmel_ssc_dt_ids[] = {
|
||||
{
|
||||
.compatible = "atmel,at91rm9200-ssc",
|
||||
.data = &at91rm9200_config,
|
||||
}, {
|
||||
.compatible = "atmel,at91sam9g45-ssc",
|
||||
.data = &at91sam9g45_config,
|
||||
}, {
|
||||
/* sentinel */
|
||||
}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, atmel_ssc_dt_ids);
|
||||
#endif
|
||||
|
||||
static inline const struct atmel_ssc_platform_data * __init
|
||||
atmel_ssc_get_driver_data(struct platform_device *pdev)
|
||||
{
|
||||
if (pdev->dev.of_node) {
|
||||
const struct of_device_id *match;
|
||||
match = of_match_node(atmel_ssc_dt_ids, pdev->dev.of_node);
|
||||
if (match == NULL)
|
||||
return NULL;
|
||||
return match->data;
|
||||
}
|
||||
|
||||
return (struct atmel_ssc_platform_data *)
|
||||
platform_get_device_id(pdev)->driver_data;
|
||||
}
|
||||
|
||||
static int ssc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int retval = 0;
|
||||
struct resource *regs;
|
||||
struct ssc_device *ssc;
|
||||
const struct atmel_ssc_platform_data *plat_dat;
|
||||
|
||||
ssc = kzalloc(sizeof(struct ssc_device), GFP_KERNEL);
|
||||
ssc = devm_kzalloc(&pdev->dev, sizeof(struct ssc_device), GFP_KERNEL);
|
||||
if (!ssc) {
|
||||
dev_dbg(&pdev->dev, "out of memory\n");
|
||||
retval = -ENOMEM;
|
||||
goto out;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ssc->pdev = pdev;
|
||||
|
||||
plat_dat = atmel_ssc_get_driver_data(pdev);
|
||||
if (!plat_dat)
|
||||
return -ENODEV;
|
||||
ssc->pdata = (struct atmel_ssc_platform_data *)plat_dat;
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!regs) {
|
||||
dev_dbg(&pdev->dev, "no mmio resource defined\n");
|
||||
retval = -ENXIO;
|
||||
goto out_free;
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ssc->clk = clk_get(&pdev->dev, "pclk");
|
||||
if (IS_ERR(ssc->clk)) {
|
||||
dev_dbg(&pdev->dev, "no pclk clock defined\n");
|
||||
retval = -ENXIO;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
ssc->pdev = pdev;
|
||||
ssc->regs = ioremap(regs->start, resource_size(regs));
|
||||
ssc->regs = devm_request_and_ioremap(&pdev->dev, regs);
|
||||
if (!ssc->regs) {
|
||||
dev_dbg(&pdev->dev, "ioremap failed\n");
|
||||
retval = -EINVAL;
|
||||
goto out_clk;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ssc->phybase = regs->start;
|
||||
|
||||
ssc->clk = devm_clk_get(&pdev->dev, "pclk");
|
||||
if (IS_ERR(ssc->clk)) {
|
||||
dev_dbg(&pdev->dev, "no pclk clock defined\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* disable all interrupts */
|
||||
|
@ -112,8 +174,7 @@ static int __init ssc_probe(struct platform_device *pdev)
|
|||
ssc->irq = platform_get_irq(pdev, 0);
|
||||
if (!ssc->irq) {
|
||||
dev_dbg(&pdev->dev, "could not get irq\n");
|
||||
retval = -ENXIO;
|
||||
goto out_unmap;
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
spin_lock(&user_lock);
|
||||
|
@ -125,16 +186,7 @@ static int __init ssc_probe(struct platform_device *pdev)
|
|||
dev_info(&pdev->dev, "Atmel SSC device at 0x%p (irq %d)\n",
|
||||
ssc->regs, ssc->irq);
|
||||
|
||||
goto out;
|
||||
|
||||
out_unmap:
|
||||
iounmap(ssc->regs);
|
||||
out_clk:
|
||||
clk_put(ssc->clk);
|
||||
out_free:
|
||||
kfree(ssc);
|
||||
out:
|
||||
return retval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit ssc_remove(struct platform_device *pdev)
|
||||
|
@ -142,34 +194,23 @@ static int __devexit ssc_remove(struct platform_device *pdev)
|
|||
struct ssc_device *ssc = platform_get_drvdata(pdev);
|
||||
|
||||
spin_lock(&user_lock);
|
||||
iounmap(ssc->regs);
|
||||
clk_put(ssc->clk);
|
||||
list_del(&ssc->list);
|
||||
kfree(ssc);
|
||||
spin_unlock(&user_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ssc_driver = {
|
||||
.remove = __devexit_p(ssc_remove),
|
||||
.driver = {
|
||||
.name = "ssc",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(atmel_ssc_dt_ids),
|
||||
},
|
||||
.id_table = atmel_ssc_devtypes,
|
||||
.probe = ssc_probe,
|
||||
.remove = __devexit_p(ssc_remove),
|
||||
};
|
||||
|
||||
static int __init ssc_init(void)
|
||||
{
|
||||
return platform_driver_probe(&ssc_driver, ssc_probe);
|
||||
}
|
||||
module_init(ssc_init);
|
||||
|
||||
static void __exit ssc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ssc_driver);
|
||||
}
|
||||
module_exit(ssc_exit);
|
||||
module_platform_driver(ssc_driver);
|
||||
|
||||
MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>");
|
||||
MODULE_DESCRIPTION("SSC driver for Atmel AVR32 and AT91");
|
||||
|
|
|
@ -5,10 +5,16 @@
|
|||
#include <linux/list.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
struct atmel_ssc_platform_data {
|
||||
int use_dma;
|
||||
};
|
||||
|
||||
struct ssc_device {
|
||||
struct list_head list;
|
||||
resource_size_t phybase;
|
||||
void __iomem *regs;
|
||||
struct platform_device *pdev;
|
||||
struct atmel_ssc_platform_data *pdata;
|
||||
struct clk *clk;
|
||||
int user;
|
||||
int irq;
|
||||
|
|
|
@ -6,6 +6,14 @@ config SND_ATMEL_SOC
|
|||
the ATMEL SSC interface. You will also need
|
||||
to select the audio interfaces to support below.
|
||||
|
||||
config SND_ATMEL_SOC_PDC
|
||||
tristate
|
||||
depends on SND_ATMEL_SOC
|
||||
|
||||
config SND_ATMEL_SOC_DMA
|
||||
tristate
|
||||
depends on SND_ATMEL_SOC
|
||||
|
||||
config SND_ATMEL_SOC_SSC
|
||||
tristate
|
||||
depends on SND_ATMEL_SOC
|
||||
|
@ -16,8 +24,8 @@ config SND_ATMEL_SOC_SSC
|
|||
|
||||
config SND_AT91_SOC_SAM9G20_WM8731
|
||||
tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
|
||||
depends on ATMEL_SSC && ARCH_AT91SAM9G20 && SND_ATMEL_SOC && \
|
||||
AT91_PROGRAMMABLE_CLOCKS
|
||||
depends on ATMEL_SSC && SND_ATMEL_SOC && AT91_PROGRAMMABLE_CLOCKS
|
||||
select SND_ATMEL_SOC_PDC
|
||||
select SND_ATMEL_SOC_SSC
|
||||
select SND_SOC_WM8731
|
||||
help
|
||||
|
@ -27,6 +35,7 @@ config SND_AT91_SOC_SAM9G20_WM8731
|
|||
config SND_AT91_SOC_AFEB9260
|
||||
tristate "SoC Audio support for AFEB9260 board"
|
||||
depends on ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
|
||||
select SND_ATMEL_SOC_PDC
|
||||
select SND_ATMEL_SOC_SSC
|
||||
select SND_SOC_TLV320AIC23
|
||||
help
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
# AT91 Platform Support
|
||||
snd-soc-atmel-pcm-objs := atmel-pcm.o
|
||||
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
|
||||
|
||||
obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.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
|
||||
|
||||
# AT91 Machine Support
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC.
|
||||
*
|
||||
* Copyright (C) 2012 Atmel
|
||||
*
|
||||
* Author: Bo Shen <voice.shen@atmel.com>
|
||||
*
|
||||
* Based on atmel-pcm by:
|
||||
* Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
||||
* Copyright 2008 Atmel
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/atmel-ssc.h>
|
||||
#include <linux/platform_data/dma-atmel.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#include "atmel-pcm.h"
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Hardware definition
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static const struct snd_pcm_hardware atmel_pcm_dma_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_RESUME |
|
||||
SNDRV_PCM_INFO_PAUSE,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.period_bytes_min = 256, /* lighting DMA overhead */
|
||||
.period_bytes_max = 2 * 0xffff, /* if 2 bytes format */
|
||||
.periods_min = 8,
|
||||
.periods_max = 1024, /* no limit */
|
||||
.buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,
|
||||
};
|
||||
|
||||
/**
|
||||
* atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC
|
||||
*
|
||||
* We use DMAENGINE to send/receive data to/from SSC so this ISR is only to
|
||||
* check if any overrun occured.
|
||||
*/
|
||||
static void atmel_pcm_dma_irq(u32 ssc_sr,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_pcm_dma_params *prtd;
|
||||
|
||||
prtd = snd_dmaengine_pcm_get_data(substream);
|
||||
|
||||
if (ssc_sr & prtd->mask->ssc_error) {
|
||||
if (snd_pcm_running(substream))
|
||||
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n",
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
|
||||
? "underrun" : "overrun", prtd->name,
|
||||
ssc_sr);
|
||||
|
||||
/* stop RX and capture: will be enabled again at restart */
|
||||
ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable);
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
|
||||
/* now drain RHR and read status to remove xrun condition */
|
||||
ssc_readx(prtd->ssc->regs, SSC_RHR);
|
||||
ssc_readx(prtd->ssc->regs, SSC_SR);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* DMAENGINE operations
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static bool filter(struct dma_chan *chan, void *slave)
|
||||
{
|
||||
struct at_dma_slave *sl = slave;
|
||||
|
||||
if (sl->dma_dev == chan->device->dev) {
|
||||
chan->private = sl;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct atmel_pcm_dma_params *prtd;
|
||||
struct ssc_device *ssc;
|
||||
struct dma_chan *dma_chan;
|
||||
struct dma_slave_config slave_config;
|
||||
int ret;
|
||||
|
||||
prtd = snd_dmaengine_pcm_get_data(substream);
|
||||
ssc = prtd->ssc;
|
||||
|
||||
ret = snd_hwparams_to_dma_slave_config(substream, params,
|
||||
&slave_config);
|
||||
if (ret) {
|
||||
pr_err("atmel-pcm: hwparams to dma slave configure failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
slave_config.dst_addr = (dma_addr_t)ssc->phybase + SSC_THR;
|
||||
slave_config.dst_maxburst = 1;
|
||||
} else {
|
||||
slave_config.src_addr = (dma_addr_t)ssc->phybase + SSC_RHR;
|
||||
slave_config.src_maxburst = 1;
|
||||
}
|
||||
|
||||
slave_config.device_fc = false;
|
||||
|
||||
dma_chan = snd_dmaengine_pcm_get_chan(substream);
|
||||
if (dmaengine_slave_config(dma_chan, &slave_config)) {
|
||||
pr_err("atmel-pcm: failed to configure dma channel\n");
|
||||
ret = -EBUSY;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct atmel_pcm_dma_params *prtd;
|
||||
struct ssc_device *ssc;
|
||||
struct at_dma_slave *sdata = NULL;
|
||||
int ret;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
ssc = prtd->ssc;
|
||||
if (ssc->pdev)
|
||||
sdata = ssc->pdev->dev.platform_data;
|
||||
|
||||
ret = snd_dmaengine_pcm_open(substream, filter, sdata);
|
||||
if (ret) {
|
||||
pr_err("atmel-pcm: dmaengine pcm open failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_dmaengine_pcm_set_data(substream, prtd);
|
||||
|
||||
ret = atmel_pcm_configure_dma(substream, params);
|
||||
if (ret) {
|
||||
pr_err("atmel-pcm: failed to configure dmai\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
prtd->dma_intr_handler = atmel_pcm_dma_irq;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
snd_dmaengine_pcm_close(substream);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_pcm_dma_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_pcm_dma_params *prtd;
|
||||
|
||||
prtd = snd_dmaengine_pcm_get_data(substream);
|
||||
|
||||
ssc_writex(prtd->ssc->regs, SSC_IER, prtd->mask->ssc_error);
|
||||
ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_enable);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_soc_set_runtime_hwparams(substream, &atmel_pcm_dma_hardware);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_dmaengine_pcm_close(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops atmel_pcm_ops = {
|
||||
.open = atmel_pcm_open,
|
||||
.close = atmel_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = atmel_pcm_hw_params,
|
||||
.prepare = atmel_pcm_dma_prepare,
|
||||
.trigger = snd_dmaengine_pcm_trigger,
|
||||
.pointer = snd_dmaengine_pcm_pointer_no_residue,
|
||||
.mmap = atmel_pcm_mmap,
|
||||
};
|
||||
|
||||
static struct snd_soc_platform_driver atmel_soc_platform = {
|
||||
.ops = &atmel_pcm_ops,
|
||||
.pcm_new = atmel_pcm_new,
|
||||
.pcm_free = atmel_pcm_free,
|
||||
};
|
||||
|
||||
int atmel_pcm_dma_platform_register(struct device *dev)
|
||||
{
|
||||
return snd_soc_register_platform(dev, &atmel_soc_platform);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_dma_platform_register);
|
||||
|
||||
void atmel_pcm_dma_platform_unregister(struct device *dev)
|
||||
{
|
||||
snd_soc_unregister_platform(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_dma_platform_unregister);
|
||||
|
||||
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
|
||||
MODULE_DESCRIPTION("Atmel DMA based PCM module");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,401 @@
|
|||
/*
|
||||
* atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
|
||||
*
|
||||
* Copyright (C) 2005 SAN People
|
||||
* Copyright (C) 2008 Atmel
|
||||
*
|
||||
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
||||
*
|
||||
* Based on at91-pcm. by:
|
||||
* Frank Mandarino <fmandarino@endrelia.com>
|
||||
* Copyright 2006 Endrelia Technologies Inc.
|
||||
*
|
||||
* Based on pxa2xx-pcm.c by:
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Nov 30, 2004
|
||||
* Copyright: (C) 2004 MontaVista Software, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/atmel_pdc.h>
|
||||
#include <linux/atmel-ssc.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "atmel-pcm.h"
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Hardware definition
|
||||
\*--------------------------------------------------------------------------*/
|
||||
/* TODO: These values were taken from the AT91 platform driver, check
|
||||
* them against real values for AT32
|
||||
*/
|
||||
static const struct snd_pcm_hardware atmel_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_PAUSE,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 8192,
|
||||
.periods_min = 2,
|
||||
.periods_max = 1024,
|
||||
.buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,
|
||||
};
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Data types
|
||||
\*--------------------------------------------------------------------------*/
|
||||
struct atmel_runtime_data {
|
||||
struct atmel_pcm_dma_params *params;
|
||||
dma_addr_t dma_buffer; /* physical address of dma buffer */
|
||||
dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
|
||||
size_t period_size;
|
||||
|
||||
dma_addr_t period_ptr; /* physical address of next period */
|
||||
|
||||
/* PDC register save */
|
||||
u32 pdc_xpr_save;
|
||||
u32 pdc_xcr_save;
|
||||
u32 pdc_xnpr_save;
|
||||
u32 pdc_xncr_save;
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* ISR
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static void atmel_pcm_dma_irq(u32 ssc_sr,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
static int count;
|
||||
|
||||
count++;
|
||||
|
||||
if (ssc_sr & params->mask->ssc_endbuf) {
|
||||
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
|
||||
? "underrun" : "overrun",
|
||||
params->name, ssc_sr, count);
|
||||
|
||||
/* re-start the PDC */
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
if (prtd->period_ptr >= prtd->dma_buffer_end)
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
}
|
||||
|
||||
if (ssc_sr & params->mask->ssc_endx) {
|
||||
/* Load the PDC next pointer and counter registers */
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
if (prtd->period_ptr >= prtd->dma_buffer_end)
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
}
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* PCM operations
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
/* this may get called several times by oss emulation
|
||||
* with different params */
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = params_buffer_bytes(params);
|
||||
|
||||
prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
|
||||
|
||||
prtd->dma_buffer = runtime->dma_addr;
|
||||
prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
|
||||
prtd->period_size = params_period_bytes(params);
|
||||
|
||||
pr_debug("atmel-pcm: "
|
||||
"hw_params: DMA for %s initialized "
|
||||
"(dma_bytes=%u, period_size=%u)\n",
|
||||
prtd->params->name,
|
||||
runtime->dma_bytes,
|
||||
prtd->period_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
|
||||
if (params != NULL) {
|
||||
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
prtd->params->dma_intr_handler = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
|
||||
ssc_writex(params->ssc->regs, SSC_IDR,
|
||||
params->mask->ssc_endx | params->mask->ssc_endbuf);
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *rtd = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = rtd->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("atmel-pcm:buffer_size = %ld,"
|
||||
"dma_area = %p, dma_bytes = %u\n",
|
||||
rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
|
||||
pr_debug("atmel-pcm: trigger: "
|
||||
"period_ptr=%lx, xpr=%u, "
|
||||
"xcr=%u, xnpr=%u, xncr=%u\n",
|
||||
(unsigned long)prtd->period_ptr,
|
||||
ssc_readx(params->ssc->regs, params->pdc->xpr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xcr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xnpr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xncr));
|
||||
|
||||
ssc_writex(params->ssc->regs, SSC_IER,
|
||||
params->mask->ssc_endx | params->mask->ssc_endbuf);
|
||||
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
|
||||
pr_debug("sr=%u imr=%u\n",
|
||||
ssc_readx(params->ssc->regs, SSC_SR),
|
||||
ssc_readx(params->ssc->regs, SSC_IER));
|
||||
break; /* SNDRV_PCM_TRIGGER_START */
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t atmel_pcm_pointer(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
dma_addr_t ptr;
|
||||
snd_pcm_uframes_t x;
|
||||
|
||||
ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
|
||||
x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
|
||||
|
||||
if (x == runtime->buffer_size)
|
||||
x = 0;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static int atmel_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd;
|
||||
int ret = 0;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
|
||||
|
||||
/* ensure that buffer size is a multiple of period size */
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
|
||||
if (prtd == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
runtime->private_data = prtd;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
kfree(prtd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops atmel_pcm_ops = {
|
||||
.open = atmel_pcm_open,
|
||||
.close = atmel_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = atmel_pcm_hw_params,
|
||||
.hw_free = atmel_pcm_hw_free,
|
||||
.prepare = atmel_pcm_prepare,
|
||||
.trigger = atmel_pcm_trigger,
|
||||
.pointer = atmel_pcm_pointer,
|
||||
.mmap = atmel_pcm_mmap,
|
||||
};
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* ASoC platform driver
|
||||
\*--------------------------------------------------------------------------*/
|
||||
#ifdef CONFIG_PM
|
||||
static int atmel_pcm_suspend(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = dai->runtime;
|
||||
struct atmel_runtime_data *prtd;
|
||||
struct atmel_pcm_dma_params *params;
|
||||
|
||||
if (!runtime)
|
||||
return 0;
|
||||
|
||||
prtd = runtime->private_data;
|
||||
params = prtd->params;
|
||||
|
||||
/* disable the PDC and save the PDC registers */
|
||||
|
||||
ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_disable);
|
||||
|
||||
prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr);
|
||||
prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr);
|
||||
prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr);
|
||||
prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_resume(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = dai->runtime;
|
||||
struct atmel_runtime_data *prtd;
|
||||
struct atmel_pcm_dma_params *params;
|
||||
|
||||
if (!runtime)
|
||||
return 0;
|
||||
|
||||
prtd = runtime->private_data;
|
||||
params = prtd->params;
|
||||
|
||||
/* restore the PDC registers and enable the PDC */
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save);
|
||||
|
||||
ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define atmel_pcm_suspend NULL
|
||||
#define atmel_pcm_resume NULL
|
||||
#endif
|
||||
|
||||
static struct snd_soc_platform_driver atmel_soc_platform = {
|
||||
.ops = &atmel_pcm_ops,
|
||||
.pcm_new = atmel_pcm_new,
|
||||
.pcm_free = atmel_pcm_free,
|
||||
.suspend = atmel_pcm_suspend,
|
||||
.resume = atmel_pcm_resume,
|
||||
};
|
||||
|
||||
int atmel_pcm_pdc_platform_register(struct device *dev)
|
||||
{
|
||||
return snd_soc_register_platform(dev, &atmel_soc_platform);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_pdc_platform_register);
|
||||
|
||||
void atmel_pcm_pdc_platform_unregister(struct device *dev)
|
||||
{
|
||||
snd_soc_unregister_platform(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_pdc_platform_unregister);
|
||||
|
||||
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
|
||||
MODULE_DESCRIPTION("Atmel PCM module");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -32,80 +32,25 @@
|
|||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/atmel_pdc.h>
|
||||
#include <linux/atmel-ssc.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "atmel-pcm.h"
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Hardware definition
|
||||
\*--------------------------------------------------------------------------*/
|
||||
/* TODO: These values were taken from the AT91 platform driver, check
|
||||
* them against real values for AT32
|
||||
*/
|
||||
static const struct snd_pcm_hardware atmel_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_PAUSE,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 8192,
|
||||
.periods_min = 2,
|
||||
.periods_max = 1024,
|
||||
.buffer_bytes_max = 32 * 1024,
|
||||
};
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Data types
|
||||
\*--------------------------------------------------------------------------*/
|
||||
struct atmel_runtime_data {
|
||||
struct atmel_pcm_dma_params *params;
|
||||
dma_addr_t dma_buffer; /* physical address of dma buffer */
|
||||
dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
|
||||
size_t period_size;
|
||||
|
||||
dma_addr_t period_ptr; /* physical address of next period */
|
||||
|
||||
/* PDC register save */
|
||||
u32 pdc_xpr_save;
|
||||
u32 pdc_xcr_save;
|
||||
u32 pdc_xnpr_save;
|
||||
u32 pdc_xncr_save;
|
||||
};
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Helper functions
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
|
||||
int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = atmel_pcm_hardware.buffer_bytes_max;
|
||||
size_t size = ATMEL_SSC_DMABUF_SIZE;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
buf->area = dma_alloc_coherent(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
pr_debug("atmel-pcm:"
|
||||
"preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
|
||||
(void *) buf->area,
|
||||
(void *) buf->addr,
|
||||
size);
|
||||
&buf->addr, GFP_KERNEL);
|
||||
pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%d\n",
|
||||
(void *)buf->area, (void *)buf->addr, size);
|
||||
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
|
@ -113,258 +58,19 @@ static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
|
|||
buf->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* ISR
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static void atmel_pcm_dma_irq(u32 ssc_sr,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
static int count;
|
||||
|
||||
count++;
|
||||
|
||||
if (ssc_sr & params->mask->ssc_endbuf) {
|
||||
pr_warning("atmel-pcm: buffer %s on %s"
|
||||
" (SSC_SR=%#x, count=%d)\n",
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
|
||||
? "underrun" : "overrun",
|
||||
params->name, ssc_sr, count);
|
||||
|
||||
/* re-start the PDC */
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
if (prtd->period_ptr >= prtd->dma_buffer_end)
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
}
|
||||
|
||||
if (ssc_sr & params->mask->ssc_endx) {
|
||||
/* Load the PDC next pointer and counter registers */
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
if (prtd->period_ptr >= prtd->dma_buffer_end)
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
}
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* PCM operations
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
/* this may get called several times by oss emulation
|
||||
* with different params */
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = params_buffer_bytes(params);
|
||||
|
||||
prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
|
||||
|
||||
prtd->dma_buffer = runtime->dma_addr;
|
||||
prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
|
||||
prtd->period_size = params_period_bytes(params);
|
||||
|
||||
pr_debug("atmel-pcm: "
|
||||
"hw_params: DMA for %s initialized "
|
||||
"(dma_bytes=%u, period_size=%u)\n",
|
||||
prtd->params->name,
|
||||
runtime->dma_bytes,
|
||||
prtd->period_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
|
||||
if (params != NULL) {
|
||||
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
prtd->params->dma_intr_handler = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
|
||||
ssc_writex(params->ssc->regs, SSC_IDR,
|
||||
params->mask->ssc_endx | params->mask->ssc_endbuf);
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *rtd = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = rtd->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("atmel-pcm:buffer_size = %ld,"
|
||||
"dma_area = %p, dma_bytes = %u\n",
|
||||
rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
|
||||
pr_debug("atmel-pcm: trigger: "
|
||||
"period_ptr=%lx, xpr=%u, "
|
||||
"xcr=%u, xnpr=%u, xncr=%u\n",
|
||||
(unsigned long)prtd->period_ptr,
|
||||
ssc_readx(params->ssc->regs, params->pdc->xpr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xcr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xnpr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xncr));
|
||||
|
||||
ssc_writex(params->ssc->regs, SSC_IER,
|
||||
params->mask->ssc_endx | params->mask->ssc_endbuf);
|
||||
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
|
||||
pr_debug("sr=%u imr=%u\n",
|
||||
ssc_readx(params->ssc->regs, SSC_SR),
|
||||
ssc_readx(params->ssc->regs, SSC_IER));
|
||||
break; /* SNDRV_PCM_TRIGGER_START */
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t atmel_pcm_pointer(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
dma_addr_t ptr;
|
||||
snd_pcm_uframes_t x;
|
||||
|
||||
ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
|
||||
x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
|
||||
|
||||
if (x == runtime->buffer_size)
|
||||
x = 0;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static int atmel_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd;
|
||||
int ret = 0;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
|
||||
|
||||
/* ensure that buffer size is a multiple of period size */
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
|
||||
if (prtd == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
runtime->private_data = prtd;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
kfree(prtd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
int atmel_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
return remap_pfn_range(vma, vma->vm_start,
|
||||
substream->dma_buffer.addr >> PAGE_SHIFT,
|
||||
vma->vm_end - vma->vm_start, vma->vm_page_prot);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_pcm_mmap);
|
||||
|
||||
static struct snd_pcm_ops atmel_pcm_ops = {
|
||||
.open = atmel_pcm_open,
|
||||
.close = atmel_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = atmel_pcm_hw_params,
|
||||
.hw_free = atmel_pcm_hw_free,
|
||||
.prepare = atmel_pcm_prepare,
|
||||
.trigger = atmel_pcm_trigger,
|
||||
.pointer = atmel_pcm_pointer,
|
||||
.mmap = atmel_pcm_mmap,
|
||||
};
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* ASoC platform driver
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static u64 atmel_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
|
@ -376,6 +82,7 @@ static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
|||
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n");
|
||||
ret = atmel_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
|
@ -383,8 +90,7 @@ static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
|||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
pr_debug("atmel-pcm:"
|
||||
"Allocating PCM capture DMA buffer\n");
|
||||
pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n");
|
||||
ret = atmel_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
|
@ -393,8 +99,9 @@ static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
|||
out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_pcm_new);
|
||||
|
||||
static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
void atmel_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
|
@ -413,89 +120,5 @@ static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
|||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_pcm_free);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int atmel_pcm_suspend(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = dai->runtime;
|
||||
struct atmel_runtime_data *prtd;
|
||||
struct atmel_pcm_dma_params *params;
|
||||
|
||||
if (!runtime)
|
||||
return 0;
|
||||
|
||||
prtd = runtime->private_data;
|
||||
params = prtd->params;
|
||||
|
||||
/* disable the PDC and save the PDC registers */
|
||||
|
||||
ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_disable);
|
||||
|
||||
prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr);
|
||||
prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr);
|
||||
prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr);
|
||||
prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_resume(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = dai->runtime;
|
||||
struct atmel_runtime_data *prtd;
|
||||
struct atmel_pcm_dma_params *params;
|
||||
|
||||
if (!runtime)
|
||||
return 0;
|
||||
|
||||
prtd = runtime->private_data;
|
||||
params = prtd->params;
|
||||
|
||||
/* restore the PDC registers and enable the PDC */
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save);
|
||||
|
||||
ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define atmel_pcm_suspend NULL
|
||||
#define atmel_pcm_resume NULL
|
||||
#endif
|
||||
|
||||
static struct snd_soc_platform_driver atmel_soc_platform = {
|
||||
.ops = &atmel_pcm_ops,
|
||||
.pcm_new = atmel_pcm_new,
|
||||
.pcm_free = atmel_pcm_free_dma_buffers,
|
||||
.suspend = atmel_pcm_suspend,
|
||||
.resume = atmel_pcm_resume,
|
||||
};
|
||||
|
||||
static int __devinit atmel_soc_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
return snd_soc_register_platform(&pdev->dev, &atmel_soc_platform);
|
||||
}
|
||||
|
||||
static int __devexit atmel_soc_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_platform(&pdev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver atmel_pcm_driver = {
|
||||
.driver = {
|
||||
.name = "atmel-pcm-audio",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
|
||||
.probe = atmel_soc_platform_probe,
|
||||
.remove = __devexit_p(atmel_soc_platform_remove),
|
||||
};
|
||||
|
||||
module_platform_driver(atmel_pcm_driver);
|
||||
|
||||
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
|
||||
MODULE_DESCRIPTION("Atmel PCM module");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
|
||||
#include <linux/atmel-ssc.h>
|
||||
|
||||
#define ATMEL_SSC_DMABUF_SIZE (64 * 1024)
|
||||
|
||||
/*
|
||||
* Registers and status bits that are required by the PCM driver.
|
||||
*/
|
||||
|
@ -50,6 +52,7 @@ struct atmel_pdc_regs {
|
|||
struct atmel_ssc_mask {
|
||||
u32 ssc_enable; /* SSC recv/trans enable */
|
||||
u32 ssc_disable; /* SSC recv/trans disable */
|
||||
u32 ssc_error; /* SSC error conditions */
|
||||
u32 ssc_endx; /* SSC ENDTX or ENDRX */
|
||||
u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
|
||||
u32 pdc_enable; /* PDC recv/trans enable */
|
||||
|
@ -80,4 +83,35 @@ struct atmel_pcm_dma_params {
|
|||
#define ssc_readx(base, reg) (__raw_readl((base) + (reg)))
|
||||
#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg))
|
||||
|
||||
int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd);
|
||||
void atmel_pcm_free(struct snd_pcm *pcm);
|
||||
int atmel_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma);
|
||||
|
||||
#ifdef CONFIG_SND_ATMEL_SOC_PDC
|
||||
int atmel_pcm_pdc_platform_register(struct device *dev);
|
||||
void atmel_pcm_pdc_platform_unregister(struct device *dev);
|
||||
#else
|
||||
static inline int atmel_pcm_pdc_platform_register(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void atmel_pcm_pdc_platform_unregister(struct device *dev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SND_ATMEL_SOC_DMA
|
||||
int atmel_pcm_dma_platform_register(struct device *dev);
|
||||
void atmel_pcm_dma_platform_unregister(struct device *dev);
|
||||
#else
|
||||
static inline int atmel_pcm_dma_platform_register(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void atmel_pcm_dma_platform_unregister(struct device *dev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ATMEL_PCM_H */
|
||||
|
|
|
@ -48,11 +48,7 @@
|
|||
#include "atmel_ssc_dai.h"
|
||||
|
||||
|
||||
#if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20)
|
||||
#define NUM_SSC_DEVICES 1
|
||||
#else
|
||||
#define NUM_SSC_DEVICES 3
|
||||
#endif
|
||||
|
||||
/*
|
||||
* SSC PDC registers required by the PCM DMA engine.
|
||||
|
@ -107,7 +103,6 @@ static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
|
|||
.pdc = &pdc_rx_reg,
|
||||
.mask = &ssc_rx_mask,
|
||||
} },
|
||||
#if NUM_SSC_DEVICES == 3
|
||||
{{
|
||||
.name = "SSC1 PCM out",
|
||||
.pdc = &pdc_tx_reg,
|
||||
|
@ -128,7 +123,6 @@ static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
|
|||
.pdc = &pdc_rx_reg,
|
||||
.mask = &ssc_rx_mask,
|
||||
} },
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
@ -139,7 +133,6 @@ static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
|
|||
.dir_mask = SSC_DIR_MASK_UNUSED,
|
||||
.initialized = 0,
|
||||
},
|
||||
#if NUM_SSC_DEVICES == 3
|
||||
{
|
||||
.name = "ssc1",
|
||||
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
|
||||
|
@ -152,7 +145,6 @@ static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
|
|||
.dir_mask = SSC_DIR_MASK_UNUSED,
|
||||
.initialized = 0,
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
@ -690,27 +682,9 @@ static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai)
|
|||
static int atmel_ssc_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
|
||||
int ret = 0;
|
||||
|
||||
snd_soc_dai_set_drvdata(dai, ssc_p);
|
||||
|
||||
/*
|
||||
* Request SSC device
|
||||
*/
|
||||
ssc_p->ssc = ssc_request(dai->id);
|
||||
if (IS_ERR(ssc_p->ssc)) {
|
||||
printk(KERN_ERR "ASoC: Failed to request SSC %d\n", dai->id);
|
||||
ret = PTR_ERR(ssc_p->ssc);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_ssc_remove(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct atmel_ssc_info *ssc_p = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
ssc_free(ssc_p->ssc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -728,11 +702,8 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = {
|
|||
.set_clkdiv = atmel_ssc_set_dai_clkdiv,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = {
|
||||
{
|
||||
.name = "atmel-ssc-dai.0",
|
||||
static struct snd_soc_dai_driver atmel_ssc_dai = {
|
||||
.probe = atmel_ssc_probe,
|
||||
.remove = atmel_ssc_remove,
|
||||
.suspend = atmel_ssc_suspend,
|
||||
.resume = atmel_ssc_resume,
|
||||
.playback = {
|
||||
|
@ -746,69 +717,50 @@ static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = {
|
|||
.rates = ATMEL_SSC_RATES,
|
||||
.formats = ATMEL_SSC_FORMATS,},
|
||||
.ops = &atmel_ssc_dai_ops,
|
||||
},
|
||||
#if NUM_SSC_DEVICES == 3
|
||||
{
|
||||
.name = "atmel-ssc-dai.1",
|
||||
.probe = atmel_ssc_probe,
|
||||
.remove = atmel_ssc_remove,
|
||||
.suspend = atmel_ssc_suspend,
|
||||
.resume = atmel_ssc_resume,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = ATMEL_SSC_RATES,
|
||||
.formats = ATMEL_SSC_FORMATS,},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = ATMEL_SSC_RATES,
|
||||
.formats = ATMEL_SSC_FORMATS,},
|
||||
.ops = &atmel_ssc_dai_ops,
|
||||
},
|
||||
{
|
||||
.name = "atmel-ssc-dai.2",
|
||||
.probe = atmel_ssc_probe,
|
||||
.remove = atmel_ssc_remove,
|
||||
.suspend = atmel_ssc_suspend,
|
||||
.resume = atmel_ssc_resume,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = ATMEL_SSC_RATES,
|
||||
.formats = ATMEL_SSC_FORMATS,},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = ATMEL_SSC_RATES,
|
||||
.formats = ATMEL_SSC_FORMATS,},
|
||||
.ops = &atmel_ssc_dai_ops,
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
static __devinit int asoc_ssc_probe(struct platform_device *pdev)
|
||||
static int asoc_ssc_init(struct device *dev)
|
||||
{
|
||||
BUG_ON(pdev->id < 0);
|
||||
BUG_ON(pdev->id >= ARRAY_SIZE(atmel_ssc_dai));
|
||||
return snd_soc_register_dai(&pdev->dev, &atmel_ssc_dai[pdev->id]);
|
||||
}
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct ssc_device *ssc = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_register_dai(dev, &atmel_ssc_dai);
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not register DAI: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ssc->pdata->use_dma)
|
||||
ret = atmel_pcm_dma_platform_register(dev);
|
||||
else
|
||||
ret = atmel_pcm_pdc_platform_register(dev);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not register PCM: %d\n", ret);
|
||||
goto err_unregister_dai;
|
||||
};
|
||||
|
||||
static int __devexit asoc_ssc_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_dai(&pdev->dev);
|
||||
return 0;
|
||||
|
||||
err_unregister_dai:
|
||||
snd_soc_unregister_dai(dev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver asoc_ssc_driver = {
|
||||
.driver = {
|
||||
.name = "atmel-ssc-dai",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
static void asoc_ssc_exit(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct ssc_device *ssc = platform_get_drvdata(pdev);
|
||||
|
||||
.probe = asoc_ssc_probe,
|
||||
.remove = __devexit_p(asoc_ssc_remove),
|
||||
};
|
||||
if (ssc->pdata->use_dma)
|
||||
atmel_pcm_dma_platform_unregister(dev);
|
||||
else
|
||||
atmel_pcm_pdc_platform_unregister(dev);
|
||||
|
||||
snd_soc_unregister_dai(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* atmel_ssc_set_audio - Allocate the specified SSC for audio use.
|
||||
|
@ -816,50 +768,32 @@ static struct platform_driver asoc_ssc_driver = {
|
|||
int atmel_ssc_set_audio(int ssc_id)
|
||||
{
|
||||
struct ssc_device *ssc;
|
||||
static struct platform_device *dma_pdev;
|
||||
struct platform_device *ssc_pdev;
|
||||
int ret;
|
||||
|
||||
if (ssc_id < 0 || ssc_id >= ARRAY_SIZE(atmel_ssc_dai))
|
||||
return -EINVAL;
|
||||
|
||||
/* Allocate a dummy device for DMA if we don't have one already */
|
||||
if (!dma_pdev) {
|
||||
dma_pdev = platform_device_alloc("atmel-pcm-audio", -1);
|
||||
if (!dma_pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = platform_device_add(dma_pdev);
|
||||
if (ret < 0) {
|
||||
platform_device_put(dma_pdev);
|
||||
dma_pdev = NULL;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ssc_pdev = platform_device_alloc("atmel-ssc-dai", ssc_id);
|
||||
if (!ssc_pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
/* If we can grab the SSC briefly to parent the DAI device off it */
|
||||
ssc = ssc_request(ssc_id);
|
||||
if (IS_ERR(ssc))
|
||||
pr_warn("Unable to parent ASoC SSC DAI on SSC: %ld\n",
|
||||
if (IS_ERR(ssc)) {
|
||||
pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n",
|
||||
PTR_ERR(ssc));
|
||||
else {
|
||||
ssc_pdev->dev.parent = &(ssc->pdev->dev);
|
||||
ssc_free(ssc);
|
||||
return PTR_ERR(ssc);
|
||||
} else {
|
||||
ssc_info[ssc_id].ssc = ssc;
|
||||
}
|
||||
|
||||
ret = platform_device_add(ssc_pdev);
|
||||
if (ret < 0)
|
||||
platform_device_put(ssc_pdev);
|
||||
ret = asoc_ssc_init(&ssc->pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_ssc_set_audio);
|
||||
|
||||
module_platform_driver(asoc_ssc_driver);
|
||||
void atmel_ssc_put_audio(int ssc_id)
|
||||
{
|
||||
struct ssc_device *ssc = ssc_info[ssc_id].ssc;
|
||||
|
||||
ssc_free(ssc);
|
||||
asoc_ssc_exit(&ssc->pdev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_ssc_put_audio);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
|
||||
|
|
|
@ -117,6 +117,7 @@ struct atmel_ssc_info {
|
|||
struct atmel_ssc_state ssc_state;
|
||||
};
|
||||
|
||||
int atmel_ssc_set_audio(int ssc);
|
||||
int atmel_ssc_set_audio(int ssc_id);
|
||||
void atmel_ssc_put_audio(int ssc_id);
|
||||
|
||||
#endif /* _AT91_SSC_DAI_H */
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
|
||||
#include <linux/atmel-ssc.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
|
@ -179,10 +181,10 @@ static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
|
|||
static struct snd_soc_dai_link at91sam9g20ek_dai = {
|
||||
.name = "WM8731",
|
||||
.stream_name = "WM8731 PCM",
|
||||
.cpu_dai_name = "atmel-ssc-dai.0",
|
||||
.cpu_dai_name = "at91rm9200_ssc.0",
|
||||
.codec_dai_name = "wm8731-hifi",
|
||||
.init = at91sam9g20ek_wm8731_init,
|
||||
.platform_name = "atmel-pcm-audio",
|
||||
.platform_name = "at91rm9200_ssc.0",
|
||||
.codec_name = "wm8731.0-001b",
|
||||
.ops = &at91sam9g20ek_ops,
|
||||
};
|
||||
|
@ -195,20 +197,31 @@ static struct snd_soc_card snd_soc_at91sam9g20ek = {
|
|||
.set_bias_level = at91sam9g20ek_set_bias_level,
|
||||
};
|
||||
|
||||
static struct platform_device *at91sam9g20ek_snd_device;
|
||||
|
||||
static int __init at91sam9g20ek_init(void)
|
||||
static int __devinit at91sam9g20ek_audio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *codec_np, *cpu_np;
|
||||
struct clk *pllb;
|
||||
struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
|
||||
struct pinctrl *pinctrl;
|
||||
int ret;
|
||||
|
||||
if (!(machine_is_at91sam9g20ek() || machine_is_at91sam9g20ek_2mmc()))
|
||||
return -ENODEV;
|
||||
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
|
||||
if (IS_ERR(pinctrl)) {
|
||||
dev_err(&pdev->dev, "Failed to request pinctrl for mck\n");
|
||||
return PTR_ERR(pinctrl);
|
||||
}
|
||||
|
||||
if (!np) {
|
||||
if (!(machine_is_at91sam9g20ek() ||
|
||||
machine_is_at91sam9g20ek_2mmc()))
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = atmel_ssc_set_audio(0);
|
||||
if (ret != 0) {
|
||||
pr_err("Failed to set SSC 0 for audio: %d\n", ret);
|
||||
return ret;
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "ssc channel is not valid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -236,45 +249,92 @@ static int __init at91sam9g20ek_init(void)
|
|||
|
||||
clk_set_rate(mclk, MCLK_RATE);
|
||||
|
||||
at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!at91sam9g20ek_snd_device) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_mclk;
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
/* Parse device node info */
|
||||
if (np) {
|
||||
ret = snd_soc_of_parse_card_name(card, "atmel,model");
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = snd_soc_of_parse_audio_routing(card,
|
||||
"atmel,audio-routing");
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* Parse codec info */
|
||||
at91sam9g20ek_dai.codec_name = NULL;
|
||||
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "codec info missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
at91sam9g20ek_dai.codec_of_node = codec_np;
|
||||
|
||||
/* Parse dai and platform info */
|
||||
at91sam9g20ek_dai.cpu_dai_name = NULL;
|
||||
at91sam9g20ek_dai.platform_name = NULL;
|
||||
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "dai and pcm info missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
at91sam9g20ek_dai.cpu_of_node = cpu_np;
|
||||
at91sam9g20ek_dai.platform_of_node = cpu_np;
|
||||
|
||||
of_node_put(codec_np);
|
||||
of_node_put(cpu_np);
|
||||
}
|
||||
|
||||
platform_set_drvdata(at91sam9g20ek_snd_device,
|
||||
&snd_soc_at91sam9g20ek);
|
||||
|
||||
ret = platform_device_add(at91sam9g20ek_snd_device);
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
goto err_device_add;
|
||||
printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
err_device_add:
|
||||
platform_device_put(at91sam9g20ek_snd_device);
|
||||
err_mclk:
|
||||
clk_put(mclk);
|
||||
mclk = NULL;
|
||||
err:
|
||||
atmel_ssc_put_audio(0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit at91sam9g20ek_exit(void)
|
||||
static int __devexit at91sam9g20ek_audio_remove(struct platform_device *pdev)
|
||||
{
|
||||
platform_device_unregister(at91sam9g20ek_snd_device);
|
||||
at91sam9g20ek_snd_device = NULL;
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
atmel_ssc_put_audio(0);
|
||||
snd_soc_unregister_card(card);
|
||||
clk_put(mclk);
|
||||
mclk = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(at91sam9g20ek_init);
|
||||
module_exit(at91sam9g20ek_exit);
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = {
|
||||
{ .compatible = "atmel,at91sam9g20ek-wm8731-audio", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids);
|
||||
#endif
|
||||
|
||||
static struct platform_driver at91sam9g20ek_audio_driver = {
|
||||
.driver = {
|
||||
.name = "at91sam9g20ek-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
|
||||
},
|
||||
.probe = at91sam9g20ek_audio_probe,
|
||||
.remove = __devexit_p(at91sam9g20ek_audio_remove),
|
||||
};
|
||||
|
||||
module_platform_driver(at91sam9g20ek_audio_driver);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
|
||||
MODULE_ALIAS("platform:at91sam9g20ek-audio");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
Loading…
Reference in New Issue