LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH] powerpc: Fix problems with 32bit PPC's running with more than 2GB of RAM
From: Benjamin Herrenschmidt @ 2008-07-12  3:33 UTC (permalink / raw)
  To: Becky Bruce; +Cc: linuxppc-dev, Stefan Roese
In-Reply-To: <E87ABEE7-3CD0-45E5-9806-BE3BAAFCFE1C@freescale.com>


> Are you *sure* you can see all 4GB?  I thought we lost some of the 32- 
> bit PCI address space for PCI IO......  Disclaimer: I'm no expert on  
> PCI :)

You are right. I didn't think that through when I replied. We do lose
some of it for MMIO (not IO). So we cannot really go all the way to 4G
because we can't DMA to all of it, right.

> FYI, I'm *very* close to having swiotlb fully functional on powerpc to  
> support larger RAM - I am able to boot with 6GB on 8641 right now.   
> There are some bugs that I'm still shaking out - I hope to push this  
> out in the next couple of weeks.

That won't make .27 then ?

Cheers,
Ben.

^ permalink raw reply

* Re: Updates to powerpc.git
From: Grant Likely @ 2008-07-12  3:35 UTC (permalink / raw)
  To: Benjamin Herrenschmidt; +Cc: linuxppc-dev list, Andrew Morton
In-Reply-To: <1215833458.7549.139.camel@pasglop>

On Sat, Jul 12, 2008 at 01:30:58PM +1000, Benjamin Herrenschmidt wrote:
> 
> > What is your intent with the 'master' branch?  I hope you do NOT plan  
> > on ever rebasing it.  I assume if a patch gets into master and we drop  
> > it you'll do a git-revert of it?
> 
> I'll try as much as possible. But I'll keep the option open. "next" is
> the one that should never be rebased I'd say.

I'm cool with that.  As long as I've got something stable to commit on
top of.  :-)

g.

^ permalink raw reply

* Re: Updates to powerpc.git
From: Benjamin Herrenschmidt @ 2008-07-12  3:32 UTC (permalink / raw)
  To: Josh Boyer; +Cc: linuxppc-dev list, Andrew Morton
In-Reply-To: <20080709091846.69b08e34@zod.rchland.ibm.com>

On Wed, 2008-07-09 at 09:18 -0400, Josh Boyer wrote:
> 
> If you want to use your master branch as a place for experimental
> stuff, that's fine by me.  But you'll want to keep next separate from
> it so it's as "clean" as possible for those trying to track what is
> definitely going into the next release.

Yes. The idea is that "next" stays clean.

> If it were up to me (which it's not), I would have master just track
> Linus, next track what's going into the next release, and "bleeding"
> or "experimental" track stuff that isn't fully vetted yet.  I might
> start doing that with my tree in the very near future.

Why keeping a branch to track linus in my public tree ? I have plenty of
these locally :-)

> Also, Paul is pretty good about not rebasing his branches when at all
> possible, and I suspect that's why his master and next were often the
> same.  It makes life lots easier for the sub-maintainers and anyone
> trying to track against the tree.  I humbly beg you to keep that
> going.

Yes. I intend to stay on that line, but as I'm new to the job, mistake
are more likely to happen.

Cheers,
Ben.

^ permalink raw reply

* Re: linux-next: manual merge of the powerpc tree
From: Benjamin Herrenschmidt @ 2008-07-12  3:41 UTC (permalink / raw)
  To: Bartlomiej Zolnierkiewicz
  Cc: Stephen Rothwell, Daniel Walker, linuxppc-dev, linux-next,
	Paul Mackerras, Andrew Morton
In-Reply-To: <200807112101.25704.bzolnier@gmail.com>

On Fri, 2008-07-11 at 21:01 +0200, Bartlomiej Zolnierkiewicz wrote:
> 
> Since I haven't heard back from Ben [1] on ide-pmac/media-bay IRQ issue
> I took another look at ide-pmac patches and I think that it should be
> possible to rework them in such way that consecutive ide patches (> 100)
> won't depend on "ide-pmac: media-bay support fixes (take 4)" patch.
> 
> This would allow us to re-schedule it to 2.6.28 (which is probably what
> we want because 2.6.26 is probably just around the corner and we will be
> pretty busy with 2.6.27 merge window soon).  Ben, what's your opinion?
> 
> [1] which doesn't surprise me given his new responsibilities ;)

That and I just moved house... been hectic lately.

I'll give another shot at the IRQ issues early next week, and if it
still doesn't work, we'll postpone.

Cheers,
Ben.

^ permalink raw reply

* Re: [PATCH v2 4/5] spi: Add OF binding support for SPI busses
From: Grant Likely @ 2008-07-12  5:21 UTC (permalink / raw)
  To: Jon Smirl
  Cc: david-b, linuxppc-dev, fabrizio.garetto, linux-kernel,
	spi-devel-general
In-Reply-To: <9e4733910807022002n3c739c7cs8ed642e4a6027bb6@mail.gmail.com>

On Wed, Jul 02, 2008 at 11:02:23PM -0400, Jon Smirl wrote:
> On 7/2/08, Grant Likely <grant.likely@secretlab.ca> wrote:
> > From: Grant Likely <grant.likely@secretlab.ca>
> >
> >  This patch adds support for populating an SPI bus based on data in the
> >  OF device tree.  This is useful for powerpc platforms which use the
> >  device tree instead of discrete code for describing platform layout.
> >
> >  Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
> >  ---
> >  +               /* Select device driver */
> >  +               sprop = of_get_property(nc, "linux,modalias", &len);
> >  +               if (sprop && len > 0)
> >  +                       strncpy(spi->modalias, sprop, KOBJ_NAME_LEN);
> >  +               else
> >  +                       strncpy(spi->modalias, "spidev", KOBJ_NAME_LEN);
> 
> You're missing a request_module("%s", info.type) to make sure the
> module is loaded.
> 
> It might make sense to share code with of_find_i2c_driver() so we have
> a common way of guessing module names.

You're right.  I've refactored the i2c code to make it usable by SPI
also.  I'll post the new patch series this evening.

g.

^ permalink raw reply

* Re: [alsa-devel] [PATCH 3/3] ALSA SoC: Add Texas Instruments TLV320AIC26 codec driver
From: Grant Likely @ 2008-07-12  6:00 UTC (permalink / raw)
  To: Liam Girdwood; +Cc: linuxppc-dev, alsa-devel, broonie, timur
In-Reply-To: <1214995713.4882.54.camel@localhost.localdomain>

On Wed, Jul 02, 2008 at 11:48:33AM +0100, Liam Girdwood wrote:
> On Tue, 2008-07-01 at 17:53 -0600, Grant Likely wrote:
> > From: Grant Likely <grant.likely@secretlab.ca>
> > 
> > ASoC Codec driver for the TLV320AIC26 device.  This driver uses the ASoC
> > v1 API, so I don't expect it to get merged as-is, but I want to get it
> > out there for review.
> > ---
> 
> Fwiw, I usually put all the codec registers defs in a separate header
> just in case we need to do any codec stuff in the machine driver.

okay, done

> > +	switch (params_rate(params)) {
> > +	 case 8000: fsref = 48000; divisor = AIC26_DIV_6; break;
> > +	 case 11025: fsref = 44100; divisor = AIC26_DIV_4; break;
> > +	 case 12000: fsref = 48000; divisor = AIC26_DIV_4; break;
> > +	 case 16000: fsref = 48000; divisor = AIC26_DIV_3; break;
> > +	 case 22050: fsref = 44100; divisor = AIC26_DIV_2; break;
> > +	 case 24000: fsref = 48000; divisor = AIC26_DIV_2; break;
> > +	 case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break;
> > +	 case 44100: fsref = 44100; divisor = AIC26_DIV_1; break;
> > +	 case 48000: fsref = 48000; divisor = AIC26_DIV_1; break;
> > +	 default: dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL;
> > +	}
> > +
> 
> Indentation.

done.

> > +	/* Configure PLL */
> > +	pval = 1;
> > +	jval = (fsref == 44100) ? 7 : 8;
> > +	dval = (fsref == 44100) ? 5264 : 1920;
> > +	qval = 0;
> > +	reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
> > +	aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
> > +	reg = dval << 2;
> > +	aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
> > +
> 
> PLL/FLL/clock config is usually done in a separate function
> (codec_set_pll(), callable by machine driver) so that we can change
> clocks depending on the available machine clocks and srate. 
> 
> > +	/* Power up CODEC */
> > +	aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0);
> > +
> 
> Codec domain (i.e Bias power) PM stuff should be done in
> codec_dapm_event(). This allows us to power the codec on when we do
> things like sidetone (with no active playback or capture stream).
> 

Ugh, I'm going to have to leave these two for now.  I don't understand
enough about the ASoC structure yet to understand what it should look
like.  I'll probably need help, but I don't think I can get it sorted
out before the merge window.

Do these two comments need to be addressed before the driver is merged?

> > +static ssize_t aic26_regs_show(struct device *dev,
> > +				struct device_attribute *attr, char *buf)
> > +{
> > +	struct aic26 *aic26 = dev_get_drvdata(dev);
> > +	char *idx = buf;
> > +	int cache_flag, addr, page, i, reg;
> > +
> > +	cache_flag = (strcmp(attr->attr.name, "regs_cache") == 0);
> > +
> > +	for (page = 0; page < 3; page++) {
> > +		for (i = 0; i < 0x20; i++) {
> > +			addr = AIC26_PAGE_ADDR(page, i);
> > +			if (i % 8 == 0)
> > +				idx += sprintf(idx, "%i:%.2i:", page,i);
> > +			if (cache_flag)
> > +				reg = aic26_reg_read_cache(&aic26->codec, addr);
> > +			else
> > +				reg = aic26_reg_read(&aic26->codec, addr);
> > +			idx += sprintf(idx, " %.4x", reg);
> > +			if (i % 8 == 7)
> > +				idx += sprintf(idx, "\n");
> > +		}
> > +	}
> > +	return idx - buf;
> > +}
> > +
> 
> The soc_core already has a codec reg dump sysfs file, so we don't need
> this.

Hmmm, I haven't been able to find this; either in the code or on a live
running system.  Where is the common reg dump implemented.

Thanks for the comments.
g.

^ permalink raw reply

* Re: [alsa-devel] [PATCH 2/3] ALSA SoC: Add mpc5200-psc I2S driver
From: Grant Likely @ 2008-07-12  6:26 UTC (permalink / raw)
  To: Jon Smirl; +Cc: liam.girdwood, alsa-devel, timur, linuxppc-dev
In-Reply-To: <9e4733910807070623u4c17b319p48e96a3022f3459b@mail.gmail.com>

On Mon, Jul 07, 2008 at 09:23:24AM -0400, Jon Smirl wrote:
> On 7/7/08, Mark Brown <broonie@opensource.wolfsonmicro.com> wrote:
> > On Sun, Jul 06, 2008 at 01:56:48PM -0400, Jon Smirl wrote:
> >
> >  > The driver is assuming a capture stream exists. My codec is output only.
> >
> >
> > While the driver declares a capture stream the core doesn't require that
> >  both capture and playback be available - it will cope with a capture
> >  only or a playback only DAI (this is fairly common due to DAC only and
> >  ADC only parts).  Unless there's some other issue specific to this
> >  driver?
> 
> Yes, it GPFs allocating a a DMA buffer on the null capture stream pointer.

Where does it GPF?  When dereferencing pcm->streams[x].substream in
psc_i2s_pcm_new?

^ permalink raw reply

* Re: [alsa-devel] [PATCH 2/3] ALSA SoC: Add mpc5200-psc I2S driver
From: Grant Likely @ 2008-07-12  6:30 UTC (permalink / raw)
  To: Jon Smirl; +Cc: liam.girdwood, alsa-devel, broonie, timur, linuxppc-dev
In-Reply-To: <9e4733910807070932q108da172n117c0670238db8a5@mail.gmail.com>

On Mon, Jul 07, 2008 at 12:32:29PM -0400, Jon Smirl wrote:
> On 7/1/08, Grant Likely <grant.likely@secretlab.ca> wrote:
> > From: Grant Likely <grant.likely@secretlab.ca>
> >
> >  This is an I2S bus driver for the MPC5200 PSC device.  It is probably
> >  will not be merged as-is because it uses v1 of the ASoC API, but I want
> >  to get it out there for comments.
> >  ---
> We need to tie the two PSCs up like this to get the audio clock in via
> PSC1 and then have PSC2 generate the frame clock when the i2s data is
> transmitted.
> 
> Do you want a diff, or do you have a new version with DMA broken out?

You may as well send me the diff.  I can't promise that I'll actually
get it merged in, but I'll try.

g.

^ permalink raw reply

* Re: [i2c] [PATCH] of/i2c: don't pass -1 to irq_dispose_mapping, otherwise kernel will oops
From: Wolfram Sang @ 2008-07-12  8:00 UTC (permalink / raw)
  To: Grant Likely; +Cc: linuxppc-dev, i2c
In-Reply-To: <20080711182323.GB15321@secretlab.ca>

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

On Fri, Jul 11, 2008 at 12:23:23PM -0600, Grant Likely wrote:
> On Fri, Jul 11, 2008 at 09:48:59PM +0400, Anton Vorontsov wrote:
> > Firstly kernel warns at set_irq_chip, and then dies completely:
> > 
> > Trying to install chip for IRQ-1
> >
> > diff --git a/drivers/of/of_i2c.c b/drivers/of/of_i2c.c
> > index b2ccdcb..95a24de 100644
> > --- a/drivers/of/of_i2c.c
> > +++ b/drivers/of/of_i2c.c
> > @@ -93,10 +93,8 @@ void of_register_i2c_devices(struct i2c_adapter *adap,
> >  		if (info.irq == NO_IRQ)
> >  			info.irq = -1;
> 
> What is the reason that info.irq is set to -1 in the first place?  This
> looks like another bug to me.  Does something in the i2c layer depend on
> the -1 value?
> 

You already acked a fix to this :) I wasn't sure if my patch would
make it on its own, as Jon Smirl was also working on fixes to of_i2c.c
and he seemed to pick up this issue, too.

(Original patch is here:
http://ozlabs.org/pipermail/linuxppc-dev/2008-June/058801.html
)

All the best,

   Wolfram

-- 
  Dipl.-Ing. Wolfram Sang | http://www.pengutronix.de
 Pengutronix - Linux Solutions for Science and Industry

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

^ permalink raw reply

* Re: [i2c] [PATCH] of/i2c: don't pass -1 to irq_dispose_mapping, otherwise kernel will oops
From: Wolfram Sang @ 2008-07-12  8:22 UTC (permalink / raw)
  To: Anton Vorontsov; +Cc: linuxppc-dev, i2c
In-Reply-To: <20080711191502.GA21847@polina.dev.rtsoft.ru>

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

On Fri, Jul 11, 2008 at 11:15:02PM +0400, Anton Vorontsov wrote:

> > Nope, it was a bug in the i2c documentation fixed recently:
> 
> Nope? I'm looking into i2c-core.c:
> 
> .. i2c_new_device(...)
> {
> 	client->irq = info->irq;
> 
> Core will blindly pass irq, so clients should ensure that irq contains
> correct value. And as far as there is no common scheme of checking that
> "there is no irq specified", the most safe option is -1.

I wonder if -1 is really the safest; even kernel functions related to
irqs are not consistent if "irq" is int or unsigned int. So, -1 could
cause subtle signedness defects.

The whole "no irq" mess really needs to be cleared generally. It just
disturbed me that i2c_core was imposing -1, whilst some other subsystem
may have chosen 0. IMHO, subsystems like i2c should pass irqs
transparently. This is why I submitted the patch for i2c documentation.

All the best,

   Wolfram

-- 
  Dipl.-Ing. Wolfram Sang | http://www.pengutronix.de
 Pengutronix - Linux Solutions for Science and Industry

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

^ permalink raw reply

* [PATCH v3 1/5] of: adapt of_find_i2c_driver() to be usable by SPI also
From: Grant Likely @ 2008-07-12  8:34 UTC (permalink / raw)
  To: linuxppc-dev, spi-devel-general, dbrownell, jonsmirl

From: Grant Likely <grant.likely@secretlab.ca>

SPI has a similar problem as I2C in that it needs to determine an
appropriate modalias value for each device node.  This patch adapts
the of_i2c of_find_i2c_driver() function to be usable by of_spi also.

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

 drivers/of/base.c   |   88 +++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/of/of_i2c.c |   64 ++-----------------------------------
 include/linux/of.h  |    1 +
 3 files changed, 92 insertions(+), 61 deletions(-)

diff --git a/drivers/of/base.c b/drivers/of/base.c
index 23ffb7c..ad8ac1a 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -385,3 +385,91 @@ struct device_node *of_find_matching_node(struct device_node *from,
 	return np;
 }
 EXPORT_SYMBOL(of_find_matching_node);
+
+/**
+ * of_modalias_table: Table of explicit compatible ==> modalias mappings
+ *
+ * This table allows particulare compatible property values to be mapped
+ * to modalias strings.  This is useful for busses which do not directly
+ * understand the OF device tree but are populated based on data contained
+ * within the device tree.  SPI and I2C are the two current users of this
+ * table.
+ *
+ * In most cases, devices do not need to be listed in this table because
+ * the modalias value can be derived directly from the compatible table.
+ * However, if for any reason a value cannot be derived, then this table
+ * provides a method to override the implicit derivation.
+ *
+ * At the moment, a single table is used for all bus types because it is
+ * assumed that the data size is small and that the compatible values
+ * should already be distinct enough to differentiate between SPI, I2C
+ * and other devices.
+ */
+struct of_modalias_table {
+	char *of_device;
+	char *modalias;
+};
+static struct of_modalias_table of_modalias_table[] = {
+	/* Empty for now; add entries as needed */
+};
+
+/**
+ * of_modalias_node - Lookup appropriate modalias for a device node
+ * @node:	pointer to a device tree node
+ * @modalias:	Pointer to buffer that modalias value will be copied into
+ * @len:	Length of modalias value
+ *
+ * Based on the value of the compatible property, this routine will determine
+ * an appropriate modalias value for a particular device tree node.  Three
+ * separate methods are used to derive a modalias value.
+ *
+ * First method is to lookup the compatible value in of_modalias_table.
+ * Second is to look for a "linux,<modalias>" entry in the compatible list
+ * and used that for modalias.  Third is to strip off the manufacturer
+ * prefix from the first compatible entry and use the remainder as modalias
+ *
+ * This routine returns 0 on success
+ */
+int of_modalias_node(struct device_node *node, char *modalias, int len)
+{
+	int i, cplen;
+	const char *compatible;
+	const char *p;
+
+	/* 1. search for exception list entry */
+	for (i = 0; i < ARRAY_SIZE(of_modalias_table); i++) {
+		compatible = of_modalias_table[i].of_device;
+		if (!of_device_is_compatible(node, compatible))
+			continue;
+		strlcpy(modalias, of_modalias_table[i].modalias, len);
+		return 0;
+	}
+
+	compatible = of_get_property(node, "compatible", &cplen);
+	if (!compatible)
+		return -ENODEV;
+
+	/* 2. search for linux,<modalias> entry */
+	p = compatible;
+	while (cplen > 0) {
+		if (!strncmp(p, "linux,", 6)) {
+			p += 6;
+			strlcpy(modalias, p, len);
+			return 0;
+		}
+
+		i = strlen(p) + 1;
+		p += i;
+		cplen -= i;
+	}
+
+	/* 3. take first compatible entry and strip manufacturer */
+	p = strchr(compatible, ',');
+	if (!p)
+		return -ENODEV;
+	p++;
+	strlcpy(modalias, p, len);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(of_modalias_node);
+
diff --git a/drivers/of/of_i2c.c b/drivers/of/of_i2c.c
index 5c015d3..65fd26d 100644
--- a/drivers/of/of_i2c.c
+++ b/drivers/of/of_i2c.c
@@ -16,62 +16,6 @@
 #include <linux/of_i2c.h>
 #include <linux/module.h>
 
-struct i2c_driver_device {
-	char    *of_device;
-	char    *i2c_type;
-};
-
-static struct i2c_driver_device i2c_devices[] = {
-};
-
-static int of_find_i2c_driver(struct device_node *node,
-			      struct i2c_board_info *info)
-{
-	int i, cplen;
-	const char *compatible;
-	const char *p;
-
-	/* 1. search for exception list entry */
-	for (i = 0; i < ARRAY_SIZE(i2c_devices); i++) {
-		if (!of_device_is_compatible(node, i2c_devices[i].of_device))
-			continue;
-		if (strlcpy(info->type, i2c_devices[i].i2c_type,
-			    I2C_NAME_SIZE) >= I2C_NAME_SIZE)
-			return -ENOMEM;
-
-		return 0;
-	}
-
-	compatible = of_get_property(node, "compatible", &cplen);
-	if (!compatible)
-		return -ENODEV;
-
-	/* 2. search for linux,<i2c-type> entry */
-	p = compatible;
-	while (cplen > 0) {
-		if (!strncmp(p, "linux,", 6)) {
-			p += 6;
-			if (strlcpy(info->type, p,
-				    I2C_NAME_SIZE) >= I2C_NAME_SIZE)
-				return -ENOMEM;
-			return 0;
-		}
-
-		i = strlen(p) + 1;
-		p += i;
-		cplen -= i;
-	}
-
-	/* 3. take fist compatible entry and strip manufacturer */
-	p = strchr(compatible, ',');
-	if (!p)
-		return -ENODEV;
-	p++;
-	if (strlcpy(info->type, p, I2C_NAME_SIZE) >= I2C_NAME_SIZE)
-		return -ENOMEM;
-	return 0;
-}
-
 void of_register_i2c_devices(struct i2c_adapter *adap,
 			     struct device_node *adap_node)
 {
@@ -83,6 +27,9 @@ void of_register_i2c_devices(struct i2c_adapter *adap,
 		const u32 *addr;
 		int len;
 
+		if (of_modalias_node(node, info.type, sizeof(info.type)) < 0)
+			continue;
+
 		addr = of_get_property(node, "reg", &len);
 		if (!addr || len < sizeof(int) || *addr > (1 << 10) - 1) {
 			printk(KERN_ERR
@@ -94,11 +41,6 @@ void of_register_i2c_devices(struct i2c_adapter *adap,
 		if (info.irq == NO_IRQ)
 			info.irq = -1;
 
-		if (of_find_i2c_driver(node, &info) < 0) {
-			irq_dispose_mapping(info.irq);
-			continue;
-		}
-
 		info.addr = *addr;
 
 		request_module(info.type);
diff --git a/include/linux/of.h b/include/linux/of.h
index 59a61bd..79886ad 100644
--- a/include/linux/of.h
+++ b/include/linux/of.h
@@ -70,5 +70,6 @@ extern int of_n_addr_cells(struct device_node *np);
 extern int of_n_size_cells(struct device_node *np);
 extern const struct of_device_id *of_match_node(
 	const struct of_device_id *matches, const struct device_node *node);
+extern int of_modalias_node(struct device_node *node, char *modalias, int len);
 
 #endif /* _LINUX_OF_H */

^ permalink raw reply related

* [PATCH v2 1/3] ALSA SoC: Add OpenFirmware helper for matching bus and codec drivers
From: Grant Likely @ 2008-07-12  8:39 UTC (permalink / raw)
  To: linuxppc-dev, alsa-devel, liam.girdwood, jonsmirl

From: Grant Likely <grant.likely@secretlab.ca>

Simple utility layer for creating ASoC machine instances based on data
in the OpenFirmware device tree.  OF aware platform drivers and codec
drivers register themselves with this framework and the framework
automatically instantiates a machine driver.

This is most likely temporary glue code to work around limitations in
the ASoC v1 framework.  I expect ASoC v2 won't need this.
---

 include/sound/soc-of.h |   21 ++++++
 sound/soc/Kconfig      |    3 +
 sound/soc/Makefile     |    1 
 sound/soc/soc-of.c     |  171 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 196 insertions(+), 0 deletions(-)

diff --git a/include/sound/soc-of.h b/include/sound/soc-of.h
new file mode 100644
index 0000000..a963032
--- /dev/null
+++ b/include/sound/soc-of.h
@@ -0,0 +1,21 @@
+/*
+ * OF helpers for ALSA SoC
+ *
+ * Copyright (C) 2008, Secret Lab Technologies Ltd.
+ */
+
+#ifndef _INCLUDE_SOC_OF_H_
+#define _INCLUDE_SOC_OF_H_
+
+#include <linux/of.h>
+#include <sound/soc.h>
+
+int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev,
+			      void *codec_data, struct snd_soc_codec_dai *dai,
+			      struct device_node *node);
+
+int of_snd_soc_register_platform(struct snd_soc_platform *platform,
+				 struct device_node *node,
+				 struct snd_soc_cpu_dai *cpu_dai);
+
+#endif /* _INCLUDE_SOC_OF_H_ */
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 18f28ac..99118ed 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -23,6 +23,9 @@ config SND_SOC
 	  This ASoC audio support can also be built as a module.  If so, the module
 	  will be called snd-soc-core.
 
+config SND_SOC_OF
+	tristate
+
 # All the supported Soc's
 source "sound/soc/at91/Kconfig"
 source "sound/soc/pxa/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 782db21..191c2e5 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -2,3 +2,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/ fsl/ davinci/ omap/
+obj-$(CONFIG_SND_SOC_OF)	+= soc-of.o
diff --git a/sound/soc/soc-of.c b/sound/soc/soc-of.c
new file mode 100644
index 0000000..0c855df
--- /dev/null
+++ b/sound/soc/soc-of.c
@@ -0,0 +1,171 @@
+/*
+ * OF helpers for ALSA SoC Layer
+ *
+ * Copyright (C) 2008, Secret Lab Technologies Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-of.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ALSA SoC OpenFirmware bindings");
+
+static DEFINE_MUTEX(of_snd_soc_mutex);
+static LIST_HEAD(of_snd_soc_device_list);
+static int of_snd_soc_next_index;
+
+struct of_snd_soc_device {
+	int id;
+	struct list_head list;
+	struct snd_soc_device device;
+	struct snd_soc_machine machine;
+	struct snd_soc_dai_link dai_link;
+	struct platform_device *pdev;
+	struct device_node *platform_node;
+	struct device_node *codec_node;
+};
+
+static struct snd_soc_ops of_snd_soc_ops = {
+};
+
+static struct of_snd_soc_device *
+of_snd_soc_get_device(struct device_node *codec_node)
+{
+	struct of_snd_soc_device *of_soc;
+
+	list_for_each_entry(of_soc, &of_snd_soc_device_list, list) {
+		if (of_soc->codec_node == codec_node)
+			return of_soc;
+	}
+
+	of_soc = kzalloc(sizeof(struct of_snd_soc_device), GFP_KERNEL);
+	if (!of_soc)
+		return NULL;
+
+	/* Initialize the structure and add it to the global list */
+	of_soc->codec_node = codec_node;
+	of_soc->id = of_snd_soc_next_index++;
+	of_soc->machine.dai_link = &of_soc->dai_link;
+	of_soc->machine.num_links = 1;
+	of_soc->device.machine = &of_soc->machine;
+	of_soc->dai_link.ops = &of_snd_soc_ops;
+	list_add(&of_soc->list, &of_snd_soc_device_list);
+
+	return of_soc;
+}
+
+static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc)
+{
+	struct platform_device *pdev;
+	int rc;
+
+	/* Only register the device if both the codec and platform have
+	 * been registered */
+	if ((!of_soc->device.codec_data) || (!of_soc->platform_node))
+		return;
+
+	pr_info("platform<-->codec match achieved; registering machine\n");
+
+	pdev = platform_device_alloc("soc-audio", of_soc->id);
+	if (!pdev) {
+		pr_err("of_soc: platform_device_alloc() failed\n");
+		return;
+	}
+
+	pdev->dev.platform_data = of_soc;
+	platform_set_drvdata(pdev, &of_soc->device);
+	of_soc->device.dev = &pdev->dev;
+
+	/* The ASoC device is complete; register it */
+	rc = platform_device_add(pdev);
+	if (rc) {
+		pr_err("of_soc: platform_device_add() failed\n");
+		return;
+	}
+
+}
+
+int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev,
+			      void *codec_data, struct snd_soc_codec_dai *dai,
+			      struct device_node *node)
+{
+	struct of_snd_soc_device *of_soc;
+	int rc = 0;
+
+	pr_info("registering ASoC codec driver: %s\n", node->full_name);
+
+	mutex_lock(&of_snd_soc_mutex);
+	of_soc = of_snd_soc_get_device(node);
+	if (!of_soc) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	/* Store the codec data */
+	of_soc->device.codec_data = codec_data;
+	of_soc->device.codec_dev = codec_dev;
+	of_soc->dai_link.name = node->name;
+	of_soc->dai_link.stream_name = node->name;
+	of_soc->dai_link.codec_dai = dai;
+
+	/* Now try to register the SoC device */
+	of_snd_soc_register_device(of_soc);
+
+ out:
+	mutex_unlock(&of_snd_soc_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(of_snd_soc_register_codec);
+
+int of_snd_soc_register_platform(struct snd_soc_platform *platform,
+				 struct device_node *node,
+				 struct snd_soc_cpu_dai *cpu_dai)
+{
+	struct of_snd_soc_device *of_soc;
+	struct device_node *codec_node;
+	const phandle *handle;
+	int len, rc = 0;
+
+	pr_info("registering ASoC platform driver: %s\n", node->full_name);
+
+	handle = of_get_property(node, "codec-handle", &len);
+	if (!handle || len < sizeof(handle))
+		return -ENODEV;
+	codec_node = of_find_node_by_phandle(*handle);
+	if (!codec_node)
+		return -ENODEV;
+	pr_info("looking for codec: %s\n", codec_node->full_name);
+
+	mutex_lock(&of_snd_soc_mutex);
+	of_soc = of_snd_soc_get_device(codec_node);
+	if (!of_soc) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	of_soc->platform_node = node;
+	of_soc->dai_link.cpu_dai = cpu_dai;
+	of_soc->device.platform = platform;
+	of_soc->machine.name = of_soc->dai_link.cpu_dai->name;
+
+	/* Now try to register the SoC device */
+	of_snd_soc_register_device(of_soc);
+
+ out:
+	mutex_unlock(&of_snd_soc_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(of_snd_soc_register_platform);

^ permalink raw reply related

* [PATCH v2 2/3] ALSA SoC: Add mpc5200-psc I2S driver
From: Grant Likely @ 2008-07-12  8:39 UTC (permalink / raw)
  To: linuxppc-dev, alsa-devel, liam.girdwood, jonsmirl
In-Reply-To: <20080712083929.15025.47682.stgit@trillian.secretlab.ca>

From: Grant Likely <grant.likely@secretlab.ca>

This is an I2S bus driver for the MPC5200 PSC device.  It is probably
will not be merged as-is because it uses v1 of the ASoC API, but I want
to get it out there for comments.
---

 include/asm-powerpc/mpc52xx_psc.h |   32 +
 sound/soc/fsl/Kconfig             |    7 
 sound/soc/fsl/Makefile            |    2 
 sound/soc/fsl/mpc5200_psc_i2s.c   |  896 +++++++++++++++++++++++++++++++++++++
 4 files changed, 936 insertions(+), 1 deletions(-)

diff --git a/include/asm-powerpc/mpc52xx_psc.h b/include/asm-powerpc/mpc52xx_psc.h
index 710c5d3..0985dc8 100644
--- a/include/asm-powerpc/mpc52xx_psc.h
+++ b/include/asm-powerpc/mpc52xx_psc.h
@@ -60,10 +60,12 @@
 #define MPC52xx_PSC_RXTX_FIFO_ALARM	0x0002
 #define MPC52xx_PSC_RXTX_FIFO_EMPTY	0x0001
 
-/* PSC interrupt mask bits */
+/* PSC interrupt status/mask bits */
 #define MPC52xx_PSC_IMR_TXRDY		0x0100
 #define MPC52xx_PSC_IMR_RXRDY		0x0200
 #define MPC52xx_PSC_IMR_DB		0x0400
+#define MPC52xx_PSC_IMR_TXEMP		0x0800
+#define MPC52xx_PSC_IMR_ORERR		0x1000
 #define MPC52xx_PSC_IMR_IPC		0x8000
 
 /* PSC input port change bit */
@@ -92,6 +94,34 @@
 
 #define MPC52xx_PSC_RFNUM_MASK	0x01ff
 
+#define MPC52xx_PSC_SICR_DTS1			(1 << 29)
+#define MPC52xx_PSC_SICR_SHDR			(1 << 28)
+#define MPC52xx_PSC_SICR_SIM_MASK		(0xf << 24)
+#define MPC52xx_PSC_SICR_SIM_UART		(0x0 << 24)
+#define MPC52xx_PSC_SICR_SIM_UART_DCD		(0x8 << 24)
+#define MPC52xx_PSC_SICR_SIM_CODEC_8		(0x1 << 24)
+#define MPC52xx_PSC_SICR_SIM_CODEC_16		(0x2 << 24)
+#define MPC52xx_PSC_SICR_SIM_AC97		(0x3 << 24)
+#define MPC52xx_PSC_SICR_SIM_SIR		(0x8 << 24)
+#define MPC52xx_PSC_SICR_SIM_SIR_DCD		(0xc << 24)
+#define MPC52xx_PSC_SICR_SIM_MIR		(0x5 << 24)
+#define MPC52xx_PSC_SICR_SIM_FIR		(0x6 << 24)
+#define MPC52xx_PSC_SICR_SIM_CODEC_24		(0x7 << 24)
+#define MPC52xx_PSC_SICR_SIM_CODEC_32		(0xf << 24)
+#define MPC52xx_PSC_SICR_GENCLK			(1 << 23)
+#define MPC52xx_PSC_SICR_I2S			(1 << 22)
+#define MPC52xx_PSC_SICR_CLKPOL			(1 << 21)
+#define MPC52xx_PSC_SICR_SYNCPOL		(1 << 20)
+#define MPC52xx_PSC_SICR_CELLSLAVE		(1 << 19)
+#define MPC52xx_PSC_SICR_CELL2XCLK		(1 << 18)
+#define MPC52xx_PSC_SICR_ESAI			(1 << 17)
+#define MPC52xx_PSC_SICR_ENAC97			(1 << 16)
+#define MPC52xx_PSC_SICR_SPI			(1 << 15)
+#define MPC52xx_PSC_SICR_MSTR			(1 << 14)
+#define MPC52xx_PSC_SICR_CPOL			(1 << 13)
+#define MPC52xx_PSC_SICR_CPHA			(1 << 12)
+#define MPC52xx_PSC_SICR_USEEOF			(1 << 11)
+#define MPC52xx_PSC_SICR_DISABLEEOF		(1 << 10)
 
 /* Structure of the hardware registers */
 struct mpc52xx_psc {
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 257101f..9ac970e 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -17,4 +17,11 @@ config SND_SOC_MPC8610_HPCD
 	help
 	  Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
 
+config SND_SOC_MPC5200_I2S
+	tristate "Freescale MPC5200 PSC in I2S mode driver"
+	select SND_SOC_OF
+	depends on SND_SOC && PPC_MPC52xx
+	help
+	  Say Y here to support the MPC5200 PSCs in I2S mode.
+
 endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 62f680a..98729a1 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o
 # MPC8610 Platform Support
 obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o
 
+obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
+
diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c
new file mode 100644
index 0000000..cdaae72
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_i2s.c
@@ -0,0 +1,896 @@
+/*
+ * Freescale MPC5200 PSC in I2S mode
+ * ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-of.h>
+
+#include <sysdev/bestcomm/bestcomm.h>
+#include <sysdev/bestcomm/gen_bd.h>
+#include <asm/mpc52xx_psc.h>
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
+MODULE_LICENSE("GPL");
+
+/**
+ * PSC_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the PSC 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 PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+			SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
+ */
+#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+			 SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \
+			 SNDRV_PCM_FMTBIT_S32_BE)
+
+/**
+ * psc_i2s_stream - Data specific to a single stream (playback or capture)
+ * @active:		flag indicating if the stream is active
+ * @psc_i2s:		pointer back to parent psc_i2s data structure
+ * @bcom_task:		bestcomm task structure
+ * @irq:		irq number for bestcomm task
+ * @period_start:	physical address of start of DMA region
+ * @period_end:		physical address of end of DMA region
+ * @period_next_pt:	physical address of next DMA buffer to enqueue
+ * @period_bytes:	size of DMA period in bytes
+ */
+struct psc_i2s_stream {
+	int active;
+	struct psc_i2s *psc_i2s;
+	struct bcom_task *bcom_task;
+	int irq;
+	struct snd_pcm_substream *stream;
+	dma_addr_t period_start;
+	dma_addr_t period_end;
+	dma_addr_t period_next_pt;
+	dma_addr_t period_current_pt;
+	int period_bytes;
+};
+
+/**
+ * psc_i2s - Private driver data
+ * @name: short name for this device ("PSC0", "PSC1", etc)
+ * @psc_regs: pointer to the PSC's registers
+ * @fifo_regs: pointer to the PSC's FIFO registers
+ * @irq: IRQ of this PSC
+ * @dev: struct device pointer
+ * @playback: the number of playback streams opened
+ * @capture: the number of capture streams opened
+ * @dai: the CPU DAI for this device
+ * @playback_stream: Playback stream context data
+ * @capture_stream: Capture stream context data
+ */
+struct psc_i2s {
+	char name[32];
+	struct mpc52xx_psc __iomem *psc_regs;
+	struct mpc52xx_psc_fifo __iomem *fifo_regs;
+	unsigned int irq;
+	struct device *dev;
+	struct snd_soc_cpu_dai dai;
+	spinlock_t lock;
+
+	/* per-stream data */
+	struct psc_i2s_stream playback_stream;
+	struct psc_i2s_stream capture_stream;
+
+	/* Statistics */
+	struct {
+		int overrun_count;
+		int underrun_count;
+	} stats;
+};
+
+/*
+ * Interrupt handlers
+ */
+static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s)
+{
+	struct psc_i2s *psc_i2s = _psc_i2s;
+	struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
+	u16 imr;
+	u16 isr;
+
+	isr = in_be16(&regs->mpc52xx_psc_isr);
+	imr = in_be16(&regs->mpc52xx_psc_imr);
+
+	/* Playback underrun error */
+	if (isr & imr & MPC52xx_PSC_IMR_TXEMP)
+		psc_i2s->stats.underrun_count++;
+
+	/* Capture overrun error */
+	if (isr & imr & MPC52xx_PSC_IMR_ORERR)
+		psc_i2s->stats.overrun_count++;
+
+	out_8(&regs->command, 4 << 4);	/* reset the error status */
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer
+ * @s: pointer to stream private data structure
+ *
+ * Enqueues another audio period buffer into the bestcomm queue.
+ *
+ * Note: The routine must only be called when there is space available in
+ * the queue.  Otherwise the enqueue will fail and the audio ring buffer
+ * will get out of sync
+ */
+static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s)
+{
+	struct bcom_bd *bd;
+
+	/* Prepare and enqueue the next buffer descriptor */
+	bd = bcom_prepare_next_buffer(s->bcom_task);
+	bd->status = s->period_bytes;
+	bd->data[0] = s->period_next_pt;
+	bcom_submit_next_buffer(s->bcom_task, NULL);
+
+	/* Update for next period */
+	s->period_next_pt += s->period_bytes;
+	if (s->period_next_pt >= s->period_end)
+		s->period_next_pt = s->period_start;
+}
+
+/* Bestcomm DMA irq handler */
+static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream)
+{
+	struct psc_i2s_stream *s = _psc_i2s_stream;
+
+	//spin_lock(&s->psc_i2s->lock);
+
+	/* For each finished period, dequeue the completed period buffer
+	 * and enqueue a new one in it's place. */
+	while (bcom_buffer_done(s->bcom_task)) {
+		bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
+		s->period_current_pt += s->period_bytes;
+		if (s->period_current_pt >= s->period_end)
+			s->period_current_pt = s->period_start;
+		psc_i2s_bcom_enqueue_next_buffer(s);
+		bcom_enable(s->bcom_task);
+	}
+
+	//spin_unlock(&s->psc_i2s->lock);
+
+	/* If the stream is active, then also inform the PCM middle layer
+	 * of the period finished event. */
+	if (s->active)
+		snd_pcm_period_elapsed(s->stream);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * psc_i2s_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 PSC registers.
+ */
+static int psc_i2s_startup(struct snd_pcm_substream *substream)
+{
+	int playback_irq, capture_irq, rc;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+	struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
+	struct mpc52xx_psc_fifo __iomem *fiforegs = psc_i2s->fifo_regs;
+
+	dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream);
+
+	/* Disable all interrupts and reset the PSC */
+	out_be16(&regs->mpc52xx_psc_imr, 0);
+	out_8(&regs->command, 3 << 4); /* reset transmitter */
+	out_8(&regs->command, 2 << 4); /* reset receiver */
+	out_8(&regs->command, 1 << 4); /* reset mode */
+	out_8(&regs->command, 4 << 4); /* reset error */
+
+	/* Default to CODEC8 mode */
+	out_be32(&regs->sicr,
+		 MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
+		 MPC52xx_PSC_SICR_CLKPOL | MPC52xx_PSC_SICR_SIM_CODEC_8);
+
+	/* First write: RxRdy (FIFO Alarm) generates receive FIFO interrupt */
+	/* Second write to mode: register Normal mode for non loopback */
+	out_8(&regs->mode, 0);
+	out_8(&regs->mode, 0);
+
+	/* Set the TX and RX fifo alarm thresholds */
+	out_be16(&fiforegs->rfalarm, 0x100);	/* set RFALARM level */
+	out_8(&fiforegs->rfcntl, 0x4);		/* set RFGRAN level (bytes) */
+	out_be16(&fiforegs->tfalarm, 0x100);	/* set TFALARM level */
+	out_8(&fiforegs->tfcntl, 0x7);		/* set TFGRAN level (bytes*4) */
+
+	/* Setup the IRQs */
+	playback_irq = bcom_get_task_irq(psc_i2s->playback_stream.bcom_task);
+	capture_irq = bcom_get_task_irq(psc_i2s->capture_stream.bcom_task);
+	rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED,
+			 "psc-i2s-status", psc_i2s);
+	rc |= request_irq(capture_irq, &psc_i2s_bcom_irq, IRQF_SHARED,
+			  "psc-i2s-capture", &psc_i2s->capture_stream);
+	rc |= request_irq(playback_irq, &psc_i2s_bcom_irq, IRQF_SHARED,
+			  "psc-i2s-playback", &psc_i2s->playback_stream);
+	if (rc) {
+		free_irq(psc_i2s->irq, psc_i2s);
+		free_irq(capture_irq, &psc_i2s->capture_stream);
+		free_irq(playback_irq, &psc_i2s->playback_stream);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+	u32 sicr;
+
+	dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+		" periods=%i buffer_size=%i  buffer_bytes=%i\n",
+		__FUNCTION__, substream, params_period_size(params),
+		params_period_bytes(params), params_periods(params),
+		params_buffer_size(params), params_buffer_bytes(params));
+
+	sicr = MPC52xx_PSC_SICR_DTS1 |
+	       MPC52xx_PSC_SICR_I2S | MPC52xx_PSC_SICR_CLKPOL;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		sicr |= MPC52xx_PSC_SICR_SIM_CODEC_8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		sicr |= MPC52xx_PSC_SICR_SIM_CODEC_16;
+		break;
+	case SNDRV_PCM_FORMAT_S24_BE:
+		sicr |= MPC52xx_PSC_SICR_SIM_CODEC_24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_BE:
+		sicr |= MPC52xx_PSC_SICR_SIM_CODEC_32;
+		break;
+	default:
+		dev_dbg(psc_i2s->dev, "invalid format\n");
+		return -EINVAL;
+	}
+	out_be32(&psc_i2s->psc_regs->sicr, sicr);
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	return 0;
+}
+
+static int psc_i2s_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	return 0;
+}
+
+/**
+ * psc_i2s_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ */
+static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct psc_i2s_stream *s;
+	struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
+	u16 imr;
+	u8 psc_cmd;
+	long flags;
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_i2s->capture_stream;
+	else
+		s = &psc_i2s->playback_stream;
+
+	dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)"
+		" stream_id=%i\n",
+		substream, cmd, substream->pstr->stream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		s->period_bytes = frames_to_bytes(runtime,
+						  runtime->period_size);
+		s->period_start = virt_to_phys(runtime->dma_area);
+		s->period_end = s->period_start +
+				(s->period_bytes * runtime->periods);
+		s->period_next_pt = s->period_start;
+		s->period_current_pt = s->period_start;
+		s->active = 1;
+
+		/* First; reset everything */
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
+			out_8(&regs->command, MPC52xx_PSC_RST_RX);
+			out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+		} else {
+			out_8(&regs->command, MPC52xx_PSC_RST_TX);
+			out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+		}
+
+		/* Next, fill up the bestcomm bd queue and enable DMA.
+		 * This will begin filling the PSC's fifo. */
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+			bcom_gen_bd_rx_reset(s->bcom_task);
+		else
+			bcom_gen_bd_tx_reset(s->bcom_task);
+		while (!bcom_queue_full(s->bcom_task))
+			psc_i2s_bcom_enqueue_next_buffer(s);
+		bcom_enable(s->bcom_task);
+
+		/* Update interrupt enable settings.  This must be done
+		 * before the PSC is enabled so that TX underrun events
+		 * are not missed. */
+		imr = 0;
+		if (psc_i2s->playback_stream.active)
+			imr |= MPC52xx_PSC_IMR_TXEMP;
+		if (psc_i2s->capture_stream.active)
+			imr |= MPC52xx_PSC_IMR_ORERR;
+		out_be16(&regs->isr_imr.imr, imr);
+
+		/* Due to errata in the i2s mode; need to line up enabling
+		 * the transmitter with a transition on the frame sync
+		 * line */
+
+		spin_lock_irqsave(&psc_i2s->lock, flags);
+		/* first make sure it is low */
+		while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) != 0);
+		/* then wait for the transition to high */
+		while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) == 0);
+		/* Finally, enable the PSC.
+		 * Receiver must always be enabled; even when we only want
+		 * transmit.  (see 15.3.2.3 of MPC5200B User's Guide) */
+		psc_cmd = MPC52xx_PSC_RX_ENABLE;
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			psc_cmd |= MPC52xx_PSC_TX_ENABLE;
+		out_8(&regs->command, psc_cmd);
+		spin_unlock_irqrestore(&psc_i2s->lock, flags);
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		/* Turn off the PSC */
+		s->active = 0;
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
+			if (!psc_i2s->playback_stream.active) {
+				out_8(&regs->command, 2 << 4);	/* reset rx */
+				out_8(&regs->command, 3 << 4);	/* reset tx */
+				out_8(&regs->command, 4 << 4);	/* reset err */
+			}
+		} else {
+			out_8(&regs->command, 3 << 4);	/* reset tx */
+			out_8(&regs->command, 4 << 4);	/* reset err */
+			if (!psc_i2s->capture_stream.active)
+				out_8(&regs->command, 2 << 4);	/* reset rx */
+		}
+
+		bcom_disable(s->bcom_task);
+		while (!bcom_queue_empty(s->bcom_task))
+			bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
+
+		break;
+
+	default:
+		dev_dbg(psc_i2s->dev, "invalid command\n");
+		return -EINVAL;
+	}
+
+	/* Update interrupt enable settings */
+	imr = 0;
+	if (psc_i2s->playback_stream.active) imr |= MPC52xx_PSC_IMR_TXEMP;
+	if (psc_i2s->capture_stream.active) imr |= MPC52xx_PSC_IMR_ORERR;
+	out_be16(&regs->isr_imr.imr, imr);
+
+	return 0;
+}
+
+/**
+ * psc_i2s_shutdown: shutdown the data transfer on a stream
+ *
+ * Shutdown the PSC if there are no other substreams open.
+ */
+static void psc_i2s_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+
+	dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream);
+
+	/*
+	 * If this is the last active substream, disable the PSC and release
+	 * the IRQ.
+	 */
+	if (!psc_i2s->playback_stream.active &&
+	    !psc_i2s->capture_stream.active) {
+		/* TODO: shut off channels */
+		free_irq(psc_i2s->irq, psc_i2s);
+		free_irq(bcom_get_task_irq(psc_i2s->capture_stream.bcom_task),
+			 &psc_i2s->capture_stream);
+		free_irq(bcom_get_task_irq(psc_i2s->playback_stream.bcom_task),
+			 &psc_i2s->playback_stream);
+	}
+}
+
+/**
+ * psc_i2s_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 psc_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
+			      int clk_id, unsigned int freq, int dir)
+{
+	struct psc_i2s *psc_i2s = cpu_dai->private_data;
+	dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
+				cpu_dai, dir);
+	return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
+}
+
+/**
+ * psc_i2s_set_fmt: set the serial format.
+ *
+ * This function is called by the machine driver to tell us what serial
+ * format to use.
+ *
+ * This driver only supports I2S mode.  Return an error if the format is
+ * not SND_SOC_DAIFMT_I2S.
+ *
+ * @format: one of SND_SOC_DAIFMT_xxx
+ */
+static int psc_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format)
+{
+	struct psc_i2s *psc_i2s = cpu_dai->private_data;
+	dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
+				cpu_dai, format);
+	return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_i2s_dai_template: template CPU Digital Audio Interface
+ */
+static struct snd_soc_cpu_dai psc_i2s_dai_template = {
+	.type = SND_SOC_DAI_I2S,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PSC_I2S_RATES,
+		.formats = PSC_I2S_FORMATS,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PSC_I2S_RATES,
+		.formats = PSC_I2S_FORMATS,
+	},
+	.ops = {
+		.startup = psc_i2s_startup,
+		.hw_params = psc_i2s_hw_params,
+		.hw_free = psc_i2s_hw_free,
+		.shutdown = psc_i2s_shutdown,
+		.trigger = psc_i2s_trigger,
+	},
+	.dai_ops = {
+		.set_sysclk = psc_i2s_set_sysclk,
+		.set_fmt = psc_i2s_set_fmt,
+	},
+};
+
+/* ---------------------------------------------------------------------
+ * The PSC I2S 'ASoC platform' driver
+ *
+ * Can be referenced by an 'ASoC machine' driver
+ * This driver only deals with the audio bus; it doesn't have any
+ * interaction with the attached codec
+ */
+
+static const struct snd_pcm_hardware psc_i2s_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats = SNDRV_PCM_FMTBIT_S8 |SNDRV_PCM_FMTBIT_S16_BE |
+		   SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
+	.rate_min = 8000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.period_bytes_max	= 1024 * 1024,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 1024 * 1024,
+	.periods_min		= 2,
+	.periods_max		= 256,
+	.buffer_bytes_max	= 2 * 1024 * 1024,
+	.fifo_size		= 0,
+};
+
+static unsigned int psc_i2s_fixed_rates[] = {
+	8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
+};
+
+static struct snd_pcm_hw_constraint_list psc_i2s_constraints_rates = {
+	.count = ARRAY_SIZE(psc_i2s_fixed_rates),
+	.list = psc_i2s_fixed_rates,
+	.mask = 0,
+};
+
+static int psc_i2s_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+	struct psc_i2s_stream *s;
+	int rc;
+
+	dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_i2s->capture_stream;
+	else
+		s = &psc_i2s->playback_stream;
+
+	snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware);
+
+	rc = snd_pcm_hw_constraint_integer(substream->runtime,
+					   SNDRV_PCM_HW_PARAM_PERIODS);
+	if (rc < 0) {
+		dev_err(psc_i2s->dev, "invalid buffer size\n");
+		return rc;
+	}
+	rc = snd_pcm_hw_constraint_list(substream->runtime, 0,
+					SNDRV_PCM_HW_PARAM_RATE,
+					&psc_i2s_constraints_rates);
+	if (rc < 0) {
+		dev_err(psc_i2s->dev, "invalid rate\n");
+		return rc;
+	}
+
+	s->stream = substream;
+	return 0;
+}
+
+static int psc_i2s_pcm_close(struct snd_pcm_substream * substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+	struct psc_i2s_stream *s;
+
+	dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_i2s->capture_stream;
+	else
+		s = &psc_i2s->playback_stream;
+
+	s->stream = NULL;
+	return 0;
+}
+
+static snd_pcm_uframes_t
+psc_i2s_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+	struct psc_i2s_stream *s;
+	dma_addr_t count;
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_i2s->capture_stream;
+	else
+		s = &psc_i2s->playback_stream;
+
+	count = s->period_current_pt - s->period_start;
+
+	return bytes_to_frames(substream->runtime, count);
+}
+
+static struct snd_pcm_ops psc_i2s_pcm_ops = {
+	.open		= psc_i2s_pcm_open,
+	.close		= psc_i2s_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.pointer	= psc_i2s_pcm_pointer,
+};
+
+static u64 psc_i2s_pcm_dmamask = 0xffffffff;
+static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai,
+			   struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+	size_t size = psc_i2s_pcm_hardware.buffer_bytes_max;
+	int rc = 0;
+
+	dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n",
+		card, dai, pcm);
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &psc_i2s_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (pcm->streams[0].substream) {
+		rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
+					&pcm->streams[0].substream->dma_buffer);
+		if (rc) {
+			dev_err(card->dev, "Cannot alloc playback buffer\n");
+			return -ENOMEM;
+		}
+	}
+
+	if (pcm->streams[1].substream) {
+		rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
+					&pcm->streams[1].substream->dma_buffer);
+		if (rc) {
+			if (pcm->streams[0].substream)
+				snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+			dev_err(card->dev, "Cannot allocate capture buffer\n");
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+static void psc_i2s_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+	struct snd_pcm_substream *substream;
+	int stream;
+
+	dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm);
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (substream) {
+			snd_dma_free_pages(&substream->dma_buffer);
+			substream->dma_buffer.area = NULL;
+			substream->dma_buffer.addr = 0;
+		}
+	}
+}
+
+struct snd_soc_platform psc_i2s_pcm_soc_platform = {
+	.name		= "mpc5200-psc-audio",
+	.pcm_ops	= &psc_i2s_pcm_ops,
+	.pcm_new	= &psc_i2s_pcm_new,
+	.pcm_free	= &psc_i2s_pcm_free,
+};
+
+/* ---------------------------------------------------------------------
+ * Sysfs attributes for debugging
+ */
+
+static ssize_t psc_i2s_status_show(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
+
+	return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x tfnum=%i tfstat=0x%.4x\n",
+			in_be16(&psc_i2s->psc_regs->sr_csr.status),
+			in_be32(&psc_i2s->psc_regs->sicr),
+			in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff,
+			in_be16(&psc_i2s->fifo_regs->rfstat),
+			in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff,
+			in_be16(&psc_i2s->fifo_regs->tfstat));
+}
+
+static int * psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s,
+				   const char *name)
+{
+	if (strcmp(name, "playback_underrun") == 0)
+		return &psc_i2s->stats.underrun_count;
+	if (strcmp(name, "capture_overrun") == 0)
+		return &psc_i2s->stats.overrun_count;
+
+	return NULL;
+}
+
+static ssize_t psc_i2s_stat_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
+	int *attrib;
+
+	attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
+	if (!attrib)
+		return 0;
+
+	return sprintf(buf, "%i\n", *attrib);
+}
+
+static ssize_t psc_i2s_stat_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf,
+				  size_t count)
+{
+	struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
+	int *attrib;
+
+	attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
+	if (!attrib)
+		return 0;
+
+	*attrib = simple_strtoul(buf, NULL, 0);
+	return count;
+}
+
+DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL);
+DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show,psc_i2s_stat_store);
+DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store);
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int __devinit psc_i2s_of_probe(struct of_device *op,
+				      const struct of_device_id *match)
+{
+	phys_addr_t fifo;
+	struct psc_i2s *psc_i2s;
+	struct resource res;
+	int size, psc_id, irq, rc;
+	const __be32 *prop;
+	void __iomem *regs;
+
+	dev_dbg(&op->dev, "probing psc i2s device\n");
+
+	/* Get the PSC ID */
+	prop = of_get_property(op->node, "cell-index", &size);
+	if (!prop || size < sizeof *prop)
+		return -ENODEV;
+	psc_id = be32_to_cpu(*prop);
+
+	/* Fetch the registers and IRQ of the PSC */
+	irq = irq_of_parse_and_map(op->node, 0);
+	if (of_address_to_resource(op->node, 0, &res)) {
+		dev_err(&op->dev, "Missing reg property\n");
+		return -ENODEV;
+	}
+	regs = ioremap(res.start, 1 + res.end - res.start);
+	if (!regs) {
+		dev_err(&op->dev, "Could not map registers\n");
+		return -ENODEV;
+	}
+
+	/* Allocate and initialize the driver private data */
+	psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL);
+	if (!psc_i2s) {
+		iounmap(regs);
+		return -ENOMEM;
+	}
+	spin_lock_init(&psc_i2s->lock);
+	psc_i2s->irq = irq;
+	psc_i2s->psc_regs = regs;
+	psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs;
+	psc_i2s->dev = &op->dev;
+	psc_i2s->playback_stream.psc_i2s = psc_i2s;
+	psc_i2s->capture_stream.psc_i2s = psc_i2s;
+	snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1);
+
+	/* Fill out the CPU DAI structure */
+	memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai);
+	psc_i2s->dai.private_data = psc_i2s;
+	psc_i2s->dai.name = psc_i2s->name;
+	psc_i2s->dai.id = psc_id;
+
+	/* Find the address of the fifo data registers and setup the
+	 * DMA tasks */
+	fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
+	psc_i2s->capture_stream.bcom_task =
+		bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512);
+	psc_i2s->playback_stream.bcom_task =
+		bcom_psc_gen_bd_tx_init(psc_id, 10, fifo);
+	if (!psc_i2s->capture_stream.bcom_task ||
+	    !psc_i2s->playback_stream.bcom_task) {
+		dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
+		iounmap(regs);
+		kfree(psc_i2s);
+		return -ENODEV;
+	}
+
+	/* Save what we've done so it can be found again later */
+	dev_set_drvdata(&op->dev, psc_i2s);
+
+	/* Register the SYSFS files */
+	rc = device_create_file(psc_i2s->dev, &dev_attr_status);
+	rc = device_create_file(psc_i2s->dev, &dev_attr_capture_overrun);
+	rc = device_create_file(psc_i2s->dev, &dev_attr_playback_underrun);
+	if (rc)
+		dev_info(psc_i2s->dev, "error creating sysfs files\n");
+
+	/* Tell the ASoC OF helpers about it */
+	of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node,
+				     &psc_i2s->dai);
+
+	return 0;
+}
+
+static int __devexit psc_i2s_of_remove(struct of_device *op)
+{
+	struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev);
+
+	dev_dbg(&op->dev, "psc_i2s_remove()\n");
+
+	bcom_gen_bd_rx_release(psc_i2s->capture_stream.bcom_task);
+	bcom_gen_bd_tx_release(psc_i2s->playback_stream.bcom_task);
+
+	iounmap(psc_i2s->psc_regs);
+	iounmap(psc_i2s->fifo_regs);
+	kfree(psc_i2s);
+	dev_set_drvdata(&op->dev, NULL);
+
+	return 0;
+}
+
+/* Match table for of_platform binding */
+static struct of_device_id psc_i2s_match[] __devinitdata = {
+	{ .compatible = "fsl,mpc5200-psc-i2s", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, psc_i2s_match);
+
+static struct of_platform_driver psc_i2s_driver = {
+	.match_table = psc_i2s_match,
+	.probe = psc_i2s_of_probe,
+	.remove = __devexit_p(psc_i2s_of_remove),
+	.driver = {
+		.name = "mpc5200-psc-i2s",
+		.owner = THIS_MODULE,
+	},
+};
+
+/* ---------------------------------------------------------------------
+ * Module setup and teardown; simply register the of_platform driver
+ * for the PSC in I2S mode.
+ */
+static int __init psc_i2s_init(void)
+{
+	return of_register_platform_driver(&psc_i2s_driver);
+}
+module_init(psc_i2s_init);
+
+static void __exit psc_i2s_exit(void)
+{
+	of_unregister_platform_driver(&psc_i2s_driver);
+}
+module_exit(psc_i2s_exit);
+
+

^ permalink raw reply related

* [PATCH v2 3/3] ALSA SoC: Add Texas Instruments TLV320AIC26 codec driver
From: Grant Likely @ 2008-07-12  8:39 UTC (permalink / raw)
  To: linuxppc-dev, alsa-devel, liam.girdwood, jonsmirl
In-Reply-To: <20080712083929.15025.47682.stgit@trillian.secretlab.ca>

From: Grant Likely <grant.likely@secretlab.ca>

ASoC Codec driver for the TLV320AIC26 device.  This driver uses the ASoC
v1 API, so I don't expect it to get merged as-is, but I want to get it
out there for review.
---

 sound/soc/codecs/Kconfig       |    4 
 sound/soc/codecs/Makefile      |    2 
 sound/soc/codecs/tlv320aic26.c |  546 ++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/tlv320aic26.h |  103 ++++++++
 4 files changed, 655 insertions(+), 0 deletions(-)

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 3903ab7..96c7bfe 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -41,6 +41,10 @@ config SND_SOC_CS4270_VD33_ERRATA
 	bool
 	depends on SND_SOC_CS4270
 
+config SND_SOC_TLV320AIC26
+	tristate "TI TLB320AIC26 Codec support"
+	depends on SND_SOC && SPI
+
 config SND_SOC_TLV320AIC3X
 	tristate
 	depends on SND_SOC && I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4e1314c..ec0cd93 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -5,6 +5,7 @@ snd-soc-wm8753-objs := wm8753.o
 snd-soc-wm9712-objs := wm9712.o
 snd-soc-wm9713-objs := wm9713.o
 snd-soc-cs4270-objs := cs4270.o
+snd-soc-tlv320aic26-objs := tlv320aic26.o
 snd-soc-tlv320aic3x-objs := tlv320aic3x.o
 
 obj-$(CONFIG_SND_SOC_AC97_CODEC)	+= snd-soc-ac97.o
@@ -14,4 +15,5 @@ obj-$(CONFIG_SND_SOC_WM8753)	+= snd-soc-wm8753.o
 obj-$(CONFIG_SND_SOC_WM9712)	+= snd-soc-wm9712.o
 obj-$(CONFIG_SND_SOC_WM9713)	+= snd-soc-wm9713.o
 obj-$(CONFIG_SND_SOC_CS4270)	+= snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_TLV320AIC26)	+= snd-soc-tlv320aic26.o
 obj-$(CONFIG_SND_SOC_TLV320AIC3X)	+= snd-soc-tlv320aic3x.o
diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c
new file mode 100644
index 0000000..3ebfa23
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.c
@@ -0,0 +1,546 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * ALSA SoC CODEC driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc-of.h>
+#include <sound/initval.h>
+
+#include "tlv320aic26.h"
+
+MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver");
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_LICENSE("GPL");
+
+/* AIC26 driver private data */
+struct aic26 {
+	struct spi_device *spi;
+	struct snd_soc_codec codec;
+	u16 reg_cache[AIC26_REG_CACHE_SIZE];	/* shadow registers */
+	int master;
+	int datfm;
+	int mclk;
+
+	/* Keyclick parameters */
+	int keyclick_amplitude;
+	int keyclick_freq;
+	int keyclick_len;
+};
+
+/* ---------------------------------------------------------------------
+ * Register access routines
+ */
+static unsigned int aic26_reg_read(struct snd_soc_codec *codec,
+				   unsigned int reg)
+{
+	struct aic26 *aic26 = codec->private_data;
+	u16 *cache = codec->reg_cache;
+	u16 cmd, value;
+	u8 buffer[2];
+	int rc;
+
+	if (reg >= AIC26_NUM_REGS) {
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	/* Do SPI transfer; first 16bits are command; remaining is
+	 * register contents */
+	cmd = AIC26_READ_COMMAND_WORD(reg);
+	buffer[0] = (cmd >> 8) & 0xff;
+	buffer[1] = cmd & 0xff;
+	rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2);
+	if (rc) {
+		dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+		return -EIO;
+	}
+	value = (buffer[0] << 8) | buffer[1];
+
+	/* Update the cache before returning with the value */
+	if (AIC26_REG_IS_CACHED(reg))
+		cache[AIC26_REG_CACHE_ADDR(reg)] = value;
+	return value;
+}
+
+static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec,
+					 unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if ((reg < 0) || (reg >= AIC26_NUM_REGS)) {
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	if (AIC26_REG_IS_CACHED(reg))
+		return cache[AIC26_REG_CACHE_ADDR(reg)];
+
+	return aic26_reg_read(codec, reg);
+}
+
+static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg,
+		           unsigned int value)
+{
+	struct aic26 *aic26 = codec->private_data;
+	u16 *cache = codec->reg_cache;
+	u16 cmd;
+	u8 buffer[4];
+	int rc;
+
+	if ((reg < 0) || (reg >= AIC26_NUM_REGS)) {
+		WARN_ON_ONCE(1);
+		return -EINVAL;
+	}
+
+	/* Do SPI transfer; first 16bits are command; remaining is data
+	 * to write into register */
+	cmd = AIC26_WRITE_COMMAND_WORD(reg);
+	buffer[0] = (cmd >> 8) & 0xff;
+	buffer[1] = cmd & 0xff;
+	buffer[2] = value >> 8;
+	buffer[3] = value;
+	rc = spi_write(aic26->spi, buffer, 4);
+	if (rc) {
+		dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+		return -EIO;
+	}
+
+	/* update cache before returning */
+	if (AIC26_REG_IS_CACHED(reg))
+		cache[AIC26_REG_CACHE_ADDR(reg)] = value;
+	return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Operations
+ */
+static int aic26_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->codec;
+	struct aic26 *aic26 = codec->private_data;
+	int fsref, divisor, wlen, pval, jval, dval, qval;
+	u16 reg;
+
+	dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n",
+		substream, params);
+	dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params),
+		params_format(params));
+
+	switch (params_rate(params)) {
+	case 8000: fsref = 48000; divisor = AIC26_DIV_6; break;
+	case 11025: fsref = 44100; divisor = AIC26_DIV_4; break;
+	case 12000: fsref = 48000; divisor = AIC26_DIV_4; break;
+	case 16000: fsref = 48000; divisor = AIC26_DIV_3; break;
+	case 22050: fsref = 44100; divisor = AIC26_DIV_2; break;
+	case 24000: fsref = 48000; divisor = AIC26_DIV_2; break;
+	case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break;
+	case 44100: fsref = 44100; divisor = AIC26_DIV_1; break;
+	case 48000: fsref = 48000; divisor = AIC26_DIV_1; break;
+	default: dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL;
+	}
+
+	/* select data word length */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break;
+	case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break;
+	case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break;
+	case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break;
+	default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+	}
+
+	/* Configure PLL */
+	pval = 1;
+	jval = (fsref == 44100) ? 7 : 8;
+	dval = (fsref == 44100) ? 5264 : 1920;
+	qval = 0;
+	reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
+	aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
+	reg = dval << 2;
+	aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
+
+	/* Power up CODEC */
+	aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0);
+
+	/* Audio Control 3 (master mode, fsref rate) */
+	reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3);
+	reg &= ~0xf800;
+	if (aic26->master)
+		reg |= 0x0800;
+	if (fsref == 48000)
+		reg |= 0x2000;
+	aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+	/* Audio Control 1 (FSref divisor) */
+	reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1);
+	reg &= ~0x0fff;
+	reg |= wlen | aic26->datfm | (divisor << 3) | divisor;
+	aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg);
+
+	return 0;
+}
+
+/**
+ * aic26_mute - Mute control to reduce noise when changing audio format
+ */
+static int aic26_mute(struct snd_soc_codec_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct aic26 *aic26 = codec->private_data;
+	u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN);
+
+	dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n",
+		dai, mute);
+
+	if (mute)
+		reg |= 0x8080;
+	else
+		reg &= ~0x8080;
+	aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg);
+
+	return 0;
+}
+
+static int aic26_set_sysclk(struct snd_soc_codec_dai *codec_dai,
+			    int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct aic26 *aic26 = codec->private_data;
+
+	dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i,"
+		" freq=%i, dir=%i)\n",
+		codec_dai, clk_id, freq, dir);
+
+	/* MCLK needs to fall between 2MHz and 50 MHz */
+	if ((freq < 2000000) || (freq > 50000000))
+		return -EINVAL;
+
+	aic26->mclk = freq;
+	return 0;
+}
+
+static int aic26_set_fmt(struct snd_soc_codec_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct aic26 *aic26 = codec->private_data;
+
+	dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n",
+		codec_dai, fmt);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break;
+	case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break;
+	default: dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break;
+	case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break;
+	case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break;
+	case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break;
+	default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Definition
+ */
+struct snd_soc_codec_dai aic26_dai = {
+	.name = "tlv320aic26",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = AIC26_RATES,
+		.formats = AIC26_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = AIC26_RATES,
+		.formats = AIC26_FORMATS,
+	},
+	.ops = {
+		.hw_params = aic26_hw_params,
+	},
+	.dai_ops = {
+		.digital_mute = aic26_mute,
+		.set_sysclk = aic26_set_sysclk,
+		.set_fmt = aic26_set_fmt,
+	},
+};
+EXPORT_SYMBOL_GPL(aic26_dai);
+
+/* ---------------------------------------------------------------------
+ * ALSA controls
+ */
+static const char *aic26_capture_src_text[] = {"Mic", "Aux"};
+static const struct soc_enum aic26_capture_src_enum =
+	SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12,2, aic26_capture_src_text);
+
+static const struct snd_kcontrol_new aic26_snd_controls[] = {
+	/* Output */
+	SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1),
+	SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1),
+	SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0),
+	SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1),
+	SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0),
+	SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0),
+	SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0),
+	SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0),
+	SOC_ENUM("Capture Source", aic26_capture_src_enum),
+};
+
+/* ---------------------------------------------------------------------
+ * SoC CODEC portion of driver: probe and release routines
+ */
+static int aic26_probe(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec;
+	struct snd_kcontrol *kcontrol;
+	struct aic26 *aic26;
+	int i, ret, err;
+
+	dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n");
+	dev_dbg(&pdev->dev, "socdev=%p\n", socdev);
+	dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data);
+
+	/* Fetch the relevant aic26 private data here (it's already been
+	 * stored in the .codec pointer) */
+	aic26 = socdev->codec_data;
+	if (aic26 == NULL) {
+		dev_err(&pdev->dev, "aic26: missing codec pointer\n");
+		return -ENODEV;
+	}
+	codec = &aic26->codec;
+	socdev->codec = codec;
+
+	dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n",
+		&pdev->dev, socdev->dev);
+	/* register pcms */
+	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "aic26: failed to create pcms\n");
+		return -ENODEV;
+	}
+
+	/* register controls */
+	dev_dbg(&pdev->dev, "Registering controls\n");
+	for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) {
+		kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL);
+		err = snd_ctl_add(codec->card, kcontrol);
+		WARN_ON(err < 0);
+	}
+
+	/* CODEC is setup, we can register the card now */
+	dev_dbg(&pdev->dev, "Registering card\n");
+	ret = snd_soc_register_card(socdev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "aic26: failed to register card\n");
+		goto card_err;
+	}
+	return 0;
+
+ card_err:
+	snd_soc_free_pcms(socdev);
+	return ret;
+}
+
+static int aic26_remove(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	snd_soc_free_pcms(socdev);
+	return 0;
+}
+
+struct snd_soc_codec_device aic26_soc_codec_dev = {
+	.probe = aic26_probe,
+	.remove = aic26_remove,
+};
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: sysfs files for debugging
+ */
+
+static ssize_t aic26_regs_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct aic26 *aic26 = dev_get_drvdata(dev);
+	char *idx = buf;
+	int cache_flag, addr, page, i, reg;
+
+	cache_flag = (strcmp(attr->attr.name, "regs_cache") == 0);
+
+	for (page = 0; page < 3; page++) {
+		for (i = 0; i < 0x20; i++) {
+			addr = AIC26_PAGE_ADDR(page, i);
+			if (i % 8 == 0)
+				idx += sprintf(idx, "%i:%.2i:", page,i);
+			if (cache_flag)
+				reg = aic26_reg_read_cache(&aic26->codec, addr);
+			else
+				reg = aic26_reg_read(&aic26->codec, addr);
+			idx += sprintf(idx, " %.4x", reg);
+			if (i % 8 == 7)
+				idx += sprintf(idx, "\n");
+		}
+	}
+	return idx - buf;
+}
+
+static ssize_t aic26_keyclick_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct aic26 *aic26 = dev_get_drvdata(dev);
+	int val, amp, freq, len;
+
+	val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+	amp = (val >> 12) & 0x7;
+	freq = (125 << ((val >> 8) & 0x7)) >> 1;
+	len = 2 * (1 +((val >> 4) & 0xf));
+
+	return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len);
+}
+
+/* Any write to the keyclick attribute will trigger the keyclick */
+static ssize_t aic26_keyclick_set(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct aic26 *aic26 = dev_get_drvdata(dev);
+	int val;
+
+	val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+	val |= 0x8000;
+	aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val);
+
+	return count;
+}
+
+DEVICE_ATTR(regs, 0644, aic26_regs_show, NULL);
+DEVICE_ATTR(regs_cache, 0644, aic26_regs_show, NULL);
+DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set);
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: probe and release routines and SPI
+ * 				 driver registration.
+ */
+static int aic26_spi_probe(struct spi_device *spi)
+{
+	struct aic26 *aic26;
+	int rc, i, reg;
+
+	dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n");
+
+	/* Allocate driver data */
+	aic26 = kzalloc(sizeof *aic26, GFP_KERNEL);
+	if (!aic26)
+		return -ENOMEM;
+
+	/* Initialize the driver data */
+	aic26->spi = spi;
+	dev_set_drvdata(&spi->dev, aic26);
+
+	/* Setup what we can in the codec structure so that the register
+	 * access functions will work as expected.  More will be filled
+	 * out when it is probed by the SoC CODEC part of this driver */
+	aic26->codec.private_data = aic26;
+	aic26->codec.name = "aic26";
+	aic26->codec.owner = THIS_MODULE;
+	aic26->codec.dai = &aic26_dai;
+	aic26->codec.num_dai = 1;
+	aic26->codec.read = aic26_reg_read;
+	aic26->codec.write = aic26_reg_write;
+	aic26->master = 1;
+	mutex_init(&aic26->codec.mutex);
+	INIT_LIST_HEAD(&aic26->codec.dapm_widgets);
+	INIT_LIST_HEAD(&aic26->codec.dapm_paths);
+	aic26->codec.reg_cache_size = sizeof(aic26->reg_cache);
+	aic26->codec.reg_cache = aic26->reg_cache;
+
+	/* Reset the codec to power on defaults */
+	aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00);
+
+	/* Power up CODEC */
+	aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0);
+
+	/* Audio Control 3 (master mode, fsref rate) */
+	reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3);
+	reg &= ~0xf800;
+	reg |= 0x0800; /* set master mode */
+	aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+	/* Fill page 2 register cache */
+	for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++)
+		aic26_reg_read(&aic26->codec, AIC26_PAGE_ADDR(2, i));
+
+	/* Register the sysfs files for debugging */
+	/* Create SysFS files */
+	rc = device_create_file(&spi->dev, &dev_attr_regs);
+	rc |= device_create_file(&spi->dev, &dev_attr_regs_cache);
+	rc |= device_create_file(&spi->dev, &dev_attr_keyclick);
+	if (rc)
+		dev_info(&spi->dev, "error creating sysfs files\n");
+
+#if defined(CONFIG_SND_SOC_OF)
+	/* Tell the of_soc helper about this codec */
+	of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai,
+				  spi->dev.archdata.of_node);
+#endif
+
+	dev_dbg(&spi->dev, "SPI device initialized\n");
+	return 0;
+}
+
+static int aic26_spi_remove(struct spi_device *spi)
+{
+	struct aic26 *aic26 = dev_get_drvdata(&spi->dev);
+
+	kfree(aic26);
+
+	return 0;
+}
+
+static struct spi_driver aic26_spi = {
+	.driver = {
+		.name = "tlv320aic26",
+		.owner = THIS_MODULE,
+	},
+	.probe = aic26_spi_probe,
+	.remove = aic26_spi_remove,
+};
+
+static int __init aic26_init(void)
+{
+	return spi_register_driver(&aic26_spi);
+}
+module_init(aic26_init);
+
+static void __exit aic26_exit(void)
+{
+	spi_unregister_driver(&aic26_spi);
+}
+module_exit(aic26_exit);
diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h
new file mode 100644
index 0000000..9edb8e9
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.h
@@ -0,0 +1,103 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * register definitions
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#ifndef _TLV320AIC16_H_
+#define _TLV320AIC16_H_
+
+/* AIC26 Registers */
+#define AIC26_READ_COMMAND_WORD(addr)	((1 << 15) | (addr << 5))
+#define AIC26_WRITE_COMMAND_WORD(addr)	((0 << 15) | (addr << 5))
+#define AIC26_PAGE_ADDR(page, offset)	((page << 6) | offset)
+#define AIC26_NUM_REGS			AIC26_PAGE_ADDR(3, 0)
+#define AIC26_REG_CACHE_SIZE		(0x20) /* only page 2 cached */
+#define AIC26_REG_IS_CACHED(addr)	((addr & ~0x1f) == (2 << 6))
+#define AIC26_REG_CACHE_ADDR(addr)	(addr & 0x1f)
+
+/* Page 0: Auxillary data registers */
+#define AIC26_REG_BAT1			AIC26_PAGE_ADDR(0, 0x05)
+#define AIC26_REG_BAT2			AIC26_PAGE_ADDR(0, 0x06)
+#define AIC26_REG_AUX			AIC26_PAGE_ADDR(0, 0x07)
+#define AIC26_REG_TEMP1			AIC26_PAGE_ADDR(0, 0x09)
+#define AIC26_REG_TEMP2			AIC26_PAGE_ADDR(0, 0x0A)
+
+/* Page 1: Auxillary control registers */
+#define AIC26_REG_AUX_ADC		AIC26_PAGE_ADDR(1, 0x00)
+#define AIC26_REG_STATUS		AIC26_PAGE_ADDR(1, 0x01)
+#define AIC26_REG_REFERENCE		AIC26_PAGE_ADDR(1, 0x03)
+#define AIC26_REG_RESET			AIC26_PAGE_ADDR(1, 0x04)
+
+/* Page 2: Audio control registers */
+#define AIC26_REG_AUDIO_CTRL1		AIC26_PAGE_ADDR(2, 0x00)
+#define AIC26_REG_ADC_GAIN		AIC26_PAGE_ADDR(2, 0x01)
+#define AIC26_REG_DAC_GAIN		AIC26_PAGE_ADDR(2, 0x02)
+#define AIC26_REG_SIDETONE		AIC26_PAGE_ADDR(2, 0x03)
+#define AIC26_REG_AUDIO_CTRL2		AIC26_PAGE_ADDR(2, 0x04)
+#define AIC26_REG_POWER_CTRL		AIC26_PAGE_ADDR(2, 0x05)
+#define AIC26_REG_AUDIO_CTRL3		AIC26_PAGE_ADDR(2, 0x06)
+
+#define AIC26_REG_FILTER_COEFF_L_N0	AIC26_PAGE_ADDR(2, 0x07)
+#define AIC26_REG_FILTER_COEFF_L_N1	AIC26_PAGE_ADDR(2, 0x08)
+#define AIC26_REG_FILTER_COEFF_L_N2	AIC26_PAGE_ADDR(2, 0x09)
+#define AIC26_REG_FILTER_COEFF_L_N3	AIC26_PAGE_ADDR(2, 0x0A)
+#define AIC26_REG_FILTER_COEFF_L_N4	AIC26_PAGE_ADDR(2, 0x0B)
+#define AIC26_REG_FILTER_COEFF_L_N5	AIC26_PAGE_ADDR(2, 0x0C)
+#define AIC26_REG_FILTER_COEFF_L_D1	AIC26_PAGE_ADDR(2, 0x0D)
+#define AIC26_REG_FILTER_COEFF_L_D2	AIC26_PAGE_ADDR(2, 0x0E)
+#define AIC26_REG_FILTER_COEFF_L_D4	AIC26_PAGE_ADDR(2, 0x0F)
+#define AIC26_REG_FILTER_COEFF_L_D5	AIC26_PAGE_ADDR(2, 0x10)
+#define AIC26_REG_FILTER_COEFF_R_N0	AIC26_PAGE_ADDR(2, 0x11)
+#define AIC26_REG_FILTER_COEFF_R_N1	AIC26_PAGE_ADDR(2, 0x12)
+#define AIC26_REG_FILTER_COEFF_R_N2	AIC26_PAGE_ADDR(2, 0x13)
+#define AIC26_REG_FILTER_COEFF_R_N3	AIC26_PAGE_ADDR(2, 0x14)
+#define AIC26_REG_FILTER_COEFF_R_N4	AIC26_PAGE_ADDR(2, 0x15)
+#define AIC26_REG_FILTER_COEFF_R_N5	AIC26_PAGE_ADDR(2, 0x16)
+#define AIC26_REG_FILTER_COEFF_R_D1	AIC26_PAGE_ADDR(2, 0x17)
+#define AIC26_REG_FILTER_COEFF_R_D2	AIC26_PAGE_ADDR(2, 0x18)
+#define AIC26_REG_FILTER_COEFF_R_D4	AIC26_PAGE_ADDR(2, 0x19)
+#define AIC26_REG_FILTER_COEFF_R_D5	AIC26_PAGE_ADDR(2, 0x1A)
+
+#define AIC26_REG_PLL_PROG1		AIC26_PAGE_ADDR(2, 0x1B)
+#define AIC26_REG_PLL_PROG2		AIC26_PAGE_ADDR(2, 0x1C)
+#define AIC26_REG_AUDIO_CTRL4		AIC26_PAGE_ADDR(2, 0x1D)
+#define AIC26_REG_AUDIO_CTRL5		AIC26_PAGE_ADDR(2, 0x1E)
+
+#define AIC26_RATES	(SNDRV_PCM_RATE_8000  | SNDRV_PCM_RATE_11025 |\
+			 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+			 SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+			 SNDRV_PCM_RATE_48000)
+#define AIC26_FORMATS	(SNDRV_PCM_FMTBIT_S8     | SNDRV_PCM_FMTBIT_S16_BE |\
+			 SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+/* fsref dividers; used in register 'Audio Control 1' */
+enum aic26_divisors {
+	AIC26_DIV_1	= 0,
+	AIC26_DIV_1_5	= 1,
+	AIC26_DIV_2	= 2,
+	AIC26_DIV_3	= 3,
+	AIC26_DIV_4	= 4,
+	AIC26_DIV_5	= 5,
+	AIC26_DIV_5_5	= 6,
+	AIC26_DIV_6	= 7,
+};
+
+/* Digital data format */
+enum aic26_datfm {
+	AIC26_DATFM_I2S		= 0 << 8,
+	AIC26_DATFM_DSP		= 1 << 8,
+	AIC26_DATFM_RIGHTJ	= 2 << 8, /* right justified */
+	AIC26_DATFM_LEFTJ	= 3 << 8, /* left justified */
+};
+
+/* Sample word length in bits; used in register 'Audio Control 1' */
+enum aic26_wlen {
+	AIC26_WLEN_16	= 0 << 10,
+	AIC26_WLEN_20	= 1 << 10,
+	AIC26_WLEN_24	= 2 << 10,
+	AIC26_WLEN_32	= 3 << 10,
+};
+
+#endif /* _TLV320AIC16_H_ */

^ permalink raw reply related

* [PATCH v3 2/5] spi: split up spi_new_device() to allow two stage registration.
From: Grant Likely @ 2008-07-12  8:34 UTC (permalink / raw)
  To: linuxppc-dev, spi-devel-general, dbrownell, jonsmirl
In-Reply-To: <20080712083437.14782.65551.stgit@trillian.secretlab.ca>

From: Grant Likely <grant.likely@secretlab.ca>

spi_new_device() allocates and registers an spi device all in one swoop.
If the driver needs to add extra data to the spi_device before it is
registered, then this causes problems.

This patch splits the allocation and registration portions of code out
of spi_new_device() and creates three new functions; spi_alloc_device(),
spi_register_device(), and spi_device_release().  spi_new_device() is
modified to use the new functions for allocation and registration.
None of the existing users of spi_new_device() should be affected by
this change.

Drivers using the new API can forego the use of an spi_board_info
structure to describe the device layout and populate data into the
spi_device structure directly.

This change is in preparation for adding an OF device tree parser to
generate spi_devices based on data in the device tree.

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

 drivers/spi/spi.c       |  139 ++++++++++++++++++++++++++++++++---------------
 include/linux/spi/spi.h |   10 +++
 2 files changed, 105 insertions(+), 44 deletions(-)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 32b7a42..e64add0 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -178,6 +178,96 @@ struct boardinfo {
 static LIST_HEAD(board_list);
 static DEFINE_MUTEX(board_lock);
 
+/**
+ * spi_alloc_device - Allocate a new SPI device
+ * @master: Controller to which device is connected
+ * Context: can sleep
+ *
+ * Allows a driver to allocate and initialize and spi_device without
+ * registering it immediately.  This allows a driver to directly
+ * fill the spi_device with device parameters before calling
+ * spi_add_device() on it.
+ *
+ * Caller is responsible to call spi_add_device() on the returned
+ * spi_device structure to add it to the SPI master.  If the caller
+ * needs to discard the spi_device without adding it, then it should
+ * call spi_dev_put() on it.
+ *
+ * Returns a pointer to the new device, or NULL.
+ */
+struct spi_device *spi_alloc_device(struct spi_master *master)
+{
+	struct spi_device	*spi;
+	struct device		*dev = master->dev.parent;
+
+	if (!spi_master_get(master))
+		return NULL;
+
+	spi = kzalloc(sizeof *spi, GFP_KERNEL);
+	if (!spi) {
+		dev_err(dev, "cannot alloc spi_device\n");
+		spi_master_put(master);
+		return NULL;
+	}
+
+	spi->master = master;
+	spi->dev.parent = dev;
+	spi->dev.bus = &spi_bus_type;
+	spi->dev.release = spidev_release;
+	device_initialize(&spi->dev);
+	return spi;
+}
+EXPORT_SYMBOL_GPL(spi_alloc_device);
+
+/**
+ * spi_add_device - Add an spi_device allocated with spi_alloc_device
+ * @spi: spi_device to register
+ *
+ * Companion function to spi_alloc_device.  Devices allocated with
+ * spi_alloc_device can be added onto the spi bus with this function.
+ *
+ * Returns 0 on success; non-zero on failure
+ */
+int spi_add_device(struct spi_device *spi)
+{
+	struct device *dev = spi->master->dev.parent;
+	int status;
+
+	/* Chipselects are numbered 0..max; validate. */
+	if (spi->chip_select >= spi->master->num_chipselect) {
+		dev_err(dev, "cs%d > max %d\n",
+			spi->chip_select,
+			spi->master->num_chipselect);
+		return -EINVAL;
+	}
+
+	/* Set the bus ID string */
+	snprintf(spi->dev.bus_id, sizeof spi->dev.bus_id,
+			"%s.%u", spi->master->dev.bus_id,
+			spi->chip_select);
+
+	/* drivers may modify this initial i/o setup */
+	status = spi->master->setup(spi);
+	if (status < 0) {
+		dev_err(dev, "can't %s %s, status %d\n",
+				"setup", spi->dev.bus_id, status);
+		return status;
+	}
+
+	/* driver core catches callers that misbehave by defining
+	 * devices that already exist.
+	 */
+	status = device_add(&spi->dev);
+	if (status < 0) {
+		dev_err(dev, "can't %s %s, status %d\n",
+				"add", spi->dev.bus_id, status);
+		return status;
+	}
+
+	dev_dbg(dev, "registered child %s\n", spi->dev.bus_id);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(spi_add_device);
 
 /**
  * spi_new_device - instantiate one new SPI device
@@ -197,7 +287,6 @@ struct spi_device *spi_new_device(struct spi_master *master,
 				  struct spi_board_info *chip)
 {
 	struct spi_device	*proxy;
-	struct device		*dev = master->dev.parent;
 	int			status;
 
 	/* NOTE:  caller did any chip->bus_num checks necessary.
@@ -207,66 +296,28 @@ struct spi_device *spi_new_device(struct spi_master *master,
 	 * suggests syslogged diagnostics are best here (ugh).
 	 */
 
-	/* Chipselects are numbered 0..max; validate. */
-	if (chip->chip_select >= master->num_chipselect) {
-		dev_err(dev, "cs%d > max %d\n",
-			chip->chip_select,
-			master->num_chipselect);
-		return NULL;
-	}
-
-	if (!spi_master_get(master))
+	proxy = spi_alloc_device(master);
+	if (!proxy)
 		return NULL;
 
 	WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));
 
-	proxy = kzalloc(sizeof *proxy, GFP_KERNEL);
-	if (!proxy) {
-		dev_err(dev, "can't alloc dev for cs%d\n",
-			chip->chip_select);
-		goto fail;
-	}
-	proxy->master = master;
 	proxy->chip_select = chip->chip_select;
 	proxy->max_speed_hz = chip->max_speed_hz;
 	proxy->mode = chip->mode;
 	proxy->irq = chip->irq;
 	strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
-
-	snprintf(proxy->dev.bus_id, sizeof proxy->dev.bus_id,
-			"%s.%u", master->dev.bus_id,
-			chip->chip_select);
-	proxy->dev.parent = dev;
-	proxy->dev.bus = &spi_bus_type;
 	proxy->dev.platform_data = (void *) chip->platform_data;
 	proxy->controller_data = chip->controller_data;
 	proxy->controller_state = NULL;
-	proxy->dev.release = spidev_release;
 
-	/* drivers may modify this initial i/o setup */
-	status = master->setup(proxy);
+	status = spi_add_device(proxy);
 	if (status < 0) {
-		dev_err(dev, "can't %s %s, status %d\n",
-				"setup", proxy->dev.bus_id, status);
-		goto fail;
+		spi_dev_put(proxy);
+		return NULL;
 	}
 
-	/* driver core catches callers that misbehave by defining
-	 * devices that already exist.
-	 */
-	status = device_register(&proxy->dev);
-	if (status < 0) {
-		dev_err(dev, "can't %s %s, status %d\n",
-				"add", proxy->dev.bus_id, status);
-		goto fail;
-	}
-	dev_dbg(dev, "registered child %s\n", proxy->dev.bus_id);
 	return proxy;
-
-fail:
-	spi_master_put(master);
-	kfree(proxy);
-	return NULL;
 }
 EXPORT_SYMBOL_GPL(spi_new_device);
 
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index b55910b..d45967e 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -778,8 +778,18 @@ spi_register_board_info(struct spi_board_info const *info, unsigned n)
  * use spi_new_device() to describe each device.  You can also call
  * spi_unregister_device() to start making that device vanish, but
  * normally that would be handled by spi_unregister_master().
+ *
+ * You can also use spi_alloc_device() and spi_add_device() to
+ * for a two stage registration of an SPI device.  This gives the caller
+ * some more control over the spi_device structure before it is registered
  */
 extern struct spi_device *
+spi_alloc_device(struct spi_master *master);
+
+extern int
+spi_add_device(struct spi_device *spi);
+
+extern struct spi_device *
 spi_new_device(struct spi_master *, struct spi_board_info *);
 
 static inline void

^ permalink raw reply related

* [PATCH v3 4/5] spi: Add OF binding support for SPI busses
From: Grant Likely @ 2008-07-12  8:34 UTC (permalink / raw)
  To: linuxppc-dev, spi-devel-general, dbrownell, jonsmirl
In-Reply-To: <20080712083437.14782.65551.stgit@trillian.secretlab.ca>

From: Grant Likely <grant.likely@secretlab.ca>

This patch adds support for populating an SPI bus based on data in the
OF device tree.  This is useful for powerpc platforms which use the
device tree instead of discrete code for describing platform layout.

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

 drivers/of/Kconfig     |    6 +++
 drivers/of/Makefile    |    1 +
 drivers/of/of_spi.c    |   93 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/of_spi.h |   18 +++++++++
 4 files changed, 118 insertions(+), 0 deletions(-)

diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index 3a7a11a..edd6e92 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -13,3 +13,9 @@ config OF_I2C
 	depends on PPC_OF && I2C
 	help
 	  OpenFirmware I2C accessors
+
+config OF_SPI
+	def_tristate SPI
+	depends on OF && PPC_OF && SPI
+	help
+	  OpenFirmware SPI accessors
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index 548772e..4c3c6f8 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -2,3 +2,4 @@ obj-y = base.o
 obj-$(CONFIG_OF_DEVICE) += device.o platform.o
 obj-$(CONFIG_OF_GPIO)   += gpio.o
 obj-$(CONFIG_OF_I2C)	+= of_i2c.o
+obj-$(CONFIG_OF_SPI)	+= of_spi.o
diff --git a/drivers/of/of_spi.c b/drivers/of/of_spi.c
new file mode 100644
index 0000000..b01eec0
--- /dev/null
+++ b/drivers/of/of_spi.c
@@ -0,0 +1,93 @@
+/*
+ * SPI OF support routines
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ *
+ * Support routines for deriving SPI device attachments from the device
+ * tree.
+ */
+
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/of_spi.h>
+
+/**
+ * of_register_spi_devices - Register child devices onto the SPI bus
+ * @master:	Pointer to spi_master device
+ * @np:		parent node of SPI device nodes
+ *
+ * Registers an spi_device for each child node of 'np' which has a 'reg'
+ * property.
+ */
+void of_register_spi_devices(struct spi_master *master, struct device_node *np)
+{
+	struct spi_device *spi;
+	struct device_node *nc;
+	const u32 *prop;
+	int rc;
+	int len;
+
+	for_each_child_of_node(np, nc) {
+		/* Alloc an spi_device */
+		spi = spi_alloc_device(master);
+		if (!spi) {
+			dev_err(&master->dev, "spi_device alloc error for %s\n",
+				nc->full_name);
+			spi_dev_put(spi);
+			continue;
+		}
+
+		/* Select device driver */
+		if (of_modalias_node(nc, spi->modalias,
+				     sizeof(spi->modalias)) < 0) {
+			dev_err(&master->dev, "cannot find modalias for %s\n",
+				nc->full_name);
+			spi_dev_put(spi);
+			continue;
+		}
+
+		/* Device address */
+		prop = of_get_property(nc, "reg", &len);
+		if (!prop || len < sizeof(*prop)) {
+			dev_err(&master->dev, "%s has no 'reg' property\n",
+				nc->full_name);
+			spi_dev_put(spi);
+			continue;
+		}
+		spi->chip_select = *prop;
+
+		/* Mode (clock phase/polarity/etc.) */
+		if (of_find_property(nc, "spi-cpha", NULL))
+			spi->mode |= SPI_CPHA;
+		if (of_find_property(nc, "spi-cpol", NULL))
+			spi->mode |= SPI_CPOL;
+
+		/* Device speed */
+		prop = of_get_property(nc, "spi-max-frequency", &len);
+		if (!prop || len < sizeof(*prop)) {
+			dev_err(&master->dev, "%s has no 'spi-max-frequency' property\n",
+				nc->full_name);
+			spi_dev_put(spi);
+			continue;
+		}
+		spi->max_speed_hz = *prop;
+
+		/* IRQ */
+		spi->irq = irq_of_parse_and_map(nc, 0);
+
+		/* Store a pointer to the node in the device structure */
+		of_node_get(nc);
+		spi->dev.archdata.of_node = nc;
+
+		/* Register the new device */
+		request_module(spi->modalias);
+		rc = spi_add_device(spi);
+		if (rc) {
+			dev_err(&master->dev, "spi_device register error %s\n",
+				nc->full_name);
+			spi_dev_put(spi);
+		}
+
+	}
+}
+EXPORT_SYMBOL(of_register_spi_devices);
diff --git a/include/linux/of_spi.h b/include/linux/of_spi.h
new file mode 100644
index 0000000..5f71ee8
--- /dev/null
+++ b/include/linux/of_spi.h
@@ -0,0 +1,18 @@
+/*
+ * OpenFirmware SPI support routines
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ *
+ * Support routines for deriving SPI device attachments from the device
+ * tree.
+ */
+
+#ifndef __LINUX_OF_SPI_H
+#define __LINUX_OF_SPI_H
+
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+extern void of_register_spi_devices(struct spi_master *master,
+				    struct device_node *np);
+
+#endif /* __LINUX_OF_SPI */

^ permalink raw reply related

* [PATCH v3 3/5] of-bindings: Add binding documentation for SPI busses and devices
From: Grant Likely @ 2008-07-12  8:34 UTC (permalink / raw)
  To: linuxppc-dev, spi-devel-general, dbrownell, jonsmirl
In-Reply-To: <20080712083437.14782.65551.stgit@trillian.secretlab.ca>

From: Grant Likely <grant.likely@secretlab.ca>

Add documentation about how to describe SPI busses in the device tree.

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

 Documentation/powerpc/booting-without-of.txt |   58 ++++++++++++++++++++++++++
 1 files changed, 58 insertions(+), 0 deletions(-)

diff --git a/Documentation/powerpc/booting-without-of.txt b/Documentation/powerpc/booting-without-of.txt
index b68684d..3b964ed 100644
--- a/Documentation/powerpc/booting-without-of.txt
+++ b/Documentation/powerpc/booting-without-of.txt
@@ -62,6 +62,7 @@ Table of Contents
       s) Freescale on board FPGA
       t) Freescael MSI interrupt controller
       u) Freescale General-purpose Timers Module
+      v) SPI busses
 
   VII - Marvell Discovery mv64[345]6x System Controller chips
     1) The /system-controller node
@@ -2967,6 +2968,63 @@ platforms are moved over to use the flattened-device-tree model.
 	clock-frequency = <0>;
     };
 
+    v) SPI (Serial Peripheral Interface) busses
+
+    SPI busses can be described with a node for the SPI master device
+    and a set of child nodes for each SPI slave on the bus.  For this
+    discussion, it is assumed that the system's SPI controller is in
+    SPI master mode.  This binding does not describe SPI controllers
+    in slave mode.
+
+    The SPI master node requires the following properties:
+    - #address-cells  - number of cells required to define a chip select
+			address on the SPI bus.
+    - #size-cells     - should be zero.
+    - compatible      - name of SPI bus controller following generic names
+			recommended practice.
+    No other properties are required in the SPI bus node.  It is assumed
+    that a driver for an SPI bus device will understand that it is an SPI bus.
+    However, the binding does not attempt to define the specific method for
+    assigning chip select numbers.  Since SPI chip select configuration is
+    flexible and non-standardized, it is left out of this binding with the
+    assumption that board specific platform code will be used to manage
+    chip selects.  Individual drivers can define additional properties to
+    support describing the chip select layout.
+
+    SPI slave nodes must be children of the SPI master node and can
+    contain the following properties.
+    - reg             - (required) chip select address of device.
+    - compatible      - (required) name of SPI device following generic names
+			recommended practice
+    - spi-max-frequency - (required) Maximum SPI clocking speed of device in Hz
+    - spi-cpol        - (optional) Empty property indicating device requires
+			inverse clock polarity (CPOL) mode
+    - spi-cpha        - (optional) Empty property indicating device requires
+			shifted clock phase (CPHA) mode
+
+    SPI example for an MPC5200 SPI bus:
+		spi@f00 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "fsl,mpc5200b-spi","fsl,mpc5200-spi";
+			reg = <0xf00 0x20>;
+			interrupts = <2 13 0 2 14 0>;
+			interrupt-parent = <&mpc5200_pic>;
+
+			ethernet-switch@0 {
+				compatible = "micrel,ks8995m";
+				spi-max-frequency = <1000000>;
+				reg = <0>;
+			};
+
+			codec@1 {
+				compatible = "ti,tlv320aic26";
+				spi-max-frequency = <100000>;
+				reg = <1>;
+			};
+		};
+
+
 VII - Marvell Discovery mv64[345]6x System Controller chips
 ===========================================================
 

^ permalink raw reply related

* [PATCH v3 5/5] powerpc/mpc5200: Add mpc5200-spi (non-PSC) device driver
From: Grant Likely @ 2008-07-12  8:34 UTC (permalink / raw)
  To: linuxppc-dev, spi-devel-general, dbrownell, jonsmirl
In-Reply-To: <20080712083437.14782.65551.stgit@trillian.secretlab.ca>

From: Grant Likely <grant.likely@secretlab.ca>

Adds support for the dedicated SPI device on the Freescale MPC5200(b)
SoC.

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

 drivers/spi/Kconfig             |    8 +
 drivers/spi/Makefile            |    1 
 drivers/spi/mpc52xx_spi.c       |  595 +++++++++++++++++++++++++++++++++++++++
 include/linux/spi/mpc52xx_spi.h |   10 +
 4 files changed, 614 insertions(+), 0 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 66ec5d8..01860ac 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -116,6 +116,14 @@ config SPI_LM70_LLP
 	  which interfaces to an LM70 temperature sensor using
 	  a parallel port.
 
+config SPI_MPC52xx
+	tristate "Freescale MPC52xx SPI (non-PSC) controller support"
+	depends on PPC_MPC52xx && SPI
+	select SPI_MASTER_OF
+	help
+	  This drivers supports the MPC52xx SPI controller in master SPI
+	  mode.
+
 config SPI_MPC52xx_PSC
 	tristate "Freescale MPC52xx PSC SPI controller"
 	depends on SPI_MASTER && PPC_MPC52xx && EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 7fca043..340b878 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
 obj-$(CONFIG_SPI_OMAP_UWIRE)		+= omap_uwire.o
 obj-$(CONFIG_SPI_OMAP24XX)		+= omap2_mcspi.o
 obj-$(CONFIG_SPI_MPC52xx_PSC)		+= mpc52xx_psc_spi.o
+obj-$(CONFIG_SPI_MPC52xx)		+= mpc52xx_spi.o
 obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 obj-$(CONFIG_SPI_S3C24XX_GPIO)		+= spi_s3c24xx_gpio.o
 obj-$(CONFIG_SPI_S3C24XX)		+= spi_s3c24xx.o
diff --git a/drivers/spi/mpc52xx_spi.c b/drivers/spi/mpc52xx_spi.c
new file mode 100644
index 0000000..453690f
--- /dev/null
+++ b/drivers/spi/mpc52xx_spi.c
@@ -0,0 +1,595 @@
+/*
+ * MPC52xx SPI master driver.
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ *
+ * This is the driver for the MPC5200's dedicated SPI device (not for a
+ * PSC in SPI mode)
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/mpc52xx_spi.h>
+#include <linux/of_spi.h>
+#include <linux/io.h>
+#include <asm/time.h>
+#include <asm/mpc52xx.h>
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("MPC52xx SPI (non-PSC) Driver");
+MODULE_LICENSE("GPL");
+
+/* Register offsets */
+#define SPI_CTRL1	0x00
+#define SPI_CTRL1_SPIE		(1 << 7)
+#define SPI_CTRL1_SPE		(1 << 6)
+#define SPI_CTRL1_MSTR		(1 << 4)
+#define SPI_CTRL1_CPOL		(1 << 3)
+#define SPI_CTRL1_CPHA		(1 << 2)
+#define SPI_CTRL1_SSOE		(1 << 1)
+#define SPI_CTRL1_LSBFE		(1 << 0)
+
+#define SPI_CTRL2	0x01
+#define SPI_BRR		0x04
+
+#define SPI_STATUS	0x05
+#define SPI_STATUS_SPIF		(1 << 7)
+#define SPI_STATUS_WCOL		(1 << 6)
+#define SPI_STATUS_MODF		(1 << 4)
+
+#define SPI_DATA	0x09
+#define SPI_PORTDATA	0x0d
+#define SPI_DATADIR	0x10
+
+/* FSM state return values */
+#define FSM_STOP	0
+#define FSM_POLL	1
+#define FSM_CONTINUE	2
+
+/* Driver internal data */
+struct mpc52xx_spi {
+	struct spi_master *master;
+	u32 sysclk;
+	void __iomem *regs;
+	int irq0;	/* MODF irq */
+	int irq1;	/* SPIF irq */
+	int ipb_freq;
+
+	/* Statistics */
+	int msg_count;
+	int wcol_count;
+	int wcol_ticks;
+	u32 wcol_tx_timestamp;
+	int modf_count;
+	int byte_count;
+
+	/* Hooks for platform modification of behaviour */
+	void (*premessage)(struct spi_message *m, void *context);
+	void *premessage_context;
+
+	struct list_head queue;		/* queue of pending messages */
+	spinlock_t lock;
+	struct work_struct work;
+
+
+	/* Details of current transfer (length, and buffer pointers) */
+	struct spi_message *message;	/* current message */
+	struct spi_transfer *transfer;	/* current transfer */
+	int (*state)(int irq, struct mpc52xx_spi *ms, u8 status, u8 data);
+	int len;
+	int timestamp;
+	u8 *rx_buf;
+	const u8 *tx_buf;
+	int cs_change;
+};
+
+/*
+ * CS control function
+ */
+static void mpc52xx_spi_chipsel(struct mpc52xx_spi *ms, int value)
+{
+	if (value)
+		writeb(0, ms->regs + SPI_PORTDATA); /* Assert SS pin */
+	else
+		writeb(0x08, ms->regs + SPI_PORTDATA); /* Deassert SS pin */
+}
+
+/*
+ * Start a new transfer.  This is called both by the idle state
+ * for the first transfer in a message, and by the wait state when the
+ * previous transfer in a message is complete.
+ */
+static void mpc52xx_spi_start_transfer(struct mpc52xx_spi *ms)
+{
+	ms->rx_buf = ms->transfer->rx_buf;
+	ms->tx_buf = ms->transfer->tx_buf;
+	ms->len = ms->transfer->len;
+
+	/* Activate the chip select */
+	if (ms->cs_change)
+		mpc52xx_spi_chipsel(ms, 1);
+	ms->cs_change = ms->transfer->cs_change;
+
+	/* Write out the first byte */
+	ms->wcol_tx_timestamp = get_tbl();
+	if (ms->tx_buf)
+		writeb(*ms->tx_buf++, ms->regs + SPI_DATA);
+	else
+		writeb(0, ms->regs + SPI_DATA);
+}
+
+/* Forward declaration of state handlers */
+static int mpc52xx_spi_fsmstate_transfer(int irq, struct mpc52xx_spi *ms,
+					 u8 status, u8 data);
+static int mpc52xx_spi_fsmstate_wait(int irq, struct mpc52xx_spi *ms,
+				     u8 status, u8 data);
+
+/*
+ * IDLE state
+ *
+ * No transfers are in progress; if another transfer is pending then retrieve
+ * it and kick it off.  Otherwise, stop processing the state machine
+ */
+static int
+mpc52xx_spi_fsmstate_idle(int irq, struct mpc52xx_spi *ms, u8 status, u8 data)
+{
+	struct spi_message *m;
+	struct spi_device *spi;
+	int spr, sppr;
+	u8 ctrl1;
+
+	if (status && (irq != NO_IRQ))
+		dev_err(&ms->master->dev, "spurious irq, status=0x%.2x\n",
+			status);
+
+	/* Check if there is another transfer waiting */
+	if (list_empty(&ms->queue))
+		return FSM_STOP;
+
+	/* Get the next message */
+	spin_lock(&ms->lock);
+
+	/* Call the pre-message hook with a pointer to the next
+	 * message.  The pre-message hook may enqueue a new message for
+	 * changing the chip select value to the head of the queue */
+	m = list_first_entry(&ms->queue, struct spi_message, queue);
+	if (ms->premessage)
+		ms->premessage(m, ms->premessage_context);
+
+	/* reget the head of the queue (the premessage hook may have enqueued
+	 * something before it.) and drop the spinlock */
+	ms->message = list_first_entry(&ms->queue, struct spi_message, queue);
+	list_del_init(&ms->message->queue);
+	spin_unlock(&ms->lock);
+
+	/* Setup the controller parameters */
+	ctrl1 = SPI_CTRL1_SPIE | SPI_CTRL1_SPE | SPI_CTRL1_MSTR;
+	spi = ms->message->spi;
+	if (spi->mode & SPI_CPHA)
+		ctrl1 |= SPI_CTRL1_CPHA;
+	if (spi->mode & SPI_CPOL)
+		ctrl1 |= SPI_CTRL1_CPOL;
+	if (spi->mode & SPI_LSB_FIRST)
+		ctrl1 |= SPI_CTRL1_LSBFE;
+	writeb(ctrl1, ms->regs + SPI_CTRL1);
+
+	/* Setup the controller speed */
+	/* minimum divider is '2'.  Also, add '1' to force rounding up. */
+	sppr = ((ms->ipb_freq / ms->message->spi->max_speed_hz) + 1) >> 1;
+	spr = 0;
+	if (sppr < 1)
+		sppr = 1;
+	while (((sppr - 1) & ~0x7) != 0) {
+		sppr = (sppr + 1) >> 1; /* add '1' to force rounding up */
+		spr++;
+	}
+	sppr--;		/* sppr quantity in register is offset by 1 */
+	if (spr > 7) {
+		/* Don't overrun limits of SPI baudrate register */
+		spr = 7;
+		sppr = 7;
+	}
+	writeb(sppr << 4 | spr, ms->regs + SPI_BRR); /* Set speed */
+
+	ms->cs_change = 1;
+	ms->transfer = container_of(ms->message->transfers.next,
+				    struct spi_transfer, transfer_list);
+
+	mpc52xx_spi_start_transfer(ms);
+	ms->state = mpc52xx_spi_fsmstate_transfer;
+
+#if defined(VERBOSE_DEBUG)
+	dev_info(&ms->master->dev, "msg:%p, max_speed:%i, brr:%.2x\n",
+		 ms->message, ms->message->spi->max_speed_hz,
+		 readb(ms->regs + SPI_BRR));
+#endif
+
+	return FSM_CONTINUE;
+}
+
+/*
+ * TRANSFER state
+ *
+ * In the middle of a transfer.  If the SPI core has completed processing
+ * a byte, then read out the received data and write out the next byte
+ * (unless this transfer is finished; in which case go on to the wait
+ * state)
+ */
+static int mpc52xx_spi_fsmstate_transfer(int irq, struct mpc52xx_spi *ms,
+					 u8 status, u8 data)
+{
+	if (!status)
+		return ms->irq0 == NO_IRQ ? FSM_POLL : FSM_STOP;
+
+	if (status & SPI_STATUS_WCOL) {
+		/* The SPI device is stoopid.  At slower speeds, it may raise
+		 * the SPIF flag before the state machine is actually finished.
+		 * which causes a collision (internal to the state machine
+		 * only).  The manual recommends inserting a delay between
+		 * receving the interrupt and sending the next byte, but
+		 * it can also be worked around simply by retrying the
+		 * transfer which is what we do here. */
+		ms->wcol_count++;
+		ms->wcol_ticks += get_tbl() - ms->wcol_tx_timestamp;
+		ms->wcol_tx_timestamp = get_tbl();
+		data = 0;
+		if (ms->tx_buf)
+			data = *(ms->tx_buf-1);
+		writeb(data, ms->regs + SPI_DATA); /* try again */
+		return FSM_CONTINUE;
+	} else if (status & SPI_STATUS_MODF) {
+		ms->modf_count++;
+		dev_err(&ms->master->dev, "mod fault\n");
+		mpc52xx_spi_chipsel(ms, 0);
+		ms->message->status = -EIO;
+		if (ms->message->complete)
+			ms->message->complete(ms->message->context);
+		ms->state = mpc52xx_spi_fsmstate_idle;
+		return FSM_CONTINUE;
+	}
+
+	/* Read data out of the spi device */
+	ms->byte_count++;
+	if (ms->rx_buf)
+		*ms->rx_buf++ = data;
+
+	/* Is the transfer complete? */
+	ms->len--;
+	if (ms->len == 0) {
+		ms->timestamp = get_tbl();
+		ms->timestamp += ms->transfer->delay_usecs * tb_ticks_per_usec;
+		ms->state = mpc52xx_spi_fsmstate_wait;
+		return FSM_CONTINUE;
+	}
+
+	/* Write out the next byte */
+	ms->wcol_tx_timestamp = get_tbl();
+	if (ms->tx_buf)
+		writeb(*ms->tx_buf++, ms->regs + SPI_DATA);
+	else
+		writeb(0, ms->regs + SPI_DATA);
+
+	return FSM_CONTINUE;
+}
+
+/*
+ * WAIT state
+ *
+ * A transfer has completed; need to wait for the delay period to complete
+ * before starting the next transfer
+ */
+static int
+mpc52xx_spi_fsmstate_wait(int irq, struct mpc52xx_spi *ms, u8 status, u8 data)
+{
+	if (status && irq != NO_IRQ)
+		dev_err(&ms->master->dev, "spurious irq, status=0x%.2x\n",
+			status);
+
+	if (((int)get_tbl()) - ms->timestamp < 0)
+		return FSM_POLL;
+
+	ms->message->actual_length += ms->transfer->len;
+
+	/* Check if there is another transfer in this message.  If there
+	 * aren't then deactivate CS, notify sender, and drop back to idle
+	 * to start the next message. */
+	if (ms->transfer->transfer_list.next == &ms->message->transfers) {
+		ms->msg_count++;
+		mpc52xx_spi_chipsel(ms, 0);
+		ms->message->status = 0;
+		if (ms->message->complete)
+			ms->message->complete(ms->message->context);
+		ms->state = mpc52xx_spi_fsmstate_idle;
+		return FSM_CONTINUE;
+	}
+
+	/* There is another transfer; kick it off */
+
+	if (ms->cs_change)
+		mpc52xx_spi_chipsel(ms, 0);
+
+	ms->transfer = container_of(ms->transfer->transfer_list.next,
+				    struct spi_transfer, transfer_list);
+	mpc52xx_spi_start_transfer(ms);
+	ms->state = mpc52xx_spi_fsmstate_transfer;
+	return FSM_CONTINUE;
+}
+
+/*
+ * IRQ handler
+ */
+static irqreturn_t mpc52xx_spi_irq(int irq, void *_ms)
+{
+	struct mpc52xx_spi *ms = _ms;
+	int rc = FSM_CONTINUE;
+	u8 status, data;
+
+	while (rc == FSM_CONTINUE) {
+		/* Interrupt cleared by read of STATUS followed by
+		 * read of DATA registers*/
+		status = readb(ms->regs + SPI_STATUS);
+		data = readb(ms->regs + SPI_DATA); /* clear status */
+		rc = ms->state(irq, ms, status, data);
+	}
+
+	if (rc == FSM_POLL)
+		schedule_work(&ms->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Workqueue method of running the state machine
+ */
+static void mpc52xx_spi_wq(struct work_struct *work)
+{
+	struct mpc52xx_spi *ms = container_of(work, struct mpc52xx_spi, work);
+	mpc52xx_spi_irq(NO_IRQ, ms);
+}
+
+/*
+ * spi_master callbacks
+ */
+
+static int mpc52xx_spi_setup(struct spi_device *spi)
+{
+	return 0;
+}
+
+static int mpc52xx_spi_transfer(struct spi_device *spi, struct spi_message *m)
+{
+	struct mpc52xx_spi *ms = spi_master_get_devdata(spi->master);
+	unsigned long flags;
+
+	m->actual_length = 0;
+	m->status = -EINPROGRESS;
+
+	spin_lock_irqsave(&ms->lock, flags);
+	list_add_tail(&m->queue, &ms->queue);
+	spin_unlock_irqrestore(&ms->lock, flags);
+	schedule_work(&ms->work);
+
+	return 0;
+}
+
+/*
+ * Hook to modify premessage hook
+ */
+void mpc52xx_spi_set_premessage_hook(struct spi_master *master,
+				     void (*hook)(struct spi_message *m,
+						  void *context),
+				     void *hook_context)
+{
+	struct mpc52xx_spi *ms = spi_master_get_devdata(master);
+	ms->premessage = hook;
+	ms->premessage_context = hook_context;
+}
+EXPORT_SYMBOL(mpc52xx_spi_set_premessage_hook);
+
+/*
+ * SysFS files
+ */
+static int
+*mpc52xx_spi_sysfs_get_counter(struct mpc52xx_spi *ms, const char *name)
+{
+	if (strcmp(name, "msg_count") == 0)
+		return &ms->msg_count;
+	if (strcmp(name, "byte_count") == 0)
+		return &ms->byte_count;
+	if (strcmp(name, "wcol_count") == 0)
+		return &ms->wcol_count;
+	if (strcmp(name, "wcol_ticks") == 0)
+		return &ms->wcol_ticks;
+	if (strcmp(name, "modf_count") == 0)
+		return &ms->modf_count;
+	return NULL;
+}
+
+static ssize_t mpc52xx_spi_show_count(struct device *dev,
+				      struct device_attribute *attr,
+				      char *buf)
+{
+	struct spi_master *master = container_of(dev, struct spi_master, dev);
+	struct mpc52xx_spi *ms = spi_master_get_devdata(master);
+	int *counter;
+
+	counter = mpc52xx_spi_sysfs_get_counter(ms, attr->attr.name);
+	if (!counter)
+		return sprintf(buf, "error\n");
+	return sprintf(buf, "%d\n", *counter);
+}
+
+static ssize_t mpc52xx_spi_set_count(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct spi_master *master = container_of(dev, struct spi_master, dev);
+	struct mpc52xx_spi *ms = spi_master_get_devdata(master);
+	int *counter;
+	int value = simple_strtoul(buf, NULL, 0);
+
+	counter = mpc52xx_spi_sysfs_get_counter(ms, attr->attr.name);
+	if (counter)
+		*counter = value;
+	return count;
+}
+
+DEVICE_ATTR(msg_count, 0644, mpc52xx_spi_show_count, mpc52xx_spi_set_count);
+DEVICE_ATTR(byte_count, 0644, mpc52xx_spi_show_count, mpc52xx_spi_set_count);
+DEVICE_ATTR(wcol_count, 0644, mpc52xx_spi_show_count, mpc52xx_spi_set_count);
+DEVICE_ATTR(wcol_ticks, 0644, mpc52xx_spi_show_count, mpc52xx_spi_set_count);
+DEVICE_ATTR(modf_count, 0644, mpc52xx_spi_show_count, mpc52xx_spi_set_count);
+
+/*
+ * OF Platform Bus Binding
+ */
+static int __devinit mpc52xx_spi_of_probe(struct of_device *op,
+					  const struct of_device_id *match)
+{
+	struct spi_master *master;
+	struct mpc52xx_spi *ms;
+	void __iomem *regs;
+	const u32 *prop;
+	int rc, len;
+
+	/* MMIO registers */
+	dev_dbg(&op->dev, "probing mpc5200 SPI device\n");
+	regs = of_iomap(op->node, 0);
+	if (!regs)
+		return -ENODEV;
+
+	/* initialize the device */
+	writeb(SPI_CTRL1_SPIE | SPI_CTRL1_SPE | SPI_CTRL1_MSTR, regs+SPI_CTRL1);
+	writeb(0x0, regs + SPI_CTRL2);
+	writeb(0xe, regs + SPI_DATADIR);	/* Set output pins */
+	writeb(0x8, regs + SPI_PORTDATA);	/* Deassert /SS signal */
+
+	/* Clear the status register and re-read it to check for a MODF
+	 * failure.  This driver cannot currently handle multiple masters
+	 * on the SPI bus.  This fault will also occur if the SPI signals
+	 * are not connected to any pins (port_config setting) */
+	readb(regs + SPI_STATUS);
+	readb(regs + SPI_DATA);
+	if (readb(regs + SPI_STATUS) & SPI_STATUS_MODF) {
+		dev_err(&op->dev, "mode fault; is port_config correct?\n");
+		return -EIO;
+	}
+
+	dev_dbg(&op->dev, "allocating spi_master struct\n");
+	master = spi_alloc_master(&op->dev, sizeof *ms);
+	if (!master)
+		return -ENOMEM;
+	master->bus_num = -1;
+	master->num_chipselect = 1;
+	prop = of_get_property(op->node, "num-slaves", &len);
+	if (prop && len >= sizeof(*prop))
+		master->num_chipselect = *prop;
+
+	master->setup = mpc52xx_spi_setup;
+	master->transfer = mpc52xx_spi_transfer;
+	dev_set_drvdata(&op->dev, master);
+
+	ms = spi_master_get_devdata(master);
+	ms->master = master;
+	ms->regs = regs;
+	ms->irq0 = irq_of_parse_and_map(op->node, 0);
+	ms->irq1 = irq_of_parse_and_map(op->node, 1);
+	ms->state = mpc52xx_spi_fsmstate_idle;
+	ms->ipb_freq = mpc52xx_find_ipb_freq(op->node);
+	spin_lock_init(&ms->lock);
+	INIT_LIST_HEAD(&ms->queue);
+	INIT_WORK(&ms->work, mpc52xx_spi_wq);
+
+	dev_dbg(&op->dev, "registering spi_master struct\n");
+	rc = spi_register_master(master);
+	if (rc < 0)
+		goto err_register;
+
+	/* Decide if interrupts can be used */
+	if ((ms->irq0 != NO_IRQ) && (ms->irq1 != NO_IRQ)) {
+		rc = request_irq(ms->irq0, mpc52xx_spi_irq, IRQF_SAMPLE_RANDOM,
+				  "mpc5200-spi-modf", ms);
+		rc |= request_irq(ms->irq1, mpc52xx_spi_irq, IRQF_SAMPLE_RANDOM,
+				  "mpc5200-spi-spiF", ms);
+		if (rc) {
+			free_irq(ms->irq0, ms);
+			free_irq(ms->irq1, ms);
+			ms->irq0 = ms->irq1 = NO_IRQ;
+			dev_info(&op->dev, "using polled mode\n");
+		}
+	} else {
+		/* operate in polled mode */
+		ms->irq0 = ms->irq1 = NO_IRQ;
+		dev_info(&op->dev, "using polled mode\n");
+	}
+
+	/* Create SysFS files */
+	rc = device_create_file(&ms->master->dev, &dev_attr_msg_count);
+	rc |= device_create_file(&ms->master->dev, &dev_attr_byte_count);
+	rc |= device_create_file(&ms->master->dev, &dev_attr_wcol_count);
+	rc |= device_create_file(&ms->master->dev, &dev_attr_wcol_ticks);
+	rc |= device_create_file(&ms->master->dev, &dev_attr_modf_count);
+	if (rc)
+		dev_info(&ms->master->dev, "error creating sysfs files\n");
+
+	dev_info(&ms->master->dev, "registered MPC5200 SPI bus\n");
+
+	of_register_spi_devices(master, op->node);
+
+	return rc;
+
+ err_register:
+	dev_err(&ms->master->dev, "initialization failed\n");
+	spi_master_put(master);
+	return rc;
+}
+
+static void __devexit mpc52xx_spi_of_remove(struct of_device *op)
+{
+	struct spi_master *master = dev_get_drvdata(&op->dev);
+	struct mpc52xx_spi *ms = spi_master_get_devdata(master);
+
+	device_remove_file(&ms->master->dev, &dev_attr_msg_count);
+	device_remove_file(&ms->master->dev, &dev_attr_byte_count);
+	device_remove_file(&ms->master->dev, &dev_attr_wcol_count);
+	device_remove_file(&ms->master->dev, &dev_attr_wcol_ticks);
+	device_remove_file(&ms->master->dev, &dev_attr_modf_count);
+
+	free_irq(ms->irq0, ms);
+	free_irq(ms->irq1, ms);
+
+	spi_unregister_master(master);
+	spi_master_put(master);
+	iounmap(ms->regs);
+}
+
+static struct of_device_id mpc52xx_spi_of_match[] __devinitdata = {
+	{ .compatible = "fsl,mpc5200-spi", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, mpc52xx_psc_spi_of_match);
+
+static struct of_platform_driver mpc52xx_spi_of_driver = {
+	.owner = THIS_MODULE,
+	.name = "mpc52xx-spi",
+	.match_table = mpc52xx_spi_of_match,
+	.probe = mpc52xx_spi_of_probe,
+	.remove = __exit_p(mpc52xx_spi_of_remove),
+};
+
+static int __init mpc52xx_spi_init(void)
+{
+	return of_register_platform_driver(&mpc52xx_spi_of_driver);
+}
+module_init(mpc52xx_spi_init);
+
+static void __exit mpc52xx_spi_exit(void)
+{
+	of_unregister_platform_driver(&mpc52xx_spi_of_driver);
+}
+module_exit(mpc52xx_spi_exit);
+
diff --git a/include/linux/spi/mpc52xx_spi.h b/include/linux/spi/mpc52xx_spi.h
new file mode 100644
index 0000000..d1004cf
--- /dev/null
+++ b/include/linux/spi/mpc52xx_spi.h
@@ -0,0 +1,10 @@
+
+#ifndef INCLUDE_MPC5200_SPI_H
+#define INCLUDE_MPC5200_SPI_H
+
+extern void mpc52xx_spi_set_premessage_hook(struct spi_master *master,
+					    void (*hook)(struct spi_message *m,
+							 void *context),
+					    void *hook_context);
+
+#endif

^ permalink raw reply related

* Re: linux boot sequence
From: Wolfgang Denk @ 2008-07-12 10:59 UTC (permalink / raw)
  To: Rami WEHBI; +Cc: linuxppc-embedded
In-Reply-To: <D3C541E264C021479BFB60B3B790C0FB497014@etoilenoire.STARWARS.local>

In message <D3C541E264C021479BFB60B3B790C0FB497014@etoilenoire.STARWARS.local> you wrote:
> 
>     I am using the ppc405 and I would like to know, how does linux on
> this architect detect the available memory size in details !!!
>         is it a parameter passed to linux at startup by the boot loader
> ??

Yes.


Best regards,

Wolfgang Denk

-- 
DENX Software Engineering GmbH,     MD: Wolfgang Denk & Detlev Zundel
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-10 Fax: (+49)-8142-66989-80 Email: wd@denx.de
The more complex the mind, the greater the need for the simplicity of
play.
	-- Kirk, "Shore Leave", stardate 3025.8

^ permalink raw reply

* [RFC] reorganize cputypes for PPC64
From: Marvin @ 2008-07-12 13:16 UTC (permalink / raw)
  To: linuxppc-dev

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

Hi,

attached patch introduces a "processor type" menu similar to ppc32. It 
_should_ not change anything upto now. 

The aim is to allow future fine graded cpu optimizations via mcpu/mtune 
compiler flags and also to clean up the arch Makefile/Kconfig.cputypes (I 
know this is a minefield).

Greetings

Marvin


[-- Attachment #2: ppc64_cputypes.patch --]
[-- Type: text/x-diff, Size: 2508 bytes --]

diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype
index f7efaa9..eebde6c 100644
--- a/arch/powerpc/platforms/Kconfig.cputype
+++ b/arch/powerpc/platforms/Kconfig.cputype
@@ -54,35 +54,65 @@ config E200
 
 endchoice
 
-config POWER4_ONLY
-	bool "Optimize for POWER4"
+choice
+	prompt "Processor Type"
 	depends on PPC64
+	default TUNE_POWER4
+	help
+	  There are serveral families of 64 bit PowerPC chips supported.
+	  These include the Power3 to Power6 series, 970, and Cell BE based
+	  CPUs made be IBM.
+
+	  If unsure, select Power4.
+
+config TUNE_POWER3
+	bool "Power3"
+
+config TUNE_POWER4
+	bool "Power4"
+
+config TUNE_970
+	bool "970/G5"
+
+config TUNE_POWER5
+	bool "Power5"
+
+config TUNE_POWER6
+	bool "Power6"
+
+config TUNE_CELL
+	bool "Cell Broadband Engine"
+	help
+	  Cause the compiler to optimize for the PPE of the Cell Broadband
+	  Engine. This will make the code run considerably faster on Cell
+	  but somewhat slower on other machines. If the resulting kernel is
+	  built to run only on Cell BE machines, select also OPT_EXCLUSIVE.
+
+endchoice
+
+config OPT_EXCLUSIVE
+	bool "Optimize to run exclusive on selected CPU"
 	default n
-	---help---
-	  Cause the compiler to optimize for POWER4/POWER5/PPC970 processors.
-	  The resulting binary will not work on POWER3 or RS64 processors
-	  when compiled with binutils 2.15 or later.
+	help
+	  Cause the compiler to optimize to run exclusive on the selected
+	  CPU. The resulting binary will probably not work on other CPUs.
+	  
+	  If the compiler/binutils combination does not support the exclusive
+	  optimization, it will try to tune only or fail.
+	  
+	  If you are unsure, select no.
 
 config POWER3
-	bool
 	depends on PPC64
-	default y if !POWER4_ONLY
+	def_bool y if !POWER4_ONLY
 
 config POWER4
 	depends on PPC64
 	def_bool y
 
-config TUNE_CELL
-	bool "Optimize for Cell Broadband Engine"
+config POWER4_ONLY
 	depends on PPC64
-	help
-	  Cause the compiler to optimize for the PPE of the Cell Broadband
-	  Engine. This will make the code run considerably faster on Cell
-	  but somewhat slower on other machines. This option only changes
-	  the scheduling of instructions, not the selection of instructions
-	  itself, so the resulting kernel will keep running on all other
-	  machines. When building a kernel that is supposed to run only
-	  on Cell, you should also select the POWER4_ONLY option.
+	def_bool y if TUNE_POWER4 && OPT_EXCLUSIVE
 
 config 6xx
 	bool

^ permalink raw reply related

* LIME on MPC5200B ioremap problem
From: Alex_SYS @ 2008-07-12 13:48 UTC (permalink / raw)
  To: linuxppc-embedded


Hello,
I have an MPC5200B with Linux and an Grafic Chip Lime on the LPB !
The Lime is configured under U-Boot und works very fine in 800x480x16 bit
colors.
In Linux I adapted the Virtual Framebuffer driver! There are only three
things necessary:
   1: check_mem (0xE0000000, 750kB)      ->thats OK
   2: io__mem_request(0xE0000000, 750kB)    ->thats OK , too
   3_ioremap(0xE0000000, 750kB)    ->thats OK , too
   3: write Io request to framebuffers screen_base
The Kernel with Grafics output works fine with 800x350 resolution or 580x480
and so on.
But when I use 800x480, the screen output is OK, But the kernel crashes with
custom trace stop.
I have found out that it must be a ioremap failure, because it has to do
with the space i request!
It works fine if the mem_remap is under 546kB! Exactly it is not the space,
but the upper adress!
Because ioremap(0xE00000860, 500kB) crashes also the Kernel!
It seems like ioremap can`t give me the adresses upper
(0xE00000000+546kB)=0xE00088B80 ?
What can be the problem?
Kernel 2.6.23-rt5 with 16MB Flash and 64MB RAM

Thanks very much!


-- 
View this message in context: http://www.nabble.com/LIME-on-MPC5200B-ioremap-problem-tp18419614p18419614.html
Sent from the linuxppc-embedded mailing list archive at Nabble.com.

^ permalink raw reply

* Re: [alsa-devel] [PATCH 3/3] ALSA SoC: Add Texas Instruments TLV320AIC26 codec driver
From: Mark Brown @ 2008-07-12 17:36 UTC (permalink / raw)
  To: Grant Likely; +Cc: Liam Girdwood, alsa-devel, timur, linuxppc-dev
In-Reply-To: <20080712060018.GB23213@secretlab.ca>

On Sat, Jul 12, 2008 at 12:00:18AM -0600, Grant Likely wrote:
> On Wed, Jul 02, 2008 at 11:48:33AM +0100, Liam Girdwood wrote:

> > PLL/FLL/clock config is usually done in a separate function
> > (codec_set_pll(), callable by machine driver) so that we can change
> > clocks depending on the available machine clocks and srate. 

...

> > Codec domain (i.e Bias power) PM stuff should be done in
> > codec_dapm_event(). This allows us to power the codec on when we do
> > things like sidetone (with no active playback or capture stream).

> Ugh, I'm going to have to leave these two for now.  I don't understand
> enough about the ASoC structure yet to understand what it should look
> like.  I'll probably need help, but I don't think I can get it sorted
> out before the merge window.

> Do these two comments need to be addressed before the driver is merged?

It wouldn't be the only driver not to implement PLL configuration in
this way so that's probably be OK for an initial merge.  What's expected
for PLL configuration is that you implement the DAI set_pll() operation
in the codec driver, allowing machine drivers to configure the PLL when
they wish.

The power configuration should be fixed, though.  Normally drivers
either fully implement DAPM (including set_bias_level()) or power
everything in the codec up when the driver is loaded.  At the minute
what the driver is doing appears to be powering the codec up in both
_hw_params() and _probe() but never powering anything down - if that is
the case then probably all you need to do is remove the extra power up
from hw_params(), giving you the simple option.

> Hmmm, I haven't been able to find this; either in the code or on a live
> running system.  Where is the common reg dump implemented.

/sys/bus/platform/devices/soc-audio/codec_reg

^ permalink raw reply

* Re: [RFC] reorganize cputypes for PPC64
From: Arnd Bergmann @ 2008-07-12 18:00 UTC (permalink / raw)
  To: linuxppc-dev
In-Reply-To: <200807121516.49576.marvin24@gmx.de>

On Saturday 12 July 2008, Marvin wrote:
> attached patch introduces a "processor type" menu similar to ppc32. It 
> _should_ not change anything upto now. 
> 
> The aim is to allow future fine graded cpu optimizations via mcpu/mtune 
> compiler flags and also to clean up the arch Makefile/Kconfig.cputypes (I 
> know this is a minefield).

I tried something similar last year, but failed miserably, because the
complexity cannot really be contained. I still think it's a good idea,
but I'm not sure whether your patch will help at all.

> diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype
> index f7efaa9..eebde6c 100644
> --- a/arch/powerpc/platforms/Kconfig.cputype
> +++ b/arch/powerpc/platforms/Kconfig.cputype
> @@ -54,35 +54,65 @@ config E200
>  
>  endchoice
>  
> -config POWER4_ONLY
> -       bool "Optimize for POWER4"
> +choice
> +       prompt "Processor Type"
>         depends on PPC64
> +       default TUNE_POWER4
> +       help
> +         There are serveral families of 64 bit PowerPC chips supported.
> +         These include the Power3 to Power6 series, 970, and Cell BE based
> +         CPUs made be IBM.
> +
> +         If unsure, select Power4.
> +
> +config TUNE_POWER3
> +       bool "Power3"
> +
> +config TUNE_POWER4
> +       bool "Power4"
> +
> +config TUNE_970
> +       bool "970/G5"
> +
> +config TUNE_POWER5
> +       bool "Power5"
> +
> +config TUNE_POWER6
> +       bool "Power6"
> +
> +config TUNE_CELL
> +       bool "Cell Broadband Engine"
> +       help
> +         Cause the compiler to optimize for the PPE of the Cell Broadband
> +         Engine. This will make the code run considerably faster on Cell
> +         but somewhat slower on other machines. If the resulting kernel is
> +         built to run only on Cell BE machines, select also OPT_EXCLUSIVE.
> +
> +endchoice

What about the other CPUs? There is also RS64, Power5+, PA6T and Power7.

> +
> +config OPT_EXCLUSIVE
> +       bool "Optimize to run exclusive on selected CPU"
>         default n
> -       ---help---
> -         Cause the compiler to optimize for POWER4/POWER5/PPC970 processors.
> -         The resulting binary will not work on POWER3 or RS64 processors
> -         when compiled with binutils 2.15 or later.
> +       help
> +         Cause the compiler to optimize to run exclusive on the selected
> +         CPU. The resulting binary will probably not work on other CPUs.
> +         
> +         If the compiler/binutils combination does not support the exclusive
> +         optimization, it will try to tune only or fail.
> +         
> +         If you are unsure, select no.

Almost all CPUs are backwards compatible, so the option should not
be labelled 'exclusive'. In general, if you build for PowerX, it will
also run on Power(X+1). 970 is backwards compatible to Power4, Cell
and PA6T are backwards compatible to 970, Power6 is backwards compatible 
to both of these.

Also, there are good reasons to have the -mcpu option different from
-mtune. E.g. you may want to use Power4 compatible instructions but
tune for Power6 in a typical distro kernel.

>  
>  config POWER3
> -       bool
>         depends on PPC64
> -       default y if !POWER4_ONLY
> +       def_bool y if !POWER4_ONLY
>  
>  config POWER4
>         depends on PPC64
>         def_bool y
>  
> -config TUNE_CELL
> -       bool "Optimize for Cell Broadband Engine"
> +config POWER4_ONLY
>         depends on PPC64
> -       help
> -         Cause the compiler to optimize for the PPE of the Cell Broadband
> -         Engine. This will make the code run considerably faster on Cell
> -         but somewhat slower on other machines. This option only changes
> -         the scheduling of instructions, not the selection of instructions
> -         itself, so the resulting kernel will keep running on all other
> -         machines. When building a kernel that is supposed to run only
> -         on Cell, you should also select the POWER4_ONLY option.
> +       def_bool y if TUNE_POWER4 && OPT_EXCLUSIVE
>  
>  config 6xx
>         bool

Tuning for Cell gives a significant performance bonus on cell based machines
with a new compiler, and again, you would typically want to use the Power4
instruction set, but not restrict to Power3 or use all of the Cell instructions.

	Arnd <><

^ permalink raw reply

* Re: [alsa-devel] [PATCH v2 3/3] ALSA SoC: Add Texas Instruments TLV320AIC26 codec driver
From: Mark Brown @ 2008-07-12 18:10 UTC (permalink / raw)
  To: Grant Likely; +Cc: linuxppc-dev, alsa-devel, liam.girdwood
In-Reply-To: <20080712083939.15025.31192.stgit@trillian.secretlab.ca>

On Sat, Jul 12, 2008 at 02:39:39AM -0600, Grant Likely wrote:

> ASoC Codec driver for the TLV320AIC26 device.  This driver uses the ASoC
> v1 API, so I don't expect it to get merged as-is, but I want to get it
> out there for review.

I've not reviewed this revision of these drivers yet but I just wanted
to point out that there's absoluely no problem with merging v1 drivers -
so long as a driver uses the existing merged APIs there's no issue from
that point of view.

^ permalink raw reply

* Re: [alsa-devel] [PATCH 3/3] ALSA SoC: Add Texas Instruments TLV320AIC26 codec driver
From: Grant Likely @ 2008-07-12 18:13 UTC (permalink / raw)
  To: Grant Likely, Liam Girdwood, linuxppc-dev, alsa-devel, timur
In-Reply-To: <20080712173609.GA6523@sirena.org.uk>

On Sat, Jul 12, 2008 at 11:36 AM, Mark Brown
<broonie@opensource.wolfsonmicro.com> wrote:
> The power configuration should be fixed, though.  Normally drivers
> either fully implement DAPM (including set_bias_level()) or power
> everything in the codec up when the driver is loaded.  At the minute
> what the driver is doing appears to be powering the codec up in both
> _hw_params() and _probe() but never powering anything down - if that is
> the case then probably all you need to do is remove the extra power up
> from hw_params(), giving you the simple option.

Okay, cool.  I'll do this for the time being then.

Thanks,
g.

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

^ 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