2008-01-11 20:15:26 +03:00
|
|
|
/**
|
|
|
|
* Freescale MPC8610HPCD ALSA SoC Fabric driver
|
|
|
|
*
|
|
|
|
* Author: Timur Tabi <timur@freescale.com>
|
|
|
|
*
|
|
|
|
* Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
|
|
|
|
* under the terms of the GNU General Public License version 2. This
|
|
|
|
* program is licensed "as is" without any warranty of any kind, whether
|
|
|
|
* express or implied.
|
|
|
|
*/
|
|
|
|
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 11:04:11 +03:00
|
|
|
#include <linux/slab.h>
|
2008-01-11 20:15:26 +03:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/of_device.h>
|
|
|
|
#include <linux/of_platform.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
#include <asm/immap_86xx.h>
|
|
|
|
|
|
|
|
#include "../codecs/cs4270.h"
|
|
|
|
#include "fsl_dma.h"
|
|
|
|
#include "fsl_ssi.h"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_data: fabric-specific ASoC device data
|
|
|
|
*
|
|
|
|
* This structure contains data for a single sound platform device on an
|
|
|
|
* MPC8610 HPCD. Some of the data is taken from the device tree.
|
|
|
|
*/
|
|
|
|
struct mpc8610_hpcd_data {
|
|
|
|
struct snd_soc_device sound_devdata;
|
|
|
|
struct snd_soc_dai_link dai;
|
2008-11-18 23:50:34 +03:00
|
|
|
struct snd_soc_card machine;
|
2008-01-11 20:15:26 +03:00
|
|
|
unsigned int dai_format;
|
|
|
|
unsigned int codec_clk_direction;
|
|
|
|
unsigned int cpu_clk_direction;
|
|
|
|
unsigned int clk_frequency;
|
|
|
|
struct ccsr_guts __iomem *guts;
|
|
|
|
struct ccsr_ssi __iomem *ssi;
|
|
|
|
unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
|
|
|
|
unsigned int ssi_irq;
|
|
|
|
unsigned int dma_id; /* 0 = DMA1, 1 = DMA2, etc */
|
|
|
|
unsigned int dma_irq[2];
|
|
|
|
struct ccsr_dma_channel __iomem *dma[2];
|
|
|
|
unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_machine_probe: initalize the board
|
|
|
|
*
|
|
|
|
* This function is called when platform_device_add() is called. It is used
|
|
|
|
* to initialize the board-specific hardware.
|
|
|
|
*
|
|
|
|
* Here we program the DMACR and PMUXCR registers.
|
|
|
|
*/
|
|
|
|
static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
|
|
|
|
{
|
|
|
|
struct mpc8610_hpcd_data *machine_data =
|
|
|
|
sound_device->dev.platform_data;
|
|
|
|
|
|
|
|
/* Program the signal routing between the SSI and the DMA */
|
2008-06-13 23:02:31 +04:00
|
|
|
guts_set_dmacr(machine_data->guts, machine_data->dma_id,
|
2008-01-11 20:15:26 +03:00
|
|
|
machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI);
|
2008-06-13 23:02:31 +04:00
|
|
|
guts_set_dmacr(machine_data->guts, machine_data->dma_id,
|
2008-01-11 20:15:26 +03:00
|
|
|
machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI);
|
|
|
|
|
|
|
|
guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
|
|
|
|
machine_data->dma_channel_id[0], 0);
|
|
|
|
guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
|
|
|
|
machine_data->dma_channel_id[1], 0);
|
|
|
|
|
|
|
|
switch (machine_data->ssi_id) {
|
|
|
|
case 0:
|
|
|
|
clrsetbits_be32(&machine_data->guts->pmuxcr,
|
|
|
|
CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
clrsetbits_be32(&machine_data->guts->pmuxcr,
|
|
|
|
CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_startup: program the board with various hardware parameters
|
|
|
|
*
|
|
|
|
* This function takes board-specific information, like clock frequencies
|
|
|
|
* and serial data formats, and passes that information to the codec and
|
|
|
|
* transport drivers.
|
|
|
|
*/
|
|
|
|
static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
2008-07-07 19:08:00 +04:00
|
|
|
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
|
|
|
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
2008-01-11 20:15:26 +03:00
|
|
|
struct mpc8610_hpcd_data *machine_data =
|
|
|
|
rtd->socdev->dev->platform_data;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
/* Tell the CPU driver what the serial protocol is. */
|
2008-07-08 16:19:18 +04:00
|
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, machine_data->dai_format);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(substream->pcm->card->dev,
|
|
|
|
"could not set CPU driver audio format\n");
|
|
|
|
return ret;
|
2008-01-11 20:15:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Tell the codec driver what the serial protocol is. */
|
2008-07-08 16:19:18 +04:00
|
|
|
ret = snd_soc_dai_set_fmt(codec_dai, machine_data->dai_format);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(substream->pcm->card->dev,
|
|
|
|
"could not set codec driver audio format\n");
|
|
|
|
return ret;
|
2008-01-11 20:15:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Tell the CPU driver what the clock frequency is, and whether it's a
|
|
|
|
* slave or master.
|
|
|
|
*/
|
2008-07-08 16:19:18 +04:00
|
|
|
ret = snd_soc_dai_set_sysclk(cpu_dai, 0,
|
|
|
|
machine_data->clk_frequency,
|
|
|
|
machine_data->cpu_clk_direction);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(substream->pcm->card->dev,
|
|
|
|
"could not set CPU driver clock parameters\n");
|
|
|
|
return ret;
|
2008-01-11 20:15:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Tell the codec driver what the MCLK frequency is, and whether it's
|
|
|
|
* a slave or master.
|
|
|
|
*/
|
2008-07-08 16:19:18 +04:00
|
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
|
|
|
|
machine_data->clk_frequency,
|
|
|
|
machine_data->codec_clk_direction);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(substream->pcm->card->dev,
|
|
|
|
"could not set codec driver clock params\n");
|
|
|
|
return ret;
|
2008-01-11 20:15:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_machine_remove: Remove the sound device
|
|
|
|
*
|
|
|
|
* This function is called to remove the sound device for one SSI. We
|
|
|
|
* de-program the DMACR and PMUXCR register.
|
|
|
|
*/
|
|
|
|
int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
|
|
|
|
{
|
|
|
|
struct mpc8610_hpcd_data *machine_data =
|
|
|
|
sound_device->dev.platform_data;
|
|
|
|
|
|
|
|
/* Restore the signal routing */
|
|
|
|
|
2008-06-13 23:02:31 +04:00
|
|
|
guts_set_dmacr(machine_data->guts, machine_data->dma_id,
|
2008-01-11 20:15:26 +03:00
|
|
|
machine_data->dma_channel_id[0], 0);
|
2008-06-13 23:02:31 +04:00
|
|
|
guts_set_dmacr(machine_data->guts, machine_data->dma_id,
|
2008-01-11 20:15:26 +03:00
|
|
|
machine_data->dma_channel_id[1], 0);
|
|
|
|
|
|
|
|
switch (machine_data->ssi_id) {
|
|
|
|
case 0:
|
|
|
|
clrsetbits_be32(&machine_data->guts->pmuxcr,
|
|
|
|
CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
clrsetbits_be32(&machine_data->guts->pmuxcr,
|
2008-06-13 23:02:31 +04:00
|
|
|
CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
|
2008-01-11 20:15:26 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_ops: ASoC fabric driver operations
|
|
|
|
*/
|
|
|
|
static struct snd_soc_ops mpc8610_hpcd_ops = {
|
|
|
|
.startup = mpc8610_hpcd_startup,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_probe: OF probe function for the fabric driver
|
|
|
|
*
|
|
|
|
* This function gets called when an SSI node is found in the device tree.
|
|
|
|
*
|
|
|
|
* Although this is a fabric driver, the SSI node is the "master" node with
|
|
|
|
* respect to audio hardware connections. Therefore, we create a new ASoC
|
|
|
|
* device for each new SSI node that has a codec attached.
|
|
|
|
*
|
|
|
|
* FIXME: Currently, we only support one DMA controller, so if there are
|
|
|
|
* multiple SSI nodes with codecs, only the first will be supported.
|
|
|
|
*
|
|
|
|
* FIXME: Even if we did support multiple DMA controllers, we have no
|
|
|
|
* mechanism for assigning DMA controllers and channels to the individual
|
|
|
|
* SSI devices. We also probably aren't compatible with the generic Elo DMA
|
|
|
|
* device driver.
|
|
|
|
*/
|
|
|
|
static int mpc8610_hpcd_probe(struct of_device *ofdev,
|
|
|
|
const struct of_device_id *match)
|
|
|
|
{
|
2010-04-14 03:12:29 +04:00
|
|
|
struct device_node *np = ofdev->dev.of_node;
|
2008-01-11 20:15:26 +03:00
|
|
|
struct device_node *codec_np = NULL;
|
|
|
|
struct device_node *guts_np = NULL;
|
|
|
|
struct device_node *dma_np = NULL;
|
|
|
|
struct device_node *dma_channel_np = NULL;
|
|
|
|
const phandle *codec_ph;
|
|
|
|
const char *sprop;
|
|
|
|
const u32 *iprop;
|
|
|
|
struct resource res;
|
|
|
|
struct platform_device *sound_device = NULL;
|
|
|
|
struct mpc8610_hpcd_data *machine_data;
|
|
|
|
struct fsl_ssi_info ssi_info;
|
|
|
|
struct fsl_dma_info dma_info;
|
|
|
|
int ret = -ENODEV;
|
2008-08-07 00:01:01 +04:00
|
|
|
unsigned int playback_dma_channel;
|
|
|
|
unsigned int capture_dma_channel;
|
2008-01-11 20:15:26 +03:00
|
|
|
|
|
|
|
machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
|
|
|
|
if (!machine_data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
memset(&ssi_info, 0, sizeof(ssi_info));
|
|
|
|
memset(&dma_info, 0, sizeof(dma_info));
|
|
|
|
|
|
|
|
ssi_info.dev = &ofdev->dev;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We are only interested in SSIs with a codec phandle in them, so let's
|
|
|
|
* make sure this SSI has one.
|
|
|
|
*/
|
|
|
|
codec_ph = of_get_property(np, "codec-handle", NULL);
|
|
|
|
if (!codec_ph)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
codec_np = of_find_node_by_phandle(*codec_ph);
|
|
|
|
if (!codec_np)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
/* The MPC8610 HPCD only knows about the CS4270 codec, so reject
|
|
|
|
anything else. */
|
|
|
|
if (!of_device_is_compatible(codec_np, "cirrus,cs4270"))
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
/* Get the device ID */
|
|
|
|
iprop = of_get_property(np, "cell-index", NULL);
|
|
|
|
if (!iprop) {
|
|
|
|
dev_err(&ofdev->dev, "cell-index property not found\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
machine_data->ssi_id = *iprop;
|
|
|
|
ssi_info.id = *iprop;
|
|
|
|
|
|
|
|
/* Get the serial format and clock direction. */
|
|
|
|
sprop = of_get_property(np, "fsl,mode", NULL);
|
|
|
|
if (!sprop) {
|
|
|
|
dev_err(&ofdev->dev, "fsl,mode property not found\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strcasecmp(sprop, "i2s-slave") == 0) {
|
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_I2S;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In i2s-slave mode, the codec has its own clock source, so we
|
|
|
|
* need to get the frequency from the device tree and pass it to
|
|
|
|
* the codec driver.
|
|
|
|
*/
|
|
|
|
iprop = of_get_property(codec_np, "clock-frequency", NULL);
|
|
|
|
if (!iprop || !*iprop) {
|
|
|
|
dev_err(&ofdev->dev, "codec bus-frequency property "
|
|
|
|
"is missing or invalid\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
machine_data->clk_frequency = *iprop;
|
|
|
|
} else if (strcasecmp(sprop, "i2s-master") == 0) {
|
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_I2S;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
|
|
|
} else if (strcasecmp(sprop, "lj-slave") == 0) {
|
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
} else if (strcasecmp(sprop, "lj-master") == 0) {
|
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
2008-02-22 20:41:41 +03:00
|
|
|
} else if (strcasecmp(sprop, "rj-slave") == 0) {
|
2008-01-11 20:15:26 +03:00
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
} else if (strcasecmp(sprop, "rj-master") == 0) {
|
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
|
|
|
} else if (strcasecmp(sprop, "ac97-slave") == 0) {
|
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_AC97;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
} else if (strcasecmp(sprop, "ac97-master") == 0) {
|
|
|
|
machine_data->dai_format = SND_SOC_DAIFMT_AC97;
|
|
|
|
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
|
|
|
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
|
|
|
} else {
|
|
|
|
dev_err(&ofdev->dev,
|
|
|
|
"unrecognized fsl,mode property \"%s\"\n", sprop);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!machine_data->clk_frequency) {
|
|
|
|
dev_err(&ofdev->dev, "unknown clock frequency\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read the SSI information from the device tree */
|
|
|
|
ret = of_address_to_resource(np, 0, &res);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&ofdev->dev, "could not obtain SSI address\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
if (!res.start) {
|
|
|
|
dev_err(&ofdev->dev, "invalid SSI address\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
ssi_info.ssi_phys = res.start;
|
|
|
|
|
|
|
|
machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi));
|
|
|
|
if (!machine_data->ssi) {
|
|
|
|
dev_err(&ofdev->dev, "could not map SSI address %x\n",
|
|
|
|
ssi_info.ssi_phys);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
ssi_info.ssi = machine_data->ssi;
|
|
|
|
|
|
|
|
|
|
|
|
/* Get the IRQ of the SSI */
|
|
|
|
machine_data->ssi_irq = irq_of_parse_and_map(np, 0);
|
|
|
|
if (!machine_data->ssi_irq) {
|
|
|
|
dev_err(&ofdev->dev, "could not get SSI IRQ\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
ssi_info.irq = machine_data->ssi_irq;
|
|
|
|
|
2009-03-06 02:23:37 +03:00
|
|
|
/* Do we want to use asynchronous mode? */
|
|
|
|
ssi_info.asynchronous =
|
|
|
|
of_find_property(np, "fsl,ssi-asynchronous", NULL) ? 1 : 0;
|
|
|
|
if (ssi_info.asynchronous)
|
|
|
|
dev_info(&ofdev->dev, "using asynchronous mode\n");
|
2008-01-11 20:15:26 +03:00
|
|
|
|
|
|
|
/* Map the global utilities registers. */
|
|
|
|
guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
|
|
|
|
if (!guts_np) {
|
|
|
|
dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
machine_data->guts = of_iomap(guts_np, 0);
|
|
|
|
of_node_put(guts_np);
|
|
|
|
if (!machine_data->guts) {
|
|
|
|
dev_err(&ofdev->dev, "could not map GUTS\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2008-08-07 00:01:01 +04:00
|
|
|
/* Find the DMA channels to use. Both SSIs need to use the same DMA
|
|
|
|
* controller, so let's use DMA#1.
|
|
|
|
*/
|
2008-01-11 20:15:26 +03:00
|
|
|
for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") {
|
|
|
|
iprop = of_get_property(dma_np, "cell-index", NULL);
|
|
|
|
if (iprop && (*iprop == 0)) {
|
|
|
|
of_node_put(dma_np);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!dma_np) {
|
|
|
|
dev_err(&ofdev->dev, "could not find DMA node\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
machine_data->dma_id = *iprop;
|
|
|
|
|
2008-08-07 00:01:01 +04:00
|
|
|
/* SSI1 needs to use DMA Channels 0 and 1, and SSI2 needs to use DMA
|
|
|
|
* channels 2 and 3. This is just how the MPC8610 is wired
|
|
|
|
* internally.
|
|
|
|
*/
|
|
|
|
playback_dma_channel = (machine_data->ssi_id == 0) ? 0 : 2;
|
|
|
|
capture_dma_channel = (machine_data->ssi_id == 0) ? 1 : 3;
|
|
|
|
|
2008-01-11 20:15:26 +03:00
|
|
|
/*
|
2008-08-07 00:01:01 +04:00
|
|
|
* Find the DMA channels to use.
|
2008-01-11 20:15:26 +03:00
|
|
|
*/
|
|
|
|
while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) {
|
|
|
|
iprop = of_get_property(dma_channel_np, "cell-index", NULL);
|
2008-08-07 00:01:01 +04:00
|
|
|
if (iprop && (*iprop == playback_dma_channel)) {
|
2008-01-11 20:15:26 +03:00
|
|
|
/* dma_channel[0] and dma_irq[0] are for playback */
|
|
|
|
dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0);
|
|
|
|
dma_info.dma_irq[0] =
|
|
|
|
irq_of_parse_and_map(dma_channel_np, 0);
|
|
|
|
machine_data->dma_channel_id[0] = *iprop;
|
|
|
|
continue;
|
|
|
|
}
|
2008-08-07 00:01:01 +04:00
|
|
|
if (iprop && (*iprop == capture_dma_channel)) {
|
2008-01-11 20:15:26 +03:00
|
|
|
/* dma_channel[1] and dma_irq[1] are for capture */
|
|
|
|
dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0);
|
|
|
|
dma_info.dma_irq[1] =
|
|
|
|
irq_of_parse_and_map(dma_channel_np, 0);
|
|
|
|
machine_data->dma_channel_id[1] = *iprop;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] ||
|
|
|
|
!dma_info.dma_irq[0] || !dma_info.dma_irq[1]) {
|
|
|
|
dev_err(&ofdev->dev, "could not find DMA channels\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
dma_info.ssi_stx_phys = ssi_info.ssi_phys +
|
|
|
|
offsetof(struct ccsr_ssi, stx0);
|
|
|
|
dma_info.ssi_srx_phys = ssi_info.ssi_phys +
|
|
|
|
offsetof(struct ccsr_ssi, srx0);
|
|
|
|
|
|
|
|
/* We have the DMA information, so tell the DMA driver what it is */
|
|
|
|
if (!fsl_dma_configure(&dma_info)) {
|
|
|
|
dev_err(&ofdev->dev, "could not instantiate DMA device\n");
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize our DAI data structure. We should probably get this
|
|
|
|
* information from the device tree.
|
|
|
|
*/
|
|
|
|
machine_data->dai.name = "CS4270";
|
|
|
|
machine_data->dai.stream_name = "CS4270";
|
|
|
|
|
|
|
|
machine_data->dai.cpu_dai = fsl_ssi_create_dai(&ssi_info);
|
|
|
|
machine_data->dai.codec_dai = &cs4270_dai; /* The codec_dai we want */
|
|
|
|
machine_data->dai.ops = &mpc8610_hpcd_ops;
|
|
|
|
|
2009-01-20 02:14:24 +03:00
|
|
|
machine_data->machine.probe = mpc8610_hpcd_machine_probe;
|
|
|
|
machine_data->machine.remove = mpc8610_hpcd_machine_remove;
|
|
|
|
machine_data->machine.name = "MPC8610 HPCD";
|
|
|
|
machine_data->machine.num_links = 1;
|
|
|
|
machine_data->machine.dai_link = &machine_data->dai;
|
2008-01-11 20:15:26 +03:00
|
|
|
|
|
|
|
/* Allocate a new audio platform device structure */
|
|
|
|
sound_device = platform_device_alloc("soc-audio", -1);
|
|
|
|
if (!sound_device) {
|
|
|
|
dev_err(&ofdev->dev, "platform device allocation failed\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2009-01-20 02:14:24 +03:00
|
|
|
machine_data->sound_devdata.card = &machine_data->machine;
|
2008-01-11 20:15:26 +03:00
|
|
|
machine_data->sound_devdata.codec_dev = &soc_codec_device_cs4270;
|
2008-12-02 19:01:14 +03:00
|
|
|
machine_data->machine.platform = &fsl_soc_platform;
|
2008-01-11 20:15:26 +03:00
|
|
|
|
|
|
|
sound_device->dev.platform_data = machine_data;
|
|
|
|
|
|
|
|
|
|
|
|
/* Set the platform device and ASoC device to point to each other */
|
|
|
|
platform_set_drvdata(sound_device, &machine_data->sound_devdata);
|
|
|
|
|
|
|
|
machine_data->sound_devdata.dev = &sound_device->dev;
|
|
|
|
|
|
|
|
|
|
|
|
/* Tell ASoC to probe us. This will call mpc8610_hpcd_machine.probe(),
|
|
|
|
if it exists. */
|
|
|
|
ret = platform_device_add(sound_device);
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&ofdev->dev, "platform device add failed\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_set_drvdata(&ofdev->dev, sound_device);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
of_node_put(codec_np);
|
|
|
|
of_node_put(guts_np);
|
|
|
|
of_node_put(dma_np);
|
|
|
|
of_node_put(dma_channel_np);
|
|
|
|
|
|
|
|
if (sound_device)
|
|
|
|
platform_device_unregister(sound_device);
|
|
|
|
|
|
|
|
if (machine_data->dai.cpu_dai)
|
|
|
|
fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
|
|
|
|
|
|
|
|
if (ssi_info.ssi)
|
|
|
|
iounmap(ssi_info.ssi);
|
|
|
|
|
|
|
|
if (ssi_info.irq)
|
|
|
|
irq_dispose_mapping(ssi_info.irq);
|
|
|
|
|
|
|
|
if (dma_info.dma_channel[0])
|
|
|
|
iounmap(dma_info.dma_channel[0]);
|
|
|
|
|
|
|
|
if (dma_info.dma_channel[1])
|
|
|
|
iounmap(dma_info.dma_channel[1]);
|
|
|
|
|
|
|
|
if (dma_info.dma_irq[0])
|
|
|
|
irq_dispose_mapping(dma_info.dma_irq[0]);
|
|
|
|
|
|
|
|
if (dma_info.dma_irq[1])
|
|
|
|
irq_dispose_mapping(dma_info.dma_irq[1]);
|
|
|
|
|
|
|
|
if (machine_data->guts)
|
|
|
|
iounmap(machine_data->guts);
|
|
|
|
|
|
|
|
kfree(machine_data);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_remove: remove the OF device
|
|
|
|
*
|
|
|
|
* This function is called when the OF device is removed.
|
|
|
|
*/
|
|
|
|
static int mpc8610_hpcd_remove(struct of_device *ofdev)
|
|
|
|
{
|
|
|
|
struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev);
|
|
|
|
struct mpc8610_hpcd_data *machine_data =
|
|
|
|
sound_device->dev.platform_data;
|
|
|
|
|
|
|
|
platform_device_unregister(sound_device);
|
|
|
|
|
|
|
|
if (machine_data->dai.cpu_dai)
|
|
|
|
fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
|
|
|
|
|
|
|
|
if (machine_data->ssi)
|
|
|
|
iounmap(machine_data->ssi);
|
|
|
|
|
|
|
|
if (machine_data->dma[0])
|
|
|
|
iounmap(machine_data->dma[0]);
|
|
|
|
|
|
|
|
if (machine_data->dma[1])
|
|
|
|
iounmap(machine_data->dma[1]);
|
|
|
|
|
|
|
|
if (machine_data->dma_irq[0])
|
|
|
|
irq_dispose_mapping(machine_data->dma_irq[0]);
|
|
|
|
|
|
|
|
if (machine_data->dma_irq[1])
|
|
|
|
irq_dispose_mapping(machine_data->dma_irq[1]);
|
|
|
|
|
|
|
|
if (machine_data->guts)
|
|
|
|
iounmap(machine_data->guts);
|
|
|
|
|
|
|
|
kfree(machine_data);
|
|
|
|
sound_device->dev.platform_data = NULL;
|
|
|
|
|
|
|
|
dev_set_drvdata(&ofdev->dev, NULL);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct of_device_id mpc8610_hpcd_match[] = {
|
|
|
|
{
|
|
|
|
.compatible = "fsl,mpc8610-ssi",
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match);
|
|
|
|
|
|
|
|
static struct of_platform_driver mpc8610_hpcd_of_driver = {
|
2010-04-14 03:13:02 +04:00
|
|
|
.driver = {
|
|
|
|
.name = "mpc8610_hpcd",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.of_match_table = mpc8610_hpcd_match,
|
|
|
|
},
|
2008-01-11 20:15:26 +03:00
|
|
|
.probe = mpc8610_hpcd_probe,
|
|
|
|
.remove = mpc8610_hpcd_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_init: fabric driver initialization.
|
|
|
|
*
|
|
|
|
* This function is called when this module is loaded.
|
|
|
|
*/
|
|
|
|
static int __init mpc8610_hpcd_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n");
|
|
|
|
|
|
|
|
ret = of_register_platform_driver(&mpc8610_hpcd_of_driver);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
printk(KERN_ERR
|
|
|
|
"mpc8610-hpcd: failed to register platform driver\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mpc8610_hpcd_exit: fabric driver exit
|
|
|
|
*
|
|
|
|
* This function is called when this driver is unloaded.
|
|
|
|
*/
|
|
|
|
static void __exit mpc8610_hpcd_exit(void)
|
|
|
|
{
|
|
|
|
of_unregister_platform_driver(&mpc8610_hpcd_of_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(mpc8610_hpcd_init);
|
|
|
|
module_exit(mpc8610_hpcd_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
|
|
|
|
MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver");
|
|
|
|
MODULE_LICENSE("GPL");
|