1
0
Fork 0

Merge remote-tracking branch 'asoc/topic/atmel' into asoc-next

hifive-unleashed-5.1
Mark Brown 2012-12-02 13:34:58 +09:00
commit 3bc3249226
32 changed files with 1108 additions and 611 deletions

View File

@ -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>;
};

View File

@ -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>;
};

View File

@ -29,6 +29,7 @@
tcb0 = &tcb0; tcb0 = &tcb0;
tcb1 = &tcb1; tcb1 = &tcb1;
i2c0 = &i2c0; i2c0 = &i2c0;
ssc0 = &ssc0;
}; };
cpus { cpus {
cpu@0 { cpu@0 {
@ -212,6 +213,13 @@
status = "disabled"; status = "disabled";
}; };
ssc0: ssc@fffbc000 {
compatible = "atmel,at91rm9200-ssc";
reg = <0xfffbc000 0x4000>;
interrupts = <14 4 5>;
status = "disable";
};
adc0: adc@fffe0000 { adc0: adc@fffe0000 {
compatible = "atmel,at91sam9260-adc"; compatible = "atmel,at91sam9260-adc";
reg = <0xfffe0000 0x100>; reg = <0xfffe0000 0x100>;

View File

@ -25,6 +25,8 @@
gpio4 = &pioE; gpio4 = &pioE;
tcb0 = &tcb0; tcb0 = &tcb0;
i2c0 = &i2c0; i2c0 = &i2c0;
ssc0 = &ssc0;
ssc1 = &ssc1;
}; };
cpus { cpus {
cpu@0 { cpu@0 {
@ -173,6 +175,20 @@
status = "disabled"; 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 { macb0: ethernet@fffbc000 {
compatible = "cdns,at32ap7000-macb", "cdns,macb"; compatible = "cdns,at32ap7000-macb", "cdns,macb";
reg = <0xfffbc000 0x100>; reg = <0xfffbc000 0x100>;

View File

@ -30,6 +30,16 @@
ahb { ahb {
apb { apb {
pinctrl@fffff400 {
board {
pinctrl_pck0_as_mck: pck0_as_mck {
atmel,pins =
<2 1 0x2 0x0>; /* PC1 periph B */
};
};
};
dbgu: serial@fffff200 { dbgu: serial@fffff200 {
status = "okay"; status = "okay";
}; };
@ -51,6 +61,11 @@
atmel,vbus-gpio = <&pioC 5 0>; atmel,vbus-gpio = <&pioC 5 0>;
status = "okay"; status = "okay";
}; };
ssc0: ssc@fffbc000 {
status = "okay";
pinctrl-0 = <&pinctrl_ssc0_tx>;
};
}; };
nand0: nand@40000000 { nand0: nand@40000000 {
@ -114,7 +129,7 @@
reg = <0x50>; reg = <0x50>;
}; };
wm8731@1b { wm8731: wm8731@1b {
compatible = "wm8731"; compatible = "wm8731";
reg = <0x1b>; reg = <0x1b>;
}; };
@ -139,4 +154,19 @@
gpio-key,wakeup; 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>;
};
}; };

View File

@ -31,6 +31,8 @@
tcb1 = &tcb1; tcb1 = &tcb1;
i2c0 = &i2c0; i2c0 = &i2c0;
i2c1 = &i2c1; i2c1 = &i2c1;
ssc0 = &ssc0;
ssc1 = &ssc1;
}; };
cpus { cpus {
cpu@0 { cpu@0 {
@ -226,6 +228,20 @@
status = "disabled"; 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 { adc0: adc@fffb0000 {
compatible = "atmel,at91sam9260-adc"; compatible = "atmel,at91sam9260-adc";
reg = <0xfffb0000 0x100>; reg = <0xfffb0000 0x100>;

View File

@ -30,6 +30,7 @@
i2c0 = &i2c0; i2c0 = &i2c0;
i2c1 = &i2c1; i2c1 = &i2c1;
i2c2 = &i2c2; i2c2 = &i2c2;
ssc0 = &ssc0;
}; };
cpus { cpus {
cpu@0 { cpu@0 {
@ -87,6 +88,13 @@
interrupts = <1 4 7>; interrupts = <1 4 7>;
}; };
ssc0: ssc@f0010000 {
compatible = "atmel,at91sam9g45-ssc";
reg = <0xf0010000 0x4000>;
interrupts = <28 4 5>;
status = "disable";
};
tcb0: timer@f8008000 { tcb0: timer@f8008000 {
compatible = "atmel,at91sam9x5-tcb"; compatible = "atmel,at91sam9x5-tcb";
reg = <0xf8008000 0x100>; reg = <0xf8008000 0x100>;

View File

@ -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("t0_clk", "atmel_tcb.1", &tc3_clk),
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.1", &tc4_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("t2_clk", "atmel_tcb.1", &tc5_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk), CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_clk), CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.1", &ssc1_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.2", &ssc2_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), CLKDEV_CON_DEV_ID(NULL, "i2c-at91rm9200.0", &twi_clk),
/* fake hclk clock */ /* fake hclk clock */
CLKDEV_CON_DEV_ID("hclk", "at91_ohci", &ohci_clk), CLKDEV_CON_DEV_ID("hclk", "at91_ohci", &ohci_clk),

View File

@ -752,7 +752,7 @@ static struct resource ssc0_resources[] = {
}; };
static struct platform_device at91rm9200_ssc0_device = { static struct platform_device at91rm9200_ssc0_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 0, .id = 0,
.dev = { .dev = {
.dma_mask = &ssc0_dmamask, .dma_mask = &ssc0_dmamask,
@ -794,7 +794,7 @@ static struct resource ssc1_resources[] = {
}; };
static struct platform_device at91rm9200_ssc1_device = { static struct platform_device at91rm9200_ssc1_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 1, .id = 1,
.dev = { .dev = {
.dma_mask = &ssc1_dmamask, .dma_mask = &ssc1_dmamask,
@ -836,7 +836,7 @@ static struct resource ssc2_resources[] = {
}; };
static struct platform_device at91rm9200_ssc2_device = { static struct platform_device at91rm9200_ssc2_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 2, .id = 2,
.dev = { .dev = {
.dma_mask = &ssc2_dmamask, .dma_mask = &ssc2_dmamask,

View File

@ -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("t0_clk", "atmel_tcb.1", &tc3_clk),
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.1", &tc4_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("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-at91sam9260.0", &twi_clk),
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g20.0", &twi_clk), CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g20.0", &twi_clk),
/* more usart lookup table for DT entries */ /* more usart lookup table for DT entries */

View File

@ -742,7 +742,7 @@ static struct resource ssc_resources[] = {
}; };
static struct platform_device at91sam9260_ssc_device = { static struct platform_device at91sam9260_ssc_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 0, .id = 0,
.dev = { .dev = {
.dma_mask = &ssc_dmamask, .dma_mask = &ssc_dmamask,

View File

@ -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("t0_clk", "atmel_tcb.0", &tc0_clk),
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.0", &tc1_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("t2_clk", "atmel_tcb.0", &tc2_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk), CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_clk), CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.1", &ssc1_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.2", &ssc2_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("hclk", "at91_ohci", &hck0),
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9261.0", &twi_clk), CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9261.0", &twi_clk),
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g10.0", &twi_clk), CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g10.0", &twi_clk),

View File

@ -706,7 +706,7 @@ static struct resource ssc0_resources[] = {
}; };
static struct platform_device at91sam9261_ssc0_device = { static struct platform_device at91sam9261_ssc0_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 0, .id = 0,
.dev = { .dev = {
.dma_mask = &ssc0_dmamask, .dma_mask = &ssc0_dmamask,
@ -748,7 +748,7 @@ static struct resource ssc1_resources[] = {
}; };
static struct platform_device at91sam9261_ssc1_device = { static struct platform_device at91sam9261_ssc1_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 1, .id = 1,
.dev = { .dev = {
.dma_mask = &ssc1_dmamask, .dma_mask = &ssc1_dmamask,
@ -790,7 +790,7 @@ static struct resource ssc2_resources[] = {
}; };
static struct platform_device at91sam9261_ssc2_device = { static struct platform_device at91sam9261_ssc2_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 2, .id = 2,
.dev = { .dev = {
.dma_mask = &ssc2_dmamask, .dma_mask = &ssc2_dmamask,

View File

@ -186,8 +186,10 @@ static struct clk *periph_clocks[] __initdata = {
static struct clk_lookup periph_clocks_lookups[] = { static struct clk_lookup periph_clocks_lookups[] = {
/* One additional fake clock for macb_hclk */ /* One additional fake clock for macb_hclk */
CLKDEV_CON_ID("hclk", &macb_clk), CLKDEV_CON_ID("hclk", &macb_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk), CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_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.0", &mmc0_clk),
CLKDEV_CON_DEV_ID("mci_clk", "atmel_mci.1", &mmc1_clk), CLKDEV_CON_DEV_ID("mci_clk", "atmel_mci.1", &mmc1_clk),
CLKDEV_CON_DEV_ID("spi_clk", "atmel_spi.0", &spi0_clk), CLKDEV_CON_DEV_ID("spi_clk", "atmel_spi.0", &spi0_clk),

View File

@ -1199,7 +1199,7 @@ static struct resource ssc0_resources[] = {
}; };
static struct platform_device at91sam9263_ssc0_device = { static struct platform_device at91sam9263_ssc0_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 0, .id = 0,
.dev = { .dev = {
.dma_mask = &ssc0_dmamask, .dma_mask = &ssc0_dmamask,
@ -1241,7 +1241,7 @@ static struct resource ssc1_resources[] = {
}; };
static struct platform_device at91sam9263_ssc1_device = { static struct platform_device at91sam9263_ssc1_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 1, .id = 1,
.dev = { .dev = {
.dma_mask = &ssc1_dmamask, .dma_mask = &ssc1_dmamask,

View File

@ -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("t0_clk", "atmel_tcb.1", &tcb0_clk),
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g10.0", &twi0_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(NULL, "i2c-at91sam9g10.1", &twi1_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk), CLKDEV_CON_DEV_ID("pclk", "at91sam9g45_ssc.0", &ssc0_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_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-trng", &trng_clk),
CLKDEV_CON_DEV_ID(NULL, "atmel_sha", &aestdessha_clk), CLKDEV_CON_DEV_ID(NULL, "atmel_sha", &aestdessha_clk),
CLKDEV_CON_DEV_ID(NULL, "atmel_tdes", &aestdessha_clk), CLKDEV_CON_DEV_ID(NULL, "atmel_tdes", &aestdessha_clk),

View File

@ -1459,7 +1459,7 @@ static struct resource ssc0_resources[] = {
}; };
static struct platform_device at91sam9g45_ssc0_device = { static struct platform_device at91sam9g45_ssc0_device = {
.name = "ssc", .name = "at91sam9g45_ssc",
.id = 0, .id = 0,
.dev = { .dev = {
.dma_mask = &ssc0_dmamask, .dma_mask = &ssc0_dmamask,
@ -1501,7 +1501,7 @@ static struct resource ssc1_resources[] = {
}; };
static struct platform_device at91sam9g45_ssc1_device = { static struct platform_device at91sam9g45_ssc1_device = {
.name = "ssc", .name = "at91sam9g45_ssc",
.id = 1, .id = 1,
.dev = { .dev = {
.dma_mask = &ssc1_dmamask, .dma_mask = &ssc1_dmamask,

View File

@ -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("t0_clk", "atmel_tcb.0", &tc0_clk),
CLKDEV_CON_DEV_ID("t1_clk", "atmel_tcb.0", &tc1_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("t2_clk", "atmel_tcb.0", &tc2_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.0", &ssc0_clk), CLKDEV_CON_DEV_ID("pclk", "at91rm9200_ssc.0", &ssc0_clk),
CLKDEV_CON_DEV_ID("pclk", "ssc.1", &ssc1_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.0", &twi0_clk),
CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g20.1", &twi1_clk), CLKDEV_CON_DEV_ID(NULL, "i2c-at91sam9g20.1", &twi1_clk),
CLKDEV_CON_ID("pioA", &pioA_clk), CLKDEV_CON_ID("pioA", &pioA_clk),

View File

@ -832,7 +832,7 @@ static struct resource ssc0_resources[] = {
}; };
static struct platform_device at91sam9rl_ssc0_device = { static struct platform_device at91sam9rl_ssc0_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 0, .id = 0,
.dev = { .dev = {
.dma_mask = &ssc0_dmamask, .dma_mask = &ssc0_dmamask,
@ -874,7 +874,7 @@ static struct resource ssc1_resources[] = {
}; };
static struct platform_device at91sam9rl_ssc1_device = { static struct platform_device at91sam9rl_ssc1_device = {
.name = "ssc", .name = "at91rm9200_ssc",
.id = 1, .id = 1,
.dev = { .dev = {
.dma_mask = &ssc1_dmamask, .dma_mask = &ssc1_dmamask,

View File

@ -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("t0_clk", "f800c000.timer", &tcb0_clk),
CLKDEV_CON_DEV_ID("dma_clk", "ffffec00.dma-controller", &dma0_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("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, "f8010000.i2c", &twi0_clk),
CLKDEV_CON_DEV_ID(NULL, "f8014000.i2c", &twi1_clk), CLKDEV_CON_DEV_ID(NULL, "f8014000.i2c", &twi1_clk),
CLKDEV_CON_DEV_ID(NULL, "f8018000.i2c", &twi2_clk), CLKDEV_CON_DEV_ID(NULL, "f8018000.i2c", &twi2_clk),

View File

@ -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) 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); at91_set_B_periph(AT91_PIN_PC1, 0);
/* SSC (for WM8731) */ /* SSC (for WM8731) */
at91_add_device_ssc(AT91SAM9260_ID_SSC, ATMEL_SSC_TX); at91_add_device_ssc(AT91SAM9260_ID_SSC, ATMEL_SSC_TX);
ek_add_device_audio();
} }
MACHINE_START(AT91SAM9G20EK, "Atmel AT91SAM9G20-EK") MACHINE_START(AT91SAM9G20EK, "Atmel AT91SAM9G20-EK")

View File

@ -18,6 +18,8 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h>
/* Serialize access to ssc_list and user count */ /* Serialize access to ssc_list and user count */
static DEFINE_SPINLOCK(user_lock); static DEFINE_SPINLOCK(user_lock);
static LIST_HEAD(ssc_list); static LIST_HEAD(ssc_list);
@ -29,7 +31,13 @@ struct ssc_device *ssc_request(unsigned int ssc_num)
spin_lock(&user_lock); spin_lock(&user_lock);
list_for_each_entry(ssc, &ssc_list, list) { 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; ssc_valid = 1;
break; break;
} }
@ -68,39 +76,93 @@ void ssc_free(struct ssc_device *ssc)
} }
EXPORT_SYMBOL(ssc_free); 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 resource *regs;
struct ssc_device *ssc; 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) { if (!ssc) {
dev_dbg(&pdev->dev, "out of memory\n"); dev_dbg(&pdev->dev, "out of memory\n");
retval = -ENOMEM; return -ENOMEM;
goto out;
} }
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); regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs) { if (!regs) {
dev_dbg(&pdev->dev, "no mmio resource defined\n"); dev_dbg(&pdev->dev, "no mmio resource defined\n");
retval = -ENXIO; return -ENXIO;
goto out_free;
} }
ssc->clk = clk_get(&pdev->dev, "pclk"); ssc->regs = devm_request_and_ioremap(&pdev->dev, regs);
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));
if (!ssc->regs) { if (!ssc->regs) {
dev_dbg(&pdev->dev, "ioremap failed\n"); dev_dbg(&pdev->dev, "ioremap failed\n");
retval = -EINVAL; return -EINVAL;
goto out_clk; }
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 */ /* disable all interrupts */
@ -112,8 +174,7 @@ static int __init ssc_probe(struct platform_device *pdev)
ssc->irq = platform_get_irq(pdev, 0); ssc->irq = platform_get_irq(pdev, 0);
if (!ssc->irq) { if (!ssc->irq) {
dev_dbg(&pdev->dev, "could not get irq\n"); dev_dbg(&pdev->dev, "could not get irq\n");
retval = -ENXIO; return -ENXIO;
goto out_unmap;
} }
spin_lock(&user_lock); 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", dev_info(&pdev->dev, "Atmel SSC device at 0x%p (irq %d)\n",
ssc->regs, ssc->irq); ssc->regs, ssc->irq);
goto out; return 0;
out_unmap:
iounmap(ssc->regs);
out_clk:
clk_put(ssc->clk);
out_free:
kfree(ssc);
out:
return retval;
} }
static int __devexit ssc_remove(struct platform_device *pdev) 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); struct ssc_device *ssc = platform_get_drvdata(pdev);
spin_lock(&user_lock); spin_lock(&user_lock);
iounmap(ssc->regs);
clk_put(ssc->clk);
list_del(&ssc->list); list_del(&ssc->list);
kfree(ssc);
spin_unlock(&user_lock); spin_unlock(&user_lock);
return 0; return 0;
} }
static struct platform_driver ssc_driver = { static struct platform_driver ssc_driver = {
.remove = __devexit_p(ssc_remove),
.driver = { .driver = {
.name = "ssc", .name = "ssc",
.owner = THIS_MODULE, .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),
}; };
module_platform_driver(ssc_driver);
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_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>"); MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>");
MODULE_DESCRIPTION("SSC driver for Atmel AVR32 and AT91"); MODULE_DESCRIPTION("SSC driver for Atmel AVR32 and AT91");

View File

@ -5,10 +5,16 @@
#include <linux/list.h> #include <linux/list.h>
#include <linux/io.h> #include <linux/io.h>
struct atmel_ssc_platform_data {
int use_dma;
};
struct ssc_device { struct ssc_device {
struct list_head list; struct list_head list;
resource_size_t phybase;
void __iomem *regs; void __iomem *regs;
struct platform_device *pdev; struct platform_device *pdev;
struct atmel_ssc_platform_data *pdata;
struct clk *clk; struct clk *clk;
int user; int user;
int irq; int irq;

View File

@ -6,6 +6,14 @@ config SND_ATMEL_SOC
the ATMEL SSC interface. You will also need the ATMEL SSC interface. You will also need
to select the audio interfaces to support below. 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 config SND_ATMEL_SOC_SSC
tristate tristate
depends on SND_ATMEL_SOC depends on SND_ATMEL_SOC
@ -16,8 +24,8 @@ config SND_ATMEL_SOC_SSC
config SND_AT91_SOC_SAM9G20_WM8731 config SND_AT91_SOC_SAM9G20_WM8731
tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board" tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
depends on ATMEL_SSC && ARCH_AT91SAM9G20 && SND_ATMEL_SOC && \ depends on ATMEL_SSC && SND_ATMEL_SOC && AT91_PROGRAMMABLE_CLOCKS
AT91_PROGRAMMABLE_CLOCKS select SND_ATMEL_SOC_PDC
select SND_ATMEL_SOC_SSC select SND_ATMEL_SOC_SSC
select SND_SOC_WM8731 select SND_SOC_WM8731
help help
@ -27,6 +35,7 @@ config SND_AT91_SOC_SAM9G20_WM8731
config SND_AT91_SOC_AFEB9260 config SND_AT91_SOC_AFEB9260
tristate "SoC Audio support for AFEB9260 board" tristate "SoC Audio support for AFEB9260 board"
depends on ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC depends on ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
select SND_ATMEL_SOC_PDC
select SND_ATMEL_SOC_SSC select SND_ATMEL_SOC_SSC
select SND_SOC_TLV320AIC23 select SND_SOC_TLV320AIC23
help help

View File

@ -1,8 +1,12 @@
# AT91 Platform Support # AT91 Platform Support
snd-soc-atmel-pcm-objs := atmel-pcm.o 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 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) += 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 obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
# AT91 Machine Support # AT91 Machine Support

View File

@ -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");

View File

@ -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");

View File

@ -32,80 +32,25 @@
*/ */
#include <linux/module.h> #include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.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.h>
#include <sound/pcm_params.h>
#include <sound/soc.h> #include <sound/soc.h>
#include "atmel-pcm.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, static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
int stream) int stream)
{ {
struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer; 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.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev; buf->dev.dev = pcm->card->dev;
buf->private_data = NULL; buf->private_data = NULL;
buf->area = dma_alloc_coherent(pcm->card->dev, size, buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL); &buf->addr, GFP_KERNEL);
pr_debug("atmel-pcm:" pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%d\n",
"preallocate_dma_buffer: area=%p, addr=%p, size=%d\n", (void *)buf->area, (void *)buf->addr, size);
(void *) buf->area,
(void *) buf->addr,
size);
if (!buf->area) if (!buf->area)
return -ENOMEM; return -ENOMEM;
@ -113,258 +58,19 @@ static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
buf->bytes = size; buf->bytes = size;
return 0; 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++; int atmel_pcm_mmap(struct snd_pcm_substream *substream,
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,
struct vm_area_struct *vma) struct vm_area_struct *vma)
{ {
return remap_pfn_range(vma, vma->vm_start, return remap_pfn_range(vma, vma->vm_start,
substream->dma_buffer.addr >> PAGE_SHIFT, substream->dma_buffer.addr >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot); 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 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_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm; 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); card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { 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, ret = atmel_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK); SNDRV_PCM_STREAM_PLAYBACK);
if (ret) 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) { if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
pr_debug("atmel-pcm:" pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n");
"Allocating PCM capture DMA buffer\n");
ret = atmel_pcm_preallocate_dma_buffer(pcm, ret = atmel_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE); SNDRV_PCM_STREAM_CAPTURE);
if (ret) if (ret)
@ -393,8 +99,9 @@ static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
out: out:
return ret; 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_pcm_substream *substream;
struct snd_dma_buffer *buf; struct snd_dma_buffer *buf;
@ -413,89 +120,5 @@ static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm)
buf->area = NULL; 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");

View File

@ -36,6 +36,8 @@
#include <linux/atmel-ssc.h> #include <linux/atmel-ssc.h>
#define ATMEL_SSC_DMABUF_SIZE (64 * 1024)
/* /*
* Registers and status bits that are required by the PCM driver. * Registers and status bits that are required by the PCM driver.
*/ */
@ -50,6 +52,7 @@ struct atmel_pdc_regs {
struct atmel_ssc_mask { struct atmel_ssc_mask {
u32 ssc_enable; /* SSC recv/trans enable */ u32 ssc_enable; /* SSC recv/trans enable */
u32 ssc_disable; /* SSC recv/trans disable */ u32 ssc_disable; /* SSC recv/trans disable */
u32 ssc_error; /* SSC error conditions */
u32 ssc_endx; /* SSC ENDTX or ENDRX */ u32 ssc_endx; /* SSC ENDTX or ENDRX */
u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */ u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
u32 pdc_enable; /* PDC recv/trans enable */ 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_readx(base, reg) (__raw_readl((base) + (reg)))
#define ssc_writex(base, reg, value) __raw_writel((value), (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 */ #endif /* _ATMEL_PCM_H */

View File

@ -48,11 +48,7 @@
#include "atmel_ssc_dai.h" #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 #define NUM_SSC_DEVICES 3
#endif
/* /*
* SSC PDC registers required by the PCM DMA engine. * 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, .pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask, .mask = &ssc_rx_mask,
} }, } },
#if NUM_SSC_DEVICES == 3
{{ {{
.name = "SSC1 PCM out", .name = "SSC1 PCM out",
.pdc = &pdc_tx_reg, .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, .pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask, .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, .dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0, .initialized = 0,
}, },
#if NUM_SSC_DEVICES == 3
{ {
.name = "ssc1", .name = "ssc1",
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock), .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, .dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0, .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) static int atmel_ssc_probe(struct snd_soc_dai *dai)
{ {
struct atmel_ssc_info *ssc_p = &ssc_info[dai->id]; struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
int ret = 0;
snd_soc_dai_set_drvdata(dai, ssc_p); 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; return 0;
} }
@ -728,11 +702,8 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = {
.set_clkdiv = atmel_ssc_set_dai_clkdiv, .set_clkdiv = atmel_ssc_set_dai_clkdiv,
}; };
static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = { static struct snd_soc_dai_driver atmel_ssc_dai = {
{
.name = "atmel-ssc-dai.0",
.probe = atmel_ssc_probe, .probe = atmel_ssc_probe,
.remove = atmel_ssc_remove,
.suspend = atmel_ssc_suspend, .suspend = atmel_ssc_suspend,
.resume = atmel_ssc_resume, .resume = atmel_ssc_resume,
.playback = { .playback = {
@ -746,69 +717,50 @@ static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = {
.rates = ATMEL_SSC_RATES, .rates = ATMEL_SSC_RATES,
.formats = ATMEL_SSC_FORMATS,}, .formats = ATMEL_SSC_FORMATS,},
.ops = &atmel_ssc_dai_ops, .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); struct platform_device *pdev = to_platform_device(dev);
BUG_ON(pdev->id >= ARRAY_SIZE(atmel_ssc_dai)); struct ssc_device *ssc = platform_get_drvdata(pdev);
return snd_soc_register_dai(&pdev->dev, &atmel_ssc_dai[pdev->id]); 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; return 0;
err_unregister_dai:
snd_soc_unregister_dai(dev);
err:
return ret;
} }
static struct platform_driver asoc_ssc_driver = { static void asoc_ssc_exit(struct device *dev)
.driver = { {
.name = "atmel-ssc-dai", struct platform_device *pdev = to_platform_device(dev);
.owner = THIS_MODULE, struct ssc_device *ssc = platform_get_drvdata(pdev);
},
.probe = asoc_ssc_probe, if (ssc->pdata->use_dma)
.remove = __devexit_p(asoc_ssc_remove), 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. * 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) int atmel_ssc_set_audio(int ssc_id)
{ {
struct ssc_device *ssc; struct ssc_device *ssc;
static struct platform_device *dma_pdev;
struct platform_device *ssc_pdev;
int ret; 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 */ /* If we can grab the SSC briefly to parent the DAI device off it */
ssc = ssc_request(ssc_id); ssc = ssc_request(ssc_id);
if (IS_ERR(ssc)) if (IS_ERR(ssc)) {
pr_warn("Unable to parent ASoC SSC DAI on SSC: %ld\n", pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n",
PTR_ERR(ssc)); PTR_ERR(ssc));
else { return PTR_ERR(ssc);
ssc_pdev->dev.parent = &(ssc->pdev->dev); } else {
ssc_free(ssc); ssc_info[ssc_id].ssc = ssc;
} }
ret = platform_device_add(ssc_pdev); ret = asoc_ssc_init(&ssc->pdev->dev);
if (ret < 0)
platform_device_put(ssc_pdev);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(atmel_ssc_set_audio); 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 information */
MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com"); MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");

View File

@ -117,6 +117,7 @@ struct atmel_ssc_info {
struct atmel_ssc_state ssc_state; 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 */ #endif /* _AT91_SSC_DAI_H */

View File

@ -38,6 +38,8 @@
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/pinctrl/consumer.h>
#include <linux/atmel-ssc.h> #include <linux/atmel-ssc.h>
#include <sound/core.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 = { static struct snd_soc_dai_link at91sam9g20ek_dai = {
.name = "WM8731", .name = "WM8731",
.stream_name = "WM8731 PCM", .stream_name = "WM8731 PCM",
.cpu_dai_name = "atmel-ssc-dai.0", .cpu_dai_name = "at91rm9200_ssc.0",
.codec_dai_name = "wm8731-hifi", .codec_dai_name = "wm8731-hifi",
.init = at91sam9g20ek_wm8731_init, .init = at91sam9g20ek_wm8731_init,
.platform_name = "atmel-pcm-audio", .platform_name = "at91rm9200_ssc.0",
.codec_name = "wm8731.0-001b", .codec_name = "wm8731.0-001b",
.ops = &at91sam9g20ek_ops, .ops = &at91sam9g20ek_ops,
}; };
@ -195,20 +197,31 @@ static struct snd_soc_card snd_soc_at91sam9g20ek = {
.set_bias_level = at91sam9g20ek_set_bias_level, .set_bias_level = at91sam9g20ek_set_bias_level,
}; };
static struct platform_device *at91sam9g20ek_snd_device; static int __devinit at91sam9g20ek_audio_probe(struct platform_device *pdev)
static int __init at91sam9g20ek_init(void)
{ {
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct clk *pllb; struct clk *pllb;
struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
struct pinctrl *pinctrl;
int ret; int ret;
if (!(machine_is_at91sam9g20ek() || machine_is_at91sam9g20ek_2mmc())) pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
return -ENODEV; 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); ret = atmel_ssc_set_audio(0);
if (ret != 0) { if (ret) {
pr_err("Failed to set SSC 0 for audio: %d\n", ret); dev_err(&pdev->dev, "ssc channel is not valid\n");
return ret; return -EINVAL;
} }
/* /*
@ -236,45 +249,92 @@ static int __init at91sam9g20ek_init(void)
clk_set_rate(mclk, MCLK_RATE); clk_set_rate(mclk, MCLK_RATE);
at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1); card->dev = &pdev->dev;
if (!at91sam9g20ek_snd_device) {
printk(KERN_ERR "ASoC: Platform device allocation failed\n"); /* Parse device node info */
ret = -ENOMEM; if (np) {
goto err_mclk; 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, ret = snd_soc_register_card(card);
&snd_soc_at91sam9g20ek);
ret = platform_device_add(at91sam9g20ek_snd_device);
if (ret) { if (ret) {
printk(KERN_ERR "ASoC: Platform device allocation failed\n"); printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n");
goto err_device_add;
} }
return ret; return ret;
err_device_add:
platform_device_put(at91sam9g20ek_snd_device);
err_mclk: err_mclk:
clk_put(mclk); clk_put(mclk);
mclk = NULL; mclk = NULL;
err: err:
atmel_ssc_put_audio(0);
return ret; return ret;
} }
static void __exit at91sam9g20ek_exit(void) static int __devexit at91sam9g20ek_audio_remove(struct platform_device *pdev)
{ {
platform_device_unregister(at91sam9g20ek_snd_device); struct snd_soc_card *card = platform_get_drvdata(pdev);
at91sam9g20ek_snd_device = NULL;
atmel_ssc_put_audio(0);
snd_soc_unregister_card(card);
clk_put(mclk); clk_put(mclk);
mclk = NULL; mclk = NULL;
return 0;
} }
module_init(at91sam9g20ek_init); #ifdef CONFIG_OF
module_exit(at91sam9g20ek_exit); 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 information */
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731"); MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
MODULE_ALIAS("platform:at91sam9g20ek-audio");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");