LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [alsa-devel] [PATCH] ASoC drivers for the Freescale MPC8610 SoC
From: Jon Smirl @ 2008-01-10 20:10 UTC (permalink / raw)
  To: Timur Tabi; +Cc: alsa-devel, Liam Girdwood, linuxppc-dev, Olof Johansson, david
In-Reply-To: <478641E3.5070404@freescale.com>

On 1/10/08, Timur Tabi <timur@freescale.com> wrote:
> Grant Likely wrote:
>
> > The driver doesn't need to be 100% correct.  Drivers are easy to
> > change if they aren't quite right.  There are no long term
> > consequences.
>  >
> > However, the device tree issues must be addressed before it is merged
> > and deployed.  Otherwise we end up having to support poorly designed
> > trees over the long term.
>
> I agree.  Correct me if I'm wrong, but I think the only device tree "issue" is
> the definition of the 'codec' node under the SSI node.  If so, I'm not sure what
> other changes need to be mode.

Isn't your codec is i2c controlled? Where does the main node for the
code live, i2c bus or ssi bus? What does the link between the buses
look like for pointing to the codec entry?

What about fsl,ssi being too generic for a compatible type?

>
> --
> Timur Tabi
> Linux kernel developer at Freescale
>


-- 
Jon Smirl
jonsmirl@gmail.com

^ permalink raw reply

* Re: [alsa-devel] [PATCH] ASoC drivers for the Freescale MPC8610 SoC
From: Timur Tabi @ 2008-01-10 20:13 UTC (permalink / raw)
  To: Jon Smirl; +Cc: alsa-devel, Liam Girdwood, linuxppc-dev, Olof Johansson, david
In-Reply-To: <9e4733910801101210w5846bb4ds38a603a489ef0522@mail.gmail.com>

Jon Smirl wrote:

> Isn't your codec is i2c controlled? Where does the main node for the
> code live, i2c bus or ssi bus? What does the link between the buses
> look like for pointing to the codec entry?

The CS4270 driver is "old style", which means it probes all possible I2C 
addresses until it finds a hit, and then stops.  This has all the obvious 
drawbacks, but I'm stuck with that design for now.

> What about fsl,ssi being too generic for a compatible type?

Already fixed:

		ssi@16000 {
			compatible = "fsl,mpc8610-ssi";
-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply

* Re: [alsa-devel] [PATCH] ASoC drivers for the Freescale MPC8610 SoC
From: Grant Likely @ 2008-01-10 20:24 UTC (permalink / raw)
  To: Timur Tabi; +Cc: alsa-devel, Liam Girdwood, linuxppc-dev, Olof Johansson, david
In-Reply-To: <47867C80.4050408@freescale.com>

On 1/10/08, Timur Tabi <timur@freescale.com> wrote:
> Jon Smirl wrote:
>
> > Isn't your codec is i2c controlled? Where does the main node for the
> > code live, i2c bus or ssi bus? What does the link between the buses
> > look like for pointing to the codec entry?
>
> The CS4270 driver is "old style", which means it probes all possible I2C
> addresses until it finds a hit, and then stops.  This has all the obvious
> drawbacks, but I'm stuck with that design for now.
>
> > What about fsl,ssi being too generic for a compatible type?
>
> Already fixed:
>
>                 ssi@16000 {
>                         compatible = "fsl,mpc8610-ssi";

Nit: node name should be either i2s@16000 (the mode that it is in), or
sound@16000 (if you feel that this node encapsulates the concept of a
sound device enough).

Cheers,
g.

-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply

* Re: [alsa-devel] [PATCH] ASoC drivers for the Freescale MPC8610 SoC
From: Timur Tabi @ 2008-01-10 20:35 UTC (permalink / raw)
  To: Grant Likely
  Cc: alsa-devel, Liam Girdwood, linuxppc-dev, Olof Johansson, david
In-Reply-To: <fa686aa40801101224p298e60bcheb622324c80690ea@mail.gmail.com>

Grant Likely wrote:

> Nit: node name should be either i2s@16000 (the mode that it is in), or
> sound@16000 (if you feel that this node encapsulates the concept of a
> sound device enough).

Well, SSI stands for "Synchronous Serial  Interface" (although it's capable of 
asynchronous communication as well).  From the manual:

"The SSI is a full-duplex, serial port that allows the chip to communicate with 
a variety of serial devices.  These serial devices can be standard CODer-DECoder 
(CODECs), Digital Signal Processors (DSPs), microprocessors, peripherals, and 
popular industry audio CODECs that implement the inter-IC sound bus standard 
(I2S) and Intel AC97 standard."

It might an I2S device in this case, but it could be an AC97 device in some 
other case.  It all depends on how the board is wired.  Do we really want to 
change the name of an SOC device based on what it's connected to?

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply

* Re: [alsa-devel] [PATCH] ASoC drivers for the Freescale MPC8610 SoC
From: Jon Smirl @ 2008-01-10 20:39 UTC (permalink / raw)
  To: Timur Tabi; +Cc: alsa-devel, Liam Girdwood, linuxppc-dev, Olof Johansson, david
In-Reply-To: <47867C80.4050408@freescale.com>

On 1/10/08, Timur Tabi <timur@freescale.com> wrote:
> Jon Smirl wrote:
>
> > Isn't your codec is i2c controlled? Where does the main node for the
> > code live, i2c bus or ssi bus? What does the link between the buses
> > look like for pointing to the codec entry?
>
> The CS4270 driver is "old style", which means it probes all possible I2C
> addresses until it finds a hit, and then stops.  This has all the obvious
> drawbacks, but I'm stuck with that design for now.

So the codec is controlled from the i2c bus and SSI is used to supply
it with data. Based on what has been said on this list, the device
tree node for the codec should be on the i2c bus with a link from the
ssi bus to it.

>
> > What about fsl,ssi being too generic for a compatible type?
>
> Already fixed:
>
>                 ssi@16000 {
>                         compatible = "fsl,mpc8610-ssi";
> --
> Timur Tabi
> Linux kernel developer at Freescale
>


-- 
Jon Smirl
jonsmirl@gmail.com

^ permalink raw reply

* Re: [alsa-devel] [PATCH] ASoC drivers for the Freescale MPC8610 SoC
From: Timur Tabi @ 2008-01-10 20:44 UTC (permalink / raw)
  To: Jon Smirl; +Cc: alsa-devel, Liam Girdwood, linuxppc-dev, Olof Johansson, david
In-Reply-To: <9e4733910801101239n539507a8sb23d1a2177e69bd9@mail.gmail.com>

Jon Smirl wrote:

> So the codec is controlled from the i2c bus and SSI is used to supply
> it with data. Based on what has been said on this list, the device
> tree node for the codec should be on the i2c bus with a link from the
> ssi bus to it.

I'm working on that now.  I'll have a new patch with this exact change this 
afternoon.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply

* RE: In PowerPC's feature, are there any difference between ML410 and ML403?
From: Stephen Neuendorffer @ 2008-01-10 21:27 UTC (permalink / raw)
  To: diak sim, linuxppc-embedded
In-Reply-To: <147365.85546.qm@web92009.mail.cnb.yahoo.com>


There shouldn't be a significant different in the cores themselves, =
however, there are many ways to get the FPGA design slightly wrong where =
it won't boot, or to get the kernel xparameters file out of synch with =
the FPGA design.  This is most likely your problem.

Steve

> -----Original Message-----
> From: linuxppc-embedded-bounces+stephen=3Dneuendorffer.name@ozlabs.org =
[mailto:linuxppc-embedded-
> bounces+stephen=3Dneuendorffer.name@ozlabs.org] On Behalf Of diak sim
> Sent: Thursday, January 10, 2008 5:56 AM
> To: linuxppc-embedded@ozlabs.org
> Subject: In PowerPC's feature,are there any difference between ML410 =
and ML403?
>=20
> Hello everyone,
>=20
> I am using ML410 to port a Linux 2.6 kernel, but init_IRQ() doesn't =
pass.
> Porting Linux 2.6 to ML403 is successful? No such a problem?
> In PowerPC's feature, are there any difference between ML410 and =
ML403?
>=20
> Thanks.
>=20
>=20
> ________________________________
>=20
> =
=D1=C5=BB=A2=D3=CA=CF=E4=B4=AB=B5=DD=D0=C2=C4=EA=D7=A3=B8=A3=A3=AC=B8=F6=D0=
=D4=BA=D8=BF=A8=CB=CD=C7=D7=C5=F3=A3=A1 =
<http://cn.mail.yahoo.com/gc/index.html?entry=3D5&souce=3Dmail_mailletter=
_tagline>

^ permalink raw reply

* Re: [PATCH 1/8] pseries: phyp dump: Docmentation
From: Mike Strosaker @ 2008-01-10 21:46 UTC (permalink / raw)
  To: linasvepstas
  Cc: linuxppc-dev, lkessler, mahuja, Nathan Lynch, Olof Johansson,
	strosake
In-Reply-To: <3ae3aa420801100834r6bd2750eqa7c8d29877350463@mail.gmail.com>

Linas Vepstas wrote:
> On 10/01/2008, Olof Johansson <olof@lixom.net> wrote:
> 
>>On Wed, Jan 09, 2008 at 10:12:13PM -0600, Linas Vepstas wrote:
>>
>>>On 09/01/2008, Olof Johansson <olof@lixom.net> wrote:
>>>
>>>>On Wed, Jan 09, 2008 at 08:33:53PM -0600, Linas Vepstas wrote:
>>>>
>>>>
>>>>>Heh. That's the elbow-grease of this thing.  The easy part is to get
>>>>>the core function working. The hard part is to test these various configs,
>>>>>and when they don't work, figure out what went wrong. That will take
>>>>>perseverence and brains.
>>>>
>>>>This just sounds like a whole lot of extra work to get a feature that
>>>>already exists.
>>>
>>>Well, no. kexec is horribly ill-behaved with respect to PCI. The
>>>kexec kernel starts running with PCI devices in some random
>>>state; maybe they're DMA'ing or who knows what. kexec tries
>>>real hard to whack a few needed pci devices into submission
>>>but it has been hit-n-miss, and the source of 90% of the kexec
>>>headaches and debugging effort. Its not pretty.
>>
>>It surprises me that this hasn't been possible to resolve with less than
>>architecting a completely new interface, given that the platform has
>>all this fancy support for isolating and resetting adapters. After all,
>>the exact same thing has to be done by the hypervisor before rebooting
>>the partition.
> 
> 
> OK, point taken.
> 
> -- The phyp interfaces are there for AIX, which I guess must
>    not have kexec-like ability. So this is a case of Linux leveraging
>   a feature architected for AIX.

Certainly AIX was in a more difficult position at the time, because they don't 
have a kexec equivalent, and thus were collecting dump data with a potentially 
faulty kernel.  It makes sense to have something outside the partition collect or 
maintain the data; ideally, some kind of service partition would extract dump 
data from a failed partition, but giving one partition total access to the memory 
of another is clearly risky.  Both the PHYP-assistance method and the kexec 
method are ways to simulate that without the risk.

At the risk of repeating what others have already said, the PHYP-assistance 
method provides some advantages that the kexec method cannot:
  - Availability of the system for production use before the dump data is 
collected.  As was mentioned before, some production systems may choose not to 
operate with the limited memory initially available after the reboot, but it sure 
is nice to provide the option.
  - Ensuring that the devices are in a good state.  PHYP doesn't expose a method 
to force adapters into a frozen state, (which I agree would be useful), and I 
don't know of any plans to do so.  What we are starting to see is that some 
drivers need modifications in order to work correctly with kdump [1].  Supporting 
PHYP-assisted dump would eliminate those issues.
  - The small possibility that the kexec area could have been munged by the 
failing kernel, preventing it from being able to collect a dump.

The NUMA issues are daunting, but not insurmountable.  Release early, release 
often, n'est-ce pas?

Mike

[1] http://ozlabs.org/pipermail/linuxppc-dev/2007-November/045663.html

^ permalink raw reply

* [PATCH 0/2] ASoC drivers for the Freescale MPC8610 SoC
From: Timur Tabi @ 2008-01-10 22:44 UTC (permalink / raw)
  To: grant.likely, jonsmirl, david, olof, alsa-devel, linuxppc-dev


These patches add ALSA SoC device drivers for the Freescale MPC8610 SoC
and the MPC8610-HPCD reference board.

The first patch updates some files in arch/powerpc and booting-without-of.txt.

The second patch adds the new audio driver files to sound/soc.

Both must be applied in order to avoid a build break.

^ permalink raw reply

* [PATCH v3] [POWERPC] Update MPC8610 HPCD to support audio drivers
From: Timur Tabi @ 2008-01-10 22:44 UTC (permalink / raw)
  To: grant.likely, jonsmirl, david, olof, alsa-devel, linuxppc-dev; +Cc: Timur Tabi
In-Reply-To: <12000050651235-git-send-email-timur@freescale.com>

Update the MPC8610 HPCD files to support the audio driver.  Update
booting-without-of.txt with information on the SSI device.

Signed-off-by: Timur Tabi <timur@freescale.com>
---

Updated the DTS based comments on the list.

This patch applies on top of
git://git.kernel.org/pub/scm/linux/kernel/git/perex/alsa.git.

 Documentation/powerpc/booting-without-of.txt |   41 ++++++
 arch/powerpc/boot/dts/mpc8610_hpcd.dts       |  113 +++++++++++++++++-
 arch/powerpc/configs/mpc8610_hpcd_defconfig  |  171 +++++++++++++++++++++++++-
 arch/powerpc/platforms/86xx/mpc8610_hpcd.c   |   15 +++
 4 files changed, 336 insertions(+), 4 deletions(-)

diff --git a/Documentation/powerpc/booting-without-of.txt b/Documentation/powerpc/booting-without-of.txt
index e9a3cb1..8ddcf0b 100644
--- a/Documentation/powerpc/booting-without-of.txt
+++ b/Documentation/powerpc/booting-without-of.txt
@@ -53,6 +53,7 @@ Table of Contents
       j) CFI or JEDEC memory-mapped NOR flash
       k) Global Utilities Block
       l) Xilinx IP cores
+      p) Freescale Synchronous Serial Interface
 
   VII - Specifying interrupt information for devices
     1) interrupts property
@@ -2514,6 +2515,46 @@ platforms are moved over to use the flattened-device-tree model.
       Requred properties:
        - current-speed : Baud rate of uartlite
 
+    p) Freescale Synchronous Serial Interface
+
+       The SSI is a serial device that communicates with audio codecs.  It can
+       be programmed in AC97, I2S, left-justified, or right-justified modes.
+
+       Required properties:
+       - compatible	  : compatible list, containing "fsl,ssi"
+       - cell-index	  : the SSI, <0> = SSI1, <1> = SSI2, and so on
+       - reg		  : offset and length of the register set for the device
+       - interrupts	  : <a b> where a is the interrupt number and b is a
+                            field that represents an encoding of the sense and
+			    level information for the interrupt.  This should be
+			    encoded based on the information in section 2)
+			    depending on the type of interrupt controller you
+			    have.
+       - interrupt-parent : the phandle for the interrupt controller that
+                            services interrupts for this device.
+       - fsl,mode	  : the operating mode for the SSI interface
+			    "i2s-slave" - I2S mode, SSI is clock slave
+			    "i2s-master" - I2S mode, SSI is clock master
+			    "lj-slave" - left-justified mode, SSI is clock slave
+			    "lj-master" - l.j. mode, SSI is clock master
+			    "rj-slave" - right-justified mode, SSI is clock slave
+			    "rj-master" - r.j., SSI is clock master
+			    "ac97-slave" - AC97 mode, SSI is clock slave
+			    "ac97-master" - AC97 mode, SSI is clock master
+
+       Optional properties:
+       - codec-handle	  : phandle to a 'codec' node that defines an audio
+			    codec connected to this SSI.  This node is typically
+			    a child of an I2C or other control node.
+
+       Child 'codec' node required properties:
+       - compatible	  : compatible list, contains the name of the codec
+
+       Child 'codec' node optional properties:
+       - clock-frequency  : The frequency of the input clock, which typically
+                            comes from an on-board dedicated oscillator.
+
+
    More devices will be defined as this spec matures.
 
 VII - Specifying interrupt information for devices
diff --git a/arch/powerpc/boot/dts/mpc8610_hpcd.dts b/arch/powerpc/boot/dts/mpc8610_hpcd.dts
index 966edf1..4c91890 100644
--- a/arch/powerpc/boot/dts/mpc8610_hpcd.dts
+++ b/arch/powerpc/boot/dts/mpc8610_hpcd.dts
@@ -1,7 +1,7 @@
 /*
  * MPC8610 HPCD Device Tree Source
  *
- * Copyright 2007 Freescale Semiconductor Inc.
+ * Copyright 2007-2008 Freescale Semiconductor Inc.
  *
  * This program is free software; you can redistribute  it and/or modify it
  * under the terms of the GNU General Public License Version 2 as published
@@ -42,6 +42,7 @@
 		#size-cells = <1>;
 		#interrupt-cells = <2>;
 		device_type = "soc";
+		compatible = "fsl,mpc8610-immr", "simple-bus";
 		ranges = <0 e0000000 00100000>;
 		reg = <e0000000 1000>;
 		bus-frequency = <0>;
@@ -55,6 +56,13 @@
 			interrupts = <2b 2>;
 			interrupt-parent = <&mpic>;
 			dfsrr;
+
+                        cs4270:codec@4f {
+				compatible = "cirrus,cs4270";
+                                reg = <4f>;
+				/* MCLK source is a stand-alone oscillator */
+				clock-frequency = <bb8000>;
+                        };
 		};
 
 		i2c@3100 {
@@ -103,6 +111,109 @@
 			reg = <e0000 1000>;
 			fsl,has-rstcr;
 		};
+
+		i2s@16000 {
+			compatible = "fsl,mpc8610-ssi";
+			cell-index = <0>;
+			reg = <16000 100>;
+			interrupt-parent = <&mpic>;
+			interrupts = <3e 2>;
+			fsl,mode = "i2s-slave";
+			codec-handle = <&cs4270>;
+		};
+
+		ssi@16100 {
+			compatible = "fsl,mpc8610-ssi";
+			cell-index = <1>;
+			reg = <16100 100>;
+			interrupt-parent = <&mpic>;
+			interrupts = <3f 2>;
+		};
+
+                dma@21300 {
+                        #address-cells = <1>;
+                        #size-cells = <1>;
+                        compatible = "fsl,mpc8610-dma", "fsl,eloplus-dma";
+                        cell-index = <0>;
+                        reg = <21300 4>; /* DMA general status register */
+                        ranges = <0 21100 200>;
+
+                        dma-channel@0 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,eloplus-dma-channel";
+				cell-index = <0>;
+				reg = <0 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <14 2>;
+                        };
+                        dma-channel@1 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,eloplus-dma-channel";
+				cell-index = <1>;
+				reg = <80 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <15 2>;
+                        };
+                        dma-channel@2 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,eloplus-dma-channel";
+				cell-index = <2>;
+				reg = <100 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <16 2>;
+                        };
+                        dma-channel@3 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,eloplus-dma-channel";
+				cell-index = <3>;
+				reg = <180 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <17 2>;
+                        };
+                };
+
+                dma@c300 {
+                        #address-cells = <1>;
+                        #size-cells = <1>;
+                        compatible = "fsl,mpc8610-dma", "fsl,mpc8540-dma";
+                        cell-index = <1>;
+                        reg = <c300 4>; /* DMA general status register */
+                        ranges = <0 c100 200>;
+
+                        dma-channel@0 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,mpc8540-dma-channel";
+				cell-index = <0>;
+				reg = <0 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <3c 2>;
+                        };
+                        dma-channel@1 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,mpc8540-dma-channel";
+				cell-index = <1>;
+				reg = <80 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <3d 2>;
+                        };
+                        dma-channel@2 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,mpc8540-dma-channel";
+				cell-index = <2>;
+				reg = <100 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <3e 2>;
+                        };
+                        dma-channel@3 {
+				compatible = "fsl,mpc8610-dma-channel",
+					"fsl,mpc8540-dma-channel";
+				cell-index = <3>;
+				reg = <180 80>;
+				interrupt-parent = <&mpic>;
+				interrupts = <3f 2>;
+                        };
+                };
+
 	};
 
 	pci@e0008000 {
diff --git a/arch/powerpc/configs/mpc8610_hpcd_defconfig b/arch/powerpc/configs/mpc8610_hpcd_defconfig
index 9614d24..2500ef4 100644
--- a/arch/powerpc/configs/mpc8610_hpcd_defconfig
+++ b/arch/powerpc/configs/mpc8610_hpcd_defconfig
@@ -696,7 +696,7 @@ CONFIG_SERIAL_8250_RSA=y
 CONFIG_SERIAL_CORE=y
 CONFIG_SERIAL_CORE_CONSOLE=y
 # CONFIG_SERIAL_JSM is not set
-CONFIG_SERIAL_OF_PLATFORM=y
+# CONFIG_SERIAL_OF_PLATFORM is not set
 CONFIG_UNIX98_PTYS=y
 # CONFIG_LEGACY_PTYS is not set
 # CONFIG_IPMI_HANDLER is not set
@@ -708,7 +708,60 @@ CONFIG_UNIX98_PTYS=y
 # CONFIG_RAW_DRIVER is not set
 # CONFIG_TCG_TPM is not set
 CONFIG_DEVPORT=y
-# CONFIG_I2C is not set
+CONFIG_I2C=y
+CONFIG_I2C_BOARDINFO=y
+# CONFIG_I2C_CHARDEV is not set
+
+#
+# I2C Algorithms
+#
+# CONFIG_I2C_ALGOBIT is not set
+# CONFIG_I2C_ALGOPCF is not set
+# CONFIG_I2C_ALGOPCA is not set
+
+#
+# I2C Hardware Bus support
+#
+# CONFIG_I2C_ALI1535 is not set
+# CONFIG_I2C_ALI1563 is not set
+# CONFIG_I2C_ALI15X3 is not set
+# CONFIG_I2C_AMD756 is not set
+# CONFIG_I2C_AMD8111 is not set
+# CONFIG_I2C_I801 is not set
+# CONFIG_I2C_I810 is not set
+# CONFIG_I2C_PIIX4 is not set
+CONFIG_I2C_MPC=y
+# CONFIG_I2C_NFORCE2 is not set
+# CONFIG_I2C_OCORES is not set
+# CONFIG_I2C_PARPORT_LIGHT is not set
+# CONFIG_I2C_PROSAVAGE is not set
+# CONFIG_I2C_SAVAGE4 is not set
+# CONFIG_I2C_SIMTEC is not set
+# CONFIG_I2C_SIS5595 is not set
+# CONFIG_I2C_SIS630 is not set
+# CONFIG_I2C_SIS96X is not set
+# CONFIG_I2C_TAOS_EVM is not set
+# CONFIG_I2C_VIA is not set
+# CONFIG_I2C_VIAPRO is not set
+# CONFIG_I2C_VOODOO3 is not set
+
+#
+# Miscellaneous I2C Chip support
+#
+# CONFIG_SENSORS_DS1337 is not set
+# CONFIG_SENSORS_DS1374 is not set
+# CONFIG_DS1682 is not set
+# CONFIG_SENSORS_EEPROM is not set
+# CONFIG_SENSORS_PCF8574 is not set
+# CONFIG_SENSORS_PCA9539 is not set
+# CONFIG_SENSORS_PCF8591 is not set
+# CONFIG_SENSORS_M41T00 is not set
+# CONFIG_SENSORS_MAX6875 is not set
+# CONFIG_SENSORS_TSL2550 is not set
+# CONFIG_I2C_DEBUG_CORE is not set
+# CONFIG_I2C_DEBUG_ALGO is not set
+# CONFIG_I2C_DEBUG_BUS is not set
+# CONFIG_I2C_DEBUG_CHIP is not set
 
 #
 # SPI support
@@ -763,7 +816,119 @@ CONFIG_DUMMY_CONSOLE=y
 #
 # Sound
 #
-# CONFIG_SOUND is not set
+CONFIG_SOUND=y
+
+#
+# Advanced Linux Sound Architecture
+#
+CONFIG_SND=y
+CONFIG_SND_TIMER=y
+CONFIG_SND_PCM=y
+# CONFIG_SND_SEQUENCER is not set
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_MIXER_OSS=y
+CONFIG_SND_PCM_OSS=y
+# CONFIG_SND_PCM_OSS_PLUGINS is not set
+# CONFIG_SND_DYNAMIC_MINORS is not set
+# CONFIG_SND_SUPPORT_OLD_API is not set
+CONFIG_SND_VERBOSE_PROCFS=y
+# CONFIG_SND_VERBOSE_PRINTK is not set
+# CONFIG_SND_DEBUG is not set
+
+#
+# Generic devices
+#
+# CONFIG_SND_DUMMY is not set
+# CONFIG_SND_MTPAV is not set
+# CONFIG_SND_SERIAL_U16550 is not set
+# CONFIG_SND_MPU401 is not set
+
+#
+# PCI devices
+#
+# CONFIG_SND_AD1889 is not set
+# CONFIG_SND_ALS300 is not set
+# CONFIG_SND_ALS4000 is not set
+# CONFIG_SND_ALI5451 is not set
+# CONFIG_SND_ATIIXP is not set
+# CONFIG_SND_ATIIXP_MODEM is not set
+# CONFIG_SND_AU8810 is not set
+# CONFIG_SND_AU8820 is not set
+# CONFIG_SND_AU8830 is not set
+# CONFIG_SND_AZT3328 is not set
+# CONFIG_SND_BT87X is not set
+# CONFIG_SND_CA0106 is not set
+# CONFIG_SND_CMIPCI is not set
+# CONFIG_SND_CS4281 is not set
+# CONFIG_SND_CS46XX is not set
+# CONFIG_SND_CS5530 is not set
+# CONFIG_SND_DARLA20 is not set
+# CONFIG_SND_GINA20 is not set
+# CONFIG_SND_LAYLA20 is not set
+# CONFIG_SND_DARLA24 is not set
+# CONFIG_SND_GINA24 is not set
+# CONFIG_SND_LAYLA24 is not set
+# CONFIG_SND_MONA is not set
+# CONFIG_SND_MIA is not set
+# CONFIG_SND_ECHO3G is not set
+# CONFIG_SND_INDIGO is not set
+# CONFIG_SND_INDIGOIO is not set
+# CONFIG_SND_INDIGODJ is not set
+# CONFIG_SND_EMU10K1 is not set
+# CONFIG_SND_EMU10K1X is not set
+# CONFIG_SND_ENS1370 is not set
+# CONFIG_SND_ENS1371 is not set
+# CONFIG_SND_ES1938 is not set
+# CONFIG_SND_ES1968 is not set
+# CONFIG_SND_FM801 is not set
+# CONFIG_SND_HDA_INTEL is not set
+# CONFIG_SND_HDSP is not set
+# CONFIG_SND_HDSPM is not set
+# CONFIG_SND_ICE1712 is not set
+# CONFIG_SND_ICE1724 is not set
+# CONFIG_SND_INTEL8X0 is not set
+# CONFIG_SND_INTEL8X0M is not set
+# CONFIG_SND_KORG1212 is not set
+# CONFIG_SND_MAESTRO3 is not set
+# CONFIG_SND_MIXART is not set
+# CONFIG_SND_NM256 is not set
+# CONFIG_SND_PCXHR is not set
+# CONFIG_SND_RIPTIDE is not set
+# CONFIG_SND_RME32 is not set
+# CONFIG_SND_RME96 is not set
+# CONFIG_SND_RME9652 is not set
+# CONFIG_SND_SONICVIBES is not set
+# CONFIG_SND_TRIDENT is not set
+# CONFIG_SND_VIA82XX is not set
+# CONFIG_SND_VIA82XX_MODEM is not set
+# CONFIG_SND_VX222 is not set
+# CONFIG_SND_YMFPCI is not set
+
+#
+# ALSA PowerMac devices
+#
+
+#
+# ALSA PowerPC devices
+#
+
+#
+# System on Chip audio support
+#
+CONFIG_SND_SOC=y
+
+#
+# SoC Audio support for SuperH
+#
+
+#
+# ALSA SoC audio for Freescale SOCs
+#
+CONFIG_SND_SOC_MPC8610=y
+CONFIG_SND_SOC_MPC8610_HPCD=y
+CONFIG_SND_SOC_CS4270=y
+CONFIG_SND_SOC_CS4270_VD33_ERRATA=y
+
 CONFIG_HID_SUPPORT=y
 CONFIG_HID=y
 # CONFIG_HID_DEBUG is not set
diff --git a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
index 6390895..36d8f14 100644
--- a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
+++ b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
@@ -34,9 +34,24 @@
 
 #include <asm/mpic.h>
 
+#include <linux/of_platform.h>
 #include <sysdev/fsl_pci.h>
 #include <sysdev/fsl_soc.h>
 
+static struct of_device_id __initdata mpc8610_ids[] = {
+	{ .compatible = "fsl,mpc8610-immr", },
+	{}
+};
+
+static int __init mpc8610_declare_of_platform_devices(void)
+{
+	/* Without this call, the SSI device driver won't get probed. */
+	of_platform_bus_probe(NULL, mpc8610_ids, NULL);
+
+	return 0;
+}
+machine_device_initcall(mpc86xx_hpcd, mpc8610_declare_of_platform_devices);
+
 void __init
 mpc86xx_hpcd_init_irq(void)
 {
-- 
1.5.2.4

^ permalink raw reply related

* [PATCH v2] [ALSA] Add ASoC drivers for the Freescale MPC8610 SoC
From: Timur Tabi @ 2008-01-10 22:44 UTC (permalink / raw)
  To: grant.likely, jonsmirl, david, olof, alsa-devel, linuxppc-dev; +Cc: Timur Tabi
In-Reply-To: <12000050664035-git-send-email-timur@freescale.com>

Add the ASoC drivers for the Freescale MPC8610 SoC and the MPC8610 HPCD
reference board.

Signed-off-by: Timur Tabi <timur@freescale.com>
---
 sound/soc/Kconfig            |    1 +
 sound/soc/Makefile           |    2 +-
 sound/soc/fsl/Kconfig        |   20 +
 sound/soc/fsl/Makefile       |    6 +
 sound/soc/fsl/fsl_dma.c      |  839 ++++++++++++++++++++++++++++++++++++++++++
 sound/soc/fsl/fsl_dma.h      |  149 ++++++++
 sound/soc/fsl/fsl_ssi.c      |  644 ++++++++++++++++++++++++++++++++
 sound/soc/fsl/fsl_ssi.h      |  224 +++++++++++
 sound/soc/fsl/mpc8610_hpcd.c |  631 +++++++++++++++++++++++++++++++
 9 files changed, 2515 insertions(+), 1 deletions(-)
 create mode 100644 sound/soc/fsl/Kconfig
 create mode 100644 sound/soc/fsl/Makefile
 create mode 100644 sound/soc/fsl/fsl_dma.c
 create mode 100644 sound/soc/fsl/fsl_dma.h
 create mode 100644 sound/soc/fsl/fsl_ssi.c
 create mode 100644 sound/soc/fsl/fsl_ssi.h
 create mode 100644 sound/soc/fsl/mpc8610_hpcd.c

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 97b2552..2765852 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -28,6 +28,7 @@ source "sound/soc/at91/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/s3c24xx/Kconfig"
 source "sound/soc/sh/Kconfig"
+source "sound/soc/fsl/Kconfig"
 
 # Supported codecs
 source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 3041403..4869c9a 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -1,4 +1,4 @@
 snd-soc-core-objs := soc-core.o soc-dapm.o
 
 obj-$(CONFIG_SND_SOC)	+= snd-soc-core.o
-obj-$(CONFIG_SND_SOC)	+= codecs/ at91/ pxa/ s3c24xx/ sh/
+obj-$(CONFIG_SND_SOC)	+= codecs/ at91/ pxa/ s3c24xx/ sh/ fsl/
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
new file mode 100644
index 0000000..257101f
--- /dev/null
+++ b/sound/soc/fsl/Kconfig
@@ -0,0 +1,20 @@
+menu "ALSA SoC audio for Freescale SOCs"
+
+config SND_SOC_MPC8610
+	bool "ALSA SoC support for the MPC8610 SOC"
+	depends on SND_SOC && MPC8610_HPCD
+	default y if MPC8610
+	help
+	  Say Y if you want to add support for codecs attached to the SSI
+          device on an MPC8610.
+
+config SND_SOC_MPC8610_HPCD
+	bool "ALSA SoC support for the Freescale MPC8610 HPCD board"
+	depends on SND_SOC_MPC8610
+	select SND_SOC_CS4270
+	select SND_SOC_CS4270_VD33_ERRATA
+	default y if MPC8610_HPCD
+	help
+	  Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
+
+endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
new file mode 100644
index 0000000..62f680a
--- /dev/null
+++ b/sound/soc/fsl/Makefile
@@ -0,0 +1,6 @@
+# MPC8610 HPCD Machine Support
+obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o
+
+# MPC8610 Platform Support
+obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o
+
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
new file mode 100644
index 0000000..2173203
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.c
@@ -0,0 +1,839 @@
+/*
+ * Freescale DMA ALSA SoC PCM driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc.  This file is licensed
+ * under the terms of the GNU General Public License version 2.  This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ *
+ * This driver implements ASoC support for the Elo DMA controller, which is
+ * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
+ * the PCM driver is what handles the DMA buffer.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/io.h>
+
+#include "fsl_dma.h"
+
+/*
+ * The formats that the DMA controller supports, which is anything
+ * that is 8, 16, or 32 bits.
+ */
+#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 	| \
+			    SNDRV_PCM_FMTBIT_U8 	| \
+			    SNDRV_PCM_FMTBIT_S16_LE     | \
+			    SNDRV_PCM_FMTBIT_S16_BE     | \
+			    SNDRV_PCM_FMTBIT_U16_LE     | \
+			    SNDRV_PCM_FMTBIT_U16_BE     | \
+			    SNDRV_PCM_FMTBIT_S24_LE     | \
+			    SNDRV_PCM_FMTBIT_S24_BE     | \
+			    SNDRV_PCM_FMTBIT_U24_LE     | \
+			    SNDRV_PCM_FMTBIT_U24_BE     | \
+			    SNDRV_PCM_FMTBIT_S32_LE     | \
+			    SNDRV_PCM_FMTBIT_S32_BE     | \
+			    SNDRV_PCM_FMTBIT_U32_LE     | \
+			    SNDRV_PCM_FMTBIT_U32_BE)
+
+#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+			  SNDRV_PCM_RATE_CONTINUOUS)
+
+/* DMA global data.  This structure is used by fsl_dma_open() to determine
+ * which DMA channels to assign to a substream.  Unfortunately, ASoC V1 does
+ * not allow the machine driver to provide this information to the PCM
+ * driver in advance, and there's no way to differentiate between the two
+ * DMA controllers.  So for now, this driver only supports one SSI device
+ * using two DMA channels.  We cannot support multiple DMA devices.
+ *
+ * ssi_stx_phys: bus address of SSI STX register
+ * ssi_srx_phys: bus address of SSI SRX register
+ * dma_channel: pointer to the DMA channel's registers
+ * irq: IRQ for this DMA channel
+ * assigned: set to 1 if that DMA channel is assigned to a substream
+ */
+static struct {
+	dma_addr_t ssi_stx_phys;
+	dma_addr_t ssi_srx_phys;
+	struct ccsr_dma_channel __iomem *dma_channel[2];
+	unsigned int irq[2];
+	unsigned int assigned[2];
+} dma_global_data;
+
+/*
+ * The number of DMA links to use.  Two is the bare minimum, but if you
+ * have really small links you might need more.
+ */
+#define NUM_DMA_LINKS   2
+
+/** fsl_dma_private: p-substream DMA data
+ *
+ * Each substream has a 1-to-1 association with a DMA channel.
+ *
+ * The link[] array is first because it needs to be aligned on a 32-byte
+ * boundary, so putting it first will ensure alignment without padding the
+ * structure.
+ *
+ * @link[]: array of link descriptors
+ * @controller_id: which DMA controller (0, 1, ...)
+ * @channel_id: which DMA channel on the controller (0, 1, 2, ...)
+ * @dma_channel: pointer to the DMA channel's registers
+ * @irq: IRQ for this DMA channel
+ * @substream: pointer to the substream object, needed by the ISR
+ * @ssi_sxx_phys: bus address of the STX or SRX register to use
+ * @ld_buf_phys: physical address of the LD buffer
+ * @current_link: index into link[] of the link currently being processed
+ * @dma_buf_phys: physical address of the DMA buffer
+ * @dma_buf_next: physical address of the next period to process
+ * @dma_buf_end: physical address of the byte after the end of the DMA
+ * @buffer period_size: the size of a single period
+ * @num_periods: the number of periods in the DMA buffer
+ */
+struct fsl_dma_private {
+	struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
+	unsigned int controller_id;
+	unsigned int channel_id;
+	struct ccsr_dma_channel __iomem *dma_channel;
+	unsigned int irq;
+	struct snd_pcm_substream *substream;
+	dma_addr_t ssi_sxx_phys;
+	dma_addr_t ld_buf_phys;
+	unsigned int current_link;
+	dma_addr_t dma_buf_phys;
+	dma_addr_t dma_buf_next;
+	dma_addr_t dma_buf_end;
+	size_t period_size;
+	unsigned int num_periods;
+};
+
+/**
+ * fsl_dma_hardare: define characteristics of the PCM hardware.
+ *
+ * The PCM hardware is the Freescale DMA controller.  This structure defines
+ * the capabilities of that hardware.
+ *
+ * Since the sampling rate and data format are not controlled by the DMA
+ * controller, we specify no limits for those values.  The only exception is
+ * period_bytes_min, which is set to a reasonably low value to prevent the
+ * DMA controller from generating too many interrupts per second.
+ *
+ * Since each link descriptor has a 32-bit byte count field, we set
+ * period_bytes_max to the largest 32-bit number.  We also have no maximum
+ * number of periods.
+ */
+static const struct snd_pcm_hardware fsl_dma_hardware = {
+
+	.info   		= SNDRV_PCM_INFO_INTERLEAVED,
+	.formats		= FSLDMA_PCM_FORMATS,
+	.rates  		= FSLDMA_PCM_RATES,
+	.rate_min       	= 5512,
+	.rate_max       	= 192000,
+	.period_bytes_min       = 512,  	/* A reasonable limit */
+	.period_bytes_max       = (u32) -1,
+	.periods_min    	= NUM_DMA_LINKS,
+	.periods_max    	= (unsigned int) -1,
+	.buffer_bytes_max       = 128 * 1024,   /* A reasonable limit */
+};
+
+/**
+ * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted
+ *
+ * This function should be called by the ISR whenever the DMA controller
+ * halts data transfer.
+ */
+static void fsl_dma_abort_stream(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+
+	snd_pcm_stream_lock_irqsave(substream, flags);
+
+	if (snd_pcm_running(substream))
+		snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+
+	snd_pcm_stream_unlock_irqrestore(substream, flags);
+}
+
+/**
+ * fsl_dma_update_pointers - update LD pointers to point to the next period
+ *
+ * As each period is completed, this function changes the the link
+ * descriptor pointers for that period to point to the next period.
+ */
+static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
+{
+	struct fsl_dma_link_descriptor *link =
+		&dma_private->link[dma_private->current_link];
+
+	/* Update our link descriptors to point to the next period */
+	if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		link->source_addr =
+			cpu_to_be32(dma_private->dma_buf_next);
+	else
+		link->dest_addr =
+			cpu_to_be32(dma_private->dma_buf_next);
+
+	/* Update our variables for next time */
+	dma_private->dma_buf_next += dma_private->period_size;
+
+	if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+		dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+	if (++dma_private->current_link >= NUM_DMA_LINKS)
+		dma_private->current_link = 0;
+}
+
+/**
+ * fsl_dma_isr: interrupt handler for the DMA controller
+ *
+ * @irq: IRQ of the DMA channel
+ * @dev_id: pointer to the dma_private structure for this DMA channel
+ */
+static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
+{
+	struct fsl_dma_private *dma_private = dev_id;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+	irqreturn_t ret = IRQ_NONE;
+	u32 sr, sr2 = 0;
+
+	/* We got an interrupt, so read the status register to see what we
+	   were interrupted for.
+	 */
+	sr = in_be32(&dma_channel->sr);
+
+	if (sr & CCSR_DMA_SR_TE) {
+		dev_err(dma_private->substream->pcm->card->dev,
+			"DMA transmit error (controller=%u channel=%u irq=%u\n",
+			dma_private->controller_id,
+			dma_private->channel_id, irq);
+		fsl_dma_abort_stream(dma_private->substream);
+		sr2 |= CCSR_DMA_SR_TE;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_CH)
+		ret = IRQ_HANDLED;
+
+	if (sr & CCSR_DMA_SR_PE) {
+		dev_err(dma_private->substream->pcm->card->dev,
+			"DMA%u programming error (channel=%u irq=%u)\n",
+			dma_private->controller_id,
+			dma_private->channel_id, irq);
+		fsl_dma_abort_stream(dma_private->substream);
+		sr2 |= CCSR_DMA_SR_PE;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_EOLNI) {
+		sr2 |= CCSR_DMA_SR_EOLNI;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_CB)
+		ret = IRQ_HANDLED;
+
+	if (sr & CCSR_DMA_SR_EOSI) {
+		struct snd_pcm_substream *substream = dma_private->substream;
+
+		/* Tell ALSA we completed a period. */
+		snd_pcm_period_elapsed(substream);
+
+		/*
+		 * Update our link descriptors to point to the next period. We
+		 * only need to do this if the number of periods is not equal to
+		 * the number of links.
+		 */
+		if (dma_private->num_periods != NUM_DMA_LINKS)
+			fsl_dma_update_pointers(dma_private);
+
+		sr2 |= CCSR_DMA_SR_EOSI;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_EOLSI) {
+		sr2 |= CCSR_DMA_SR_EOLSI;
+		ret = IRQ_HANDLED;
+	}
+
+	/* Clear the bits that we set */
+	if (sr2)
+		out_be32(&dma_channel->sr, sr2);
+
+	return ret;
+}
+
+/**
+ * fsl_dma_new: initialize this PCM driver.
+ *
+ * This function is called when the codec driver calls snd_soc_new_pcms(),
+ * once for each .dai_link in the machine driver's snd_soc_machine
+ * structure.
+ */
+static int fsl_dma_new(struct snd_card *card, struct snd_soc_codec_dai *dai,
+	struct snd_pcm *pcm)
+{
+	static u64 fsl_dma_dmamask = DMA_BIT_MASK(32);
+	int ret;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &fsl_dma_dmamask;
+
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = fsl_dma_dmamask;
+
+	ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
+		fsl_dma_hardware.buffer_bytes_max,
+		&pcm->streams[0].substream->dma_buffer);
+	if (ret) {
+		dev_err(card->dev,
+			"Can't allocate playback DMA buffer (size=%u)\n",
+			fsl_dma_hardware.buffer_bytes_max);
+		return -ENOMEM;
+	}
+
+	ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
+		fsl_dma_hardware.buffer_bytes_max,
+		&pcm->streams[1].substream->dma_buffer);
+	if (ret) {
+		snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+		dev_err(card->dev,
+			"Can't allocate capture DMA buffer (size=%u)\n",
+			fsl_dma_hardware.buffer_bytes_max);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_open: open a new substream.
+ *
+ * Each substream has its own DMA buffer.
+ */
+static int fsl_dma_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private;
+	dma_addr_t ld_buf_phys;
+	unsigned int channel;
+	int ret = 0;
+
+	/*
+	 * Reject any DMA buffer whose size is not a multiple of the period
+	 * size.  We need to make sure that the DMA buffer can be evenly divided
+	 * into periods.
+	 */
+	ret = snd_pcm_hw_constraint_integer(runtime,
+		SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		dev_err(substream->pcm->card->dev, "invalid buffer size\n");
+		return ret;
+	}
+
+	channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+	if (dma_global_data.assigned[channel]) {
+		dev_err(substream->pcm->card->dev,
+			"DMA channel already assigned\n");
+		return -EBUSY;
+	}
+
+	dma_private = dma_alloc_coherent(substream->pcm->dev,
+		sizeof(struct fsl_dma_private), &ld_buf_phys, GFP_KERNEL);
+	if (!dma_private) {
+		dev_err(substream->pcm->card->dev,
+			"can't allocate DMA private data\n");
+		return -ENOMEM;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_private->ssi_sxx_phys = dma_global_data.ssi_stx_phys;
+	else
+		dma_private->ssi_sxx_phys = dma_global_data.ssi_srx_phys;
+
+	dma_private->dma_channel = dma_global_data.dma_channel[channel];
+	dma_private->irq = dma_global_data.irq[channel];
+	dma_private->substream = substream;
+	dma_private->ld_buf_phys = ld_buf_phys;
+	dma_private->dma_buf_phys = substream->dma_buffer.addr;
+
+	/* We only support one DMA controller for now */
+	dma_private->controller_id = 0;
+	dma_private->channel_id = channel;
+
+	ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "DMA", dma_private);
+	if (ret) {
+		dev_err(substream->pcm->card->dev,
+			"can't register ISR for IRQ %u (ret=%i)\n",
+			dma_private->irq, ret);
+		dma_free_coherent(substream->pcm->dev,
+			sizeof(struct fsl_dma_private),
+			dma_private, dma_private->ld_buf_phys);
+		return ret;
+	}
+
+	dma_global_data.assigned[channel] = 1;
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
+	runtime->private_data = dma_private;
+
+	return 0;
+}
+
+/**
+ * fsl_dma_hw_params: allocate the DMA buffer and the DMA link descriptors.
+ *
+ * ALSA divides the DMA buffer into N periods.  We create NUM_DMA_LINKS link
+ * descriptors that ping-pong from one period to the next.  For example, if
+ * there are six periods and two link descriptors, this is how they look
+ * before playback starts:
+ *
+ *      	   The last link descriptor
+ *   ____________  points back to the first
+ *  |   	 |
+ *  V   	 |
+ *  ___    ___   |
+ * |   |->|   |->|
+ * |___|  |___|
+ *   |      |
+ *   |      |
+ *   V      V
+ *  _________________________________________
+ * |      |      |      |      |      |      |  The DMA buffer is
+ * |      |      |      |      |      |      |    divided into 6 parts
+ * |______|______|______|______|______|______|
+ *
+ * and here's how they look after the first period is finished playing:
+ *
+ *   ____________
+ *  |   	 |
+ *  V   	 |
+ *  ___    ___   |
+ * |   |->|   |->|
+ * |___|  |___|
+ *   |      |
+ *   |______________
+ *          |       |
+ *          V       V
+ *  _________________________________________
+ * |      |      |      |      |      |      |
+ * |      |      |      |      |      |      |
+ * |______|______|______|______|______|______|
+ *
+ * The first link descriptor now points to the third period.  The DMA
+ * controller is currently playing the second period.  When it finishes, it
+ * will jump back to the first descriptor and play the third period.
+ *
+ * There are four reasons we do this:
+ *
+ * 1. The only way to get the DMA controller to automatically restart the
+ *    transfer when it gets to the end of the buffer is to use chaining
+ *    mode.  Basic direct mode doesn't offer that feature.
+ * 2. We need to receive an interrupt at the end of every period.  The DMA
+ *    controller can generate an interrupt at the end of every link transfer
+ *    (aka segment).  Making each period into a DMA segment will give us the
+ *    interrupts we need.
+ * 3. By creating only two link descriptors, regardless of the number of
+ *    periods, we do not need to reallocate the link descriptors if the
+ *    number of periods changes.
+ * 4. All of the audio data is still stored in a single, contiguous DMA
+ *    buffer, which is what ALSA expects.  We're just dividing it into
+ *    contiguous parts, and creating a link descriptor for each one.
+ *
+ * Note that due to a quirk of the SSI's STX register, the target address
+ * for the DMA operations depends on the sample size.  So we don't program
+ * the dest_addr (for playback -- source_addr for capture) fields in the
+ * link descriptors here.  We do that in fsl_dma_prepare()
+ */
+static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+
+	dma_addr_t temp_addr;   /* Pointer to next period */
+	u64 temp_link;  	/* Pointer to next link descriptor */
+	u32 mr; 		/* Temporary variable for MR register */
+
+	unsigned int i;
+
+	/* Get all the parameters we need */
+	size_t buffer_size = params_buffer_bytes(hw_params);
+	size_t period_size = params_period_bytes(hw_params);
+
+	/* Initialize our DMA tracking variables */
+	dma_private->period_size = period_size;
+	dma_private->num_periods = params_periods(hw_params);
+	dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size;
+	dma_private->dma_buf_next = dma_private->dma_buf_phys +
+		(NUM_DMA_LINKS * period_size);
+	if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+		dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+	/*
+	 * Initialize each link descriptor.
+	 *
+	 * The actual address in STX0 (destination for playback, source for
+	 * capture) is based on the sample size, but we don't know the sample
+	 * size in this function, so we'll have to adjust that later.  See
+	 * comments in fsl_dma_prepare().
+	 *
+	 * The DMA controller does not have a cache, so the CPU does not
+	 * need to tell it to flush its cache.  However, the DMA
+	 * controller does need to tell the CPU to flush its cache.
+	 * That's what the SNOOP bit does.
+	 *
+	 * Also, even though the DMA controller supports 36-bit addressing, for
+	 * simplicity we currently support only 32-bit addresses for the audio
+	 * buffer itself.
+	 */
+	temp_addr = substream->dma_buffer.addr;
+	temp_link = dma_private->ld_buf_phys +
+		sizeof(struct fsl_dma_link_descriptor);
+
+	for (i = 0; i < NUM_DMA_LINKS; i++) {
+		struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+		link->count = cpu_to_be32(period_size);
+		link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP);
+		link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP);
+		link->next = cpu_to_be64(temp_link);
+
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			link->source_addr = cpu_to_be32(temp_addr);
+		else
+			link->dest_addr = cpu_to_be32(temp_addr);
+
+		temp_addr += period_size;
+		temp_link += sizeof(struct fsl_dma_link_descriptor);
+	}
+	/* The last link descriptor points to the first */
+	dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys);
+
+	/* Tell the DMA controller where the first link descriptor is */
+	out_be32(&dma_channel->clndar,
+		CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys));
+	out_be32(&dma_channel->eclndar,
+		CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys));
+
+	/* The manual says the BCR must be clear before enabling EMP */
+	out_be32(&dma_channel->bcr, 0);
+
+	/*
+	 * Program the mode register for interrupts, external master control,
+	 * and source/destination hold.  Also clear the Channel Abort bit.
+	 */
+	mr = in_be32(&dma_channel->mr) &
+		~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE);
+
+	/*
+	 * We want External Master Start and External Master Pause enabled,
+	 * because the SSI is controlling the DMA controller.  We want the DMA
+	 * controller to be set up in advance, and then we signal only the SSI
+	 * to start transfering.
+	 *
+	 * We want End-Of-Segment Interrupts enabled, because this will generate
+	 * an interrupt at the end of each segment (each link descriptor
+	 * represents one segment).  Each DMA segment is the same thing as an
+	 * ALSA period, so this is how we get an interrupt at the end of every
+	 * period.
+	 *
+	 * We want Error Interrupt enabled, so that we can get an error if
+	 * the DMA controller is mis-programmed somehow.
+	 */
+	mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN |
+		CCSR_DMA_MR_EMS_EN;
+
+	/* For playback, we want the destination address to be held.  For
+	   capture, set the source address to be held. */
+	mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE;
+
+	out_be32(&dma_channel->mr, mr);
+
+	return 0;
+}
+
+/**
+ * fsl_dma_prepare - prepare the DMA registers for playback.
+ *
+ * This function is called after the specifics of the audio data are known,
+ * i.e. snd_pcm_runtime is initialized.
+ *
+ * In this function, we finish programming the registers of the DMA
+ * controller that are dependent on the sample size.
+ *
+ * One of the drawbacks with big-endian is that when copying integers of
+ * different sizes to a fixed-sized register, the address to which the
+ * integer must be copied is dependent on the size of the integer.
+ *
+ * For example, if P is the address of a 32-bit register, and X is a 32-bit
+ * integer, then X should be copied to address P.  However, if X is a 16-bit
+ * integer, then it should be copied to P+2.  If X is an 8-bit register,
+ * then it should be copied to P+3.
+ *
+ * So for playback of 8-bit samples, the DMA controller must transfer single
+ * bytes from the DMA buffer to the last byte of the STX0 register, i.e.
+ * offset by 3 bytes. For 16-bit samples, the offset is two bytes.
+ *
+ * For 24-bit samples, the offset is 1 byte.  However, the DMA controller
+ * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4,
+ * and 8 bytes at a time).  So we do not support packed 24-bit samples.
+ * 24-bit data must be padded to 32 bits.
+ */
+static int fsl_dma_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+	u32 mr;
+	unsigned int i;
+	dma_addr_t ssi_sxx_phys;	/* Bus address of SSI STX register */
+	unsigned int frame_size;	/* Number of bytes per frame */
+
+	ssi_sxx_phys = dma_private->ssi_sxx_phys;
+
+	mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK |
+		  CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK);
+
+	switch (runtime->sample_bits) {
+	case 8:
+		mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1;
+		ssi_sxx_phys += 3;
+		break;
+	case 16:
+		mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2;
+		ssi_sxx_phys += 2;
+		break;
+	case 32:
+		mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4;
+		break;
+	default:
+		dev_err(substream->pcm->card->dev,
+			"unsupported sample size %u\n", runtime->sample_bits);
+		return -EINVAL;
+	}
+
+	frame_size = runtime->frame_bits / 8;
+	/*
+	 * BWC should always be a multiple of the frame size.  BWC determines
+	 * how many bytes are sent/received before the DMA controller checks the
+	 * SSI to see if it needs to stop.  For playback, the transmit FIFO can
+	 * hold three frames, so we want to send two frames at a time. For
+	 * capture, the receive FIFO is triggered when it contains one frame, so
+	 * we want to receive one frame at a time.
+	 */
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		mr |= CCSR_DMA_MR_BWC(2 * frame_size);
+	else
+		mr |= CCSR_DMA_MR_BWC(frame_size);
+
+	out_be32(&dma_channel->mr, mr);
+
+	/*
+	 * Program the address of the DMA transfer to/from the SSI.
+	 */
+	for (i = 0; i < NUM_DMA_LINKS; i++) {
+		struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			link->dest_addr = cpu_to_be32(ssi_sxx_phys);
+		else
+			link->source_addr = cpu_to_be32(ssi_sxx_phys);
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_pointer: determine the current position of the DMA transfer
+ *
+ * This function is called by ALSA when ALSA wants to know where in the
+ * stream buffer the hardware currently is.
+ *
+ * For playback, the SAR register contains the physical address of the most
+ * recent DMA transfer.  For capture, the value is in the DAR register.
+ *
+ * The base address of the buffer is stored in the source_addr field of the
+ * first link descriptor.
+ */
+static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+	dma_addr_t position;
+	snd_pcm_uframes_t frames;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		position = in_be32(&dma_channel->sar);
+	else
+		position = in_be32(&dma_channel->dar);
+
+	frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys);
+
+	/*
+	 * If the current address is just past the end of the buffer, wrap it
+	 * around.
+	 */
+	if (frames == runtime->buffer_size)
+		frames = 0;
+
+	return frames;
+}
+
+/**
+ * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params()
+ *
+ * Release the resources allocated in fsl_dma_hw_params() and de-program the
+ * registers.
+ *
+ * This function can be called multiple times.
+ */
+static int fsl_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+
+	if (dma_private) {
+		struct ccsr_dma_channel __iomem *dma_channel;
+
+		dma_channel = dma_private->dma_channel;
+
+		/* Stop the DMA */
+		out_be32(&dma_channel->mr, CCSR_DMA_MR_CA);
+		out_be32(&dma_channel->mr, 0);
+
+		/* Reset all the other registers */
+		out_be32(&dma_channel->sr, -1);
+		out_be32(&dma_channel->clndar, 0);
+		out_be32(&dma_channel->eclndar, 0);
+		out_be32(&dma_channel->satr, 0);
+		out_be32(&dma_channel->sar, 0);
+		out_be32(&dma_channel->datr, 0);
+		out_be32(&dma_channel->dar, 0);
+		out_be32(&dma_channel->bcr, 0);
+		out_be32(&dma_channel->nlndar, 0);
+		out_be32(&dma_channel->enlndar, 0);
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_close: close the stream.
+ */
+static int fsl_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	int dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+	if (dma_private) {
+		if (dma_private->irq)
+			free_irq(dma_private->irq, dma_private);
+
+		if (dma_private->ld_buf_phys) {
+			dma_unmap_single(substream->pcm->dev,
+				dma_private->ld_buf_phys,
+				sizeof(dma_private->link), DMA_TO_DEVICE);
+		}
+
+		/* Deallocate the fsl_dma_private structure */
+		dma_free_coherent(substream->pcm->dev,
+			sizeof(struct fsl_dma_private),
+			dma_private, dma_private->ld_buf_phys);
+		substream->runtime->private_data = NULL;
+	}
+
+	dma_global_data.assigned[dir] = 0;
+
+	return 0;
+}
+
+/*
+ * Remove this PCM driver.
+ */
+static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
+		substream = pcm->streams[i].substream;
+		if (substream) {
+			snd_dma_free_pages(&substream->dma_buffer);
+			substream->dma_buffer.area = NULL;
+			substream->dma_buffer.addr = 0;
+		}
+	}
+}
+
+static struct snd_pcm_ops fsl_dma_ops = {
+	.open   	= fsl_dma_open,
+	.close  	= fsl_dma_close,
+	.ioctl  	= snd_pcm_lib_ioctl,
+	.hw_params      = fsl_dma_hw_params,
+	.hw_free	= fsl_dma_hw_free,
+	.prepare	= fsl_dma_prepare,
+	.pointer	= fsl_dma_pointer,
+};
+
+struct snd_soc_platform fsl_soc_platform = {
+	.name   	= "fsl-dma",
+	.pcm_ops	= &fsl_dma_ops,
+	.pcm_new	= fsl_dma_new,
+	.pcm_free       = fsl_dma_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(fsl_soc_platform);
+
+/**
+ * fsl_dma_configure: store the DMA parameters from the fabric driver.
+ *
+ * This function is called by the ASoC fabric driver to give us the DMA and
+ * SSI channel information.
+ *
+ * Unfortunately, ASoC V1 does make it possible to determine the DMA/SSI
+ * data when a substream is created, so for now we need to store this data
+ * into a global variable.  This means that we can only support one DMA
+ * controller, and hence only one SSI.
+ */
+int fsl_dma_configure(struct fsl_dma_info *dma_info)
+{
+	static int initialized;
+
+	/* We only support one DMA controller for now */
+	if (initialized)
+		return 0;
+
+	dma_global_data.ssi_stx_phys = dma_info->ssi_stx_phys;
+	dma_global_data.ssi_srx_phys = dma_info->ssi_srx_phys;
+	dma_global_data.dma_channel[0] = dma_info->dma_channel[0];
+	dma_global_data.dma_channel[1] = dma_info->dma_channel[1];
+	dma_global_data.irq[0] = dma_info->dma_irq[0];
+	dma_global_data.irq[1] = dma_info->dma_irq[1];
+	dma_global_data.assigned[0] = 0;
+	dma_global_data.assigned[1] = 0;
+
+	initialized = 1;
+	return 1;
+}
+EXPORT_SYMBOL_GPL(fsl_dma_configure);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h
new file mode 100644
index 0000000..430a6ce
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.h
@@ -0,0 +1,149 @@
+/*
+ * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MPC8610_PCM_H
+#define _MPC8610_PCM_H
+
+struct ccsr_dma {
+	u8 res0[0x100];
+	struct ccsr_dma_channel {
+		__be32 mr;      /* Mode register */
+		__be32 sr;      /* Status register */
+		__be32 eclndar; /* Current link descriptor extended addr reg */
+		__be32 clndar;  /* Current link descriptor address register */
+		__be32 satr;    /* Source attributes register */
+		__be32 sar;     /* Source address register */
+		__be32 datr;    /* Destination attributes register */
+		__be32 dar;     /* Destination address register */
+		__be32 bcr;     /* Byte count register */
+		__be32 enlndar; /* Next link descriptor extended address reg */
+		__be32 nlndar;  /* Next link descriptor address register */
+		u8 res1[4];
+		__be32 eclsdar; /* Current list descriptor extended addr reg */
+		__be32 clsdar;  /* Current list descriptor address register */
+		__be32 enlsdar; /* Next list descriptor extended address reg */
+		__be32 nlsdar;  /* Next list descriptor address register */
+		__be32 ssr;     /* Source stride register */
+		__be32 dsr;     /* Destination stride register */
+		u8 res2[0x38];
+	} channel[4];
+	__be32 dgsr;
+};
+
+#define CCSR_DMA_MR_BWC_DISABLED	0x0F000000
+#define CCSR_DMA_MR_BWC_SHIFT   	24
+#define CCSR_DMA_MR_BWC_MASK    	0x0F000000
+#define CCSR_DMA_MR_BWC(x) \
+	((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK)
+#define CCSR_DMA_MR_EMP_EN      	0x00200000
+#define CCSR_DMA_MR_EMS_EN      	0x00040000
+#define CCSR_DMA_MR_DAHTS_MASK  	0x00030000
+#define CCSR_DMA_MR_DAHTS_1     	0x00000000
+#define CCSR_DMA_MR_DAHTS_2     	0x00010000
+#define CCSR_DMA_MR_DAHTS_4     	0x00020000
+#define CCSR_DMA_MR_DAHTS_8     	0x00030000
+#define CCSR_DMA_MR_SAHTS_MASK  	0x0000C000
+#define CCSR_DMA_MR_SAHTS_1     	0x00000000
+#define CCSR_DMA_MR_SAHTS_2     	0x00004000
+#define CCSR_DMA_MR_SAHTS_4     	0x00008000
+#define CCSR_DMA_MR_SAHTS_8     	0x0000C000
+#define CCSR_DMA_MR_DAHE		0x00002000
+#define CCSR_DMA_MR_SAHE		0x00001000
+#define CCSR_DMA_MR_SRW 		0x00000400
+#define CCSR_DMA_MR_EOSIE       	0x00000200
+#define CCSR_DMA_MR_EOLNIE      	0x00000100
+#define CCSR_DMA_MR_EOLSIE      	0x00000080
+#define CCSR_DMA_MR_EIE 		0x00000040
+#define CCSR_DMA_MR_XFE 		0x00000020
+#define CCSR_DMA_MR_CDSM_SWSM   	0x00000010
+#define CCSR_DMA_MR_CA  		0x00000008
+#define CCSR_DMA_MR_CTM 		0x00000004
+#define CCSR_DMA_MR_CC  		0x00000002
+#define CCSR_DMA_MR_CS  		0x00000001
+
+#define CCSR_DMA_SR_TE  		0x00000080
+#define CCSR_DMA_SR_CH  		0x00000020
+#define CCSR_DMA_SR_PE  		0x00000010
+#define CCSR_DMA_SR_EOLNI       	0x00000008
+#define CCSR_DMA_SR_CB  		0x00000004
+#define CCSR_DMA_SR_EOSI		0x00000002
+#define CCSR_DMA_SR_EOLSI       	0x00000001
+
+/* ECLNDAR takes bits 32-36 of the CLNDAR register */
+static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x)
+{
+	return (x >> 32) & 0xf;
+}
+
+#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE)
+#define CCSR_DMA_CLNDAR_EOSIE   	0x00000008
+
+/* SATR and DATR, combined */
+#define CCSR_DMA_ATR_PBATMU     	0x20000000
+#define CCSR_DMA_ATR_TFLOWLVL_0 	0x00000000
+#define CCSR_DMA_ATR_TFLOWLVL_1 	0x06000000
+#define CCSR_DMA_ATR_TFLOWLVL_2 	0x08000000
+#define CCSR_DMA_ATR_TFLOWLVL_3 	0x0C000000
+#define CCSR_DMA_ATR_PCIORDER   	0x02000000
+#define CCSR_DMA_ATR_SME		0x01000000
+#define CCSR_DMA_ATR_NOSNOOP    	0x00040000
+#define CCSR_DMA_ATR_SNOOP      	0x00050000
+#define CCSR_DMA_ATR_ESAD_MASK  	0x0000000F
+
+/**
+ *  List Descriptor for extended chaining mode DMA operations.
+ *
+ *  The CLSDAR register points to the first (in a linked-list) List
+ *  Descriptor.  Each object must be aligned on a 32-byte boundary. Each
+ *  list descriptor points to a linked-list of link Descriptors.
+ */
+struct fsl_dma_list_descriptor {
+	__be64 next;    	/* Address of next list descriptor */
+	__be64 first_link;      /* Address of first link descriptor */
+	__be32 source;  	/* Source stride */
+	__be32 dest;    	/* Destination stride */
+	u8 res[8];      	/* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+/**
+ *  Link Descriptor for basic and extended chaining mode DMA operations.
+ *
+ *  A Link Descriptor points to a single DMA buffer.  Each link descriptor
+ *  must be aligned on a 32-byte boundary.
+ */
+struct fsl_dma_link_descriptor {
+	__be32 source_attr;     /* Programmed into SATR register */
+	__be32 source_addr;     /* Programmed into SAR register */
+	__be32 dest_attr;       /* Programmed into DATR register */
+	__be32 dest_addr;       /* Programmed into DAR register */
+	__be64 next;    /* Address of next link descriptor */
+	__be32 count;   /* Byte count */
+	u8 res[4];      /* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+/* DMA information needed to create a snd_soc_cpu_dai object
+ *
+ * ssi_stx_phys: bus address of SSI STX register to use
+ * ssi_srx_phys: bus address of SSI SRX register to use
+ * dma[0]: points to the DMA channel to use for playback
+ * dma[1]: points to the DMA channel to use for capture
+ * dma_irq[0]: IRQ of the DMA channel to use for playback
+ * dma_irq[1]: IRQ of the DMA channel to use for capture
+ */
+struct fsl_dma_info {
+	dma_addr_t ssi_stx_phys;
+	dma_addr_t ssi_srx_phys;
+	struct ccsr_dma_channel __iomem *dma_channel[2];
+	unsigned int dma_irq[2];
+};
+
+extern struct snd_soc_platform fsl_soc_platform;
+
+int fsl_dma_configure(struct fsl_dma_info *dma_info);
+
+#endif
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
new file mode 100644
index 0000000..145ad13
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -0,0 +1,644 @@
+/*
+ * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc.  This file is licensed
+ * under the terms of the GNU General Public License version 2.  This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/immap_86xx.h>
+
+#include "fsl_ssi.h"
+
+/**
+ * FSLSSI_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the SSI running in I2S slave mode,
+ * which means the codec determines the sample rate.  Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+			  SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * FSLSSI_I2S_FORMATS: audio formats supported by the SSI
+ *
+ * This driver currently only supports the SSI running in I2S slave mode.
+ *
+ * The SSI has a limitation in that the samples must be in the same byte
+ * order as the host CPU.  This is because when multiple bytes are written
+ * to the STX register, the bytes and bits must be written in the same
+ * order.  The STX is a shift register, so all the bits need to be aligned
+ * (bit-endianness must match byte-endianness).  Processors typically write
+ * the bits within a byte in the same order that the bytes of a word are
+ * written in.  So if the host CPU is big-endian, then only big-endian
+ * samples will be written to STX properly.
+ */
+#ifdef __BIG_ENDIAN
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+	 SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \
+	 SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE)
+#else
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+	 SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
+#endif
+
+/**
+ * fsl_ssi_private: per-SSI private data
+ *
+ * @name: short name for this device ("SSI0", "SSI1", etc)
+ * @ssi: pointer to the SSI's registers
+ * @ssi_phys: physical address of the SSI registers
+ * @irq: IRQ of this SSI
+ * @dev: struct device pointer
+ * @playback: the number of playback streams opened
+ * @capture: the number of capture streams opened
+ * @cpu_dai: the CPU DAI for this device
+ * @dev_attr: the sysfs device attribute structure
+ * @stats: SSI statistics
+ */
+struct fsl_ssi_private {
+	char name[8];
+	struct ccsr_ssi __iomem *ssi;
+	dma_addr_t ssi_phys;
+	unsigned int irq;
+	struct device *dev;
+	unsigned int playback;
+	unsigned int capture;
+	struct snd_soc_cpu_dai cpu_dai;
+	struct device_attribute dev_attr;
+
+	struct {
+		unsigned int rfrc;
+		unsigned int tfrc;
+		unsigned int cmdau;
+		unsigned int cmddu;
+		unsigned int rxt;
+		unsigned int rdr1;
+		unsigned int rdr0;
+		unsigned int tde1;
+		unsigned int tde0;
+		unsigned int roe1;
+		unsigned int roe0;
+		unsigned int tue1;
+		unsigned int tue0;
+		unsigned int tfs;
+		unsigned int rfs;
+		unsigned int tls;
+		unsigned int rls;
+		unsigned int rff1;
+		unsigned int rff0;
+		unsigned int tfe1;
+		unsigned int tfe0;
+	} stats;
+};
+
+/**
+ * fsl_ssi_isr: SSI interrupt handler
+ *
+ * Although it's possible to use the interrupt handler to send and receive
+ * data to/from the SSI, we use the DMA instead.  Programming is more
+ * complicated, but the performance is much better.
+ *
+ * This interrupt handler is used only to gather statistics.
+ *
+ * @irq: IRQ of the SSI device
+ * @dev_id: pointer to the ssi_private structure for this SSI device
+ */
+static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
+{
+	struct fsl_ssi_private *ssi_private = dev_id;
+	struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+	irqreturn_t ret = IRQ_NONE;
+	__be32 sisr;
+	__be32 sisr2 = 0;
+
+	/* We got an interrupt, so read the status register to see what we
+	   were interrupted for.  We mask it with the Interrupt Enable register
+	   so that we only check for events that we're interested in.
+	 */
+	sisr = in_be32(&ssi->sisr) & in_be32(&ssi->sier);
+
+	if (sisr & CCSR_SSI_SISR_RFRC) {
+		ssi_private->stats.rfrc++;
+		sisr2 |= CCSR_SSI_SISR_RFRC;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFRC) {
+		ssi_private->stats.tfrc++;
+		sisr2 |= CCSR_SSI_SISR_TFRC;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_CMDAU) {
+		ssi_private->stats.cmdau++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_CMDDU) {
+		ssi_private->stats.cmddu++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RXT) {
+		ssi_private->stats.rxt++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RDR1) {
+		ssi_private->stats.rdr1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RDR0) {
+		ssi_private->stats.rdr0++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TDE1) {
+		ssi_private->stats.tde1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TDE0) {
+		ssi_private->stats.tde0++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_ROE1) {
+		ssi_private->stats.roe1++;
+		sisr2 |= CCSR_SSI_SISR_ROE1;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_ROE0) {
+		ssi_private->stats.roe0++;
+		sisr2 |= CCSR_SSI_SISR_ROE0;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TUE1) {
+		ssi_private->stats.tue1++;
+		sisr2 |= CCSR_SSI_SISR_TUE1;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TUE0) {
+		ssi_private->stats.tue0++;
+		sisr2 |= CCSR_SSI_SISR_TUE0;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFS) {
+		ssi_private->stats.tfs++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RFS) {
+		ssi_private->stats.rfs++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TLS) {
+		ssi_private->stats.tls++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RLS) {
+		ssi_private->stats.rls++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RFF1) {
+		ssi_private->stats.rff1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RFF0) {
+		ssi_private->stats.rff0++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFE1) {
+		ssi_private->stats.tfe1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFE0) {
+		ssi_private->stats.tfe0++;
+		ret = IRQ_HANDLED;
+	}
+
+	/* Clear the bits that we set */
+	if (sisr2)
+		out_be32(&ssi->sisr, sisr2);
+
+	return ret;
+}
+
+/**
+ * fsl_ssi_startup: create a new substream
+ *
+ * This is the first function called when a stream is opened.
+ *
+ * If this is the first stream open, then grab the IRQ and program most of
+ * the SSI registers.
+ */
+static int fsl_ssi_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+
+	/*
+	 * If this is the first stream opened, then request the IRQ
+	 * and initialize the SSI registers.
+	 */
+	if (!ssi_private->playback && !ssi_private->capture) {
+		struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+		int ret;
+
+		ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0,
+				  ssi_private->name, ssi_private);
+		if (ret < 0) {
+			dev_err(substream->pcm->card->dev,
+				"could not claim irq %u\n", ssi_private->irq);
+			return ret;
+		}
+
+		/*
+		 * Section 16.5 of the MPC8610 reference manual says that the
+		 * SSI needs to be disabled before updating the registers we set
+		 * here.
+		 */
+		clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+		/*
+		 * Program the SSI into I2S Slave Non-Network Synchronous mode.
+		 * Also enable the transmit and receive FIFO.
+		 *
+		 * FIXME: Little-endian samples require a different shift dir
+		 */
+		clrsetbits_be32(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK,
+			CCSR_SSI_SCR_TFR_CLK_DIS |
+			CCSR_SSI_SCR_I2S_MODE_SLAVE | CCSR_SSI_SCR_SYN);
+
+		out_be32(&ssi->stcr,
+			 CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
+			 CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
+			 CCSR_SSI_STCR_TSCKP);
+
+		out_be32(&ssi->srcr,
+			 CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
+			 CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
+			 CCSR_SSI_SRCR_RSCKP);
+
+		/*
+		 * The DC and PM bits are only used if the SSI is the clock
+		 * master.
+		 */
+
+		/* 4. Enable the interrupts and DMA requests */
+		out_be32(&ssi->sier,
+			 CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE |
+			 CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN |
+			 CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN |
+			 CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE |
+			 CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN);
+
+		/*
+		 * Set the watermark for transmit FIFI 0 and receive FIFO 0. We
+		 * don't use FIFO 1.  Since the SSI only supports stereo, the
+		 * watermark should never be an odd number.
+		 */
+		out_be32(&ssi->sfcsr,
+			 CCSR_SSI_SFCSR_TFWM0(6) | CCSR_SSI_SFCSR_RFWM0(2));
+
+		/*
+		 * We keep the SSI disabled because if we enable it, then the
+		 * DMA controller will start.  It's not supposed to start until
+		 * the SCR.TE (or SCR.RE) bit is set, but it does anyway.  The
+		 * DMA controller will transfer one "BWC" of data (i.e. the
+		 * amount of data that the MR.BWC bits are set to).  The reason
+		 * this is bad is because at this point, the PCM driver has not
+		 * finished initializing the DMA controller.
+		 */
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ssi_private->playback++;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		ssi_private->capture++;
+
+	return 0;
+}
+
+/**
+ * fsl_ssi_prepare: prepare the SSI.
+ *
+ * Most of the SSI registers have been programmed in the startup function,
+ * but the word length must be programmed here.  Unfortunately, programming
+ * the SxCCR.WL bits requires the SSI to be temporarily disabled.  This can
+ * cause a problem with supporting simultaneous playback and capture.  If
+ * the SSI is already playing a stream, then that stream may be temporarily
+ * stopped when you start capture.
+ *
+ * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the
+ * clock master.
+ */
+static int fsl_ssi_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+
+	struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+	u32 wl;
+
+	wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format));
+
+	clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl);
+	else
+		clrsetbits_be32(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl);
+
+	setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+	return 0;
+}
+
+/**
+ * fsl_ssi_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ *
+ * The DMA channel is in external master start and pause mode, which
+ * means the SSI completely controls the flow of data.
+ */
+static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+	struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			setbits32(&ssi->scr, CCSR_SSI_SCR_TE);
+		} else {
+			setbits32(&ssi->scr, CCSR_SSI_SCR_RE);
+
+			/*
+			 * I think we need this delay to allow time for the SSI
+			 * to put data into its FIFO.  Without it, ALSA starts
+			 * to complain about overruns.
+			 */
+			msleep(1);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			clrbits32(&ssi->scr, CCSR_SSI_SCR_TE);
+		else
+			clrbits32(&ssi->scr, CCSR_SSI_SCR_RE);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_ssi_shutdown: shutdown the SSI
+ *
+ * Shutdown the SSI if there are no other substreams open.
+ */
+static void fsl_ssi_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ssi_private->playback--;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		ssi_private->capture--;
+
+	/*
+	 * If this is the last active substream, disable the SSI and release
+	 * the IRQ.
+	 */
+	if (!ssi_private->playback && !ssi_private->capture) {
+		struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+		clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+		free_irq(ssi_private->irq, ssi_private);
+	}
+}
+
+/**
+ * fsl_ssi_set_sysclk: set the clock frequency and direction
+ *
+ * This function is called by the machine driver to tell us what the clock
+ * frequency and direction are.
+ *
+ * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
+ * and we don't care about the frequency.  Return an error if the direction
+ * is not SND_SOC_CLOCK_IN.
+ *
+ * @clk_id: reserved, should be zero
+ * @freq: the frequency of the given clock ID, currently ignored
+ * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
+ */
+static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
+			      int clk_id, unsigned int freq, int dir)
+{
+
+	return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
+}
+
+/**
+ * fsl_ssi_set_fmt: set the serial format.
+ *
+ * This function is called by the machine driver to tell us what serial
+ * format to use.
+ *
+ * Currently, we only support I2S mode.  Return an error if the format is
+ * not SND_SOC_DAIFMT_I2S.
+ *
+ * @format: one of SND_SOC_DAIFMT_xxx
+ */
+static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format)
+{
+	return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
+}
+
+/**
+ * fsl_ssi_dai_template: template CPU DAI for the SSI
+ */
+static struct snd_soc_cpu_dai fsl_ssi_dai_template = {
+	.playback = {
+		/* The SSI does not support monaural audio. */
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = FSLSSI_I2S_RATES,
+		.formats = FSLSSI_I2S_FORMATS,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = FSLSSI_I2S_RATES,
+		.formats = FSLSSI_I2S_FORMATS,
+	},
+	.ops = {
+		.startup = fsl_ssi_startup,
+		.prepare = fsl_ssi_prepare,
+		.shutdown = fsl_ssi_shutdown,
+		.trigger = fsl_ssi_trigger,
+	},
+	.dai_ops = {
+		.set_sysclk = fsl_ssi_set_sysclk,
+		.set_fmt = fsl_ssi_set_fmt,
+	},
+};
+
+/**
+ * fsl_sysfs_ssi_show: display SSI statistics
+ *
+ * Display the statistics for the current SSI device.
+ */
+static ssize_t fsl_sysfs_ssi_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct fsl_ssi_private *ssi_private =
+	container_of(attr, struct fsl_ssi_private, dev_attr);
+	ssize_t length;
+
+	length = sprintf(buf, "rfrc=%u", ssi_private->stats.rfrc);
+	length += sprintf(buf + length, "\ttfrc=%u", ssi_private->stats.tfrc);
+	length += sprintf(buf + length, "\tcmdau=%u", ssi_private->stats.cmdau);
+	length += sprintf(buf + length, "\tcmddu=%u", ssi_private->stats.cmddu);
+	length += sprintf(buf + length, "\trxt=%u", ssi_private->stats.rxt);
+	length += sprintf(buf + length, "\trdr1=%u", ssi_private->stats.rdr1);
+	length += sprintf(buf + length, "\trdr0=%u", ssi_private->stats.rdr0);
+	length += sprintf(buf + length, "\ttde1=%u", ssi_private->stats.tde1);
+	length += sprintf(buf + length, "\ttde0=%u", ssi_private->stats.tde0);
+	length += sprintf(buf + length, "\troe1=%u", ssi_private->stats.roe1);
+	length += sprintf(buf + length, "\troe0=%u", ssi_private->stats.roe0);
+	length += sprintf(buf + length, "\ttue1=%u", ssi_private->stats.tue1);
+	length += sprintf(buf + length, "\ttue0=%u", ssi_private->stats.tue0);
+	length += sprintf(buf + length, "\ttfs=%u", ssi_private->stats.tfs);
+	length += sprintf(buf + length, "\trfs=%u", ssi_private->stats.rfs);
+	length += sprintf(buf + length, "\ttls=%u", ssi_private->stats.tls);
+	length += sprintf(buf + length, "\trls=%u", ssi_private->stats.rls);
+	length += sprintf(buf + length, "\trff1=%u", ssi_private->stats.rff1);
+	length += sprintf(buf + length, "\trff0=%u", ssi_private->stats.rff0);
+	length += sprintf(buf + length, "\ttfe1=%u", ssi_private->stats.tfe1);
+	length += sprintf(buf + length, "\ttfe0=%u\n", ssi_private->stats.tfe0);
+
+	return length;
+}
+
+/**
+ * fsl_ssi_create_dai: create a snd_soc_cpu_dai structure
+ *
+ * This function is called by the machine driver to create a snd_soc_cpu_dai
+ * structure.  The function creates an ssi_private object, which contains
+ * the snd_soc_cpu_dai.  It also creates the sysfs statistics device.
+ */
+struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
+{
+	struct snd_soc_cpu_dai *fsl_ssi_dai;
+	struct fsl_ssi_private *ssi_private;
+	int ret = 0;
+	struct device_attribute *dev_attr;
+
+	ssi_private = kzalloc(sizeof(struct fsl_ssi_private), GFP_KERNEL);
+	if (!ssi_private) {
+		dev_err(ssi_info->dev, "could not allocate DAI object\n");
+		return NULL;
+	}
+	memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template,
+	       sizeof(struct snd_soc_cpu_dai));
+
+	fsl_ssi_dai = &ssi_private->cpu_dai;
+	dev_attr = &ssi_private->dev_attr;
+
+	sprintf(ssi_private->name, "ssi%u", (u8) ssi_info->id);
+	ssi_private->ssi = ssi_info->ssi;
+	ssi_private->ssi_phys = ssi_info->ssi_phys;
+	ssi_private->irq = ssi_info->irq;
+	ssi_private->dev = ssi_info->dev;
+
+	ssi_private->dev->driver_data = fsl_ssi_dai;
+
+	/* Initialize the the device_attribute structure */
+	dev_attr->attr.name = "ssi-stats";
+	dev_attr->attr.mode = S_IRUGO;
+	dev_attr->show = fsl_sysfs_ssi_show;
+
+	ret = device_create_file(ssi_private->dev, dev_attr);
+	if (ret) {
+		dev_err(ssi_info->dev, "could not create sysfs %s file\n",
+			ssi_private->dev_attr.attr.name);
+		kfree(fsl_ssi_dai);
+		return NULL;
+	}
+
+	fsl_ssi_dai->private_data = ssi_private;
+	fsl_ssi_dai->name = ssi_private->name;
+	fsl_ssi_dai->id = ssi_info->id;
+
+	return fsl_ssi_dai;
+}
+EXPORT_SYMBOL_GPL(fsl_ssi_create_dai);
+
+/**
+ * fsl_ssi_destroy_dai: destroy the snd_soc_cpu_dai object
+ *
+ * This function undoes the operations of fsl_ssi_create_dai()
+ */
+void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai)
+{
+	struct fsl_ssi_private *ssi_private =
+	container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai);
+
+	device_remove_file(ssi_private->dev, &ssi_private->dev_attr);
+
+	kfree(ssi_private);
+}
+EXPORT_SYMBOL_GPL(fsl_ssi_destroy_dai);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h
new file mode 100644
index 0000000..c5ce88e
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -0,0 +1,224 @@
+/*
+ * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 SoC
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc.  This file is licensed
+ * under the terms of the GNU General Public License version 2.  This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#ifndef _MPC8610_I2S_H
+#define _MPC8610_I2S_H
+
+/* SSI Register Map */
+struct ccsr_ssi {
+	__be32 stx0;	/* 0x.0000 - SSI Transmit Data Register 0 */
+	__be32 stx1;	/* 0x.0004 - SSI Transmit Data Register 1 */
+	__be32 srx0;	/* 0x.0008 - SSI Receive Data Register 0 */
+	__be32 srx1;	/* 0x.000C - SSI Receive Data Register 1 */
+	__be32 scr;	/* 0x.0010 - SSI Control Register */
+	__be32 sisr;	/* 0x.0014 - SSI Interrupt Status Register Mixed */
+	__be32 sier;	/* 0x.0018 - SSI Interrupt Enable Register */
+	__be32 stcr;	/* 0x.001C - SSI Transmit Configuration Register */
+	__be32 srcr;	/* 0x.0020 - SSI Receive Configuration Register */
+	__be32 stccr;	/* 0x.0024 - SSI Transmit Clock Control Register */
+	__be32 srccr;	/* 0x.0028 - SSI Receive Clock Control Register */
+	__be32 sfcsr;	/* 0x.002C - SSI FIFO Control/Status Register */
+	__be32 str;	/* 0x.0030 - SSI Test Register */
+	__be32 sor;	/* 0x.0034 - SSI Option Register */
+	__be32 sacnt;	/* 0x.0038 - SSI AC97 Control Register */
+	__be32 sacadd;	/* 0x.003C - SSI AC97 Command Address Register */
+	__be32 sacdat;	/* 0x.0040 - SSI AC97 Command Data Register */
+	__be32 satag;	/* 0x.0044 - SSI AC97 Tag Register */
+	__be32 stmsk;	/* 0x.0048 - SSI Transmit Time Slot Mask Register */
+	__be32 srmsk;	/* 0x.004C - SSI Receive Time Slot Mask Register */
+	__be32 saccst;	/* 0x.0050 - SSI AC97 Channel Status Register */
+	__be32 saccen;	/* 0x.0054 - SSI AC97 Channel Enable Register */
+	__be32 saccdis; /* 0x.0058 - SSI AC97 Channel Disable Register */
+};
+
+#define CCSR_SSI_SCR_RFR_CLK_DIS	0x00000800
+#define CCSR_SSI_SCR_TFR_CLK_DIS	0x00000400
+#define CCSR_SSI_SCR_TCH_EN		0x00000100
+#define CCSR_SSI_SCR_SYS_CLK_EN		0x00000080
+#define CCSR_SSI_SCR_I2S_MODE_MASK	0x00000060
+#define CCSR_SSI_SCR_I2S_MODE_NORMAL	0x00000000
+#define CCSR_SSI_SCR_I2S_MODE_MASTER	0x00000020
+#define CCSR_SSI_SCR_I2S_MODE_SLAVE	0x00000040
+#define CCSR_SSI_SCR_SYN		0x00000010
+#define CCSR_SSI_SCR_NET		0x00000008
+#define CCSR_SSI_SCR_RE			0x00000004
+#define CCSR_SSI_SCR_TE			0x00000002
+#define CCSR_SSI_SCR_SSIEN		0x00000001
+
+#define CCSR_SSI_SISR_RFRC		0x01000000
+#define CCSR_SSI_SISR_TFRC		0x00800000
+#define CCSR_SSI_SISR_CMDAU		0x00040000
+#define CCSR_SSI_SISR_CMDDU		0x00020000
+#define CCSR_SSI_SISR_RXT		0x00010000
+#define CCSR_SSI_SISR_RDR1		0x00008000
+#define CCSR_SSI_SISR_RDR0		0x00004000
+#define CCSR_SSI_SISR_TDE1		0x00002000
+#define CCSR_SSI_SISR_TDE0		0x00001000
+#define CCSR_SSI_SISR_ROE1		0x00000800
+#define CCSR_SSI_SISR_ROE0		0x00000400
+#define CCSR_SSI_SISR_TUE1		0x00000200
+#define CCSR_SSI_SISR_TUE0		0x00000100
+#define CCSR_SSI_SISR_TFS		0x00000080
+#define CCSR_SSI_SISR_RFS		0x00000040
+#define CCSR_SSI_SISR_TLS		0x00000020
+#define CCSR_SSI_SISR_RLS		0x00000010
+#define CCSR_SSI_SISR_RFF1		0x00000008
+#define CCSR_SSI_SISR_RFF0		0x00000004
+#define CCSR_SSI_SISR_TFE1		0x00000002
+#define CCSR_SSI_SISR_TFE0		0x00000001
+
+#define CCSR_SSI_SIER_RFRC_EN		0x01000000
+#define CCSR_SSI_SIER_TFRC_EN		0x00800000
+#define CCSR_SSI_SIER_RDMAE		0x00400000
+#define CCSR_SSI_SIER_RIE		0x00200000
+#define CCSR_SSI_SIER_TDMAE		0x00100000
+#define CCSR_SSI_SIER_TIE		0x00080000
+#define CCSR_SSI_SIER_CMDAU_EN		0x00040000
+#define CCSR_SSI_SIER_CMDDU_EN		0x00020000
+#define CCSR_SSI_SIER_RXT_EN		0x00010000
+#define CCSR_SSI_SIER_RDR1_EN		0x00008000
+#define CCSR_SSI_SIER_RDR0_EN		0x00004000
+#define CCSR_SSI_SIER_TDE1_EN		0x00002000
+#define CCSR_SSI_SIER_TDE0_EN		0x00001000
+#define CCSR_SSI_SIER_ROE1_EN		0x00000800
+#define CCSR_SSI_SIER_ROE0_EN		0x00000400
+#define CCSR_SSI_SIER_TUE1_EN		0x00000200
+#define CCSR_SSI_SIER_TUE0_EN		0x00000100
+#define CCSR_SSI_SIER_TFS_EN		0x00000080
+#define CCSR_SSI_SIER_RFS_EN		0x00000040
+#define CCSR_SSI_SIER_TLS_EN		0x00000020
+#define CCSR_SSI_SIER_RLS_EN		0x00000010
+#define CCSR_SSI_SIER_RFF1_EN		0x00000008
+#define CCSR_SSI_SIER_RFF0_EN		0x00000004
+#define CCSR_SSI_SIER_TFE1_EN		0x00000002
+#define CCSR_SSI_SIER_TFE0_EN		0x00000001
+
+#define CCSR_SSI_STCR_TXBIT0		0x00000200
+#define CCSR_SSI_STCR_TFEN1		0x00000100
+#define CCSR_SSI_STCR_TFEN0		0x00000080
+#define CCSR_SSI_STCR_TFDIR		0x00000040
+#define CCSR_SSI_STCR_TXDIR		0x00000020
+#define CCSR_SSI_STCR_TSHFD		0x00000010
+#define CCSR_SSI_STCR_TSCKP		0x00000008
+#define CCSR_SSI_STCR_TFSI		0x00000004
+#define CCSR_SSI_STCR_TFSL		0x00000002
+#define CCSR_SSI_STCR_TEFS		0x00000001
+
+#define CCSR_SSI_SRCR_RXEXT		0x00000400
+#define CCSR_SSI_SRCR_RXBIT0		0x00000200
+#define CCSR_SSI_SRCR_RFEN1		0x00000100
+#define CCSR_SSI_SRCR_RFEN0		0x00000080
+#define CCSR_SSI_SRCR_RFDIR		0x00000040
+#define CCSR_SSI_SRCR_RXDIR		0x00000020
+#define CCSR_SSI_SRCR_RSHFD		0x00000010
+#define CCSR_SSI_SRCR_RSCKP		0x00000008
+#define CCSR_SSI_SRCR_RFSI		0x00000004
+#define CCSR_SSI_SRCR_RFSL		0x00000002
+#define CCSR_SSI_SRCR_REFS		0x00000001
+
+/* STCCR and SRCCR */
+#define CCSR_SSI_SxCCR_DIV2		0x00040000
+#define CCSR_SSI_SxCCR_PSR		0x00020000
+#define CCSR_SSI_SxCCR_WL_SHIFT		13
+#define CCSR_SSI_SxCCR_WL_MASK		0x0001E000
+#define CCSR_SSI_SxCCR_WL(x) \
+	(((((x) / 2) - 1) << CCSR_SSI_SxCCR_WL_SHIFT) & CCSR_SSI_SxCCR_WL_MASK)
+#define CCSR_SSI_SxCCR_DC_SHIFT		8
+#define CCSR_SSI_SxCCR_DC_MASK		0x00001F00
+#define CCSR_SSI_SxCCR_DC(x) \
+	((((x) - 1) << CCSR_SSI_SxCCR_DC_SHIFT) & CCSR_SSI_SxCCR_DC_MASK)
+#define CCSR_SSI_SxCCR_PM_SHIFT		0
+#define CCSR_SSI_SxCCR_PM_MASK		0x000000FF
+#define CCSR_SSI_SxCCR_PM(x) \
+	((((x) - 1) << CCSR_SSI_SxCCR_PM_SHIFT) & CCSR_SSI_SxCCR_PM_MASK)
+
+/*
+ * The xFCNT bits are read-only, and the xFWM bits are read/write.  Use the
+ * CCSR_SSI_SFCSR_xFCNTy() macros to read the FIFO counters, and use the
+ * CCSR_SSI_SFCSR_xFWMy() macros to set the watermarks.
+ */
+#define CCSR_SSI_SFCSR_RFCNT1_SHIFT	28
+#define CCSR_SSI_SFCSR_RFCNT1_MASK	0xF0000000
+#define CCSR_SSI_SFCSR_RFCNT1(x) \
+	(((x) & CCSR_SSI_SFCSR_RFCNT1_MASK) >> CCSR_SSI_SFCSR_RFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT1_SHIFT	24
+#define CCSR_SSI_SFCSR_TFCNT1_MASK	0x0F000000
+#define CCSR_SSI_SFCSR_TFCNT1(x) \
+	(((x) & CCSR_SSI_SFCSR_TFCNT1_MASK) >> CCSR_SSI_SFCSR_TFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM1_SHIFT	20
+#define CCSR_SSI_SFCSR_RFWM1_MASK	0x00F00000
+#define CCSR_SSI_SFCSR_RFWM1(x)	\
+	(((x) << CCSR_SSI_SFCSR_RFWM1_SHIFT) & CCSR_SSI_SFCSR_RFWM1_MASK)
+#define CCSR_SSI_SFCSR_TFWM1_SHIFT	16
+#define CCSR_SSI_SFCSR_TFWM1_MASK	0x000F0000
+#define CCSR_SSI_SFCSR_TFWM1(x)	\
+	(((x) << CCSR_SSI_SFCSR_TFWM1_SHIFT) & CCSR_SSI_SFCSR_TFWM1_MASK)
+#define CCSR_SSI_SFCSR_RFCNT0_SHIFT	12
+#define CCSR_SSI_SFCSR_RFCNT0_MASK	0x0000F000
+#define CCSR_SSI_SFCSR_RFCNT0(x) \
+	(((x) & CCSR_SSI_SFCSR_RFCNT0_MASK) >> CCSR_SSI_SFCSR_RFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT0_SHIFT	8
+#define CCSR_SSI_SFCSR_TFCNT0_MASK	0x00000F00
+#define CCSR_SSI_SFCSR_TFCNT0(x) \
+	(((x) & CCSR_SSI_SFCSR_TFCNT0_MASK) >> CCSR_SSI_SFCSR_TFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM0_SHIFT	4
+#define CCSR_SSI_SFCSR_RFWM0_MASK	0x000000F0
+#define CCSR_SSI_SFCSR_RFWM0(x)	\
+	(((x) << CCSR_SSI_SFCSR_RFWM0_SHIFT) & CCSR_SSI_SFCSR_RFWM0_MASK)
+#define CCSR_SSI_SFCSR_TFWM0_SHIFT	0
+#define CCSR_SSI_SFCSR_TFWM0_MASK	0x0000000F
+#define CCSR_SSI_SFCSR_TFWM0(x)	\
+	(((x) << CCSR_SSI_SFCSR_TFWM0_SHIFT) & CCSR_SSI_SFCSR_TFWM0_MASK)
+
+#define CCSR_SSI_STR_TEST		0x00008000
+#define CCSR_SSI_STR_RCK2TCK		0x00004000
+#define CCSR_SSI_STR_RFS2TFS		0x00002000
+#define CCSR_SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F)
+#define CCSR_SSI_STR_TXD2RXD		0x00000080
+#define CCSR_SSI_STR_TCK2RCK		0x00000040
+#define CCSR_SSI_STR_TFS2RFS		0x00000020
+#define CCSR_SSI_STR_TXSTATE(x) ((x) & 0x1F)
+
+#define CCSR_SSI_SOR_CLKOFF		0x00000040
+#define CCSR_SSI_SOR_RX_CLR		0x00000020
+#define CCSR_SSI_SOR_TX_CLR		0x00000010
+#define CCSR_SSI_SOR_INIT		0x00000008
+#define CCSR_SSI_SOR_WAIT_SHIFT		1
+#define CCSR_SSI_SOR_WAIT_MASK		0x00000006
+#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
+#define CCSR_SSI_SOR_SYNRST 		0x00000001
+
+/* Instantiation data for an SSI interface
+ *
+ * This structure contains all the information that the the SSI driver needs
+ * to instantiate an SSI interface with ALSA.  The machine driver should
+ * create this structure, fill it in, call fsl_ssi_create_dai(), and then
+ * delete the structure.
+ *
+ * id: which SSI this is (0, 1, etc. )
+ * ssi: pointer to the SSI's registers
+ * ssi_phys: physical address of the SSI registers
+ * irq: IRQ of this SSI
+ * dev: struct device, used to create the sysfs statistics file
+*/
+struct fsl_ssi_info {
+	unsigned int id;
+	struct ccsr_ssi __iomem *ssi;
+	dma_addr_t ssi_phys;
+	unsigned int irq;
+	struct device *dev;
+};
+
+struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info);
+void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai);
+
+#endif
+
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
new file mode 100644
index 0000000..f26c4b2
--- /dev/null
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -0,0 +1,631 @@
+/**
+ * Freescale MPC8610HPCD ALSA SoC Fabric driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc.  This file is licensed
+ * under the terms of the GNU General Public License version 2.  This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <asm/immap_86xx.h>
+
+#include "../codecs/cs4270.h"
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/**
+ * mpc8610_hpcd_data: fabric-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * MPC8610 HPCD.  Some of the data is taken from the device tree.
+ */
+struct mpc8610_hpcd_data {
+	struct snd_soc_device sound_devdata;
+	struct snd_soc_dai_link dai;
+	struct snd_soc_machine machine;
+	unsigned int dai_format;
+	unsigned int codec_clk_direction;
+	unsigned int cpu_clk_direction;
+	unsigned int clk_frequency;
+	struct ccsr_guts __iomem *guts;
+	struct ccsr_ssi __iomem *ssi;
+	unsigned int ssi_id;    	/* 0 = SSI1, 1 = SSI2, etc */
+	unsigned int ssi_irq;
+	unsigned int dma_id;    	/* 0 = DMA1, 1 = DMA2, etc */
+	unsigned int dma_irq[2];
+	struct ccsr_dma_channel __iomem *dma[2];
+	unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+};
+
+/**
+ * mpc8610_hpcd_machine_probe: initalize the board
+ *
+ * This function is called when platform_device_add() is called.  It is used
+ * to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
+{
+	struct mpc8610_hpcd_data *machine_data =
+		sound_device->dev.platform_data;
+
+	/* Program the signal routing between the SSI and the DMA */
+	guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1,
+		machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI);
+	guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1,
+		machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI);
+
+	guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
+		machine_data->dma_channel_id[0], 0);
+	guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
+		machine_data->dma_channel_id[1], 0);
+
+	guts_set_pmuxcr_dma(machine_data->guts, 1, 0, 0);
+	guts_set_pmuxcr_dma(machine_data->guts, 1, 3, 0);
+	guts_set_pmuxcr_dma(machine_data->guts, 0, 3, 0);
+
+	switch (machine_data->ssi_id) {
+	case 0:
+		clrsetbits_be32(&machine_data->guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
+		break;
+	case 1:
+		clrsetbits_be32(&machine_data->guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai;
+	struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
+	struct mpc8610_hpcd_data *machine_data =
+		rtd->socdev->dev->platform_data;
+	int ret = 0;
+
+	/* Tell the CPU driver what the serial protocol is. */
+	if (cpu_dai->dai_ops.set_fmt) {
+		ret = cpu_dai->dai_ops.set_fmt(cpu_dai,
+			machine_data->dai_format);
+		if (ret < 0) {
+			dev_err(substream->pcm->card->dev,
+				"could not set CPU driver audio format\n");
+			return ret;
+		}
+	}
+
+	/* Tell the codec driver what the serial protocol is. */
+	if (codec_dai->dai_ops.set_fmt) {
+		ret = codec_dai->dai_ops.set_fmt(codec_dai,
+			machine_data->dai_format);
+		if (ret < 0) {
+			dev_err(substream->pcm->card->dev,
+				"could not set codec driver audio format\n");
+			return ret;
+		}
+	}
+
+	/*
+	 * Tell the CPU driver what the clock frequency is, and whether it's a
+	 * slave or master.
+	 */
+	if (cpu_dai->dai_ops.set_sysclk) {
+		ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, 0,
+			machine_data->clk_frequency,
+			machine_data->cpu_clk_direction);
+		if (ret < 0) {
+			dev_err(substream->pcm->card->dev,
+				"could not set CPU driver clock parameters\n");
+			return ret;
+		}
+	}
+
+	/*
+	 * Tell the codec driver what the MCLK frequency is, and whether it's
+	 * a slave or master.
+	 */
+	if (codec_dai->dai_ops.set_sysclk) {
+		ret = codec_dai->dai_ops.set_sysclk(codec_dai, 0,
+			machine_data->clk_frequency,
+			machine_data->codec_clk_direction);
+		if (ret < 0) {
+			dev_err(substream->pcm->card->dev,
+				"could not set codec driver clock params\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI.  We
+ * de-program the DMACR and PMUXCR register.
+ */
+int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
+{
+	struct mpc8610_hpcd_data *machine_data =
+		sound_device->dev.platform_data;
+
+	/* Restore the signal routing */
+
+	guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1,
+		machine_data->dma_channel_id[0], 0);
+	guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1,
+		machine_data->dma_channel_id[1], 0);
+
+	switch (machine_data->ssi_id) {
+	case 0:
+		clrsetbits_be32(&machine_data->guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+		break;
+	case 1:
+		clrsetbits_be32(&machine_data->guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_ops: ASoC fabric driver operations
+ */
+static struct snd_soc_ops mpc8610_hpcd_ops = {
+	.startup = mpc8610_hpcd_startup,
+};
+
+/**
+ * mpc8610_hpcd_machine: ASoC machine data
+ */
+static struct snd_soc_machine mpc8610_hpcd_machine = {
+	.probe = mpc8610_hpcd_machine_probe,
+	.remove = mpc8610_hpcd_machine_remove,
+	.name = "MPC8610 HPCD",
+	.num_links = 1,
+};
+
+/**
+ * mpc8610_hpcd_probe: OF probe function for the fabric driver
+ *
+ * This function gets called when an SSI node is found in the device tree.
+ *
+ * Although this is a fabric driver, the SSI node is the "master" node with
+ * respect to audio hardware connections.  Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ *
+ * FIXME: Currently, we only support one DMA controller, so if there are
+ * multiple SSI nodes with codecs, only the first will be supported.
+ *
+ * FIXME: Even if we did support multiple DMA controllers, we have no
+ * mechanism for assigning DMA controllers and channels to the individual
+ * SSI devices.  We also probably aren't compatible with the generic Elo DMA
+ * device driver.
+ */
+static int mpc8610_hpcd_probe(struct of_device *ofdev,
+	const struct of_device_id *match)
+{
+	struct device_node *np = ofdev->node;
+	struct device_node *codec_np = NULL;
+	struct device_node *guts_np = NULL;
+	struct device_node *dma_np = NULL;
+	struct device_node *dma_channel_np = NULL;
+	const phandle *codec_ph;
+	const char *sprop;
+	const u32 *iprop;
+	struct resource res;
+	struct platform_device *sound_device = NULL;
+	struct mpc8610_hpcd_data *machine_data;
+	struct fsl_ssi_info ssi_info;
+	struct fsl_dma_info dma_info;
+	int ret = -ENODEV;
+
+	machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
+	if (!machine_data)
+		return -ENOMEM;
+
+	memset(&ssi_info, 0, sizeof(ssi_info));
+	memset(&dma_info, 0, sizeof(dma_info));
+
+	ssi_info.dev = &ofdev->dev;
+
+	/*
+	 * We are only interested in SSIs with a codec phandle in them, so let's
+	 * make sure this SSI has one.
+	 */
+	codec_ph = of_get_property(np, "codec-handle", NULL);
+	if (!codec_ph)
+		goto error;
+
+	codec_np = of_find_node_by_phandle(*codec_ph);
+	if (!codec_np)
+		goto error;
+
+	/* The MPC8610 HPCD only knows about the CS4270 codec, so reject
+	   anything else. */
+	if (!of_device_is_compatible(codec_np, "cirrus,cs4270"))
+		goto error;
+
+	/* Get the device ID */
+	iprop = of_get_property(np, "cell-index", NULL);
+	if (!iprop) {
+		dev_err(&ofdev->dev, "cell-index property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	machine_data->ssi_id = *iprop;
+	ssi_info.id = *iprop;
+
+	/* Get the serial format and clock direction. */
+	sprop = of_get_property(np, "fsl,mode", NULL);
+	if (!sprop) {
+		dev_err(&ofdev->dev, "fsl,mode property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (strcasecmp(sprop, "i2s-slave") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+		/*
+		 * In i2s-slave mode, the codec has its own clock source, so we
+		 * need to get the frequency from the device tree and pass it to
+		 * the codec driver.
+		 */
+		iprop = of_get_property(codec_np, "clock-frequency", NULL);
+		if (!iprop || !*iprop) {
+			dev_err(&ofdev->dev, "codec bus-frequency property "
+				"is missing or invalid\n");
+			ret = -EINVAL;
+			goto error;
+		}
+		machine_data->clk_frequency = *iprop;
+	} else if (strcasecmp(sprop, "i2s-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "lj-slave") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "lj-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "rj-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "rj-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "ac97-slave") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "ac97-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else {
+		dev_err(&ofdev->dev,
+			"unrecognized fsl,mode property \"%s\"\n", sprop);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (!machine_data->clk_frequency) {
+		dev_err(&ofdev->dev, "unknown clock frequency\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* Read the SSI information from the device tree */
+	ret = of_address_to_resource(np, 0, &res);
+	if (ret) {
+		dev_err(&ofdev->dev, "could not obtain SSI address\n");
+		goto error;
+	}
+	if (!res.start) {
+		dev_err(&ofdev->dev, "invalid SSI address\n");
+		goto error;
+	}
+	ssi_info.ssi_phys = res.start;
+
+	machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi));
+	if (!machine_data->ssi) {
+		dev_err(&ofdev->dev, "could not map SSI address %x\n",
+			ssi_info.ssi_phys);
+		ret = -EINVAL;
+		goto error;
+	}
+	ssi_info.ssi = machine_data->ssi;
+
+
+	/* Get the IRQ of the SSI */
+	machine_data->ssi_irq = irq_of_parse_and_map(np, 0);
+	if (!machine_data->ssi_irq) {
+		dev_err(&ofdev->dev, "could not get SSI IRQ\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	ssi_info.irq = machine_data->ssi_irq;
+
+
+	/* Map the global utilities registers. */
+	guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
+	if (!guts_np) {
+		dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	machine_data->guts = of_iomap(guts_np, 0);
+	of_node_put(guts_np);
+	if (!machine_data->guts) {
+		dev_err(&ofdev->dev, "could not map GUTS\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* Find the DMA channels to use.  For now, we always use the first DMA
+	   controller. */
+	for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") {
+		iprop = of_get_property(dma_np, "cell-index", NULL);
+		if (iprop && (*iprop == 0)) {
+			of_node_put(dma_np);
+			break;
+		}
+	}
+	if (!dma_np) {
+		dev_err(&ofdev->dev, "could not find DMA node\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	machine_data->dma_id = *iprop;
+
+	/*
+	 * Find the DMA channels to use.  For now, we always use DMA channel 0
+	 * for playback, and DMA channel 1 for capture.
+	 */
+	while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) {
+		iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+		/* Is it DMA channel 0? */
+		if (iprop && (*iprop == 0)) {
+			/* dma_channel[0] and dma_irq[0] are for playback */
+			dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0);
+			dma_info.dma_irq[0] =
+				irq_of_parse_and_map(dma_channel_np, 0);
+			machine_data->dma_channel_id[0] = *iprop;
+			continue;
+		}
+		if (iprop && (*iprop == 1)) {
+			/* dma_channel[1] and dma_irq[1] are for capture */
+			dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0);
+			dma_info.dma_irq[1] =
+				irq_of_parse_and_map(dma_channel_np, 0);
+			machine_data->dma_channel_id[1] = *iprop;
+			continue;
+		}
+	}
+	if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] ||
+	    !dma_info.dma_irq[0] || !dma_info.dma_irq[1]) {
+		dev_err(&ofdev->dev, "could not find DMA channels\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	dma_info.ssi_stx_phys = ssi_info.ssi_phys +
+		offsetof(struct ccsr_ssi, stx0);
+	dma_info.ssi_srx_phys = ssi_info.ssi_phys +
+		offsetof(struct ccsr_ssi, srx0);
+
+	/* We have the DMA information, so tell the DMA driver what it is */
+	if (!fsl_dma_configure(&dma_info)) {
+		dev_err(&ofdev->dev, "could not instantiate DMA device\n");
+		ret = -EBUSY;
+		goto error;
+	}
+
+	/*
+	 * Initialize our DAI data structure.  We should probably get this
+	 * information from the device tree.
+	 */
+	machine_data->dai.name = "CS4270";
+	machine_data->dai.stream_name = "CS4270";
+
+	machine_data->dai.cpu_dai = fsl_ssi_create_dai(&ssi_info);
+	machine_data->dai.codec_dai = &cs4270_dai; /* The codec_dai we want */
+	machine_data->dai.ops = &mpc8610_hpcd_ops;
+
+	mpc8610_hpcd_machine.dai_link = &machine_data->dai;
+
+	/* Allocate a new audio platform device structure */
+	sound_device = platform_device_alloc("soc-audio", -1);
+	if (!sound_device) {
+		dev_err(&ofdev->dev, "platform device allocation failed\n");
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	machine_data->sound_devdata.machine = &mpc8610_hpcd_machine;
+	machine_data->sound_devdata.codec_dev = &soc_codec_device_cs4270;
+	machine_data->sound_devdata.platform = &fsl_soc_platform;
+
+	sound_device->dev.platform_data = machine_data;
+
+
+	/* Set the platform device and ASoC device to point to each other */
+	platform_set_drvdata(sound_device, &machine_data->sound_devdata);
+
+	machine_data->sound_devdata.dev = &sound_device->dev;
+
+
+	/* Tell ASoC to probe us.  This will call mpc8610_hpcd_machine.probe(),
+	   if it exists. */
+	ret = platform_device_add(sound_device);
+
+	if (ret) {
+		dev_err(&ofdev->dev, "platform device add failed\n");
+		goto error;
+	}
+
+	dev_set_drvdata(&ofdev->dev, sound_device);
+
+	return 0;
+
+error:
+	of_node_put(codec_np);
+	of_node_put(guts_np);
+	of_node_put(dma_np);
+	of_node_put(dma_channel_np);
+
+	if (sound_device)
+		platform_device_unregister(sound_device);
+
+	if (machine_data->dai.cpu_dai)
+		fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
+
+	if (ssi_info.ssi)
+		iounmap(ssi_info.ssi);
+
+	if (ssi_info.irq)
+		irq_dispose_mapping(ssi_info.irq);
+
+	if (dma_info.dma_channel[0])
+		iounmap(dma_info.dma_channel[0]);
+
+	if (dma_info.dma_channel[1])
+		iounmap(dma_info.dma_channel[1]);
+
+	if (dma_info.dma_irq[0])
+		irq_dispose_mapping(dma_info.dma_irq[0]);
+
+	if (dma_info.dma_irq[1])
+		irq_dispose_mapping(dma_info.dma_irq[1]);
+
+	if (machine_data->guts)
+		iounmap(machine_data->guts);
+
+	kfree(machine_data);
+
+	return ret;
+}
+
+/**
+ * mpc8610_hpcd_remove: remove the OF device
+ *
+ * This function is called when the OF device is removed.
+ */
+static int mpc8610_hpcd_remove(struct of_device *ofdev)
+{
+	struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev);
+	struct mpc8610_hpcd_data *machine_data =
+		sound_device->dev.platform_data;
+
+	platform_device_unregister(sound_device);
+
+	if (machine_data->dai.cpu_dai)
+		fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
+
+	if (machine_data->ssi)
+		iounmap(machine_data->ssi);
+
+	if (machine_data->dma[0])
+		iounmap(machine_data->dma[0]);
+
+	if (machine_data->dma[1])
+		iounmap(machine_data->dma[1]);
+
+	if (machine_data->dma_irq[0])
+		irq_dispose_mapping(machine_data->dma_irq[0]);
+
+	if (machine_data->dma_irq[1])
+		irq_dispose_mapping(machine_data->dma_irq[1]);
+
+	if (machine_data->guts)
+		iounmap(machine_data->guts);
+
+	kfree(machine_data);
+	sound_device->dev.platform_data = NULL;
+
+	dev_set_drvdata(&ofdev->dev, NULL);
+
+	return 0;
+}
+
+static struct of_device_id mpc8610_hpcd_match[] = {
+	{
+		.compatible = "fsl,mpc8610-ssi",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match);
+
+static struct of_platform_driver mpc8610_hpcd_of_driver = {
+	.owner  	= THIS_MODULE,
+	.name   	= "mpc8610_hpcd",
+	.match_table    = mpc8610_hpcd_match,
+	.probe  	= mpc8610_hpcd_probe,
+	.remove 	= mpc8610_hpcd_remove,
+};
+
+/**
+ * mpc8610_hpcd_init: fabric driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init mpc8610_hpcd_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n");
+
+	ret = of_register_platform_driver(&mpc8610_hpcd_of_driver);
+
+	if (ret)
+		printk(KERN_ERR
+			"mpc8610-hpcd: failed to register platform driver\n");
+
+	return ret;
+}
+
+/**
+ * mpc8610_hpcd_exit: fabric driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit mpc8610_hpcd_exit(void)
+{
+	of_unregister_platform_driver(&mpc8610_hpcd_of_driver);
+}
+
+module_init(mpc8610_hpcd_init);
+module_exit(mpc8610_hpcd_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver");
+MODULE_LICENSE("GPL");
-- 
1.5.2.4

^ permalink raw reply related

* Re: [PATCH v3] [POWERPC] Update MPC8610 HPCD to support audio drivers
From: Grant Likely @ 2008-01-10 22:52 UTC (permalink / raw)
  To: Timur Tabi; +Cc: olof, linuxppc-dev, alsa-devel, david
In-Reply-To: <12000050664035-git-send-email-timur@freescale.com>

On 1/10/08, Timur Tabi <timur@freescale.com> wrote:
> Update the MPC8610 HPCD files to support the audio driver.  Update
> booting-without-of.txt with information on the SSI device.
>
> Signed-off-by: Timur Tabi <timur@freescale.com>

<snip>

> diff --git a/arch/powerpc/boot/dts/mpc8610_hpcd.dts b/arch/powerpc/boot/dts/mpc8610_hpcd.dts
> index 966edf1..4c91890 100644
> --- a/arch/powerpc/boot/dts/mpc8610_hpcd.dts
> +++ b/arch/powerpc/boot/dts/mpc8610_hpcd.dts
> @@ -103,6 +111,109 @@
>                         reg = <e0000 1000>;
>                         fsl,has-rstcr;
>                 };
> +
> +               i2s@16000 {
> +                       compatible = "fsl,mpc8610-ssi";
> +                       cell-index = <0>;
> +                       reg = <16000 100>;
> +                       interrupt-parent = <&mpic>;
> +                       interrupts = <3e 2>;
> +                       fsl,mode = "i2s-slave";
> +                       codec-handle = <&cs4270>;
> +               };
> +
> +               ssi@16100 {
> +                       compatible = "fsl,mpc8610-ssi";
> +                       cell-index = <1>;
> +                       reg = <16100 100>;
> +                       interrupt-parent = <&mpic>;
> +                       interrupts = <3f 2>;
> +               };
> +
> +                dma@21300 {
> +                        #address-cells = <1>;
> +                        #size-cells = <1>;
> +                        compatible = "fsl,mpc8610-dma", "fsl,eloplus-dma";
> +                        cell-index = <0>;
> +                        reg = <21300 4>; /* DMA general status register */
> +                        ranges = <0 21100 200>;
> +
> +                        dma-channel@0 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,eloplus-dma-channel";

Nit: you probably don't need the compatible property for these channel
nodes since they are 100% dependent on the parent for describing the
behaviour.

Otherwise;

Acked-by: Grant Likely <grant.likely@secretlab.ca>


> +                               cell-index = <0>;
> +                               reg = <0 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <14 2>;
> +                        };
> +                        dma-channel@1 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,eloplus-dma-channel";
> +                               cell-index = <1>;
> +                               reg = <80 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <15 2>;
> +                        };
> +                        dma-channel@2 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,eloplus-dma-channel";
> +                               cell-index = <2>;
> +                               reg = <100 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <16 2>;
> +                        };
> +                        dma-channel@3 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,eloplus-dma-channel";
> +                               cell-index = <3>;
> +                               reg = <180 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <17 2>;
> +                        };
> +                };
> +
> +                dma@c300 {
> +                        #address-cells = <1>;
> +                        #size-cells = <1>;
> +                        compatible = "fsl,mpc8610-dma", "fsl,mpc8540-dma";
> +                        cell-index = <1>;
> +                        reg = <c300 4>; /* DMA general status register */
> +                        ranges = <0 c100 200>;
> +
> +                        dma-channel@0 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,mpc8540-dma-channel";
> +                               cell-index = <0>;
> +                               reg = <0 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <3c 2>;
> +                        };
> +                        dma-channel@1 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,mpc8540-dma-channel";
> +                               cell-index = <1>;
> +                               reg = <80 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <3d 2>;
> +                        };
> +                        dma-channel@2 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,mpc8540-dma-channel";
> +                               cell-index = <2>;
> +                               reg = <100 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <3e 2>;
> +                        };
> +                        dma-channel@3 {
> +                               compatible = "fsl,mpc8610-dma-channel",
> +                                       "fsl,mpc8540-dma-channel";
> +                               cell-index = <3>;
> +                               reg = <180 80>;
> +                               interrupt-parent = <&mpic>;
> +                               interrupts = <3f 2>;
> +                        };
> +                };
> +
>         };
>
>         pci@e0008000 {
> diff --git a/arch/powerpc/configs/mpc8610_hpcd_defconfig b/arch/powerpc/configs/mpc8610_hpcd_defconfig
> index 9614d24..2500ef4 100644
> --- a/arch/powerpc/configs/mpc8610_hpcd_defconfig
> +++ b/arch/powerpc/configs/mpc8610_hpcd_defconfig
> @@ -696,7 +696,7 @@ CONFIG_SERIAL_8250_RSA=y
>  CONFIG_SERIAL_CORE=y
>  CONFIG_SERIAL_CORE_CONSOLE=y
>  # CONFIG_SERIAL_JSM is not set
> -CONFIG_SERIAL_OF_PLATFORM=y
> +# CONFIG_SERIAL_OF_PLATFORM is not set
>  CONFIG_UNIX98_PTYS=y
>  # CONFIG_LEGACY_PTYS is not set
>  # CONFIG_IPMI_HANDLER is not set
> @@ -708,7 +708,60 @@ CONFIG_UNIX98_PTYS=y
>  # CONFIG_RAW_DRIVER is not set
>  # CONFIG_TCG_TPM is not set
>  CONFIG_DEVPORT=y
> -# CONFIG_I2C is not set
> +CONFIG_I2C=y
> +CONFIG_I2C_BOARDINFO=y
> +# CONFIG_I2C_CHARDEV is not set
> +
> +#
> +# I2C Algorithms
> +#
> +# CONFIG_I2C_ALGOBIT is not set
> +# CONFIG_I2C_ALGOPCF is not set
> +# CONFIG_I2C_ALGOPCA is not set
> +
> +#
> +# I2C Hardware Bus support
> +#
> +# CONFIG_I2C_ALI1535 is not set
> +# CONFIG_I2C_ALI1563 is not set
> +# CONFIG_I2C_ALI15X3 is not set
> +# CONFIG_I2C_AMD756 is not set
> +# CONFIG_I2C_AMD8111 is not set
> +# CONFIG_I2C_I801 is not set
> +# CONFIG_I2C_I810 is not set
> +# CONFIG_I2C_PIIX4 is not set
> +CONFIG_I2C_MPC=y
> +# CONFIG_I2C_NFORCE2 is not set
> +# CONFIG_I2C_OCORES is not set
> +# CONFIG_I2C_PARPORT_LIGHT is not set
> +# CONFIG_I2C_PROSAVAGE is not set
> +# CONFIG_I2C_SAVAGE4 is not set
> +# CONFIG_I2C_SIMTEC is not set
> +# CONFIG_I2C_SIS5595 is not set
> +# CONFIG_I2C_SIS630 is not set
> +# CONFIG_I2C_SIS96X is not set
> +# CONFIG_I2C_TAOS_EVM is not set
> +# CONFIG_I2C_VIA is not set
> +# CONFIG_I2C_VIAPRO is not set
> +# CONFIG_I2C_VOODOO3 is not set
> +
> +#
> +# Miscellaneous I2C Chip support
> +#
> +# CONFIG_SENSORS_DS1337 is not set
> +# CONFIG_SENSORS_DS1374 is not set
> +# CONFIG_DS1682 is not set
> +# CONFIG_SENSORS_EEPROM is not set
> +# CONFIG_SENSORS_PCF8574 is not set
> +# CONFIG_SENSORS_PCA9539 is not set
> +# CONFIG_SENSORS_PCF8591 is not set
> +# CONFIG_SENSORS_M41T00 is not set
> +# CONFIG_SENSORS_MAX6875 is not set
> +# CONFIG_SENSORS_TSL2550 is not set
> +# CONFIG_I2C_DEBUG_CORE is not set
> +# CONFIG_I2C_DEBUG_ALGO is not set
> +# CONFIG_I2C_DEBUG_BUS is not set
> +# CONFIG_I2C_DEBUG_CHIP is not set
>
>  #
>  # SPI support
> @@ -763,7 +816,119 @@ CONFIG_DUMMY_CONSOLE=y
>  #
>  # Sound
>  #
> -# CONFIG_SOUND is not set
> +CONFIG_SOUND=y
> +
> +#
> +# Advanced Linux Sound Architecture
> +#
> +CONFIG_SND=y
> +CONFIG_SND_TIMER=y
> +CONFIG_SND_PCM=y
> +# CONFIG_SND_SEQUENCER is not set
> +CONFIG_SND_OSSEMUL=y
> +CONFIG_SND_MIXER_OSS=y
> +CONFIG_SND_PCM_OSS=y
> +# CONFIG_SND_PCM_OSS_PLUGINS is not set
> +# CONFIG_SND_DYNAMIC_MINORS is not set
> +# CONFIG_SND_SUPPORT_OLD_API is not set
> +CONFIG_SND_VERBOSE_PROCFS=y
> +# CONFIG_SND_VERBOSE_PRINTK is not set
> +# CONFIG_SND_DEBUG is not set
> +
> +#
> +# Generic devices
> +#
> +# CONFIG_SND_DUMMY is not set
> +# CONFIG_SND_MTPAV is not set
> +# CONFIG_SND_SERIAL_U16550 is not set
> +# CONFIG_SND_MPU401 is not set
> +
> +#
> +# PCI devices
> +#
> +# CONFIG_SND_AD1889 is not set
> +# CONFIG_SND_ALS300 is not set
> +# CONFIG_SND_ALS4000 is not set
> +# CONFIG_SND_ALI5451 is not set
> +# CONFIG_SND_ATIIXP is not set
> +# CONFIG_SND_ATIIXP_MODEM is not set
> +# CONFIG_SND_AU8810 is not set
> +# CONFIG_SND_AU8820 is not set
> +# CONFIG_SND_AU8830 is not set
> +# CONFIG_SND_AZT3328 is not set
> +# CONFIG_SND_BT87X is not set
> +# CONFIG_SND_CA0106 is not set
> +# CONFIG_SND_CMIPCI is not set
> +# CONFIG_SND_CS4281 is not set
> +# CONFIG_SND_CS46XX is not set
> +# CONFIG_SND_CS5530 is not set
> +# CONFIG_SND_DARLA20 is not set
> +# CONFIG_SND_GINA20 is not set
> +# CONFIG_SND_LAYLA20 is not set
> +# CONFIG_SND_DARLA24 is not set
> +# CONFIG_SND_GINA24 is not set
> +# CONFIG_SND_LAYLA24 is not set
> +# CONFIG_SND_MONA is not set
> +# CONFIG_SND_MIA is not set
> +# CONFIG_SND_ECHO3G is not set
> +# CONFIG_SND_INDIGO is not set
> +# CONFIG_SND_INDIGOIO is not set
> +# CONFIG_SND_INDIGODJ is not set
> +# CONFIG_SND_EMU10K1 is not set
> +# CONFIG_SND_EMU10K1X is not set
> +# CONFIG_SND_ENS1370 is not set
> +# CONFIG_SND_ENS1371 is not set
> +# CONFIG_SND_ES1938 is not set
> +# CONFIG_SND_ES1968 is not set
> +# CONFIG_SND_FM801 is not set
> +# CONFIG_SND_HDA_INTEL is not set
> +# CONFIG_SND_HDSP is not set
> +# CONFIG_SND_HDSPM is not set
> +# CONFIG_SND_ICE1712 is not set
> +# CONFIG_SND_ICE1724 is not set
> +# CONFIG_SND_INTEL8X0 is not set
> +# CONFIG_SND_INTEL8X0M is not set
> +# CONFIG_SND_KORG1212 is not set
> +# CONFIG_SND_MAESTRO3 is not set
> +# CONFIG_SND_MIXART is not set
> +# CONFIG_SND_NM256 is not set
> +# CONFIG_SND_PCXHR is not set
> +# CONFIG_SND_RIPTIDE is not set
> +# CONFIG_SND_RME32 is not set
> +# CONFIG_SND_RME96 is not set
> +# CONFIG_SND_RME9652 is not set
> +# CONFIG_SND_SONICVIBES is not set
> +# CONFIG_SND_TRIDENT is not set
> +# CONFIG_SND_VIA82XX is not set
> +# CONFIG_SND_VIA82XX_MODEM is not set
> +# CONFIG_SND_VX222 is not set
> +# CONFIG_SND_YMFPCI is not set
> +
> +#
> +# ALSA PowerMac devices
> +#
> +
> +#
> +# ALSA PowerPC devices
> +#
> +
> +#
> +# System on Chip audio support
> +#
> +CONFIG_SND_SOC=y
> +
> +#
> +# SoC Audio support for SuperH
> +#
> +
> +#
> +# ALSA SoC audio for Freescale SOCs
> +#
> +CONFIG_SND_SOC_MPC8610=y
> +CONFIG_SND_SOC_MPC8610_HPCD=y
> +CONFIG_SND_SOC_CS4270=y
> +CONFIG_SND_SOC_CS4270_VD33_ERRATA=y
> +
>  CONFIG_HID_SUPPORT=y
>  CONFIG_HID=y
>  # CONFIG_HID_DEBUG is not set
> diff --git a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
> index 6390895..36d8f14 100644
> --- a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
> +++ b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
> @@ -34,9 +34,24 @@
>
>  #include <asm/mpic.h>
>
> +#include <linux/of_platform.h>
>  #include <sysdev/fsl_pci.h>
>  #include <sysdev/fsl_soc.h>
>
> +static struct of_device_id __initdata mpc8610_ids[] = {
> +       { .compatible = "fsl,mpc8610-immr", },
> +       {}
> +};
> +
> +static int __init mpc8610_declare_of_platform_devices(void)
> +{
> +       /* Without this call, the SSI device driver won't get probed. */
> +       of_platform_bus_probe(NULL, mpc8610_ids, NULL);
> +
> +       return 0;
> +}
> +machine_device_initcall(mpc86xx_hpcd, mpc8610_declare_of_platform_devices);
> +
>  void __init
>  mpc86xx_hpcd_init_irq(void)
>  {
> --
> 1.5.2.4
>
>


-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply

* Re: [PATCH 2/5] Warp Base Platform - dts
From: Sean MacLennan @ 2008-01-10 23:59 UTC (permalink / raw)
  To: linuxppc-dev
In-Reply-To: <20080110024753.GC17816@localhost.localdomain>

New version with recommended changes. Device types removed. FPGA moved to correct bus.

Signed-off-by: Sean MacLennan <smaclennan@pikatech.com>
---
--- /dev/null	2005-11-20 22:22:37.000000000 -0500
+++ arch/powerpc/boot/dts/warp.dts	2008-01-10 13:04:49.000000000 -0500
@@ -0,0 +1,230 @@
+/*
+ * Device Tree Source for PIKA Warp
+ *
+ * Copyright (c) 2008 PIKA Technologies
+ *   Sean MacLennan <smaclennan@pikatech.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without
+ * any warranty of any kind, whether express or implied.
+ */
+
+/ {
+	#address-cells = <2>;
+	#size-cells = <1>;
+	model = "pika,warp";
+	compatible = "pika,warp";
+	dcr-parent = <&/cpus/cpu@0>;
+
+	aliases {
+		ethernet0 = &EMAC0;
+		serial0 = &UART0;
+	};
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		cpu@0 {
+			device_type = "cpu";
+			model = "PowerPC,440EP";
+			reg = <0>;
+			clock-frequency = <0>; /* Filled in by zImage */
+			timebase-frequency = <0>; /* Filled in by zImage */
+			i-cache-line-size = <20>;
+			d-cache-line-size = <20>;
+			i-cache-size = <8000>;
+			d-cache-size = <8000>;
+			dcr-controller;
+			dcr-access-method = "native";
+		};
+	};
+
+	memory {
+		device_type = "memory";
+		reg = <0 0 0>; /* Filled in by zImage */
+	};
+
+	UIC0: interrupt-controller0 {
+		compatible = "ibm,uic-440ep","ibm,uic";
+		interrupt-controller;
+		cell-index = <0>;
+		dcr-reg = <0c0 009>;
+		#address-cells = <0>;
+		#size-cells = <0>;
+		#interrupt-cells = <2>;
+	};
+
+	UIC1: interrupt-controller1 {
+		compatible = "ibm,uic-440ep","ibm,uic";
+		interrupt-controller;
+		cell-index = <1>;
+		dcr-reg = <0d0 009>;
+		#address-cells = <0>;
+		#size-cells = <0>;
+		#interrupt-cells = <2>;
+		interrupts = <1e 4 1f 4>; /* cascade */
+		interrupt-parent = <&UIC0>;
+	};
+
+	SDR0: sdr {
+		compatible = "ibm,sdr-440ep";
+		dcr-reg = <00e 002>;
+	};
+
+	CPR0: cpr {
+		compatible = "ibm,cpr-440ep";
+		dcr-reg = <00c 002>;
+	};
+
+	plb {
+		compatible = "ibm,plb-440ep", "ibm,plb-440gp", "ibm,plb4";
+		#address-cells = <2>;
+		#size-cells = <1>;
+		ranges;
+		clock-frequency = <0>; /* Filled in by zImage */
+
+		SDRAM0: sdram {
+			compatible = "ibm,sdram-440ep", "ibm,sdram-405gp";
+			dcr-reg = <010 2>;
+		};
+
+		DMA0: dma {
+			compatible = "ibm,dma-440ep", "ibm,dma-440gp";
+			dcr-reg = <100 027>;
+		};
+
+		MAL0: mcmal {
+			compatible = "ibm,mcmal-440ep", "ibm,mcmal-440gp", "ibm,mcmal";
+			dcr-reg = <180 62>;
+			num-tx-chans = <4>;
+			num-rx-chans = <2>;
+			interrupt-parent = <&MAL0>;
+			interrupts = <0 1 2 3 4>;
+			#interrupt-cells = <1>;
+			#address-cells = <0>;
+			#size-cells = <0>;
+			interrupt-map = </*TXEOB*/ 0 &UIC0 a 4
+					/*RXEOB*/ 1 &UIC0 b 4
+					/*SERR*/  2 &UIC1 0 4
+					/*TXDE*/  3 &UIC1 1 4
+					/*RXDE*/  4 &UIC1 2 4>;
+		};
+
+		POB0: opb {
+		  	compatible = "ibm,opb-440ep", "ibm,opb-440gp", "ibm,opb";
+			#address-cells = <1>;
+			#size-cells = <1>;
+		  	ranges = <00000000 0 00000000 80000000
+			          80000000 0 80000000 80000000>;
+		  	interrupt-parent = <&UIC1>;
+		  	interrupts = <7 4>;
+		  	clock-frequency = <0>; /* Filled in by zImage */
+
+			EBC0: ebc {
+				compatible = "ibm,ebc-440ep", "ibm,ebc-440gp", "ibm,ebc";
+				dcr-reg = <012 2>;
+				#address-cells = <2>;
+				#size-cells = <1>;
+				clock-frequency = <0>; /* Filled in by zImage */
+				interrupts = <5 1>;
+				interrupt-parent = <&UIC1>;
+
+				fpga@2,0 {
+					compatible = "pika,fpga";
+			   		reg = <0 80000000 2200>;
+					interrupts = <18 8>;
+					interrupt-parent = <&UIC0>;
+				};
+
+				nor_flash@0,0 {
+					/* Really spansion */
+					compatible = "amd,s29gl512n", "cfi-flash";
+					bank-width = <2>;
+					reg = <0 ffc00000 4000000>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+					partition@0 {
+						label = "kernel";
+						reg = <0 180000>;
+					};
+					partition@180000 {
+						label = "root";
+						reg = <180000 3480000>;
+					};
+					partition@3600000 {
+						label = "user";
+						reg = <3600000 900000>;
+					};
+					partition@3f00000 {
+						label = "fpga";
+						reg = <3f00000 40000>;
+					};
+					partition@3f40000 {
+						label = "env";
+						reg = <3f40000 40000>;
+					};
+					partition@3f80000 {
+						label = "u-boot";
+						reg = <3f80000 80000>;
+					};
+				};
+			};
+
+			UART0: serial@ef600300 {
+		   		device_type = "serial";
+		   		compatible = "ns16550";
+		   		reg = <ef600300 8>;
+		   		virtual-reg = <ef600300>;
+		   		clock-frequency = <0>; /* Filled in by zImage */
+		   		current-speed = <1c200>;
+		   		interrupt-parent = <&UIC0>;
+		   		interrupts = <0 4>;
+	   		};
+
+			IIC0: i2c@ef600700 {
+				compatible = "ibm,iic-440ep", "ibm,iic-440gp", "ibm,iic";
+				reg = <ef600700 14>;
+				interrupt-parent = <&UIC0>;
+				interrupts = <2 4>;
+			};
+
+			ZMII0: emac-zmii@ef600d00 {
+				compatible = "ibm,zmii-440ep", "ibm,zmii-440gp", "ibm,zmii";
+				reg = <ef600d00 c>;
+			};
+
+			EMAC0: ethernet@ef600e00 {
+				linux,network-index = <0>;
+				device_type = "network";
+				compatible = "ibm,emac-440ep", "ibm,emac-440gp", "ibm,emac";
+				interrupt-parent = <&UIC1>;
+				interrupts = <1c 4 1d 4>;
+				reg = <ef600e00 70>;
+				local-mac-address = [000000000000];
+				mal-device = <&MAL0>;
+				mal-tx-channel = <0 1>;
+				mal-rx-channel = <0>;
+				cell-index = <0>;
+				max-frame-size = <5dc>;
+				rx-fifo-size = <1000>;
+				tx-fifo-size = <800>;
+				phy-mode = "rmii";
+				phy-map = <00000000>;
+				zmii-device = <&ZMII0>;
+				zmii-channel = <0>;
+			};
+
+			usb@ef601000 {
+				compatible = "ohci-be";
+				reg = <ef601000 80>;
+				interrupts = <8 1 9 1>;
+				interrupt-parent = < &UIC1 >;
+			};
+		};
+	};
+
+	chosen {
+		linux,stdout-path = "/plb/opb/serial@ef600300";
+	};
+};

^ permalink raw reply

* Re: [PATCH v2] [ALSA] Add ASoC drivers for the Freescale MPC8610 SoC
From: Grant Likely @ 2008-01-10 23:02 UTC (permalink / raw)
  To: Timur Tabi; +Cc: olof, linuxppc-dev, alsa-devel, david
In-Reply-To: <12000050682718-git-send-email-timur@freescale.com>

On 1/10/08, Timur Tabi <timur@freescale.com> wrote:
> Add the ASoC drivers for the Freescale MPC8610 SoC and the MPC8610 HPCD
> reference board.
>
> Signed-off-by: Timur Tabi <timur@freescale.com>

This is a very big patch, but I'm going to keep my nose out of the
ALSA stuff.  I've got no problem against it being merged, but I do
have a concern about access to shared registers (see below)

> diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
> new file mode 100644
> index 0000000..f26c4b2
> --- /dev/null
> +++ b/sound/soc/fsl/mpc8610_hpcd.c
> +/**
> + * mpc8610_hpcd_probe: OF probe function for the fabric driver
> + *
> + * This function gets called when an SSI node is found in the device tree.
> + *
> + * Although this is a fabric driver, the SSI node is the "master" node with
> + * respect to audio hardware connections.  Therefore, we create a new ASoC
> + * device for each new SSI node that has a codec attached.
> + *
> + * FIXME: Currently, we only support one DMA controller, so if there are
> + * multiple SSI nodes with codecs, only the first will be supported.
> + *
> + * FIXME: Even if we did support multiple DMA controllers, we have no
> + * mechanism for assigning DMA controllers and channels to the individual
> + * SSI devices.  We also probably aren't compatible with the generic Elo DMA
> + * device driver.
> + */
> +static int mpc8610_hpcd_probe(struct of_device *ofdev,
> +       const struct of_device_id *match)
> +{
<snip>
> +       /* Map the global utilities registers. */
> +       guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
> +       if (!guts_np) {
> +               dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
> +               ret = -EINVAL;
> +               goto error;
> +       }

This...

> +       /* Find the DMA channels to use.  For now, we always use the first DMA
> +          controller. */
> +       for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") {
> +               iprop = of_get_property(dma_np, "cell-index", NULL);
> +               if (iprop && (*iprop == 0)) {
> +                       of_node_put(dma_np);
> +                       break;
> +               }
> +       }

and this...

Does the driver access the DMA and GUTS registers directly?  If so,
what do you have to protect against race conditions of other drivers
accessing them also.

Cheers,
g.

-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply

* Re: [PATCH 3/5] Warp Base Platform
From: Sean MacLennan @ 2008-01-11  0:04 UTC (permalink / raw)
  To: linuxppc-dev
In-Reply-To: <20080110024926.GD17816@localhost.localdomain>

David Gibson wrote:
>
> Fold all this into cuboot-warp.c, unless you actually anticipate
> adding another wrapper for another firmware which will also use the
> functions in warp.c.
>
>   
Done.

Signed-off-by: Sean MacLennan <smaclennan@pikatech.com>
---
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 66a3d8c..b3e4c35 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -469,7 +469,7 @@ config MCA
 config PCI
 	bool "PCI support" if 40x || CPM2 || PPC_83xx || PPC_85xx || PPC_86xx \
 		|| PPC_MPC52xx || (EMBEDDED && (PPC_PSERIES || PPC_ISERIES)) \
-		|| PPC_PS3
+		|| PPC_PS3 || 44x
 	default y if !40x && !CPM2 && !8xx && !PPC_83xx \
 		&& !PPC_85xx && !PPC_86xx
 	default PCI_PERMEDIA if !4xx && !CPM2 && !8xx
diff --git a/arch/powerpc/boot/Makefile b/arch/powerpc/boot/Makefile
index d1e625c..cd83c4f 100644
--- a/arch/powerpc/boot/Makefile
+++ b/arch/powerpc/boot/Makefile
@@ -62,7 +62,7 @@ src-plat := of.c cuboot-52xx.c cuboot-83xx.c cuboot-85xx.c holly.c \
 		ps3-head.S ps3-hvcall.S ps3.c treeboot-bamboo.c cuboot-8xx.c \
 		cuboot-pq2.c cuboot-sequoia.c treeboot-walnut.c cuboot-bamboo.c \
 		fixed-head.S ep88xc.c cuboot-hpc2.c ep405.c cuboot-taishan.c \
-		cuboot-katmai.c cuboot-rainier.c
+		cuboot-katmai.c cuboot-rainier.c cuboot-warp.c
 src-boot := $(src-wlib) $(src-plat) empty.c
 
 src-boot := $(addprefix $(obj)/, $(src-boot))
@@ -206,6 +206,7 @@ image-$(CONFIG_RAINIER)			+= cuImage.rainier
 image-$(CONFIG_WALNUT)			+= treeImage.walnut
 image-$(CONFIG_TAISHAN)			+= cuImage.taishan
 image-$(CONFIG_KATMAI)			+= cuImage.katmai
+image-$(CONFIG_WARP)			+= cuImage.warp
 endif
 
 # For 32-bit powermacs, build the COFF and miboot images
--- /dev/null	2005-11-20 22:22:37.000000000 -0500
+++ arch/powerpc/boot/cuboot-warp.c	2008-01-10 15:28:30.000000000 -0500
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2008 PIKA Technologies
+ *   Sean MacLennan <smaclennan@pikatech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include "ops.h"
+#include "4xx.h"
+#include "cuboot.h"
+
+#define TARGET_44x
+#include "ppcboot.h"
+
+static bd_t bd;
+
+static void warp_fixups(void)
+{
+	unsigned long sysclk = 66000000;
+
+	ibm440ep_fixup_clocks(sysclk, 11059200, 50000000);
+	ibm4xx_sdram_fixup_memsize();
+	dt_fixup_mac_addresses(&bd.bi_enetaddr);
+}
+
+
+void platform_init(unsigned long r3, unsigned long r4, unsigned long r5,
+		   unsigned long r6, unsigned long r7)
+{
+	CUBOOT_INIT();
+
+	platform_ops.fixups = warp_fixups;
+	platform_ops.exit = ibm44x_dbcr_reset;
+	fdt_init(_dtb_start);
+	serial_console_init();
+}

^ permalink raw reply related

* Re: [alsa-devel] [PATCH v2] [ALSA] Add ASoC drivers for the Freescale MPC8610 SoC
From: Timur Tabi @ 2008-01-10 23:12 UTC (permalink / raw)
  To: Grant Likely; +Cc: olof, linuxppc-dev, alsa-devel, david
In-Reply-To: <fa686aa40801101502v264d8caw6faf935d71b15c9@mail.gmail.com>

Grant Likely wrote:

> Does the driver access the DMA and GUTS registers directly?  If so,
> what do you have to protect against race conditions of other drivers
> accessing them also.

I don't have any more protection than any other driver that accesses SOC 
registers directly.  Last I heard, Zhang's DMA driver was in limbo, and that 
driver would be the best place to arbitrate DMA register access.  I was planning 
on adding arbitration support to that driver after both drivers were applied.

As for the GUTS driver, well, I just program a few registers at startup, and I 
don't think any other driver touches them.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply

* Re: [alsa-devel] [PATCH v2] [ALSA] Add ASoC drivers for the Freescale MPC8610 SoC
From: Grant Likely @ 2008-01-10 23:17 UTC (permalink / raw)
  To: Timur Tabi; +Cc: olof, linuxppc-dev, alsa-devel, david
In-Reply-To: <4786A650.2080107@freescale.com>

On 1/10/08, Timur Tabi <timur@freescale.com> wrote:
> Grant Likely wrote:
>
> > Does the driver access the DMA and GUTS registers directly?  If so,
> > what do you have to protect against race conditions of other drivers
> > accessing them also.
>
> I don't have any more protection than any other driver that accesses SOC
> registers directly.  Last I heard, Zhang's DMA driver was in limbo, and that
> driver would be the best place to arbitrate DMA register access.  I was planning
> on adding arbitration support to that driver after both drivers were applied.

That sounds appropriate

>
> As for the GUTS driver, well, I just program a few registers at startup, and I
> don't think any other driver touches them.

Okay; at some point in the future they'll need to be accessed from a
common routine too.

g.

-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply

* Re: [alsa-devel] [PATCH v2] [ALSA] Add ASoC drivers for the Freescale MPC8610 SoC
From: Timur Tabi @ 2008-01-10 23:19 UTC (permalink / raw)
  To: Grant Likely; +Cc: olof, linuxppc-dev, alsa-devel, david
In-Reply-To: <fa686aa40801101517q2d4e6673l995ef4123f0d0ad4@mail.gmail.com>

Grant Likely wrote:

>> As for the GUTS driver, well, I just program a few registers at startup, and I
>> don't think any other driver touches them.
> 
> Okay; at some point in the future they'll need to be accessed from a
> common routine too.

I'll add that to my to-do list.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply

* [patch 4/4 v4] PS3: Add logical performance monitor driver support
From: Geoff Levand @ 2008-01-10 23:39 UTC (permalink / raw)
  To: paulus; +Cc: linuxppc-dev, Takashi Yamamoto
In-Reply-To: <47856E80.2080209@am.sony.com>


From: Takashi Yamamoto <TakashiA.Yamamoto@jp.sony.com>

Add PS3 logical performance monitor (lpm) device driver.

The PS3's LV1 hypervisor provides a Logical Performance Monitor that
abstracts the Cell processor's performance monitor features for use
by guest operating systems.

Signed-off-by: Takashi Yamamoto <TakashiA.Yamamoto@jp.sony.com>
Signed-off-by: Geoff Levand <geoffrey.levand@am.sony.com>
---
v2: o Correct Yamamoto-san's mail addr.
    o Text cleanups.
    o Added more comments explaining lpm operation.
    o Split SRPN_BKMK into separate patch.
    o Use get_hard_smp_processor_id() in ps3_get_hw_thread_id().
    o Split ps3_copy_trace_buffer() into ps3_lpm_copy_tb() and ps3_lpm_copy_tb_to_user().
    o Replace mutex with atomic_t in ps3_lpm_open()/ps3_lpm_close().
    o General cleanup of ps3_lpm_open()/ps3_lpm_close().
v3: o Add BE node_id to struct lpm_priv.
    o Change some dev_err() to dev_dbg().
    o Fix kzalloc() bug.
    o Text fix s/lost/loss/.
    o Use lpm_priv->node_id with lv1_construct_lpm().
v4: o Text fix s/whos/whose/.

 arch/powerpc/platforms/ps3/Kconfig |   13 
 drivers/ps3/Makefile               |    1 
 drivers/ps3/ps3-lpm.c              | 1248 +++++++++++++++++++++++++++++++++++++
 include/asm-powerpc/ps3.h          |   62 +
 4 files changed, 1324 insertions(+)
 create mode 100644 arch/powerpc/platforms/ps3/ps3-lpm.c

--- a/arch/powerpc/platforms/ps3/Kconfig
+++ b/arch/powerpc/platforms/ps3/Kconfig
@@ -138,4 +138,17 @@ config PS3_FLASH
 	  be disabled on the kernel command line using "ps3flash=off", to
 	  not allocate this fixed buffer.
 
+config PS3_LPM
+	tristate "PS3 Logical Performance Monitor support"
+	depends on PPC_PS3
+	help
+	  Include support for the PS3 Logical Performance Monitor.
+
+	  This support is required to use the logical performance monitor
+	  of the PS3's LV1 hypervisor.
+
+	  If you intend to use the advanced performance monitoring and
+	  profiling support of the Cell processor with programs like
+	  oprofile and perfmon2, then say Y or M, otherwise say N.
+
 endmenu
--- a/drivers/ps3/Makefile
+++ b/drivers/ps3/Makefile
@@ -4,3 +4,4 @@ ps3av_mod-objs		+= ps3av.o ps3av_cmd.o
 obj-$(CONFIG_PPC_PS3) += sys-manager-core.o
 obj-$(CONFIG_PS3_SYS_MANAGER) += ps3-sys-manager.o
 obj-$(CONFIG_PS3_STORAGE) += ps3stor_lib.o
+obj-$(CONFIG_PS3_LPM) += ps3-lpm.o
--- /dev/null
+++ b/drivers/ps3/ps3-lpm.c
@@ -0,0 +1,1248 @@
+/*
+ * PS3 Logical Performance Monitor.
+ *
+ *  Copyright (C) 2007 Sony Computer Entertainment Inc.
+ *  Copyright 2007 Sony Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  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/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <asm/ps3.h>
+#include <asm/lv1call.h>
+#include <asm/cell-pmu.h>
+
+
+/* BOOKMARK tag macros */
+#define PS3_PM_BOOKMARK_START                    0x8000000000000000ULL
+#define PS3_PM_BOOKMARK_STOP                     0x4000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_KERNEL               0x1000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_USER                 0x3000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_MASK_HI              0xF000000000000000ULL
+#define PS3_PM_BOOKMARK_TAG_MASK_LO              0x0F00000000000000ULL
+
+/* CBE PM CONTROL register macros */
+#define PS3_PM_CONTROL_PPU_TH0_BOOKMARK          0x00001000
+#define PS3_PM_CONTROL_PPU_TH1_BOOKMARK          0x00000800
+#define PS3_PM_CONTROL_PPU_COUNT_MODE_MASK       0x000C0000
+#define PS3_PM_CONTROL_PPU_COUNT_MODE_PROBLEM    0x00080000
+#define PS3_WRITE_PM_MASK                        0xFFFFFFFFFFFFFFFFULL
+
+/* CBE PM START STOP register macros */
+#define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_START 0x02000000
+#define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_START 0x01000000
+#define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_STOP  0x00020000
+#define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_STOP  0x00010000
+#define PS3_PM_START_STOP_START_MASK             0xFF000000
+#define PS3_PM_START_STOP_STOP_MASK              0x00FF0000
+
+/* CBE PM COUNTER register macres */
+#define PS3_PM_COUNTER_MASK_HI                   0xFFFFFFFF00000000ULL
+#define PS3_PM_COUNTER_MASK_LO                   0x00000000FFFFFFFFULL
+
+/* BASE SIGNAL GROUP NUMBER macros */
+#define PM_ISLAND2_BASE_SIGNAL_GROUP_NUMBER  0
+#define PM_ISLAND2_SIGNAL_GROUP_NUMBER1      6
+#define PM_ISLAND2_SIGNAL_GROUP_NUMBER2      7
+#define PM_ISLAND3_BASE_SIGNAL_GROUP_NUMBER  7
+#define PM_ISLAND4_BASE_SIGNAL_GROUP_NUMBER  15
+#define PM_SPU_TRIGGER_SIGNAL_GROUP_NUMBER   17
+#define PM_SPU_EVENT_SIGNAL_GROUP_NUMBER     18
+#define PM_ISLAND5_BASE_SIGNAL_GROUP_NUMBER  18
+#define PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER  24
+#define PM_ISLAND7_BASE_SIGNAL_GROUP_NUMBER  49
+#define PM_ISLAND8_BASE_SIGNAL_GROUP_NUMBER  52
+#define PM_SIG_GROUP_SPU                     41
+#define PM_SIG_GROUP_SPU_TRIGGER             42
+#define PM_SIG_GROUP_SPU_EVENT               43
+#define PM_SIG_GROUP_MFC_MAX                 60
+
+/**
+ * struct ps3_lpm_shadow_regs - Performance monitor shadow registers.
+ *
+ * @pm_control: Shadow of the processor's pm_control register.
+ * @pm_start_stop: Shadow of the processor's pm_start_stop register.
+ * @pm_interval: Shadow of the processor's pm_interval register.
+ * @group_control: Shadow of the processor's group_control register.
+ * @debug_bus_control: Shadow of the processor's debug_bus_control register.
+ *
+ * The logical performance monitor provides a write-only interface to
+ * these processor registers.  These shadow variables cache the processor
+ * register values for reading.
+ *
+ * The initial value of the shadow registers at lpm creation is
+ * PS3_LPM_SHADOW_REG_INIT.
+ */
+
+struct ps3_lpm_shadow_regs {
+	u64 pm_control;
+	u64 pm_start_stop;
+	u64 pm_interval;
+	u64 group_control;
+	u64 debug_bus_control;
+};
+
+#define PS3_LPM_SHADOW_REG_INIT 0xFFFFFFFF00000000ULL
+
+/**
+ * struct ps3_lpm_priv - Private lpm device data.
+ *
+ * @open: An atomic variable indicating the lpm driver has been opened.
+ * @rights: The lpm rigths granted by the system policy module.  A logical
+ *  OR of enum ps3_lpm_rights.
+ * @node_id: The node id of a BE prosessor whose performance monitor this
+ *  lpar has the right to use.
+ * @pu_id: The lv1 id of the logical PU.
+ * @lpm_id: The lv1 id of this lpm instance.
+ * @outlet_id: The outlet created by lv1 for this lpm instance.
+ * @tb_count: The number of bytes of data held in the lv1 trace buffer.
+ * @tb_cache: Kernel buffer to receive the data from the lv1 trace buffer.
+ *  Must be 128 byte aligned.
+ * @tb_cache_size: Size of the kernel @tb_cache buffer.  Must be 128 byte
+ *  aligned.
+ * @tb_cache_internal: An unaligned buffer allocated by this driver to be
+ *  used for the trace buffer cache when ps3_lpm_open() is called with a
+ *  NULL tb_cache argument.  Otherwise unused.
+ * @shadow: Processor register shadow of type struct ps3_lpm_shadow_regs.
+ * @sbd: The struct ps3_system_bus_device attached to this driver.
+ *
+ * The trace buffer is a buffer allocated and used internally to the lv1
+ * hypervisor to collect trace data.  The trace buffer cache is a guest
+ * buffer that accepts the trace data from the trace buffer.
+ */
+
+struct ps3_lpm_priv {
+	atomic_t open;
+	u64 rights;
+	u64 node_id;
+	u64 pu_id;
+	u64 lpm_id;
+	u64 outlet_id;
+	u64 tb_count;
+	void *tb_cache;
+	u64 tb_cache_size;
+	void *tb_cache_internal;
+	struct ps3_lpm_shadow_regs shadow;
+	struct ps3_system_bus_device *sbd;
+};
+
+enum {
+	PS3_LPM_DEFAULT_TB_CACHE_SIZE = 0x4000,
+};
+
+/**
+ * lpm_priv - Static instance of the lpm data.
+ *
+ * Since the exported routines don't support the notion of a device
+ * instance we need to hold the instance in this static variable
+ * and then only allow at most one instance at a time to be created.
+ */
+
+static struct ps3_lpm_priv *lpm_priv;
+
+static struct device *sbd_core(void)
+{
+	BUG_ON(!lpm_priv || !lpm_priv->sbd);
+	return &lpm_priv->sbd->core;
+}
+
+/**
+ * use_start_stop_bookmark - Enable the PPU bookmark trace.
+ *
+ * And it enables PPU bookmark triggers ONLY if the other triggers are not set.
+ * The start/stop bookmarks are inserted at ps3_enable_pm() and ps3_disable_pm()
+ * to start/stop LPM.
+ *
+ * Used to get good quality of the performance counter.
+ */
+
+enum {use_start_stop_bookmark = 1,};
+
+void ps3_set_bookmark(u64 bookmark)
+{
+	/*
+	 * As per the PPE book IV, to avoid bookmark loss there must
+	 * not be a traced branch within 10 cycles of setting the
+	 * SPRN_BKMK register.  The actual text is unclear if 'within'
+	 * includes cycles before the call.
+	 */
+
+	asm volatile("or 29, 29, 29;"); /* db10cyc */
+	mtspr(SPRN_BKMK, bookmark);
+	asm volatile("or 29, 29, 29;"); /* db10cyc */
+}
+EXPORT_SYMBOL_GPL(ps3_set_bookmark);
+
+void ps3_set_pm_bookmark(u64 tag, u64 incident, u64 th_id)
+{
+	u64 bookmark;
+
+	bookmark = (get_tb() & 0x00000000FFFFFFFFULL) |
+		PS3_PM_BOOKMARK_TAG_KERNEL;
+	bookmark = ((tag << 56) & PS3_PM_BOOKMARK_TAG_MASK_LO) |
+		(incident << 48) | (th_id << 32) | bookmark;
+	ps3_set_bookmark(bookmark);
+}
+EXPORT_SYMBOL_GPL(ps3_set_pm_bookmark);
+
+/**
+ * ps3_read_phys_ctr - Read physical counter registers.
+ *
+ * Each physical counter can act as one 32 bit counter or as two 16 bit
+ * counters.
+ */
+
+u32 ps3_read_phys_ctr(u32 cpu, u32 phys_ctr)
+{
+	int result;
+	u64 counter0415;
+	u64 counter2637;
+
+	if (phys_ctr >= NR_PHYS_CTRS) {
+		dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+			__LINE__, phys_ctr);
+		return 0;
+	}
+
+	result = lv1_set_lpm_counter(lpm_priv->lpm_id, 0, 0, 0, 0, &counter0415,
+				     &counter2637);
+	if (result) {
+		dev_err(sbd_core(), "%s:%u: lv1_set_lpm_counter failed: "
+			"phys_ctr %u, %s\n", __func__, __LINE__, phys_ctr,
+			ps3_result(result));
+		return 0;
+	}
+
+	switch (phys_ctr) {
+	case 0:
+		return counter0415 >> 32;
+	case 1:
+		return counter0415 & PS3_PM_COUNTER_MASK_LO;
+	case 2:
+		return counter2637 >> 32;
+	case 3:
+		return counter2637 & PS3_PM_COUNTER_MASK_LO;
+	default:
+		BUG();
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_read_phys_ctr);
+
+/**
+ * ps3_write_phys_ctr - Write physical counter registers.
+ *
+ * Each physical counter can act as one 32 bit counter or as two 16 bit
+ * counters.
+ */
+
+void ps3_write_phys_ctr(u32 cpu, u32 phys_ctr, u32 val)
+{
+	u64 counter0415;
+	u64 counter0415_mask;
+	u64 counter2637;
+	u64 counter2637_mask;
+	int result;
+
+	if (phys_ctr >= NR_PHYS_CTRS) {
+		dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+			__LINE__, phys_ctr);
+		return;
+	}
+
+	switch (phys_ctr) {
+	case 0:
+		counter0415 = (u64)val << 32;
+		counter0415_mask = PS3_PM_COUNTER_MASK_HI;
+		counter2637 = 0x0;
+		counter2637_mask = 0x0;
+		break;
+	case 1:
+		counter0415 = (u64)val;
+		counter0415_mask = PS3_PM_COUNTER_MASK_LO;
+		counter2637 = 0x0;
+		counter2637_mask = 0x0;
+		break;
+	case 2:
+		counter0415 = 0x0;
+		counter0415_mask = 0x0;
+		counter2637 = (u64)val << 32;
+		counter2637_mask = PS3_PM_COUNTER_MASK_HI;
+		break;
+	case 3:
+		counter0415 = 0x0;
+		counter0415_mask = 0x0;
+		counter2637 = (u64)val;
+		counter2637_mask = PS3_PM_COUNTER_MASK_LO;
+		break;
+	default:
+		BUG();
+	}
+
+	result = lv1_set_lpm_counter(lpm_priv->lpm_id,
+				     counter0415, counter0415_mask,
+				     counter2637, counter2637_mask,
+				     &counter0415, &counter2637);
+	if (result)
+		dev_err(sbd_core(), "%s:%u: lv1_set_lpm_counter failed: "
+			"phys_ctr %u, val %u, %s\n", __func__, __LINE__,
+			phys_ctr, val, ps3_result(result));
+}
+EXPORT_SYMBOL_GPL(ps3_write_phys_ctr);
+
+/**
+ * ps3_read_ctr - Read counter.
+ *
+ * Read 16 or 32 bits depending on the current size of the counter.
+ * Counters 4, 5, 6 & 7 are always 16 bit.
+ */
+
+u32 ps3_read_ctr(u32 cpu, u32 ctr)
+{
+	u32 val;
+	u32 phys_ctr = ctr & (NR_PHYS_CTRS - 1);
+
+	val = ps3_read_phys_ctr(cpu, phys_ctr);
+
+	if (ps3_get_ctr_size(cpu, phys_ctr) == 16)
+		val = (ctr < NR_PHYS_CTRS) ? (val >> 16) : (val & 0xffff);
+
+	return val;
+}
+EXPORT_SYMBOL_GPL(ps3_read_ctr);
+
+/**
+ * ps3_write_ctr - Write counter.
+ *
+ * Write 16 or 32 bits depending on the current size of the counter.
+ * Counters 4, 5, 6 & 7 are always 16 bit.
+ */
+
+void ps3_write_ctr(u32 cpu, u32 ctr, u32 val)
+{
+	u32 phys_ctr;
+	u32 phys_val;
+
+	phys_ctr = ctr & (NR_PHYS_CTRS - 1);
+
+	if (ps3_get_ctr_size(cpu, phys_ctr) == 16) {
+		phys_val = ps3_read_phys_ctr(cpu, phys_ctr);
+
+		if (ctr < NR_PHYS_CTRS)
+			val = (val << 16) | (phys_val & 0xffff);
+		else
+			val = (val & 0xffff) | (phys_val & 0xffff0000);
+	}
+
+	ps3_write_phys_ctr(cpu, phys_ctr, val);
+}
+EXPORT_SYMBOL_GPL(ps3_write_ctr);
+
+/**
+ * ps3_read_pm07_control - Read counter control registers.
+ *
+ * Each logical counter has a corresponding control register.
+ */
+
+u32 ps3_read_pm07_control(u32 cpu, u32 ctr)
+{
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_read_pm07_control);
+
+/**
+ * ps3_write_pm07_control - Write counter control registers.
+ *
+ * Each logical counter has a corresponding control register.
+ */
+
+void ps3_write_pm07_control(u32 cpu, u32 ctr, u32 val)
+{
+	int result;
+	static const u64 mask = 0xFFFFFFFFFFFFFFFFULL;
+	u64 old_value;
+
+	if (ctr >= NR_CTRS) {
+		dev_dbg(sbd_core(), "%s:%u: ctr too big: %u\n", __func__,
+			__LINE__, ctr);
+		return;
+	}
+
+	result = lv1_set_lpm_counter_control(lpm_priv->lpm_id, ctr, val, mask,
+					     &old_value);
+	if (result)
+		dev_err(sbd_core(), "%s:%u: lv1_set_lpm_counter_control "
+			"failed: ctr %u, %s\n", __func__, __LINE__, ctr,
+			ps3_result(result));
+}
+EXPORT_SYMBOL_GPL(ps3_write_pm07_control);
+
+/**
+ * ps3_read_pm - Read Other LPM control registers.
+ */
+
+u32 ps3_read_pm(u32 cpu, enum pm_reg_name reg)
+{
+	int result = 0;
+	u64 val = 0;
+
+	switch (reg) {
+	case pm_control:
+		return lpm_priv->shadow.pm_control;
+	case trace_address:
+		return CBE_PM_TRACE_BUF_EMPTY;
+	case pm_start_stop:
+		return lpm_priv->shadow.pm_start_stop;
+	case pm_interval:
+		return lpm_priv->shadow.pm_interval;
+	case group_control:
+		return lpm_priv->shadow.group_control;
+	case debug_bus_control:
+		return lpm_priv->shadow.debug_bus_control;
+	case pm_status:
+		result = lv1_get_lpm_interrupt_status(lpm_priv->lpm_id,
+						      &val);
+		if (result) {
+			val = 0;
+			dev_dbg(sbd_core(), "%s:%u: lv1 get_lpm_status failed: "
+				"reg %u, %s\n", __func__, __LINE__, reg,
+				ps3_result(result));
+		}
+		return (u32)val;
+	case ext_tr_timer:
+		return 0;
+	default:
+		dev_dbg(sbd_core(), "%s:%u: unknown reg: %d\n", __func__,
+			__LINE__, reg);
+		BUG();
+		break;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_read_pm);
+
+/**
+ * ps3_write_pm - Write Other LPM control registers.
+ */
+
+void ps3_write_pm(u32 cpu, enum pm_reg_name reg, u32 val)
+{
+	int result = 0;
+	u64 dummy;
+
+	switch (reg) {
+	case group_control:
+		if (val != lpm_priv->shadow.group_control)
+			result = lv1_set_lpm_group_control(lpm_priv->lpm_id,
+							   val,
+							   PS3_WRITE_PM_MASK,
+							   &dummy);
+		lpm_priv->shadow.group_control = val;
+		break;
+	case debug_bus_control:
+		if (val != lpm_priv->shadow.debug_bus_control)
+			result = lv1_set_lpm_debug_bus_control(lpm_priv->lpm_id,
+							      val,
+							      PS3_WRITE_PM_MASK,
+							      &dummy);
+		lpm_priv->shadow.debug_bus_control = val;
+		break;
+	case pm_control:
+		if (use_start_stop_bookmark)
+			val |= (PS3_PM_CONTROL_PPU_TH0_BOOKMARK |
+				PS3_PM_CONTROL_PPU_TH1_BOOKMARK);
+		if (val != lpm_priv->shadow.pm_control)
+			result = lv1_set_lpm_general_control(lpm_priv->lpm_id,
+							     val,
+							     PS3_WRITE_PM_MASK,
+							     0, 0, &dummy,
+							     &dummy);
+		lpm_priv->shadow.pm_control = val;
+		break;
+	case pm_interval:
+		if (val != lpm_priv->shadow.pm_interval)
+			result = lv1_set_lpm_interval(lpm_priv->lpm_id, val,
+						   PS3_WRITE_PM_MASK, &dummy);
+		lpm_priv->shadow.pm_interval = val;
+		break;
+	case pm_start_stop:
+		if (val != lpm_priv->shadow.pm_start_stop)
+			result = lv1_set_lpm_trigger_control(lpm_priv->lpm_id,
+							     val,
+							     PS3_WRITE_PM_MASK,
+							     &dummy);
+		lpm_priv->shadow.pm_start_stop = val;
+		break;
+	case trace_address:
+	case ext_tr_timer:
+	case pm_status:
+		break;
+	default:
+		dev_dbg(sbd_core(), "%s:%u: unknown reg: %d\n", __func__,
+			__LINE__, reg);
+		BUG();
+		break;
+	}
+
+	if (result)
+		dev_err(sbd_core(), "%s:%u: lv1 set_control failed: "
+			"reg %u, %s\n", __func__, __LINE__, reg,
+			ps3_result(result));
+}
+EXPORT_SYMBOL_GPL(ps3_write_pm);
+
+/**
+ * ps3_get_ctr_size - Get the size of a physical counter.
+ *
+ * Returns either 16 or 32.
+ */
+
+u32 ps3_get_ctr_size(u32 cpu, u32 phys_ctr)
+{
+	u32 pm_ctrl;
+
+	if (phys_ctr >= NR_PHYS_CTRS) {
+		dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+			__LINE__, phys_ctr);
+		return 0;
+	}
+
+	pm_ctrl = ps3_read_pm(cpu, pm_control);
+	return (pm_ctrl & CBE_PM_16BIT_CTR(phys_ctr)) ? 16 : 32;
+}
+EXPORT_SYMBOL_GPL(ps3_get_ctr_size);
+
+/**
+ * ps3_set_ctr_size - Set the size of a physical counter to 16 or 32 bits.
+ */
+
+void ps3_set_ctr_size(u32 cpu, u32 phys_ctr, u32 ctr_size)
+{
+	u32 pm_ctrl;
+
+	if (phys_ctr >= NR_PHYS_CTRS) {
+		dev_dbg(sbd_core(), "%s:%u: phys_ctr too big: %u\n", __func__,
+			__LINE__, phys_ctr);
+		return;
+	}
+
+	pm_ctrl = ps3_read_pm(cpu, pm_control);
+
+	switch (ctr_size) {
+	case 16:
+		pm_ctrl |= CBE_PM_16BIT_CTR(phys_ctr);
+		ps3_write_pm(cpu, pm_control, pm_ctrl);
+		break;
+
+	case 32:
+		pm_ctrl &= ~CBE_PM_16BIT_CTR(phys_ctr);
+		ps3_write_pm(cpu, pm_control, pm_ctrl);
+		break;
+	default:
+		BUG();
+	}
+}
+EXPORT_SYMBOL_GPL(ps3_set_ctr_size);
+
+static u64 pm_translate_signal_group_number_on_island2(u64 subgroup)
+{
+
+	if (subgroup == 2)
+		subgroup = 3;
+
+	if (subgroup <= 6)
+		return PM_ISLAND2_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+	else if (subgroup == 7)
+		return PM_ISLAND2_SIGNAL_GROUP_NUMBER1;
+	else
+		return PM_ISLAND2_SIGNAL_GROUP_NUMBER2;
+}
+
+static u64 pm_translate_signal_group_number_on_island3(u64 subgroup)
+{
+
+	switch (subgroup) {
+	case 2:
+	case 3:
+	case 4:
+		subgroup += 2;
+		break;
+	case 5:
+		subgroup = 8;
+		break;
+	default:
+		break;
+	}
+	return PM_ISLAND3_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island4(u64 subgroup)
+{
+	return PM_ISLAND4_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island5(u64 subgroup)
+{
+
+	switch (subgroup) {
+	case 3:
+		subgroup = 4;
+		break;
+	case 4:
+		subgroup = 6;
+		break;
+	default:
+		break;
+	}
+	return PM_ISLAND5_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island6(u64 subgroup,
+						       u64 subsubgroup)
+{
+	switch (subgroup) {
+	case 3:
+	case 4:
+	case 5:
+		subgroup += 1;
+		break;
+	default:
+		break;
+	}
+
+	switch (subsubgroup) {
+	case 4:
+	case 5:
+	case 6:
+		subsubgroup += 2;
+		break;
+	case 7:
+	case 8:
+	case 9:
+	case 10:
+		subsubgroup += 4;
+		break;
+	case 11:
+	case 12:
+	case 13:
+		subsubgroup += 5;
+		break;
+	default:
+		break;
+	}
+
+	if (subgroup <= 5)
+		return (PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER + subgroup);
+	else
+		return (PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER + subgroup
+			+ subsubgroup - 1);
+}
+
+static u64 pm_translate_signal_group_number_on_island7(u64 subgroup)
+{
+	return PM_ISLAND7_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_translate_signal_group_number_on_island8(u64 subgroup)
+{
+	return PM_ISLAND8_BASE_SIGNAL_GROUP_NUMBER + subgroup;
+}
+
+static u64 pm_signal_group_to_ps3_lv1_signal_group(u64 group)
+{
+	u64 island;
+	u64 subgroup;
+	u64 subsubgroup;
+
+	subgroup = 0;
+	subsubgroup = 0;
+	island = 0;
+	if (group < 1000) {
+		if (group < 100) {
+			if (20 <= group && group < 30) {
+				island = 2;
+				subgroup = group - 20;
+			} else if (30 <= group && group < 40) {
+				island = 3;
+				subgroup = group - 30;
+			} else if (40 <= group && group < 50) {
+				island = 4;
+				subgroup = group - 40;
+			} else if (50 <= group && group < 60) {
+				island = 5;
+				subgroup = group - 50;
+			} else if (60 <= group && group < 70) {
+				island = 6;
+				subgroup = group - 60;
+			} else if (70 <= group && group < 80) {
+				island = 7;
+				subgroup = group - 70;
+			} else if (80 <= group && group < 90) {
+				island = 8;
+				subgroup = group - 80;
+			}
+		} else if (200 <= group && group < 300) {
+			island = 2;
+			subgroup = group - 200;
+		} else if (600 <= group && group < 700) {
+			island = 6;
+			subgroup = 5;
+			subsubgroup = group - 650;
+		}
+	} else if (6000 <= group && group < 7000) {
+		island = 6;
+		subgroup = 5;
+		subsubgroup = group - 6500;
+	}
+
+	switch (island) {
+	case 2:
+		return pm_translate_signal_group_number_on_island2(subgroup);
+	case 3:
+		return pm_translate_signal_group_number_on_island3(subgroup);
+	case 4:
+		return pm_translate_signal_group_number_on_island4(subgroup);
+	case 5:
+		return pm_translate_signal_group_number_on_island5(subgroup);
+	case 6:
+		return pm_translate_signal_group_number_on_island6(subgroup,
+								   subsubgroup);
+	case 7:
+		return pm_translate_signal_group_number_on_island7(subgroup);
+	case 8:
+		return pm_translate_signal_group_number_on_island8(subgroup);
+	default:
+		dev_dbg(sbd_core(), "%s:%u: island not found: %lu\n", __func__,
+			__LINE__, group);
+		BUG();
+		break;
+	}
+	return 0;
+}
+
+static u64 pm_bus_word_to_ps3_lv1_bus_word(u8 word)
+{
+
+	switch (word) {
+	case 1:
+		return 0xF000;
+	case 2:
+		return 0x0F00;
+	case 4:
+		return 0x00F0;
+	case 8:
+	default:
+		return 0x000F;
+	}
+}
+
+static int __ps3_set_signal(u64 lv1_signal_group, u64 bus_select,
+			    u64 signal_select, u64 attr1, u64 attr2, u64 attr3)
+{
+	int ret;
+
+	ret = lv1_set_lpm_signal(lpm_priv->lpm_id, lv1_signal_group, bus_select,
+				 signal_select, attr1, attr2, attr3);
+	if (ret)
+		dev_err(sbd_core(),
+			"%s:%u: error:%d 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
+			__func__, __LINE__, ret, lv1_signal_group, bus_select,
+			signal_select, attr1, attr2, attr3);
+
+	return ret;
+}
+
+int ps3_set_signal(u64 signal_group, u8 signal_bit, u16 sub_unit,
+		   u8 bus_word)
+{
+	int ret;
+	u64 lv1_signal_group;
+	u64 bus_select;
+	u64 signal_select;
+	u64 attr1, attr2, attr3;
+
+	if (signal_group == 0)
+		return __ps3_set_signal(0, 0, 0, 0, 0, 0);
+
+	lv1_signal_group =
+		pm_signal_group_to_ps3_lv1_signal_group(signal_group);
+	bus_select = pm_bus_word_to_ps3_lv1_bus_word(bus_word);
+
+	switch (signal_group) {
+	case PM_SIG_GROUP_SPU_TRIGGER:
+		signal_select = 1;
+		signal_select = signal_select << (63 - signal_bit);
+		break;
+	case PM_SIG_GROUP_SPU_EVENT:
+		signal_select = 1;
+		signal_select = (signal_select << (63 - signal_bit)) | 0x3;
+		break;
+	default:
+		signal_select = 0;
+		break;
+	}
+
+	/*
+	 * 0: physical object.
+	 * 1: logical object.
+	 * This parameter is only used for the PPE and SPE signals.
+	 */
+	attr1 = 1;
+
+	/*
+	 * This parameter is used to specify the target physical/logical
+	 * PPE/SPE object.
+	 */
+	if (PM_SIG_GROUP_SPU <= signal_group &&
+		signal_group < PM_SIG_GROUP_MFC_MAX)
+		attr2 = sub_unit;
+	else
+		attr2 = lpm_priv->pu_id;
+
+	/*
+	 * This parameter is only used for setting the SPE signal.
+	 */
+	attr3 = 0;
+
+	ret = __ps3_set_signal(lv1_signal_group, bus_select, signal_select,
+			       attr1, attr2, attr3);
+	if (ret)
+		dev_err(sbd_core(), "%s:%u: __ps3_set_signal failed: %d\n",
+			__func__, __LINE__, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(ps3_set_signal);
+
+u32 ps3_get_hw_thread_id(int cpu)
+{
+	return get_hard_smp_processor_id(cpu);
+}
+EXPORT_SYMBOL_GPL(ps3_get_hw_thread_id);
+
+/**
+ * ps3_enable_pm - Enable the entire performance monitoring unit.
+ *
+ * When we enable the LPM, all pending writes to counters get committed.
+ */
+
+void ps3_enable_pm(u32 cpu)
+{
+	int result;
+	u64 tmp;
+	int insert_bookmark = 0;
+
+	lpm_priv->tb_count = 0;
+
+	if (use_start_stop_bookmark) {
+		if (!(lpm_priv->shadow.pm_start_stop &
+			(PS3_PM_START_STOP_START_MASK
+			| PS3_PM_START_STOP_STOP_MASK))) {
+			result = lv1_set_lpm_trigger_control(lpm_priv->lpm_id,
+				(PS3_PM_START_STOP_PPU_TH0_BOOKMARK_START |
+				PS3_PM_START_STOP_PPU_TH1_BOOKMARK_START |
+				PS3_PM_START_STOP_PPU_TH0_BOOKMARK_STOP |
+				PS3_PM_START_STOP_PPU_TH1_BOOKMARK_STOP),
+				0xFFFFFFFFFFFFFFFFULL, &tmp);
+
+			if (result)
+				dev_err(sbd_core(), "%s:%u: "
+					"lv1_set_lpm_trigger_control failed: "
+					"%s\n", __func__, __LINE__,
+					ps3_result(result));
+
+			insert_bookmark = !result;
+		}
+	}
+
+	result = lv1_start_lpm(lpm_priv->lpm_id);
+
+	if (result)
+		dev_err(sbd_core(), "%s:%u: lv1_start_lpm failed: %s\n",
+			__func__, __LINE__, ps3_result(result));
+
+	if (use_start_stop_bookmark && !result && insert_bookmark)
+		ps3_set_bookmark(get_tb() | PS3_PM_BOOKMARK_START);
+}
+EXPORT_SYMBOL_GPL(ps3_enable_pm);
+
+/**
+ * ps3_disable_pm - Disable the entire performance monitoring unit.
+ */
+
+void ps3_disable_pm(u32 cpu)
+{
+	int result;
+	u64 tmp;
+
+	ps3_set_bookmark(get_tb() | PS3_PM_BOOKMARK_STOP);
+
+	result = lv1_stop_lpm(lpm_priv->lpm_id, &tmp);
+
+	if (result) {
+		if(result != LV1_WRONG_STATE)
+			dev_err(sbd_core(), "%s:%u: lv1_stop_lpm failed: %s\n",
+				__func__, __LINE__, ps3_result(result));
+		return;
+	}
+
+	lpm_priv->tb_count = tmp;
+
+	dev_dbg(sbd_core(), "%s:%u: tb_count %lu (%lxh)\n", __func__, __LINE__,
+		lpm_priv->tb_count, lpm_priv->tb_count);
+}
+EXPORT_SYMBOL_GPL(ps3_disable_pm);
+
+/**
+ * ps3_lpm_copy_tb - Copy data from the trace buffer to a kernel buffer.
+ * @offset: Offset in bytes from the start of the trace buffer.
+ * @buf: Copy destination.
+ * @count: Maximum count of bytes to copy.
+ * @bytes_copied: Pointer to a variable that will recieve the number of
+ *  bytes copied to @buf.
+ *
+ * On error @buf will contain any successfully copied trace buffer data
+ * and bytes_copied will be set to the number of bytes successfully copied.
+ */
+
+int ps3_lpm_copy_tb(unsigned long offset, void *buf, unsigned long count,
+		    unsigned long *bytes_copied)
+{
+	int result;
+
+	*bytes_copied = 0;
+
+	if (!lpm_priv->tb_cache)
+		return -EPERM;
+
+	if (offset >= lpm_priv->tb_count)
+		return 0;
+
+	count = min(count, lpm_priv->tb_count - offset);
+
+	while (*bytes_copied < count) {
+		const unsigned long request = count - *bytes_copied;
+		u64 tmp;
+
+		result = lv1_copy_lpm_trace_buffer(lpm_priv->lpm_id, offset,
+						   request, &tmp);
+		if (result) {
+			dev_dbg(sbd_core(), "%s:%u: 0x%lx bytes at 0x%lx\n",
+				__func__, __LINE__, request, offset);
+
+			dev_err(sbd_core(), "%s:%u: lv1_copy_lpm_trace_buffer "
+				"failed: %s\n", __func__, __LINE__,
+				ps3_result(result));
+			return result == LV1_WRONG_STATE ? -EBUSY : -EINVAL;
+		}
+
+		memcpy(buf, lpm_priv->tb_cache, tmp);
+		buf += tmp;
+		*bytes_copied += tmp;
+		offset += tmp;
+	}
+	dev_dbg(sbd_core(), "%s:%u: copied %lxh bytes\n", __func__, __LINE__,
+		*bytes_copied);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_copy_tb);
+
+/**
+ * ps3_lpm_copy_tb_to_user - Copy data from the trace buffer to a user buffer.
+ * @offset: Offset in bytes from the start of the trace buffer.
+ * @buf: A __user copy destination.
+ * @count: Maximum count of bytes to copy.
+ * @bytes_copied: Pointer to a variable that will recieve the number of
+ *  bytes copied to @buf.
+ *
+ * On error @buf will contain any successfully copied trace buffer data
+ * and bytes_copied will be set to the number of bytes successfully copied.
+ */
+
+int ps3_lpm_copy_tb_to_user(unsigned long offset, void __user *buf,
+			    unsigned long count, unsigned long *bytes_copied)
+{
+	int result;
+
+	*bytes_copied = 0;
+
+	if (!lpm_priv->tb_cache)
+		return -EPERM;
+
+	if (offset >= lpm_priv->tb_count)
+		return 0;
+
+	count = min(count, lpm_priv->tb_count - offset);
+
+	while (*bytes_copied < count) {
+		const unsigned long request = count - *bytes_copied;
+		u64 tmp;
+
+		result = lv1_copy_lpm_trace_buffer(lpm_priv->lpm_id, offset,
+						   request, &tmp);
+		if (result) {
+			dev_dbg(sbd_core(), "%s:%u: 0x%lx bytes at 0x%lx\n",
+				__func__, __LINE__, request, offset);
+			dev_err(sbd_core(), "%s:%u: lv1_copy_lpm_trace_buffer "
+				"failed: %s\n", __func__, __LINE__,
+				ps3_result(result));
+			return result == LV1_WRONG_STATE ? -EBUSY : -EINVAL;
+		}
+
+		result = copy_to_user(buf, lpm_priv->tb_cache, tmp);
+
+		if (result) {
+			dev_dbg(sbd_core(), "%s:%u: 0x%lx bytes at 0x%p\n",
+				__func__, __LINE__, tmp, buf);
+			dev_err(sbd_core(), "%s:%u: copy_to_user failed: %d\n",
+				__func__, __LINE__, result);
+			return -EFAULT;
+		}
+
+		buf += tmp;
+		*bytes_copied += tmp;
+		offset += tmp;
+	}
+	dev_dbg(sbd_core(), "%s:%u: copied %lxh bytes\n", __func__, __LINE__,
+		*bytes_copied);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_copy_tb_to_user);
+
+/**
+ * ps3_get_and_clear_pm_interrupts -
+ *
+ * Clearing interrupts for the entire performance monitoring unit.
+ * Reading pm_status clears the interrupt bits.
+ */
+
+u32 ps3_get_and_clear_pm_interrupts(u32 cpu)
+{
+	return ps3_read_pm(cpu, pm_status);
+}
+EXPORT_SYMBOL_GPL(ps3_get_and_clear_pm_interrupts);
+
+/**
+ * ps3_enable_pm_interrupts -
+ *
+ * Enabling interrupts for the entire performance monitoring unit.
+ * Enables the interrupt bits in the pm_status register.
+ */
+
+void ps3_enable_pm_interrupts(u32 cpu, u32 thread, u32 mask)
+{
+	if (mask)
+		ps3_write_pm(cpu, pm_status, mask);
+}
+EXPORT_SYMBOL_GPL(ps3_enable_pm_interrupts);
+
+/**
+ * ps3_enable_pm_interrupts -
+ *
+ * Disabling interrupts for the entire performance monitoring unit.
+ */
+
+void ps3_disable_pm_interrupts(u32 cpu)
+{
+	ps3_get_and_clear_pm_interrupts(cpu);
+	ps3_write_pm(cpu, pm_status, 0);
+}
+EXPORT_SYMBOL_GPL(ps3_disable_pm_interrupts);
+
+/**
+ * ps3_lpm_open - Open the logical performance monitor device.
+ * @tb_type: Specifies the type of trace buffer lv1 sould use for this lpm
+ *  instance, specified by one of enum ps3_lpm_tb_type.
+ * @tb_cache: Optional user supplied buffer to use as the trace buffer cache.
+ *  If NULL, the driver will allocate and manage an internal buffer.
+ *  Unused when when @tb_type is PS3_LPM_TB_TYPE_NONE.
+ * @tb_cache_size: The size in bytes of the user supplied @tb_cache buffer.
+ *  Unused when @tb_cache is NULL or @tb_type is PS3_LPM_TB_TYPE_NONE.
+ */
+
+int ps3_lpm_open(enum ps3_lpm_tb_type tb_type, void *tb_cache,
+	u64 tb_cache_size)
+{
+	int result;
+	u64 tb_size;
+
+	BUG_ON(!lpm_priv);
+	BUG_ON(tb_type != PS3_LPM_TB_TYPE_NONE
+		&& tb_type != PS3_LPM_TB_TYPE_INTERNAL);
+
+	if (tb_type == PS3_LPM_TB_TYPE_NONE && tb_cache)
+		dev_dbg(sbd_core(), "%s:%u: bad in vals\n", __func__, __LINE__);
+
+	if (!atomic_add_unless(&lpm_priv->open, 1, 1)) {
+		dev_dbg(sbd_core(), "%s:%u: busy\n", __func__, __LINE__);
+		return -EBUSY;
+	}
+
+	/* Note tb_cache needs 128 byte alignment. */
+
+	if (tb_type == PS3_LPM_TB_TYPE_NONE) {
+		lpm_priv->tb_cache_size = 0;
+		lpm_priv->tb_cache_internal = NULL;
+		lpm_priv->tb_cache = NULL;
+	} else if (tb_cache) {
+		if (tb_cache != (void *)_ALIGN_UP((unsigned long)tb_cache, 128)
+			|| tb_cache_size != _ALIGN_UP(tb_cache_size, 128)) {
+			dev_err(sbd_core(), "%s:%u: unaligned tb_cache\n",
+				__func__, __LINE__);
+			result = -EINVAL;
+			goto fail_align;
+		}
+		lpm_priv->tb_cache_size = tb_cache_size;
+		lpm_priv->tb_cache_internal = NULL;
+		lpm_priv->tb_cache = tb_cache;
+	} else {
+		lpm_priv->tb_cache_size = PS3_LPM_DEFAULT_TB_CACHE_SIZE;
+		lpm_priv->tb_cache_internal = kzalloc(
+			lpm_priv->tb_cache_size + 127, GFP_KERNEL);
+		if (!lpm_priv->tb_cache_internal) {
+			dev_err(sbd_core(), "%s:%u: alloc internal tb_cache "
+				"failed\n", __func__, __LINE__);
+			result = -ENOMEM;
+			goto fail_malloc;
+		}
+		lpm_priv->tb_cache = (void *)_ALIGN_UP(
+			(unsigned long)lpm_priv->tb_cache_internal, 128);
+	}
+
+	result = lv1_construct_lpm(lpm_priv->node_id, tb_type, 0, 0,
+				ps3_mm_phys_to_lpar(__pa(lpm_priv->tb_cache)),
+				lpm_priv->tb_cache_size, &lpm_priv->lpm_id,
+				&lpm_priv->outlet_id, &tb_size);
+
+	if (result) {
+		dev_err(sbd_core(), "%s:%u: lv1_construct_lpm failed: %s\n",
+			__func__, __LINE__, ps3_result(result));
+		result = -EINVAL;
+		goto fail_construct;
+	}
+
+	lpm_priv->shadow.pm_control = PS3_LPM_SHADOW_REG_INIT;
+	lpm_priv->shadow.pm_start_stop = PS3_LPM_SHADOW_REG_INIT;
+	lpm_priv->shadow.pm_interval = PS3_LPM_SHADOW_REG_INIT;
+	lpm_priv->shadow.group_control = PS3_LPM_SHADOW_REG_INIT;
+	lpm_priv->shadow.debug_bus_control = PS3_LPM_SHADOW_REG_INIT;
+
+	dev_dbg(sbd_core(), "%s:%u: lpm_id 0x%lx, outlet_id 0x%lx, "
+		"tb_size 0x%lx\n", __func__, __LINE__, lpm_priv->lpm_id,
+		lpm_priv->outlet_id, tb_size);
+
+	return 0;
+
+fail_construct:
+	kfree(lpm_priv->tb_cache_internal);
+	lpm_priv->tb_cache_internal = NULL;
+fail_malloc:
+fail_align:
+	atomic_dec(&lpm_priv->open);
+	return result;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_open);
+
+/**
+ * ps3_lpm_close - Close the lpm device.
+ *
+ */
+
+int ps3_lpm_close(void)
+{
+	dev_dbg(sbd_core(), "%s:%u\n", __func__, __LINE__);
+
+	lv1_destruct_lpm(lpm_priv->lpm_id);
+	lpm_priv->lpm_id = 0;
+
+	kfree(lpm_priv->tb_cache_internal);
+	lpm_priv->tb_cache_internal = NULL;
+
+	atomic_dec(&lpm_priv->open);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ps3_lpm_close);
+
+static int __devinit ps3_lpm_probe(struct ps3_system_bus_device *dev)
+{
+	dev_dbg(&dev->core, " -> %s:%u\n", __func__, __LINE__);
+
+	if (lpm_priv) {
+		dev_info(&dev->core, "%s:%u: called twice\n",
+			__func__, __LINE__);
+		return -EBUSY;
+	}
+
+	lpm_priv = kzalloc(sizeof(*lpm_priv), GFP_KERNEL);
+
+	if (!lpm_priv)
+		return -ENOMEM;
+
+	lpm_priv->sbd = dev;
+	lpm_priv->node_id = dev->lpm.node_id;
+	lpm_priv->pu_id = dev->lpm.pu_id;
+	lpm_priv->rights = dev->lpm.rights;
+
+	dev_info(&dev->core, " <- %s:%u:\n", __func__, __LINE__);
+
+	return 0;
+}
+
+static int ps3_lpm_remove(struct ps3_system_bus_device *dev)
+{
+	dev_dbg(&dev->core, " -> %s:%u:\n", __func__, __LINE__);
+
+	ps3_lpm_close();
+
+	kfree(lpm_priv);
+	lpm_priv = NULL;
+
+	dev_info(&dev->core, " <- %s:%u:\n", __func__, __LINE__);
+	return 0;
+}
+
+static struct ps3_system_bus_driver ps3_lpm_driver = {
+	.match_id = PS3_MATCH_ID_LPM,
+	.core.name	= "ps3-lpm",
+	.core.owner	= THIS_MODULE,
+	.probe		= ps3_lpm_probe,
+	.remove		= ps3_lpm_remove,
+	.shutdown	= ps3_lpm_remove,
+};
+
+static int __init ps3_lpm_init(void)
+{
+	pr_debug("%s:%d:\n", __func__, __LINE__);
+	return ps3_system_bus_driver_register(&ps3_lpm_driver);
+}
+
+static void __exit ps3_lpm_exit(void)
+{
+	pr_debug("%s:%d:\n", __func__, __LINE__);
+	ps3_system_bus_driver_unregister(&ps3_lpm_driver);
+}
+
+module_init(ps3_lpm_init);
+module_exit(ps3_lpm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PS3 Logical Performance Monitor Driver");
+MODULE_AUTHOR("Sony Corporation");
+MODULE_ALIAS(PS3_MODULE_ALIAS_LPM);
--- a/include/asm-powerpc/ps3.h
+++ b/include/asm-powerpc/ps3.h
@@ -24,6 +24,7 @@
 #include <linux/init.h>
 #include <linux/types.h>
 #include <linux/device.h>
+#include "cell-pmu.h"
 
 union ps3_firmware_version {
 	u64 raw;
@@ -446,5 +447,66 @@ struct ps3_prealloc {
 extern struct ps3_prealloc ps3fb_videomemory;
 extern struct ps3_prealloc ps3flash_bounce_buffer;
 
+/* logical performance monitor */
+
+/**
+ * enum ps3_lpm_rights - Rigths granted by the system policy module.
+ *
+ * @PS3_LPM_RIGHTS_USE_LPM: The right to use the lpm.
+ * @PS3_LPM_RIGHTS_USE_TB: The right to use the internal trace buffer.
+ */
+
+enum ps3_lpm_rights {
+	PS3_LPM_RIGHTS_USE_LPM = 0x001,
+	PS3_LPM_RIGHTS_USE_TB = 0x100,
+};
+
+/**
+ * enum ps3_lpm_tb_type - Type of trace buffer lv1 should use.
+ *
+ * @PS3_LPM_TB_TYPE_NONE: Do not use a trace buffer.
+ * @PS3_LPM_RIGHTS_USE_TB: Use the lv1 internal trace buffer.  Must have
+ *  rights @PS3_LPM_RIGHTS_USE_TB.
+ */
+
+enum ps3_lpm_tb_type {
+	PS3_LPM_TB_TYPE_NONE = 0,
+	PS3_LPM_TB_TYPE_INTERNAL = 1,
+};
+
+int ps3_lpm_open(enum ps3_lpm_tb_type tb_type, void *tb_cache,
+	u64 tb_cache_size);
+int ps3_lpm_close(void);
+int ps3_lpm_copy_tb(unsigned long offset, void *buf, unsigned long count,
+	unsigned long *bytes_copied);
+int ps3_lpm_copy_tb_to_user(unsigned long offset, void __user *buf,
+	unsigned long count, unsigned long *bytes_copied);
+void ps3_set_bookmark(u64 bookmark);
+void ps3_set_pm_bookmark(u64 tag, u64 incident, u64 th_id);
+int ps3_set_signal(u64 rtas_signal_group, u8 signal_bit, u16 sub_unit,
+	u8 bus_word);
+
+u32 ps3_read_phys_ctr(u32 cpu, u32 phys_ctr);
+void ps3_write_phys_ctr(u32 cpu, u32 phys_ctr, u32 val);
+u32 ps3_read_ctr(u32 cpu, u32 ctr);
+void ps3_write_ctr(u32 cpu, u32 ctr, u32 val);
+
+u32 ps3_read_pm07_control(u32 cpu, u32 ctr);
+void ps3_write_pm07_control(u32 cpu, u32 ctr, u32 val);
+u32 ps3_read_pm(u32 cpu, enum pm_reg_name reg);
+void ps3_write_pm(u32 cpu, enum pm_reg_name reg, u32 val);
+
+u32 ps3_get_ctr_size(u32 cpu, u32 phys_ctr);
+void ps3_set_ctr_size(u32 cpu, u32 phys_ctr, u32 ctr_size);
+
+void ps3_enable_pm(u32 cpu);
+void ps3_disable_pm(u32 cpu);
+void ps3_enable_pm_interrupts(u32 cpu, u32 thread, u32 mask);
+void ps3_disable_pm_interrupts(u32 cpu);
+
+u32 ps3_get_and_clear_pm_interrupts(u32 cpu);
+void ps3_sync_irq(int node);
+u32 ps3_get_hw_thread_id(int cpu);
+u64 ps3_get_spe_id(void *arg);
 
 #endif

^ permalink raw reply

* Re: [PATCH V3] [POWERPC] Add common clock setting routine mpc52xx_psc_set_clkdiv()
From: Stephen Rothwell @ 2008-01-10 23:41 UTC (permalink / raw)
  To: Grant Likely; +Cc: linuxppc-dev, dragos.carp
In-Reply-To: <fa686aa40801100701q7454d8d0o8542ce9cc10f3bfb@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 899 bytes --]

On Thu, 10 Jan 2008 08:01:09 -0700 "Grant Likely" <grant.likely@secretlab.ca> wrote:
>
> On 1/10/08, Stephen Rothwell <sfr@canb.auug.org.au> wrote:
> > Hi Grant,
> >
> > On Wed, 09 Jan 2008 22:26:30 -0700 Grant Likely <grant.likely@secretlab.ca> wrote:
> > >
> > > +mpc52xx_map_common_devices(void)
> > >  {
> >
> > > +     /* Clock Distribution Module, used by PSC clock setting function */
> > > +     np = of_find_matching_node(NULL, mpc52xx_cdm_ids);
> >
> > What happens if we find no node?
> 
> Both of_iomap and of_put fail gracefully if np is NULL.  IIRC that was
> done on purpose so this exact thing could be done.
> 
> Users of mpc52xx_cdm (and the others done in this style) all check for
> mpc52xx_cdm being NULL before dereferencing.

Excellent, thanks.

-- 
Cheers,
Stephen Rothwell                    sfr@canb.auug.org.au
http://www.canb.auug.org.au/~sfr/

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: [PATCH v3] [POWERPC] Update MPC8610 HPCD to support audio drivers
From: Scott Wood @ 2008-01-10 23:00 UTC (permalink / raw)
  To: Grant Likely; +Cc: olof, linuxppc-dev, alsa-devel, Timur Tabi, david
In-Reply-To: <fa686aa40801101452q608907f3rf6f98a5d091d09d9@mail.gmail.com>

Grant Likely wrote:
>> +                dma@21300 {
>> +                        #address-cells = <1>;
>> +                        #size-cells = <1>;
>> +                        compatible = "fsl,mpc8610-dma", "fsl,eloplus-dma";
>> +                        cell-index = <0>;
>> +                        reg = <21300 4>; /* DMA general status register */
>> +                        ranges = <0 21100 200>;
>> +
>> +                        dma-channel@0 {
>> +                               compatible = "fsl,mpc8610-dma-channel",
>> +                                       "fsl,eloplus-dma-channel";
> 
> Nit: you probably don't need the compatible property for these channel
> nodes since they are 100% dependent on the parent for describing the
> behaviour.

The compatible property in the child nodes gives the driver the option 
of matching the channel rather than the whole thing.

-Scott

^ permalink raw reply

* RE: Help with device tree binding for SMC serial
From: Rune Torgersen @ 2008-01-11  0:07 UTC (permalink / raw)
  To: Scott Wood; +Cc: linuxppc-dev
In-Reply-To: <47866FDE.5070307@freescale.com>

> From: Scott Wood [
> Rune Torgersen wrote:
> > Not sure what was wrong. We took a step back, and redid=20
> some stuff, and
> > now we have serial output from the boot-wrapper.
> > THe checkstop came from the wrapper grying to access the=20
> bcsr and doing
> > the chip select fixup. We don';t have a bcsr on our board,=20
> and the cs
> > layout is different.
>=20
> There's no bcsr access in the upstream bootwrapper (the=20
> mention of bcsr=20
> in cuboot-pq2.c is just one reason why some boards require us=20
> to do the=20
> CS programming), and the CS programming is chip-level, not=20
> board-level=20
> (it just requires that the device tree have a correct=20
> localbus node for=20
> the board).

Finally got it (sort-of) working.
Turned out that for some reason the console init is setting the baudrate
to 9600
the options string passed in to the console init fuunction is NULL.

Any idea oon how this should be passed in from u-boot?

^ permalink raw reply

* Re: [alsa-devel] [PATCH v2] [ALSA] Add ASoC drivers for the Freescale MPC8610 SoC
From: Olof Johansson @ 2008-01-11  0:24 UTC (permalink / raw)
  To: Timur Tabi; +Cc: linuxppc-dev, alsa-devel, david
In-Reply-To: <4786A650.2080107@freescale.com>

On Thu, Jan 10, 2008 at 05:12:16PM -0600, Timur Tabi wrote:
> Grant Likely wrote:
> 
> > Does the driver access the DMA and GUTS registers directly?  If so,
> > what do you have to protect against race conditions of other drivers
> > accessing them also.
> 
> I don't have any more protection than any other driver that accesses SOC 
> registers directly.  Last I heard, Zhang's DMA driver was in limbo, and that 
> driver would be the best place to arbitrate DMA register access.  I was planning 
> on adding arbitration support to that driver after both drivers were applied.

Having been in a similar situation myself (needing to share resources
between DMA, ethernet and function offload), I recommend creating a
separate small library that all those drivers use, instead of making
some sort of dependency between drivers in completely different parts
of the kernel.


-Olof

^ permalink raw reply

* Re: [PATCH 2/5] Warp Base Platform - dts
From: David Gibson @ 2008-01-11  0:20 UTC (permalink / raw)
  To: Sean MacLennan; +Cc: linuxppc-dev
In-Reply-To: <4786B147.5070206@pikatech.com>

On Thu, Jan 10, 2008 at 06:59:03PM -0500, Sean MacLennan wrote:
> New version with recommended changes. Device types removed. FPGA
> moved to correct bus.

[snip]
> +				fpga@2,0 {
> +					compatible = "pika,fpga";
> +			   		reg = <0 80000000 2200>;

Your reg property doesn't match your node name here...

> +					interrupts = <18 8>;
> +					interrupt-parent = <&UIC0>;
> +				};

-- 
David Gibson			| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you.  NOT _the_ _other_
				| _way_ _around_!
http://www.ozlabs.org/~dgibson

^ permalink raw reply

* [PATCH] Fix CPU hotplug when using the SLB shadow buffer
From: Michael Neuling @ 2008-01-11  0:47 UTC (permalink / raw)
  To: paulus; +Cc: linuxppc-dev, Nathan Lynch
In-Reply-To: <26792.1199949114@neuling.org>

Before we register the SLB shadow buffer, we need to invalidate the
entries in the buffer otherwise we can end up stale entries from when we
offlined the CPU.

This patch does this invalidate as well as unregistering the buffer with
PHYP before we offline the cpu.  Tested and fixes crashes seen on 970MP
(thanks to tonyb) and POWER5.

Signed-off-by: Michael Neuling <mikey@neuling.org>
---
Updates for offline comments from paulus

 arch/powerpc/mm/slb.c                        |    8 ++++++++
 arch/powerpc/platforms/pseries/hotplug-cpu.c |    2 ++
 arch/powerpc/platforms/pseries/lpar.c        |    1 +
 include/asm-powerpc/mmu-hash64.h             |    1 +
 4 files changed, 12 insertions(+)

Index: linux-2.6-ozlabs/arch/powerpc/mm/slb.c
===================================================================
--- linux-2.6-ozlabs.orig/arch/powerpc/mm/slb.c
+++ linux-2.6-ozlabs/arch/powerpc/mm/slb.c
@@ -82,6 +82,14 @@ static inline void slb_shadow_clear(unsi
 	get_slb_shadow()->save_area[entry].esid = 0;
 }
 
+void slb_shadow_clear_all(void)
+{
+	int i;
+
+	for (i = 0; i < SLB_NUM_BOLTED; i++)
+		slb_shadow_clear(i);
+}
+
 static inline void create_shadowed_slbe(unsigned long ea, int ssize,
 					unsigned long flags,
 					unsigned long entry)
Index: linux-2.6-ozlabs/arch/powerpc/platforms/pseries/hotplug-cpu.c
===================================================================
--- linux-2.6-ozlabs.orig/arch/powerpc/platforms/pseries/hotplug-cpu.c
+++ linux-2.6-ozlabs/arch/powerpc/platforms/pseries/hotplug-cpu.c
@@ -29,6 +29,7 @@
 #include <asm/vdso_datapage.h>
 #include <asm/pSeries_reconfig.h>
 #include "xics.h"
+#include "plpar_wrappers.h"
 
 /* This version can't take the spinlock, because it never returns */
 static struct rtas_args rtas_stop_self_args = {
@@ -58,6 +59,7 @@ static void pseries_mach_cpu_die(void)
 	local_irq_disable();
 	idle_task_exit();
 	xics_teardown_cpu(0);
+	unregister_slb_shadow(smp_processor_id(), __pa(get_slb_shadow()));
 	rtas_stop_self();
 	/* Should never get here... */
 	BUG();
Index: linux-2.6-ozlabs/arch/powerpc/platforms/pseries/lpar.c
===================================================================
--- linux-2.6-ozlabs.orig/arch/powerpc/platforms/pseries/lpar.c
+++ linux-2.6-ozlabs/arch/powerpc/platforms/pseries/lpar.c
@@ -272,6 +272,7 @@ void vpa_init(int cpu)
 	 */
 	addr = __pa(&slb_shadow[cpu]);
 	if (firmware_has_feature(FW_FEATURE_SPLPAR)) {
+		slb_shadow_clear_all();
 		ret = register_slb_shadow(hwcpu, addr);
 		if (ret)
 			printk(KERN_ERR
Index: linux-2.6-ozlabs/include/asm-powerpc/mmu-hash64.h
===================================================================
--- linux-2.6-ozlabs.orig/include/asm-powerpc/mmu-hash64.h
+++ linux-2.6-ozlabs/include/asm-powerpc/mmu-hash64.h
@@ -286,6 +286,7 @@ extern void hpte_init_iSeries(void);
 extern void hpte_init_beat(void);
 extern void hpte_init_beat_v3(void);
 
+extern void slb_shadow_clear_all(void);
 extern void stabs_alloc(void);
 extern void slb_initialize(void);
 extern void slb_flush_and_rebolt(void);

^ permalink raw reply


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