Append ssp driver for pxa168 since PLL could be generated by a new way. Append aspenite also that could support pxa168-ssp.
Signed-off-by: Haojian Zhuang haojian.zhuang@marvell.com --- sound/soc/pxa/Kconfig | 21 ++ sound/soc/pxa/Makefile | 5 + sound/soc/pxa/aspenite.c | 206 ++++++++++++++ sound/soc/pxa/pxa168-ssp.c | 645 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/pxa/pxa168-ssp.h | 29 ++ 5 files changed, 906 insertions(+), 0 deletions(-) create mode 100644 sound/soc/pxa/aspenite.c create mode 100644 sound/soc/pxa/pxa168-ssp.c create mode 100644 sound/soc/pxa/pxa168-ssp.h
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 376e14a..71778be 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -1,3 +1,15 @@ +config SND_PXA168_SOC + tristate "SoC Audio for the Marvell chip" + depends on ARCH_MMP + select SND_PXA2XX_LIB + help + Say Y or M if you want to add support for codecs attached to + the PXA168 I2S or SSP interface. You will also need + to select the audio interfaces to support below. + +config SND_PXA168_SOC_SSP + tristate + config SND_PXA2XX_SOC tristate "SoC Audio for the Intel PXA2xx chip" depends on ARCH_PXA @@ -108,6 +120,15 @@ config SND_PXA2XX_SOC_PALM27X Say Y if you want to add support for SoC audio on Palm T|X, T5, E2 or LifeDrive handheld computer.
+config SND_SOC_ASPENITE + tristate "SoC Audio support for Marvell Aspenite" + depends on SND_PXA168_SOC && MACH_ASPENITE + select SND_PXA168_SOC_SSP + select SND_SOC_WM8753 + help + Say Y if you want to add support for SoC audio on the + Marvell Aspenite reference platform. + config SND_SOC_ZYLONITE tristate "SoC Audio support for Marvell Zylonite" depends on SND_PXA2XX_SOC && MACH_ZYLONITE diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index f3e08fd..46e9334 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -3,13 +3,17 @@ snd-soc-pxa2xx-objs := pxa2xx-pcm.o snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o snd-soc-pxa-ssp-objs := pxa-ssp.o +snd-soc-pxa168-ssp-objs := pxa168-ssp.o
obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o +obj-$(CONFIG_SND_PXA168_SOC) += snd-soc-pxa2xx.o +obj-$(CONFIG_SND_PXA168_SOC_SSP) += snd-soc-pxa168-ssp.o
# PXA Machine Support +snd-soc-aspenite-objs := aspenite.o snd-soc-corgi-objs := corgi.o snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o @@ -36,6 +40,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o +obj-$(CONFIG_SND_SOC_ASPENITE) += snd-soc-aspenite.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o diff --git a/sound/soc/pxa/aspenite.c b/sound/soc/pxa/aspenite.c new file mode 100644 index 0000000..12594fa --- /dev/null +++ b/sound/soc/pxa/aspenite.c @@ -0,0 +1,206 @@ +/* + * aspenite.c -- SoC audio for Aspenite + * + * Copyright (C) 2009-2010 Marvell International Ltd. + * Haojian Zhuang haojian.zhuang@marvell.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <plat/ssp.h> + +#include "../codecs/wm8753.h" +#include "pxa2xx-pcm.h" +#include "pxa168-ssp.h" + +static struct snd_soc_card aspenite; + +/* aspenite machine dapm widgets */ +static const struct snd_soc_dapm_widget aspenite_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Headset Speaker", NULL), + SND_SOC_DAPM_LINE("Line LIN", NULL), + SND_SOC_DAPM_LINE("Line RIN", NULL), +}; + +/* aspenite machine audio map */ +static const struct snd_soc_dapm_route audio_map[] = { + /* Headphone connected to LOUT1/ROUT1 */ + {"Headphone", NULL, "LOUT1"}, + {"Headphone", NULL, "ROUT1"}, + + /* Speaker connected to LOUT2/OUT4 & OUT3/ROUT2 */ + {"Headset Speaker", NULL, "LOUT2"}, + {"Headset Speaker", NULL, "OUT4"}, + {"Headset Speaker", NULL, "OUT3"}, + {"Headset Speaker", NULL, "ROUT2"}, + + /* Line connected to LINE1/LINE2 */ + {"Line LIN", NULL, "LINE1"}, + {"Line RIN", NULL, "LINE2"}, + + /* Mic */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, + + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, +}; + +static const struct snd_kcontrol_new wm8753_aspenite_controls[] = { + SOC_DAPM_PIN_SWITCH("Headset Speaker"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Line LIN"), + SOC_DAPM_PIN_SWITCH("Line RIN"), +}; + +static int aspenite_hifi_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_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int rate, width, channel; + int index, mclk, ret; + + rate = params_rate(params); + width = snd_pcm_format_physical_width(params_format(params)); + channel = params_channels(params); + ret = seek_mclk_conf(rate, width, channel); + if (ret < 0) + return ret; + index = ret; + mclk = get_mclk(ret); + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_IF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_IF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, mclk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set cpu system clock */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA168_ASYSCLK_MASTER, index, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static int aspenite_hifi_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_soc_ops aspenite_hifi_ops = { + .hw_params = aspenite_hifi_hw_params, + .hw_free = aspenite_hifi_hw_free, +}; + +static int aspenite_wm8753_init(struct snd_soc_codec *codec) +{ + int ret; + + /* set up NC codec pins */ + snd_soc_dapm_nc_pin(codec, "MONO1"); + snd_soc_dapm_nc_pin(codec, "MONO2"); + snd_soc_dapm_nc_pin(codec, "RXP"); + snd_soc_dapm_nc_pin(codec, "RXN"); + + snd_soc_dapm_new_controls(codec, aspenite_dapm_widgets, + ARRAY_SIZE(aspenite_dapm_widgets)); + ret = snd_soc_add_controls(codec, wm8753_aspenite_controls, + ARRAY_SIZE(wm8753_aspenite_controls)); + if (ret < 0) + return ret; + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* Static setup for now */ + snd_soc_dapm_enable_pin(codec, "Headset Speaker"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Headphone"); + + snd_soc_dapm_sync(codec); + return 0; +} + +static struct snd_soc_dai_link aspenite_dai[] = { + { + .name = "WM8753 HiFi", + .stream_name = "WM8753 HiFi", + .cpu_dai = &pxa168_ssp_dai[PXA168_DAI_SSP1], + .codec_dai = &wm8753_dai[0], + .init = aspenite_wm8753_init, + .ops = &aspenite_hifi_ops, + }, +}; + +static struct snd_soc_card aspenite = { + .name = "Aspenite", + .platform = &pxa2xx_soc_platform, + .dai_link = aspenite_dai, + .num_links = ARRAY_SIZE(aspenite_dai), +}; + +static struct snd_soc_device aspenite_snd_devdata = { + .card = &aspenite, + .codec_dev = &soc_codec_dev_wm8753, +}; + +static struct platform_device *aspenite_snd_device; + +static int __init aspenite_init(void) +{ + int ret; + + aspenite_snd_device = platform_device_alloc("soc-audio", -1); + if (!aspenite_snd_device) + return -ENOMEM; + + platform_set_drvdata(aspenite_snd_device, &aspenite_snd_devdata); + aspenite_snd_devdata.dev = &aspenite_snd_device->dev; + + ret = platform_device_add(aspenite_snd_device); + if (ret) + platform_device_put(aspenite_snd_device); + return ret; +} +module_init(aspenite_init); + +static void __exit aspenite_exit(void) +{ + platform_device_unregister(aspenite_snd_device); +} +module_exit(aspenite_exit); + +MODULE_DESCRIPTION("ALSA SoC WM8753 Aspenite"); +MODULE_AUTHOR("Haojian Zhuang haojian.zhuang@marvell.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/pxa168-ssp.c b/sound/soc/pxa/pxa168-ssp.c new file mode 100644 index 0000000..2b20979 --- /dev/null +++ b/sound/soc/pxa/pxa168-ssp.c @@ -0,0 +1,645 @@ +/* + * pxa168-ssp.c -- ALSA Soc Audio Layer + * + * Copyright 2009-2010 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <asm/irq.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/pxa2xx-lib.h> + +#include <mach/hardware.h> +#include <mach/dma.h> +#include <mach/regs-ssp.h> +#include <mach/regs-apbc.h> +#include <mach/regs-apmu.h> +#include <mach/regs-mpmu.h> +#include <plat/ssp.h> + +#include "pxa2xx-pcm.h" +#include "pxa168-ssp.h" + +/* + * SSP audio private data + */ +struct ssp_priv { + struct ssp_device *ssp; + unsigned int sysclk; + int dai_fmt; +#ifdef CONFIG_PM + uint32_t cr0; + uint32_t cr1; + uint32_t to; + uint32_t psp; +#endif +}; + +struct pxa2xx_pcm_dma_data { + struct pxa2xx_pcm_dma_params params; + char name[20]; +}; + +struct ssp_mclk { + unsigned int rate; + unsigned int format; + unsigned int channel; + unsigned int mclk; + unsigned int mclk_denom; + unsigned int mclk_num; + unsigned int bclk; + unsigned int bclk_denom; + unsigned int bclk_num; +}; + +/* + * This table is used while CPU is clock master. + * MCLK = 312MHz * (ASYSCLK_DENOM + 1) / ASYSCLK_NUM + * BCLK = 2 * MCLK * (SSPSCLK_DENOM + 1) / SSPSCLK_NUM + */ +static const struct ssp_mclk mclk_conf[] = { + /* rate, fmt, chn, mclk, den, num, bclk, den, num */ + {96000, 16, 2, 12288000, 63, 1625, 3072000, 1, 2}, + {96000, 16, 1, 12288000, 63, 1625, 3072000, 1, 8}, + {88200, 16, 2, 11289600, 293, 8125, 2822400, 1, 2}, + {88200, 16, 1, 11289600, 293, 8125, 2822400, 1, 8}, + {48000, 16, 2, 12288000, 63, 1625, 1536000, 1, 4}, + {48000, 16, 1, 12288000, 63, 1625, 1536000, 1, 16}, + {44100, 16, 2, 11289600, 293, 8125, 1411200, 1, 4}, + {44100, 16, 1, 11289600, 293, 8125, 1411200, 1, 16}, + {32000, 16, 2, 12288000, 63, 1625, 1024000, 1, 6}, + {32000, 16, 1, 12288000, 63, 1625, 1024000, 1, 24}, + {22050, 16, 2, 11289600, 293, 8125, 705600, 1, 8}, + {22050, 16, 1, 11289600, 293, 8125, 705600, 1, 32}, + {16000, 16, 2, 12288000, 63, 1625, 512000, 1, 12}, + {16000, 16, 1, 12288000, 63, 1625, 512000, 1, 48}, + {11025, 16, 2, 11289600, 293, 8125, 352800, 1, 16}, + {11025, 16, 1, 11289600, 293, 8125, 352800, 1, 64}, + { 8000, 16, 2, 12288000, 63, 1625, 256000, 1, 24}, + { 8000, 16, 1, 12288000, 63, 1625, 256000, 1, 96}, +}; + +/* Seek the index of MCLK configuration table */ +int seek_mclk_conf(int rate, int format, int channel) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mclk_conf); i++) { + if ((mclk_conf[i].rate == rate) + && (mclk_conf[i].format == format) + && (mclk_conf[i].channel == channel)) + return i; + } + return -EINVAL; +} + +/* Get the MCLK frequency */ +int get_mclk(int i) +{ + if ((i < 0) || (i >= ARRAY_SIZE(mclk_conf))) + return -EINVAL; + return mclk_conf[i].mclk; +} + +/* Get the BCLK frequency */ +int get_bclk(int i) +{ + if ((i < 0) || (i >= ARRAY_SIZE(mclk_conf))) + return -EINVAL; + return mclk_conf[i].bclk; +} + +static void dump_registers(struct ssp_device *ssp) +{ + dev_dbg(&ssp->pdev->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n", + ssp_read_reg(ssp, SSCR0), ssp_read_reg(ssp, SSCR1), + ssp_read_reg(ssp, SSTO)); + + dev_dbg(&ssp->pdev->dev, "SSPSP 0x%08x SSSR 0x%08x\n", + ssp_read_reg(ssp, SSPSP), ssp_read_reg(ssp, SSSR)); +} + +static void ssp_enable(struct ssp_device *ssp) +{ + uint32_t sscr0; + + sscr0 = __raw_readl(ssp->mmio_base + SSCR0) | SSCR0_SSE; + __raw_writel(sscr0, ssp->mmio_base + SSCR0); +} + +static void ssp_disable(struct ssp_device *ssp) +{ + uint32_t sscr0; + + sscr0 = __raw_readl(ssp->mmio_base + SSCR0) & ~SSCR0_SSE; + __raw_writel(sscr0, ssp->mmio_base + SSCR0); +} + +static struct pxa2xx_pcm_dma_params * +ssp_get_dma_params(struct ssp_device *ssp, int width4, int out) +{ + struct pxa2xx_pcm_dma_data *dma; + + dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL); + if (dma == NULL) + return NULL; + + snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id, + width4 ? "32-bit" : "16-bit", out ? "out" : "in"); + + dma->params.name = dma->name; + dma->params.drcmr = &DRCMR(out ? ssp->drcmr_tx : ssp->drcmr_rx); + dma->params.dcmd = (out ? (DCMD_INCSRCADDR | DCMD_FLOWTRG) : + (DCMD_INCTRGADDR | DCMD_FLOWSRC)) | + (width4 ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16; + dma->params.dev_addr = ssp->phys_base + SSDR; + + return &dma->params; +} + +static int pxa168_ssp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + int ret = 0; + + if (!cpu_dai->active) { + clk_enable(ssp->clk); + ssp_disable(ssp); + } + return ret; +} + +static void pxa168_ssp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + + if (!cpu_dai->active) { + ssp_disable(ssp); + clk_disable(ssp->clk); + } +} + +#ifdef CONFIG_PM +static int pxa168_ssp_suspend(struct snd_soc_dai *cpu_dai) +{ + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + + if (!cpu_dai->active) + return 0; + + priv->cr0 = __raw_readl(ssp->mmio_base + SSCR0); + priv->cr1 = __raw_readl(ssp->mmio_base + SSCR1); + priv->to = __raw_readl(ssp->mmio_base + SSTO); + priv->psp = __raw_readl(ssp->mmio_base + SSPSP); + + ssp_disable(ssp); + clk_disable(ssp->clk); + return 0; +} + +static int pxa168_ssp_resume(struct snd_soc_dai *cpu_dai) +{ + struct ssp_priv *priv = cpu_dai->private_data; + + if (!cpu_dai->active) + return 0; + + __raw_writel(sssr, ssp->mmio_base + SSSR); + + __raw_writel(priv->cr0 & ~SSCR0_SSE, ssp->mmio_base + SSCR0); + __raw_writel(priv->cr1, ssp->mmio_base + SSCR1); + __raw_writel(priv->to, ssp->mmio_base + SSTO); + __raw_writel(priv->psp, ssp->mmio_base + SSPSP); + __raw_writel(priv->cr0 | SSCR0_SSE, ssp->mmio_base + SSCR0); + return 0; +} + +#else +#define pxa168_ssp_suspend NULL +#define pxa168_ssp_resume NULL +#endif + +/* + * Set the SSP ports SYSCLK only from Audio SYSCLK. + */ +static int pxa168_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + unsigned int sscr0, data, asysdr, asspdr; + + dev_dbg(&ssp->pdev->dev, "%s id: %d, clk_id %d, freq %u\n", + __func__, cpu_dai->id, clk_id, freq); + + switch (clk_id) { + case PXA168_ASYSCLK_MASTER: + case PXA168_ASYSCLK_SLAVE: + break; + default: + return -EINVAL; + } + + /* freq is the index of mclk_conf table */ + if ((freq < 0) || (freq >= ARRAY_SIZE(mclk_conf))) { + dev_warn(&ssp->pdev->dev, "Wrong frequency index:%d\n", freq); + return -EINVAL; + } + asysdr = (mclk_conf[freq].mclk_num << 16) + | mclk_conf[freq].mclk_denom; + asspdr = 0; + if (clk_id == PXA168_ASYSCLK_MASTER) + asspdr = (mclk_conf[freq].bclk_num << 16) + | mclk_conf[freq].bclk_denom; + + ssp_disable(ssp); + clk_disable(ssp->clk); /* SSP port internal clock */ + + /* clear ECS, NCS, MOD, ACS */ + sscr0 = ssp_read_reg(ssp, SSCR0); + data = sscr0 & ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS); + if (sscr0 != data) + ssp_write_reg(ssp, SSCR0, data); + + /* update divider register in MPMU */ + __raw_writel(asysdr, MPMU_ASYSDR); + __raw_writel(asspdr, MPMU_ASSPDR); + + clk_enable(ssp->clk); /* SSP port internal clock */ + ssp_enable(ssp); + return 0; +} + +/* + * Tristate the SSP DAI lines + */ +static int pxa168_ssp_set_dai_tristate(struct snd_soc_dai *cpu_dai, + int tristate) +{ + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + u32 sscr1; + + sscr1 = ssp_read_reg(ssp, SSCR1); + if (tristate) + sscr1 &= ~SSCR1_TTE; + else + sscr1 |= SSCR1_TTE; + ssp_write_reg(ssp, SSCR1, sscr1); + + return 0; +} + +/* + * Set up the SSP DAI format. + * The SSP Port must be inactive before calling this function as the + * physical interface format is changed. + */ +static int pxa168_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + u32 sscr0; + u32 sscr1; + u32 sspsp; + + /* check if we need to change anything at all */ + if (priv->dai_fmt == fmt) + return 0; + + ssp_disable(ssp); + + sscr0 = ssp_read_reg(ssp, SSCR0); + sscr1 = SSCR1_RFT(8) | SSCR1_TFT(7); + sspsp = 0; + + /* we can only change the settings if the port is not in use */ + if (sscr0 & SSCR0_SSE) { + dev_err(&ssp->pdev->dev, + "can't change hardware dai format: stream is in use"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + sscr1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + sscr1 |= SSCR1_SCLKDIR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + sspsp |= SSPSP_SFRMP; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_IF: + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + sscr0 |= SSCR0_PSP; + sscr1 |= SSCR1_RWOT | SSCR1_TRAIL; + /* See hw_params() */ + break; + + default: + return -EINVAL; + } + + ssp_write_reg(ssp, SSCR0, sscr0); + ssp_write_reg(ssp, SSCR1, sscr1); + ssp_write_reg(ssp, SSPSP, sspsp); + ssp_enable(ssp); + + /* Since we are configuring the timings for the format by hand + * we have to defer some things until hw_params() where we + * know parameters like the sample size. + */ + priv->dai_fmt = fmt; + + dump_registers(ssp); + + return 0; +} + +static int pxa168_ssp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + int width = snd_pcm_format_physical_width(params_format(params)); + int channels = params_channels(params); + int dma_16b = 0, stream_out, data_size; + u32 sscr0, sspsp; + + /* generate correct DMA params */ + if (cpu_dai->dma_data) + kfree(cpu_dai->dma_data); + + if ((width == 16) && (params_channels(params) == 1)) + dma_16b = 1; + stream_out = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0; + cpu_dai->dma_data = ssp_get_dma_params(ssp, dma_16b, stream_out); + + /* clear selected SSP bits */ + sscr0 = ssp_read_reg(ssp, SSCR0); + sscr0 &= ~(SSCR0_DSS_MASK | SSCR0_EDSS); + + /* data_size should only be 16-bit or 32-bit because of DMA */ + data_size = width * channels; + switch (data_size) { + case 16: + sscr0 |= SSCR0_DSS(16); + break; + case 32: + sscr0 |= (SSCR0_EDSS | SSCR0_DSS(16)); + break; + } + + ssp_disable(ssp); + sspsp = ssp_read_reg(ssp, SSPSP); + sspsp &= ~SSPSP_TIMING_MASK; + switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* The polarity of frame sync should be inverted at here. */ + sspsp |= SSPSP_SFRMWDTH(width); + if (channels == 1) { + sspsp |= SSPSP_DMYSTRT(1); + sspsp |= SSPSP_DMYSTOP((width - 1) & 0x3); + sspsp |= SSPSP_EDMYSTOP(((width - 1) >> 2) & 0x7); + } else if (channels == 2) { + if (width == 32) { + dev_err(&ssp->pdev->dev, "can't support %d-" + "data with %-channels in I2S mode\n", + width, channels); + return -EINVAL; + } + sspsp |= SSPSP_FSRT; + } + break; + case SND_SOC_DAIFMT_RIGHT_J: + /* Right Justified mode doesn't support 32-bit data */ + if (params_format(params) == SNDRV_PCM_FORMAT_S32_LE) + return -EINVAL; + break; + case SND_SOC_DAIFMT_LEFT_J: + sspsp |= SSPSP_SFRMWDTH(width); + break; + } + + /* update SSP register at the same time */ + ssp_write_reg(ssp, SSCR0, sscr0); + ssp_write_reg(ssp, SSPSP, sspsp); + ssp_enable(ssp); + + dump_registers(ssp); + + return 0; +} + +static int pxa168_ssp_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + struct ssp_priv *priv = cpu_dai->private_data; + struct ssp_device *ssp = priv->ssp; + int val; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + ssp_enable(ssp); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = ssp_read_reg(ssp, SSCR1); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val |= SSCR1_TSRE; + else + val |= SSCR1_RSRE; + ssp_write_reg(ssp, SSCR1, val); + val = ssp_read_reg(ssp, SSSR); + ssp_write_reg(ssp, SSSR, val); + break; + case SNDRV_PCM_TRIGGER_START: + val = ssp_read_reg(ssp, SSCR1); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val |= SSCR1_TSRE; + else + val |= SSCR1_RSRE; + ssp_write_reg(ssp, SSCR1, val); + ssp_enable(ssp); + break; + case SNDRV_PCM_TRIGGER_STOP: + val = ssp_read_reg(ssp, SSCR1); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val &= ~SSCR1_TSRE; + else + val &= ~SSCR1_RSRE; + ssp_write_reg(ssp, SSCR1, val); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + ssp_disable(ssp); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = ssp_read_reg(ssp, SSCR1); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val &= ~SSCR1_TSRE; + else + val &= ~SSCR1_RSRE; + ssp_write_reg(ssp, SSCR1, val); + break; + + default: + ret = -EINVAL; + } + + dump_registers(ssp); + + return ret; +} + +static int pxa168_ssp_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct ssp_priv *priv; + int ret; + + priv = kzalloc(sizeof(struct ssp_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ssp = ssp_request(dai->id + 1, "SoC audio"); + if (priv->ssp == NULL) { + ret = -ENODEV; + goto err_priv; + } + + priv->dai_fmt = (unsigned int) -1; + dai->private_data = priv; + + return 0; + +err_priv: + kfree(priv); + return ret; +} + +static void pxa168_ssp_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct ssp_priv *priv = dai->private_data; + ssp_free(priv->ssp); +} + +#define PXA_SSP_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 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops pxa168_ssp_dai_ops = { + .startup = pxa168_ssp_startup, + .shutdown = pxa168_ssp_shutdown, + .trigger = pxa168_ssp_trigger, + .hw_params = pxa168_ssp_hw_params, + .set_sysclk = pxa168_ssp_set_dai_sysclk, + .set_fmt = pxa168_ssp_set_dai_fmt, + .set_tristate = pxa168_ssp_set_dai_tristate, +}; + +#define PXA168_SSP_DAI(_id) \ +{ \ + .name = "pxa168-ssp" #_id, \ + .id = 0, \ + .probe = pxa168_ssp_probe, \ + .remove = pxa168_ssp_remove, \ + .suspend = pxa168_ssp_suspend, \ + .resume = pxa168_ssp_resume, \ + .playback = { \ + .channels_min = 1, \ + .channels_max = 2, \ + .rates = PXA_SSP_RATES, \ + .formats = PXA_SSP_FORMATS, \ + }, \ + .capture = { \ + .channels_min = 1, \ + .channels_max = 2, \ + .rates = PXA_SSP_RATES, \ + .formats = PXA_SSP_FORMATS, \ + }, \ + .ops = &pxa168_ssp_dai_ops, \ +} + +struct snd_soc_dai pxa168_ssp_dai[] = { + PXA168_SSP_DAI(0), + PXA168_SSP_DAI(1), + PXA168_SSP_DAI(2), + PXA168_SSP_DAI(3), + PXA168_SSP_DAI(4), +}; +EXPORT_SYMBOL_GPL(pxa168_ssp_dai); + +static int __init pxa168_ssp_init(void) +{ + return snd_soc_register_dais(pxa168_ssp_dai, ARRAY_SIZE(pxa168_ssp_dai)); +} +module_init(pxa168_ssp_init); + +static void __exit pxa168_ssp_exit(void) +{ + snd_soc_unregister_dais(pxa168_ssp_dai, ARRAY_SIZE(pxa168_ssp_dai)); +} +module_exit(pxa168_ssp_exit); + +/* Module information */ +MODULE_AUTHOR("Haojian Zhuang haojian.zhuang@marvell.com"); +MODULE_DESCRIPTION("PXA168 SSP SoC Interface"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/pxa/pxa168-ssp.h b/sound/soc/pxa/pxa168-ssp.h new file mode 100644 index 0000000..10131fc --- /dev/null +++ b/sound/soc/pxa/pxa168-ssp.h @@ -0,0 +1,29 @@ +/* + * ASoC PXA168 SSP port support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PXA168_SSP_H +#define _PXA168_SSP_H + +/* pxa DAI SSP IDs */ +#define PXA168_DAI_SSP1 0 +#define PXA168_DAI_SSP2 1 +#define PXA168_DAI_SSP3 2 +#define PXA168_DAI_SSP4 3 +#define PXA168_DAI_SSP5 4 + +/* PXA168 SSP SYSCLK source */ +#define PXA168_ASYSCLK_MASTER 0 +#define PXA168_ASYSCLK_SLAVE 1 + +extern struct snd_soc_dai pxa168_ssp_dai[5]; + +extern int seek_mclk_conf(int rate, int format, int channel); +extern int get_mclk(int i); +extern int get_bclk(int i); + +#endif