Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

66
sound/soc/Kconfig Normal file
View file

@ -0,0 +1,66 @@
#
# SoC audio configuration
#
menuconfig SND_SOC
tristate "ALSA for SoC audio support"
select SND_PCM
select AC97_BUS if SND_SOC_AC97_BUS
select SND_JACK if INPUT=y || INPUT=SND
select REGMAP_I2C if I2C
select REGMAP_SPI if SPI_MASTER
select SND_COMPRESS_OFFLOAD
---help---
If you want ASoC support, you should say Y here and also to the
specific driver for your SoC platform below.
ASoC provides power efficient ALSA support for embedded battery powered
SoC based systems like PDA's, Phones and Personal Media Players.
This ASoC audio support can also be built as a module. If so, the module
will be called snd-soc-core.
if SND_SOC
config SND_SOC_AC97_BUS
bool
config SND_SOC_GENERIC_DMAENGINE_PCM
bool
select SND_DMAENGINE_PCM
# All the supported SoCs
source "sound/soc/adi/Kconfig"
source "sound/soc/atmel/Kconfig"
source "sound/soc/au1x/Kconfig"
source "sound/soc/bcm/Kconfig"
source "sound/soc/blackfin/Kconfig"
source "sound/soc/cirrus/Kconfig"
source "sound/soc/davinci/Kconfig"
source "sound/soc/dwc/Kconfig"
source "sound/soc/fsl/Kconfig"
source "sound/soc/jz4740/Kconfig"
source "sound/soc/nuc900/Kconfig"
source "sound/soc/omap/Kconfig"
source "sound/soc/kirkwood/Kconfig"
source "sound/soc/intel/Kconfig"
source "sound/soc/mxs/Kconfig"
source "sound/soc/pxa/Kconfig"
source "sound/soc/rockchip/Kconfig"
source "sound/soc/samsung/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/sirf/Kconfig"
source "sound/soc/spear/Kconfig"
source "sound/soc/tegra/Kconfig"
source "sound/soc/txx9/Kconfig"
source "sound/soc/ux500/Kconfig"
# Supported codecs
source "sound/soc/codecs/Kconfig"
# generic frame-work
source "sound/soc/generic/Kconfig"
endif # SND_SOC

34
sound/soc/Makefile Normal file
View file

@ -0,0 +1,34 @@
snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o soc-utils.o
snd-soc-core-objs += soc-pcm.o soc-compress.o soc-io.o soc-devres.o
ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),)
snd-soc-core-objs += soc-generic-dmaengine-pcm.o
endif
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
obj-$(CONFIG_SND_SOC) += codecs/
obj-$(CONFIG_SND_SOC) += generic/
obj-$(CONFIG_SND_SOC) += adi/
obj-$(CONFIG_SND_SOC) += atmel/
obj-$(CONFIG_SND_SOC) += au1x/
obj-$(CONFIG_SND_SOC) += bcm/
obj-$(CONFIG_SND_SOC) += blackfin/
obj-$(CONFIG_SND_SOC) += cirrus/
obj-$(CONFIG_SND_SOC) += davinci/
obj-$(CONFIG_SND_SOC) += dwc/
obj-$(CONFIG_SND_SOC) += fsl/
obj-$(CONFIG_SND_SOC) += jz4740/
obj-$(CONFIG_SND_SOC) += intel/
obj-$(CONFIG_SND_SOC) += mxs/
obj-$(CONFIG_SND_SOC) += nuc900/
obj-$(CONFIG_SND_SOC) += omap/
obj-$(CONFIG_SND_SOC) += kirkwood/
obj-$(CONFIG_SND_SOC) += pxa/
obj-$(CONFIG_SND_SOC) += rockchip/
obj-$(CONFIG_SND_SOC) += samsung/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += sirf/
obj-$(CONFIG_SND_SOC) += spear/
obj-$(CONFIG_SND_SOC) += tegra/
obj-$(CONFIG_SND_SOC) += txx9/
obj-$(CONFIG_SND_SOC) += ux500/

21
sound/soc/adi/Kconfig Normal file
View file

@ -0,0 +1,21 @@
config SND_SOC_ADI
tristate "Audio support for Analog Devices reference designs"
depends on MICROBLAZE || ARCH_ZYNQ || COMPILE_TEST
help
Audio support for various reference designs by Analog Devices.
config SND_SOC_ADI_AXI_I2S
tristate "AXI-I2S support"
depends on SND_SOC_ADI
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
ASoC driver for the Analog Devices AXI-I2S softcore peripheral.
config SND_SOC_ADI_AXI_SPDIF
tristate "AXI-SPDIF support"
depends on SND_SOC_ADI
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
ASoC driver for the Analog Devices AXI-SPDIF softcore peripheral.

5
sound/soc/adi/Makefile Normal file
View file

@ -0,0 +1,5 @@
snd-soc-adi-axi-i2s-objs := axi-i2s.o
snd-soc-adi-axi-spdif-objs := axi-spdif.o
obj-$(CONFIG_SND_SOC_ADI_AXI_I2S) += snd-soc-adi-axi-i2s.o
obj-$(CONFIG_SND_SOC_ADI_AXI_SPDIF) += snd-soc-adi-axi-spdif.o

276
sound/soc/adi/axi-i2s.c Normal file
View file

@ -0,0 +1,276 @@
/*
* Copyright (C) 2012-2013, Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#define AXI_I2S_REG_RESET 0x00
#define AXI_I2S_REG_CTRL 0x04
#define AXI_I2S_REG_CLK_CTRL 0x08
#define AXI_I2S_REG_STATUS 0x10
#define AXI_I2S_REG_RX_FIFO 0x28
#define AXI_I2S_REG_TX_FIFO 0x2C
#define AXI_I2S_RESET_GLOBAL BIT(0)
#define AXI_I2S_RESET_TX_FIFO BIT(1)
#define AXI_I2S_RESET_RX_FIFO BIT(2)
#define AXI_I2S_CTRL_TX_EN BIT(0)
#define AXI_I2S_CTRL_RX_EN BIT(1)
/* The frame size is configurable, but for now we always set it 64 bit */
#define AXI_I2S_BITS_PER_FRAME 64
struct axi_i2s {
struct regmap *regmap;
struct clk *clk;
struct clk *clk_ref;
struct snd_soc_dai_driver dai_driver;
struct snd_dmaengine_dai_dma_data capture_dma_data;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct snd_ratnum ratnum;
struct snd_pcm_hw_constraint_ratnums rate_constraints;
};
static int axi_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int mask, val;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
mask = AXI_I2S_CTRL_RX_EN;
else
mask = AXI_I2S_CTRL_TX_EN;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
val = mask;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
val = 0;
break;
default:
return -EINVAL;
}
regmap_update_bits(i2s->regmap, AXI_I2S_REG_CTRL, mask, val);
return 0;
}
static int axi_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int bclk_div, word_size;
unsigned int bclk_rate;
bclk_rate = params_rate(params) * AXI_I2S_BITS_PER_FRAME;
word_size = AXI_I2S_BITS_PER_FRAME / 2 - 1;
bclk_div = DIV_ROUND_UP(clk_get_rate(i2s->clk_ref), bclk_rate) / 2 - 1;
regmap_write(i2s->regmap, AXI_I2S_REG_CLK_CTRL, (word_size << 16) |
bclk_div);
return 0;
}
static int axi_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
uint32_t mask;
int ret;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
mask = AXI_I2S_RESET_RX_FIFO;
else
mask = AXI_I2S_RESET_TX_FIFO;
regmap_write(i2s->regmap, AXI_I2S_REG_RESET, mask);
ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&i2s->rate_constraints);
if (ret)
return ret;
return clk_prepare_enable(i2s->clk_ref);
}
static void axi_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(i2s->clk_ref);
}
static int axi_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data,
&i2s->capture_dma_data);
return 0;
}
static const struct snd_soc_dai_ops axi_i2s_dai_ops = {
.startup = axi_i2s_startup,
.shutdown = axi_i2s_shutdown,
.trigger = axi_i2s_trigger,
.hw_params = axi_i2s_hw_params,
};
static struct snd_soc_dai_driver axi_i2s_dai = {
.probe = axi_i2s_dai_probe,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,
},
.ops = &axi_i2s_dai_ops,
.symmetric_rates = 1,
};
static const struct snd_soc_component_driver axi_i2s_component = {
.name = "axi-i2s",
};
static const struct regmap_config axi_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = AXI_I2S_REG_STATUS,
};
static int axi_i2s_probe(struct platform_device *pdev)
{
struct resource *res;
struct axi_i2s *i2s;
void __iomem *base;
int ret;
i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
if (!i2s)
return -ENOMEM;
platform_set_drvdata(pdev, i2s);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
i2s->regmap = devm_regmap_init_mmio(&pdev->dev, base,
&axi_i2s_regmap_config);
if (IS_ERR(i2s->regmap))
return PTR_ERR(i2s->regmap);
i2s->clk = devm_clk_get(&pdev->dev, "axi");
if (IS_ERR(i2s->clk))
return PTR_ERR(i2s->clk);
i2s->clk_ref = devm_clk_get(&pdev->dev, "ref");
if (IS_ERR(i2s->clk_ref))
return PTR_ERR(i2s->clk_ref);
ret = clk_prepare_enable(i2s->clk);
if (ret)
return ret;
i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO;
i2s->playback_dma_data.addr_width = 4;
i2s->playback_dma_data.maxburst = 1;
i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO;
i2s->capture_dma_data.addr_width = 4;
i2s->capture_dma_data.maxburst = 1;
i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME;
i2s->ratnum.den_step = 1;
i2s->ratnum.den_min = 1;
i2s->ratnum.den_max = 64;
i2s->rate_constraints.rats = &i2s->ratnum;
i2s->rate_constraints.nrats = 1;
regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL);
ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component,
&axi_i2s_dai, 1);
if (ret)
goto err_clk_disable;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
goto err_clk_disable;
err_clk_disable:
clk_disable_unprepare(i2s->clk);
return ret;
}
static int axi_i2s_dev_remove(struct platform_device *pdev)
{
struct axi_i2s *i2s = platform_get_drvdata(pdev);
clk_disable_unprepare(i2s->clk);
return 0;
}
static const struct of_device_id axi_i2s_of_match[] = {
{ .compatible = "adi,axi-i2s-1.00.a", },
{},
};
MODULE_DEVICE_TABLE(of, axi_i2s_of_match);
static struct platform_driver axi_i2s_driver = {
.driver = {
.name = "axi-i2s",
.owner = THIS_MODULE,
.of_match_table = axi_i2s_of_match,
},
.probe = axi_i2s_probe,
.remove = axi_i2s_dev_remove,
};
module_platform_driver(axi_i2s_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("AXI I2S driver");
MODULE_LICENSE("GPL");

271
sound/soc/adi/axi-spdif.c Normal file
View file

@ -0,0 +1,271 @@
/*
* Copyright (C) 2012-2013, Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/dmaengine_pcm.h>
#define AXI_SPDIF_REG_CTRL 0x0
#define AXI_SPDIF_REG_STAT 0x4
#define AXI_SPDIF_REG_TX_FIFO 0xc
#define AXI_SPDIF_CTRL_TXDATA BIT(1)
#define AXI_SPDIF_CTRL_TXEN BIT(0)
#define AXI_SPDIF_CTRL_CLKDIV_OFFSET 8
#define AXI_SPDIF_CTRL_CLKDIV_MASK (0xff << 8)
#define AXI_SPDIF_FREQ_44100 (0x0 << 6)
#define AXI_SPDIF_FREQ_48000 (0x1 << 6)
#define AXI_SPDIF_FREQ_32000 (0x2 << 6)
#define AXI_SPDIF_FREQ_NA (0x3 << 6)
struct axi_spdif {
struct regmap *regmap;
struct clk *clk;
struct clk *clk_ref;
struct snd_dmaengine_dai_dma_data dma_data;
struct snd_ratnum ratnum;
struct snd_pcm_hw_constraint_ratnums rate_constraints;
};
static int axi_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
unsigned int val;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
val = AXI_SPDIF_CTRL_TXDATA;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
val = 0;
break;
default:
return -EINVAL;
}
regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
AXI_SPDIF_CTRL_TXDATA, val);
return 0;
}
static int axi_spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
unsigned int rate = params_rate(params);
unsigned int clkdiv, stat;
switch (params_rate(params)) {
case 32000:
stat = AXI_SPDIF_FREQ_32000;
break;
case 44100:
stat = AXI_SPDIF_FREQ_44100;
break;
case 48000:
stat = AXI_SPDIF_FREQ_48000;
break;
default:
stat = AXI_SPDIF_FREQ_NA;
break;
}
clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(spdif->clk_ref),
rate * 64 * 2) - 1;
clkdiv <<= AXI_SPDIF_CTRL_CLKDIV_OFFSET;
regmap_write(spdif->regmap, AXI_SPDIF_REG_STAT, stat);
regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
AXI_SPDIF_CTRL_CLKDIV_MASK, clkdiv);
return 0;
}
static int axi_spdif_dai_probe(struct snd_soc_dai *dai)
{
struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL);
return 0;
}
static int axi_spdif_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
int ret;
ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&spdif->rate_constraints);
if (ret)
return ret;
ret = clk_prepare_enable(spdif->clk_ref);
if (ret)
return ret;
regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
AXI_SPDIF_CTRL_TXEN, AXI_SPDIF_CTRL_TXEN);
return 0;
}
static void axi_spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
AXI_SPDIF_CTRL_TXEN, 0);
clk_disable_unprepare(spdif->clk_ref);
}
static const struct snd_soc_dai_ops axi_spdif_dai_ops = {
.startup = axi_spdif_startup,
.shutdown = axi_spdif_shutdown,
.trigger = axi_spdif_trigger,
.hw_params = axi_spdif_hw_params,
};
static struct snd_soc_dai_driver axi_spdif_dai = {
.probe = axi_spdif_dai_probe,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &axi_spdif_dai_ops,
};
static const struct snd_soc_component_driver axi_spdif_component = {
.name = "axi-spdif",
};
static const struct regmap_config axi_spdif_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = AXI_SPDIF_REG_STAT,
};
static int axi_spdif_probe(struct platform_device *pdev)
{
struct axi_spdif *spdif;
struct resource *res;
void __iomem *base;
int ret;
spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
if (!spdif)
return -ENOMEM;
platform_set_drvdata(pdev, spdif);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
spdif->regmap = devm_regmap_init_mmio(&pdev->dev, base,
&axi_spdif_regmap_config);
if (IS_ERR(spdif->regmap))
return PTR_ERR(spdif->regmap);
spdif->clk = devm_clk_get(&pdev->dev, "axi");
if (IS_ERR(spdif->clk))
return PTR_ERR(spdif->clk);
spdif->clk_ref = devm_clk_get(&pdev->dev, "ref");
if (IS_ERR(spdif->clk_ref))
return PTR_ERR(spdif->clk_ref);
ret = clk_prepare_enable(spdif->clk);
if (ret)
return ret;
spdif->dma_data.addr = res->start + AXI_SPDIF_REG_TX_FIFO;
spdif->dma_data.addr_width = 4;
spdif->dma_data.maxburst = 1;
spdif->ratnum.num = clk_get_rate(spdif->clk_ref) / 128;
spdif->ratnum.den_step = 1;
spdif->ratnum.den_min = 1;
spdif->ratnum.den_max = 64;
spdif->rate_constraints.rats = &spdif->ratnum;
spdif->rate_constraints.nrats = 1;
ret = devm_snd_soc_register_component(&pdev->dev, &axi_spdif_component,
&axi_spdif_dai, 1);
if (ret)
goto err_clk_disable;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
goto err_clk_disable;
return 0;
err_clk_disable:
clk_disable_unprepare(spdif->clk);
return ret;
}
static int axi_spdif_dev_remove(struct platform_device *pdev)
{
struct axi_spdif *spdif = platform_get_drvdata(pdev);
clk_disable_unprepare(spdif->clk);
return 0;
}
static const struct of_device_id axi_spdif_of_match[] = {
{ .compatible = "adi,axi-spdif-tx-1.00.a", },
{},
};
MODULE_DEVICE_TABLE(of, axi_spdif_of_match);
static struct platform_driver axi_spdif_driver = {
.driver = {
.name = "axi-spdif",
.owner = THIS_MODULE,
.of_match_table = axi_spdif_of_match,
},
.probe = axi_spdif_probe,
.remove = axi_spdif_dev_remove,
};
module_platform_driver(axi_spdif_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("AXI SPDIF driver");
MODULE_LICENSE("GPL");

63
sound/soc/atmel/Kconfig Normal file
View file

@ -0,0 +1,63 @@
config SND_ATMEL_SOC
tristate "SoC Audio for the Atmel System-on-Chip"
depends on HAS_IOMEM
help
Say Y or M if you want to add support for codecs attached to
the ATMEL SSC interface. You will also need
to select the audio interfaces to support below.
config SND_ATMEL_SOC_PDC
tristate
depends on SND_ATMEL_SOC
config SND_ATMEL_SOC_DMA
tristate
depends on SND_ATMEL_SOC
select SND_SOC_GENERIC_DMAENGINE_PCM
config SND_ATMEL_SOC_SSC
tristate
depends on SND_ATMEL_SOC
help
Say Y or M if you want to add support for codecs the
ATMEL SSC interface. You will also needs to select the individual
machine drivers to support below.
config SND_AT91_SOC_SAM9G20_WM8731
tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC
select SND_ATMEL_SOC_PDC
select SND_ATMEL_SOC_SSC
select SND_SOC_WM8731
help
Say Y if you want to add support for SoC audio on WM8731-based
AT91sam9g20 evaluation board.
config SND_ATMEL_SOC_WM8904
tristate "Atmel ASoC driver for boards using WM8904 codec"
depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC && I2C
select SND_ATMEL_SOC_SSC
select SND_ATMEL_SOC_DMA
select SND_SOC_WM8904
help
Say Y if you want to add support for Atmel ASoC driver for boards using
WM8904 codec.
config SND_AT91_SOC_SAM9X5_WM8731
tristate "SoC Audio support for WM8731-based at91sam9x5 board"
depends on ATMEL_SSC && SND_ATMEL_SOC && SOC_AT91SAM9X5
select SND_ATMEL_SOC_SSC
select SND_ATMEL_SOC_DMA
select SND_SOC_WM8731
help
Say Y if you want to add support for audio SoC on an
at91sam9x5 based board that is using WM8731 codec.
config SND_AT91_SOC_AFEB9260
tristate "SoC Audio support for AFEB9260 board"
depends on ARCH_AT91 && ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
select SND_ATMEL_SOC_PDC
select SND_ATMEL_SOC_SSC
select SND_SOC_TLV320AIC23_I2C
help
Say Y here to support sound on AFEB9260 board.

20
sound/soc/atmel/Makefile Normal file
View file

@ -0,0 +1,20 @@
# AT91 Platform Support
snd-soc-atmel-pcm-objs := atmel-pcm.o
snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o
snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o
snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o
obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o
obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o
obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o
obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
# AT91 Machine Support
snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o
snd-atmel-soc-wm8904-objs := atmel_wm8904.o
snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o
obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o
obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o
obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o

View file

@ -0,0 +1,144 @@
/*
* atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC.
*
* Copyright (C) 2012 Atmel
*
* Author: Bo Shen <voice.shen@atmel.com>
*
* Based on atmel-pcm by:
* Sedji Gaouaou <sedji.gaouaou@atmel.com>
* Copyright 2008 Atmel
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/atmel-ssc.h>
#include <linux/platform_data/dma-atmel.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "atmel-pcm.h"
/*--------------------------------------------------------------------------*\
* Hardware definition
\*--------------------------------------------------------------------------*/
static const struct snd_pcm_hardware atmel_pcm_dma_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_PAUSE,
.period_bytes_min = 256, /* lighting DMA overhead */
.period_bytes_max = 2 * 0xffff, /* if 2 bytes format */
.periods_min = 8,
.periods_max = 1024, /* no limit */
.buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,
};
/**
* atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC
*
* We use DMAENGINE to send/receive data to/from SSC so this ISR is only to
* check if any overrun occured.
*/
static void atmel_pcm_dma_irq(u32 ssc_sr,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct atmel_pcm_dma_params *prtd;
prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
if (ssc_sr & prtd->mask->ssc_error) {
if (snd_pcm_running(substream))
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n",
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? "underrun" : "overrun", prtd->name,
ssc_sr);
/* stop RX and capture: will be enabled again at restart */
ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable);
snd_pcm_stream_lock(substream);
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
snd_pcm_stream_unlock(substream);
/* now drain RHR and read status to remove xrun condition */
ssc_readx(prtd->ssc->regs, SSC_RHR);
ssc_readx(prtd->ssc->regs, SSC_SR);
}
}
static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct atmel_pcm_dma_params *prtd;
struct ssc_device *ssc;
int ret;
prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
ssc = prtd->ssc;
ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
if (ret) {
pr_err("atmel-pcm: hwparams to dma slave configure failed\n");
return ret;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
slave_config->dst_addr = ssc->phybase + SSC_THR;
slave_config->dst_maxburst = 1;
} else {
slave_config->src_addr = ssc->phybase + SSC_RHR;
slave_config->src_maxburst = 1;
}
prtd->dma_intr_handler = atmel_pcm_dma_irq;
return 0;
}
static const struct snd_dmaengine_pcm_config atmel_dmaengine_pcm_config = {
.prepare_slave_config = atmel_pcm_configure_dma,
.pcm_hardware = &atmel_pcm_dma_hardware,
.prealloc_buffer_size = ATMEL_SSC_DMABUF_SIZE,
};
int atmel_pcm_dma_platform_register(struct device *dev)
{
return snd_dmaengine_pcm_register(dev, &atmel_dmaengine_pcm_config,
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
}
EXPORT_SYMBOL(atmel_pcm_dma_platform_register);
void atmel_pcm_dma_platform_unregister(struct device *dev)
{
snd_dmaengine_pcm_unregister(dev);
}
EXPORT_SYMBOL(atmel_pcm_dma_platform_unregister);
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
MODULE_DESCRIPTION("Atmel DMA based PCM module");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,337 @@
/*
* atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* Based on at91-pcm. by:
* Frank Mandarino <fmandarino@endrelia.com>
* Copyright 2006 Endrelia Technologies Inc.
*
* Based on pxa2xx-pcm.c by:
*
* Author: Nicolas Pitre
* Created: Nov 30, 2004
* Copyright: (C) 2004 MontaVista Software, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/atmel_pdc.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "atmel-pcm.h"
/*--------------------------------------------------------------------------*\
* Hardware definition
\*--------------------------------------------------------------------------*/
/* TODO: These values were taken from the AT91 platform driver, check
* them against real values for AT32
*/
static const struct snd_pcm_hardware atmel_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE,
.period_bytes_min = 32,
.period_bytes_max = 8192,
.periods_min = 2,
.periods_max = 1024,
.buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,
};
/*--------------------------------------------------------------------------*\
* Data types
\*--------------------------------------------------------------------------*/
struct atmel_runtime_data {
struct atmel_pcm_dma_params *params;
dma_addr_t dma_buffer; /* physical address of dma buffer */
dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
size_t period_size;
dma_addr_t period_ptr; /* physical address of next period */
};
/*--------------------------------------------------------------------------*\
* ISR
\*--------------------------------------------------------------------------*/
static void atmel_pcm_dma_irq(u32 ssc_sr,
struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
static int count;
count++;
if (ssc_sr & params->mask->ssc_endbuf) {
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? "underrun" : "overrun",
params->name, ssc_sr, count);
/* re-start the PDC */
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_disable);
prtd->period_ptr += prtd->period_size;
if (prtd->period_ptr >= prtd->dma_buffer_end)
prtd->period_ptr = prtd->dma_buffer;
ssc_writex(params->ssc->regs, params->pdc->xpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xcr,
prtd->period_size / params->pdc_xfer_size);
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_enable);
}
if (ssc_sr & params->mask->ssc_endx) {
/* Load the PDC next pointer and counter registers */
prtd->period_ptr += prtd->period_size;
if (prtd->period_ptr >= prtd->dma_buffer_end)
prtd->period_ptr = prtd->dma_buffer;
ssc_writex(params->ssc->regs, params->pdc->xnpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xncr,
prtd->period_size / params->pdc_xfer_size);
}
snd_pcm_period_elapsed(substream);
}
/*--------------------------------------------------------------------------*\
* PCM operations
\*--------------------------------------------------------------------------*/
static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct atmel_runtime_data *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
/* this may get called several times by oss emulation
* with different params */
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = params_buffer_bytes(params);
prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
prtd->dma_buffer = runtime->dma_addr;
prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
prtd->period_size = params_period_bytes(params);
pr_debug("atmel-pcm: "
"hw_params: DMA for %s initialized "
"(dma_bytes=%zu, period_size=%zu)\n",
prtd->params->name,
runtime->dma_bytes,
prtd->period_size);
return 0;
}
static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
if (params != NULL) {
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
params->mask->pdc_disable);
prtd->params->dma_intr_handler = NULL;
}
return 0;
}
static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
ssc_writex(params->ssc->regs, SSC_IDR,
params->mask->ssc_endx | params->mask->ssc_endbuf);
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_disable);
return 0;
}
static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_pcm_runtime *rtd = substream->runtime;
struct atmel_runtime_data *prtd = rtd->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
int ret = 0;
pr_debug("atmel-pcm:buffer_size = %ld,"
"dma_area = %p, dma_bytes = %zu\n",
rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
prtd->period_ptr = prtd->dma_buffer;
ssc_writex(params->ssc->regs, params->pdc->xpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xcr,
prtd->period_size / params->pdc_xfer_size);
prtd->period_ptr += prtd->period_size;
ssc_writex(params->ssc->regs, params->pdc->xnpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xncr,
prtd->period_size / params->pdc_xfer_size);
pr_debug("atmel-pcm: trigger: "
"period_ptr=%lx, xpr=%u, "
"xcr=%u, xnpr=%u, xncr=%u\n",
(unsigned long)prtd->period_ptr,
ssc_readx(params->ssc->regs, params->pdc->xpr),
ssc_readx(params->ssc->regs, params->pdc->xcr),
ssc_readx(params->ssc->regs, params->pdc->xnpr),
ssc_readx(params->ssc->regs, params->pdc->xncr));
ssc_writex(params->ssc->regs, SSC_IER,
params->mask->ssc_endx | params->mask->ssc_endbuf);
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
params->mask->pdc_enable);
pr_debug("sr=%u imr=%u\n",
ssc_readx(params->ssc->regs, SSC_SR),
ssc_readx(params->ssc->regs, SSC_IER));
break; /* SNDRV_PCM_TRIGGER_START */
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_disable);
break;
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_enable);
break;
default:
ret = -EINVAL;
}
return ret;
}
static snd_pcm_uframes_t atmel_pcm_pointer(
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct atmel_runtime_data *prtd = runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
dma_addr_t ptr;
snd_pcm_uframes_t x;
ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
if (x == runtime->buffer_size)
x = 0;
return x;
}
static int atmel_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct atmel_runtime_data *prtd;
int ret = 0;
snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
/* ensure that buffer size is a multiple of period size */
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
goto out;
prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
if (prtd == NULL) {
ret = -ENOMEM;
goto out;
}
runtime->private_data = prtd;
out:
return ret;
}
static int atmel_pcm_close(struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
kfree(prtd);
return 0;
}
static struct snd_pcm_ops atmel_pcm_ops = {
.open = atmel_pcm_open,
.close = atmel_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = atmel_pcm_hw_params,
.hw_free = atmel_pcm_hw_free,
.prepare = atmel_pcm_prepare,
.trigger = atmel_pcm_trigger,
.pointer = atmel_pcm_pointer,
.mmap = atmel_pcm_mmap,
};
static struct snd_soc_platform_driver atmel_soc_platform = {
.ops = &atmel_pcm_ops,
.pcm_new = atmel_pcm_new,
.pcm_free = atmel_pcm_free,
};
int atmel_pcm_pdc_platform_register(struct device *dev)
{
return snd_soc_register_platform(dev, &atmel_soc_platform);
}
EXPORT_SYMBOL(atmel_pcm_pdc_platform_register);
void atmel_pcm_pdc_platform_unregister(struct device *dev)
{
snd_soc_unregister_platform(dev);
}
EXPORT_SYMBOL(atmel_pcm_pdc_platform_unregister);
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
MODULE_DESCRIPTION("Atmel PCM module");
MODULE_LICENSE("GPL");

121
sound/soc/atmel/atmel-pcm.c Normal file
View file

@ -0,0 +1,121 @@
/*
* atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* Based on at91-pcm. by:
* Frank Mandarino <fmandarino@endrelia.com>
* Copyright 2006 Endrelia Technologies Inc.
*
* Based on pxa2xx-pcm.c by:
*
* Author: Nicolas Pitre
* Created: Nov 30, 2004
* Copyright: (C) 2004 MontaVista Software, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include "atmel-pcm.h"
static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
int stream)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
size_t size = ATMEL_SSC_DMABUF_SIZE;
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n",
(void *)buf->area, (void *)(long)buf->addr, size);
if (!buf->area)
return -ENOMEM;
buf->bytes = size;
return 0;
}
int atmel_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
return remap_pfn_range(vma, vma->vm_start,
substream->dma_buffer.addr >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot);
}
EXPORT_SYMBOL_GPL(atmel_pcm_mmap);
int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n");
ret = atmel_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n");
ret = atmel_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE);
if (ret)
goto out;
}
out:
return ret;
}
EXPORT_SYMBOL_GPL(atmel_pcm_new);
void atmel_pcm_free(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
int stream;
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
continue;
buf = &substream->dma_buffer;
if (!buf->area)
continue;
dma_free_coherent(pcm->card->dev, buf->bytes,
buf->area, buf->addr);
buf->area = NULL;
}
}
EXPORT_SYMBOL_GPL(atmel_pcm_free);

119
sound/soc/atmel/atmel-pcm.h Normal file
View file

@ -0,0 +1,119 @@
/*
* at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC.
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* Based on at91-pcm. by:
* Frank Mandarino <fmandarino@endrelia.com>
* Copyright 2006 Endrelia Technologies Inc.
*
* Based on pxa2xx-pcm.c by:
*
* Author: Nicolas Pitre
* Created: Nov 30, 2004
* Copyright: (C) 2004 MontaVista Software, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _ATMEL_PCM_H
#define _ATMEL_PCM_H
#include <linux/atmel-ssc.h>
#define ATMEL_SSC_DMABUF_SIZE (64 * 1024)
/*
* Registers and status bits that are required by the PCM driver.
*/
struct atmel_pdc_regs {
unsigned int xpr; /* PDC recv/trans pointer */
unsigned int xcr; /* PDC recv/trans counter */
unsigned int xnpr; /* PDC next recv/trans pointer */
unsigned int xncr; /* PDC next recv/trans counter */
unsigned int ptcr; /* PDC transfer control */
};
struct atmel_ssc_mask {
u32 ssc_enable; /* SSC recv/trans enable */
u32 ssc_disable; /* SSC recv/trans disable */
u32 ssc_error; /* SSC error conditions */
u32 ssc_endx; /* SSC ENDTX or ENDRX */
u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
u32 pdc_enable; /* PDC recv/trans enable */
u32 pdc_disable; /* PDC recv/trans disable */
};
/*
* This structure, shared between the PCM driver and the interface,
* contains all information required by the PCM driver to perform the
* PDC DMA operation. All fields except dma_intr_handler() are initialized
* by the interface. The dma_intr_handler() pointer is set by the PCM
* driver and called by the interface SSC interrupt handler if it is
* non-NULL.
*/
struct atmel_pcm_dma_params {
char *name; /* stream identifier */
int pdc_xfer_size; /* PDC counter increment in bytes */
struct ssc_device *ssc; /* SSC device for stream */
struct atmel_pdc_regs *pdc; /* PDC receive or transmit registers */
struct atmel_ssc_mask *mask; /* SSC & PDC status bits */
struct snd_pcm_substream *substream;
void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
};
/*
* SSC register access (since ssc_writel() / ssc_readl() require literal name)
*/
#define ssc_readx(base, reg) (__raw_readl((base) + (reg)))
#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg))
int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd);
void atmel_pcm_free(struct snd_pcm *pcm);
int atmel_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma);
#if defined(CONFIG_SND_ATMEL_SOC_PDC) || \
defined(CONFIG_SND_ATMEL_SOC_PDC_MODULE)
int atmel_pcm_pdc_platform_register(struct device *dev);
void atmel_pcm_pdc_platform_unregister(struct device *dev);
#else
static inline int atmel_pcm_pdc_platform_register(struct device *dev)
{
return 0;
}
static inline void atmel_pcm_pdc_platform_unregister(struct device *dev)
{
}
#endif
#if defined(CONFIG_SND_ATMEL_SOC_DMA) || \
defined(CONFIG_SND_ATMEL_SOC_DMA_MODULE)
int atmel_pcm_dma_platform_register(struct device *dev);
void atmel_pcm_dma_platform_unregister(struct device *dev);
#else
static inline int atmel_pcm_dma_platform_register(struct device *dev)
{
return 0;
}
static inline void atmel_pcm_dma_platform_unregister(struct device *dev)
{
}
#endif
#endif /* _ATMEL_PCM_H */

View file

@ -0,0 +1,864 @@
/*
* atmel_ssc_dai.c -- ALSA SoC ATMEL SSC Audio Layer Platform driver
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
* ATMEL CORP.
*
* Based on at91-ssc.c by
* Frank Mandarino <fmandarino@endrelia.com>
* Based on pxa2xx Platform drivers by
* Liam Girdwood <lrg@slimlogic.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/atmel_pdc.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include "atmel-pcm.h"
#include "atmel_ssc_dai.h"
#define NUM_SSC_DEVICES 3
/*
* SSC PDC registers required by the PCM DMA engine.
*/
static struct atmel_pdc_regs pdc_tx_reg = {
.xpr = ATMEL_PDC_TPR,
.xcr = ATMEL_PDC_TCR,
.xnpr = ATMEL_PDC_TNPR,
.xncr = ATMEL_PDC_TNCR,
};
static struct atmel_pdc_regs pdc_rx_reg = {
.xpr = ATMEL_PDC_RPR,
.xcr = ATMEL_PDC_RCR,
.xnpr = ATMEL_PDC_RNPR,
.xncr = ATMEL_PDC_RNCR,
};
/*
* SSC & PDC status bits for transmit and receive.
*/
static struct atmel_ssc_mask ssc_tx_mask = {
.ssc_enable = SSC_BIT(CR_TXEN),
.ssc_disable = SSC_BIT(CR_TXDIS),
.ssc_endx = SSC_BIT(SR_ENDTX),
.ssc_endbuf = SSC_BIT(SR_TXBUFE),
.ssc_error = SSC_BIT(SR_OVRUN),
.pdc_enable = ATMEL_PDC_TXTEN,
.pdc_disable = ATMEL_PDC_TXTDIS,
};
static struct atmel_ssc_mask ssc_rx_mask = {
.ssc_enable = SSC_BIT(CR_RXEN),
.ssc_disable = SSC_BIT(CR_RXDIS),
.ssc_endx = SSC_BIT(SR_ENDRX),
.ssc_endbuf = SSC_BIT(SR_RXBUFF),
.ssc_error = SSC_BIT(SR_OVRUN),
.pdc_enable = ATMEL_PDC_RXTEN,
.pdc_disable = ATMEL_PDC_RXTDIS,
};
/*
* DMA parameters.
*/
static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
{{
.name = "SSC0 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
.name = "SSC0 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
} },
{{
.name = "SSC1 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
.name = "SSC1 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
} },
{{
.name = "SSC2 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
.name = "SSC2 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
} },
};
static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
{
.name = "ssc0",
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
.dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0,
},
{
.name = "ssc1",
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
.dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0,
},
{
.name = "ssc2",
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
.dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0,
},
};
/*
* SSC interrupt handler. Passes PDC interrupts to the DMA
* interrupt handler in the PCM driver.
*/
static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id)
{
struct atmel_ssc_info *ssc_p = dev_id;
struct atmel_pcm_dma_params *dma_params;
u32 ssc_sr;
u32 ssc_substream_mask;
int i;
ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR)
& (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR);
/*
* Loop through the substreams attached to this SSC. If
* a DMA-related interrupt occurred on that substream, call
* the DMA interrupt handler function, if one has been
* registered in the dma_params structure by the PCM driver.
*/
for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
dma_params = ssc_p->dma_params[i];
if ((dma_params != NULL) &&
(dma_params->dma_intr_handler != NULL)) {
ssc_substream_mask = (dma_params->mask->ssc_endx |
dma_params->mask->ssc_endbuf);
if (ssc_sr & ssc_substream_mask) {
dma_params->dma_intr_handler(ssc_sr,
dma_params->
substream);
}
}
}
return IRQ_HANDLED;
}
/*-------------------------------------------------------------------------*\
* DAI functions
\*-------------------------------------------------------------------------*/
/*
* Startup. Only that one substream allowed in each direction.
*/
static int atmel_ssc_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
struct atmel_pcm_dma_params *dma_params;
int dir, dir_mask;
pr_debug("atmel_ssc_startup: SSC_SR=0x%u\n",
ssc_readl(ssc_p->ssc->regs, SR));
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
dir = 0;
dir_mask = SSC_DIR_MASK_PLAYBACK;
} else {
dir = 1;
dir_mask = SSC_DIR_MASK_CAPTURE;
}
dma_params = &ssc_dma_params[dai->id][dir];
dma_params->ssc = ssc_p->ssc;
dma_params->substream = substream;
ssc_p->dma_params[dir] = dma_params;
snd_soc_dai_set_dma_data(dai, substream, dma_params);
spin_lock_irq(&ssc_p->lock);
if (ssc_p->dir_mask & dir_mask) {
spin_unlock_irq(&ssc_p->lock);
return -EBUSY;
}
ssc_p->dir_mask |= dir_mask;
spin_unlock_irq(&ssc_p->lock);
return 0;
}
/*
* Shutdown. Clear DMA parameters and shutdown the SSC if there
* are no other substreams open.
*/
static void atmel_ssc_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
struct atmel_pcm_dma_params *dma_params;
int dir, dir_mask;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
dma_params = ssc_p->dma_params[dir];
if (dma_params != NULL) {
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
pr_debug("atmel_ssc_shutdown: %s disabled SSC_SR=0x%08x\n",
(dir ? "receive" : "transmit"),
ssc_readl(ssc_p->ssc->regs, SR));
dma_params->ssc = NULL;
dma_params->substream = NULL;
ssc_p->dma_params[dir] = NULL;
}
dir_mask = 1 << dir;
spin_lock_irq(&ssc_p->lock);
ssc_p->dir_mask &= ~dir_mask;
if (!ssc_p->dir_mask) {
if (ssc_p->initialized) {
/* Shutdown the SSC clock. */
pr_debug("atmel_ssc_dau: Stopping clock\n");
clk_disable(ssc_p->ssc->clk);
free_irq(ssc_p->ssc->irq, ssc_p);
ssc_p->initialized = 0;
}
/* Reset the SSC */
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
/* Clear the SSC dividers */
ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
}
spin_unlock_irq(&ssc_p->lock);
}
/*
* Record the DAI format for use in hw_params().
*/
static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
ssc_p->daifmt = fmt;
return 0;
}
/*
* Record SSC clock dividers for use in hw_params().
*/
static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
switch (div_id) {
case ATMEL_SSC_CMR_DIV:
/*
* The same master clock divider is used for both
* transmit and receive, so if a value has already
* been set, it must match this value.
*/
if (ssc_p->cmr_div == 0)
ssc_p->cmr_div = div;
else
if (div != ssc_p->cmr_div)
return -EBUSY;
break;
case ATMEL_SSC_TCMR_PERIOD:
ssc_p->tcmr_period = div;
break;
case ATMEL_SSC_RCMR_PERIOD:
ssc_p->rcmr_period = div;
break;
default:
return -EINVAL;
}
return 0;
}
/*
* Configure the SSC.
*/
static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
int id = dai->id;
struct atmel_ssc_info *ssc_p = &ssc_info[id];
struct ssc_device *ssc = ssc_p->ssc;
struct atmel_pcm_dma_params *dma_params;
int dir, channels, bits;
u32 tfmr, rfmr, tcmr, rcmr;
int ret;
int fslen, fslen_ext;
/*
* Currently, there is only one set of dma params for
* each direction. If more are added, this code will
* have to be changed to select the proper set.
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
dma_params = ssc_p->dma_params[dir];
channels = params_channels(params);
/*
* Determine sample size in bits and the PDC increment.
*/
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
bits = 8;
dma_params->pdc_xfer_size = 1;
break;
case SNDRV_PCM_FORMAT_S16_LE:
bits = 16;
dma_params->pdc_xfer_size = 2;
break;
case SNDRV_PCM_FORMAT_S24_LE:
bits = 24;
dma_params->pdc_xfer_size = 4;
break;
case SNDRV_PCM_FORMAT_S32_LE:
bits = 32;
dma_params->pdc_xfer_size = 4;
break;
default:
printk(KERN_WARNING "atmel_ssc_dai: unsupported PCM format");
return -EINVAL;
}
/*
* Compute SSC register settings.
*/
switch (ssc_p->daifmt
& (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
/*
* I2S format, SSC provides BCLK and LRC clocks.
*
* The SSC transmit and receive clocks are generated
* from the MCK divider, and the BCLK signal
* is output on the SSC TK line.
*/
if (bits > 16 && !ssc->pdata->has_fslen_ext) {
dev_err(dai->dev,
"sample size %d is too large for SSC device\n",
bits);
return -EINVAL;
}
fslen_ext = (bits - 1) / 16;
fslen = (bits - 1) % 16;
rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
| SSC_BF(RCMR_STTDLY, START_DELAY)
| SSC_BF(RCMR_START, SSC_START_FALLING_RF)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
| SSC_BF(RCMR_CKS, SSC_CKS_DIV);
rfmr = SSC_BF(RFMR_FSLEN_EXT, fslen_ext)
| SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE)
| SSC_BF(RFMR_FSLEN, fslen)
| SSC_BF(RFMR_DATNB, (channels - 1))
| SSC_BIT(RFMR_MSBF)
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
| SSC_BF(TCMR_STTDLY, START_DELAY)
| SSC_BF(TCMR_START, SSC_START_FALLING_RF)
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
| SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
| SSC_BF(TCMR_CKS, SSC_CKS_DIV);
tfmr = SSC_BF(TFMR_FSLEN_EXT, fslen_ext)
| SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(TFMR_FSDEN, 0)
| SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE)
| SSC_BF(TFMR_FSLEN, fslen)
| SSC_BF(TFMR_DATNB, (channels - 1))
| SSC_BIT(TFMR_MSBF)
| SSC_BF(TFMR_DATDEF, 0)
| SSC_BF(TFMR_DATLEN, (bits - 1));
break;
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
/*
* I2S format, CODEC supplies BCLK and LRC clocks.
*
* The SSC transmit clock is obtained from the BCLK signal on
* on the TK line, and the SSC receive clock is
* generated from the transmit clock.
*/
rcmr = SSC_BF(RCMR_PERIOD, 0)
| SSC_BF(RCMR_STTDLY, START_DELAY)
| SSC_BF(RCMR_START, SSC_START_FALLING_RF)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
| SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ?
SSC_CKS_PIN : SSC_CKS_CLOCK);
rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(RFMR_FSOS, SSC_FSOS_NONE)
| SSC_BF(RFMR_FSLEN, 0)
| SSC_BF(RFMR_DATNB, (channels - 1))
| SSC_BIT(RFMR_MSBF)
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tcmr = SSC_BF(TCMR_PERIOD, 0)
| SSC_BF(TCMR_STTDLY, START_DELAY)
| SSC_BF(TCMR_START, SSC_START_FALLING_RF)
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
| SSC_BF(TCMR_CKO, SSC_CKO_NONE)
| SSC_BF(TCMR_CKS, ssc->clk_from_rk_pin ?
SSC_CKS_CLOCK : SSC_CKS_PIN);
tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(TFMR_FSDEN, 0)
| SSC_BF(TFMR_FSOS, SSC_FSOS_NONE)
| SSC_BF(TFMR_FSLEN, 0)
| SSC_BF(TFMR_DATNB, (channels - 1))
| SSC_BIT(TFMR_MSBF)
| SSC_BF(TFMR_DATDEF, 0)
| SSC_BF(TFMR_DATLEN, (bits - 1));
break;
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
/*
* DSP/PCM Mode A format, SSC provides BCLK and LRC clocks.
*
* The SSC transmit and receive clocks are generated from the
* MCK divider, and the BCLK signal is output
* on the SSC TK line.
*/
rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
| SSC_BF(RCMR_STTDLY, 1)
| SSC_BF(RCMR_START, SSC_START_RISING_RF)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
| SSC_BF(RCMR_CKS, SSC_CKS_DIV);
rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE)
| SSC_BF(RFMR_FSLEN, 0)
| SSC_BF(RFMR_DATNB, (channels - 1))
| SSC_BIT(RFMR_MSBF)
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
| SSC_BF(TCMR_STTDLY, 1)
| SSC_BF(TCMR_START, SSC_START_RISING_RF)
| SSC_BF(TCMR_CKI, SSC_CKI_RISING)
| SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
| SSC_BF(TCMR_CKS, SSC_CKS_DIV);
tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(TFMR_FSDEN, 0)
| SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE)
| SSC_BF(TFMR_FSLEN, 0)
| SSC_BF(TFMR_DATNB, (channels - 1))
| SSC_BIT(TFMR_MSBF)
| SSC_BF(TFMR_DATDEF, 0)
| SSC_BF(TFMR_DATLEN, (bits - 1));
break;
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
/*
* DSP/PCM Mode A format, CODEC supplies BCLK and LRC clocks.
*
* The SSC transmit clock is obtained from the BCLK signal on
* on the TK line, and the SSC receive clock is
* generated from the transmit clock.
*
* Data is transferred on first BCLK after LRC pulse rising
* edge.If stereo, the right channel data is contiguous with
* the left channel data.
*/
rcmr = SSC_BF(RCMR_PERIOD, 0)
| SSC_BF(RCMR_STTDLY, START_DELAY)
| SSC_BF(RCMR_START, SSC_START_RISING_RF)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
| SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ?
SSC_CKS_PIN : SSC_CKS_CLOCK);
rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(RFMR_FSOS, SSC_FSOS_NONE)
| SSC_BF(RFMR_FSLEN, 0)
| SSC_BF(RFMR_DATNB, (channels - 1))
| SSC_BIT(RFMR_MSBF)
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tcmr = SSC_BF(TCMR_PERIOD, 0)
| SSC_BF(TCMR_STTDLY, START_DELAY)
| SSC_BF(TCMR_START, SSC_START_RISING_RF)
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
| SSC_BF(TCMR_CKO, SSC_CKO_NONE)
| SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ?
SSC_CKS_CLOCK : SSC_CKS_PIN);
tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(TFMR_FSDEN, 0)
| SSC_BF(TFMR_FSOS, SSC_FSOS_NONE)
| SSC_BF(TFMR_FSLEN, 0)
| SSC_BF(TFMR_DATNB, (channels - 1))
| SSC_BIT(TFMR_MSBF)
| SSC_BF(TFMR_DATDEF, 0)
| SSC_BF(TFMR_DATLEN, (bits - 1));
break;
default:
printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
ssc_p->daifmt);
return -EINVAL;
}
pr_debug("atmel_ssc_hw_params: "
"RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
rcmr, rfmr, tcmr, tfmr);
if (!ssc_p->initialized) {
/* Enable PMC peripheral clock for this SSC */
pr_debug("atmel_ssc_dai: Starting clock\n");
clk_enable(ssc_p->ssc->clk);
/* Reset the SSC and its PDC registers */
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0);
ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0,
ssc_p->name, ssc_p);
if (ret < 0) {
printk(KERN_WARNING
"atmel_ssc_dai: request_irq failure\n");
pr_debug("Atmel_ssc_dai: Stoping clock\n");
clk_disable(ssc_p->ssc->clk);
return ret;
}
ssc_p->initialized = 1;
}
/* set SSC clock mode register */
ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div);
/* set receive clock mode and format */
ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
ssc_writel(ssc_p->ssc->regs, RFMR, rfmr);
/* set transmit clock mode and format */
ssc_writel(ssc_p->ssc->regs, TCMR, tcmr);
ssc_writel(ssc_p->ssc->regs, TFMR, tfmr);
pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n");
return 0;
}
static int atmel_ssc_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
struct atmel_pcm_dma_params *dma_params;
int dir;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
dma_params = ssc_p->dma_params[dir];
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
ssc_writel(ssc_p->ssc->regs, IDR, dma_params->mask->ssc_error);
pr_debug("%s enabled SSC_SR=0x%08x\n",
dir ? "receive" : "transmit",
ssc_readl(ssc_p->ssc->regs, SR));
return 0;
}
static int atmel_ssc_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
struct atmel_pcm_dma_params *dma_params;
int dir;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
dma_params = ssc_p->dma_params[dir];
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable);
break;
default:
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
break;
}
return 0;
}
#ifdef CONFIG_PM
static int atmel_ssc_suspend(struct snd_soc_dai *cpu_dai)
{
struct atmel_ssc_info *ssc_p;
if (!cpu_dai->active)
return 0;
ssc_p = &ssc_info[cpu_dai->id];
/* Save the status register before disabling transmit and receive */
ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR);
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
/* Save the current interrupt mask, then disable unmasked interrupts */
ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR);
ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR);
ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR);
ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR);
ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR);
return 0;
}
static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai)
{
struct atmel_ssc_info *ssc_p;
u32 cr;
if (!cpu_dai->active)
return 0;
ssc_p = &ssc_info[cpu_dai->id];
/* restore SSC register settings */
ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr);
ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr);
ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr);
ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr);
ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr);
/* re-enable interrupts */
ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr);
/* Re-enable receive and transmit as appropriate */
cr = 0;
cr |=
(ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0;
cr |=
(ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0;
ssc_writel(ssc_p->ssc->regs, CR, cr);
return 0;
}
#else /* CONFIG_PM */
# define atmel_ssc_suspend NULL
# define atmel_ssc_resume NULL
#endif /* CONFIG_PM */
#define ATMEL_SSC_RATES (SNDRV_PCM_RATE_8000_96000)
#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops atmel_ssc_dai_ops = {
.startup = atmel_ssc_startup,
.shutdown = atmel_ssc_shutdown,
.prepare = atmel_ssc_prepare,
.trigger = atmel_ssc_trigger,
.hw_params = atmel_ssc_hw_params,
.set_fmt = atmel_ssc_set_dai_fmt,
.set_clkdiv = atmel_ssc_set_dai_clkdiv,
};
static struct snd_soc_dai_driver atmel_ssc_dai = {
.suspend = atmel_ssc_suspend,
.resume = atmel_ssc_resume,
.playback = {
.channels_min = 1,
.channels_max = 2,
.rates = ATMEL_SSC_RATES,
.formats = ATMEL_SSC_FORMATS,},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rates = ATMEL_SSC_RATES,
.formats = ATMEL_SSC_FORMATS,},
.ops = &atmel_ssc_dai_ops,
};
static const struct snd_soc_component_driver atmel_ssc_component = {
.name = "atmel-ssc",
};
static int asoc_ssc_init(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct ssc_device *ssc = platform_get_drvdata(pdev);
int ret;
ret = snd_soc_register_component(dev, &atmel_ssc_component,
&atmel_ssc_dai, 1);
if (ret) {
dev_err(dev, "Could not register DAI: %d\n", ret);
goto err;
}
if (ssc->pdata->use_dma)
ret = atmel_pcm_dma_platform_register(dev);
else
ret = atmel_pcm_pdc_platform_register(dev);
if (ret) {
dev_err(dev, "Could not register PCM: %d\n", ret);
goto err_unregister_dai;
}
return 0;
err_unregister_dai:
snd_soc_unregister_component(dev);
err:
return ret;
}
static void asoc_ssc_exit(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct ssc_device *ssc = platform_get_drvdata(pdev);
if (ssc->pdata->use_dma)
atmel_pcm_dma_platform_unregister(dev);
else
atmel_pcm_pdc_platform_unregister(dev);
snd_soc_unregister_component(dev);
}
/**
* atmel_ssc_set_audio - Allocate the specified SSC for audio use.
*/
int atmel_ssc_set_audio(int ssc_id)
{
struct ssc_device *ssc;
int ret;
/* If we can grab the SSC briefly to parent the DAI device off it */
ssc = ssc_request(ssc_id);
if (IS_ERR(ssc)) {
pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n",
PTR_ERR(ssc));
return PTR_ERR(ssc);
} else {
ssc_info[ssc_id].ssc = ssc;
}
ret = asoc_ssc_init(&ssc->pdev->dev);
return ret;
}
EXPORT_SYMBOL_GPL(atmel_ssc_set_audio);
void atmel_ssc_put_audio(int ssc_id)
{
struct ssc_device *ssc = ssc_info[ssc_id].ssc;
asoc_ssc_exit(&ssc->pdev->dev);
ssc_free(ssc);
}
EXPORT_SYMBOL_GPL(atmel_ssc_put_audio);
/* Module information */
MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
MODULE_DESCRIPTION("ATMEL SSC ASoC Interface");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,123 @@
/*
* atmel_ssc_dai.h - ALSA SSC interface for the Atmel SoC
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
* ATMEL CORP.
*
* Based on at91-ssc.c by
* Frank Mandarino <fmandarino@endrelia.com>
* Based on pxa2xx Platform drivers by
* Liam Girdwood <lrg@slimlogic.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _ATMEL_SSC_DAI_H
#define _ATMEL_SSC_DAI_H
#include <linux/types.h>
#include <linux/atmel-ssc.h>
#include "atmel-pcm.h"
/* SSC system clock ids */
#define ATMEL_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */
/* SSC divider ids */
#define ATMEL_SSC_CMR_DIV 0 /* MCK divider for BCLK */
#define ATMEL_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */
#define ATMEL_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */
/*
* SSC direction masks
*/
#define SSC_DIR_MASK_UNUSED 0
#define SSC_DIR_MASK_PLAYBACK 1
#define SSC_DIR_MASK_CAPTURE 2
/*
* SSC register values that Atmel left out of <linux/atmel-ssc.h>. These
* are expected to be used with SSC_BF
*/
/* START bit field values */
#define SSC_START_CONTINUOUS 0
#define SSC_START_TX_RX 1
#define SSC_START_LOW_RF 2
#define SSC_START_HIGH_RF 3
#define SSC_START_FALLING_RF 4
#define SSC_START_RISING_RF 5
#define SSC_START_LEVEL_RF 6
#define SSC_START_EDGE_RF 7
#define SSS_START_COMPARE_0 8
/* CKI bit field values */
#define SSC_CKI_FALLING 0
#define SSC_CKI_RISING 1
/* CKO bit field values */
#define SSC_CKO_NONE 0
#define SSC_CKO_CONTINUOUS 1
#define SSC_CKO_TRANSFER 2
/* CKS bit field values */
#define SSC_CKS_DIV 0
#define SSC_CKS_CLOCK 1
#define SSC_CKS_PIN 2
/* FSEDGE bit field values */
#define SSC_FSEDGE_POSITIVE 0
#define SSC_FSEDGE_NEGATIVE 1
/* FSOS bit field values */
#define SSC_FSOS_NONE 0
#define SSC_FSOS_NEGATIVE 1
#define SSC_FSOS_POSITIVE 2
#define SSC_FSOS_LOW 3
#define SSC_FSOS_HIGH 4
#define SSC_FSOS_TOGGLE 5
#define START_DELAY 1
struct atmel_ssc_state {
u32 ssc_cmr;
u32 ssc_rcmr;
u32 ssc_rfmr;
u32 ssc_tcmr;
u32 ssc_tfmr;
u32 ssc_sr;
u32 ssc_imr;
};
struct atmel_ssc_info {
char *name;
struct ssc_device *ssc;
spinlock_t lock; /* lock for dir_mask */
unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */
unsigned short initialized; /* true if SSC has been initialized */
unsigned short daifmt;
unsigned short cmr_div;
unsigned short tcmr_period;
unsigned short rcmr_period;
struct atmel_pcm_dma_params *dma_params[2];
struct atmel_ssc_state ssc_state;
};
int atmel_ssc_set_audio(int ssc_id);
void atmel_ssc_put_audio(int ssc_id);
#endif /* _AT91_SSC_DAI_H */

View file

@ -0,0 +1,196 @@
/*
* atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec.
*
* Copyright (C) 2012 Atmel
*
* Author: Bo Shen <voice.shen@atmel.com>
*
* GPLv2 or later
*/
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <sound/soc.h>
#include "../codecs/wm8904.h"
#include "atmel_ssc_dai.h"
static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic", NULL),
SND_SOC_DAPM_LINE("Line In Jack", NULL),
};
static int atmel_asoc_wm8904_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->codec_dai;
int ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK,
32768, params_rate(params) * 256);
if (ret < 0) {
pr_err("%s - failed to set wm8904 codec PLL.", __func__);
return ret;
}
/*
* As here wm8904 use FLL output as its system clock
* so calling set_sysclk won't care freq parameter
* then we pass 0
*/
ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL,
0, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("%s -failed to set wm8904 SYSCLK\n", __func__);
return ret;
}
return 0;
}
static struct snd_soc_ops atmel_asoc_wm8904_ops = {
.hw_params = atmel_asoc_wm8904_hw_params,
};
static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = {
.name = "WM8904",
.stream_name = "WM8904 PCM",
.codec_dai_name = "wm8904-hifi",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ops = &atmel_asoc_wm8904_ops,
};
static struct snd_soc_card atmel_asoc_wm8904_card = {
.name = "atmel_asoc_wm8904",
.owner = THIS_MODULE,
.dai_link = &atmel_asoc_wm8904_dailink,
.num_links = 1,
.dapm_widgets = atmel_asoc_wm8904_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets),
.fully_routed = true,
};
static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_card *card = &atmel_asoc_wm8904_card;
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
int ret;
if (!np) {
dev_err(&pdev->dev, "only device tree supported\n");
return -EINVAL;
}
ret = snd_soc_of_parse_card_name(card, "atmel,model");
if (ret) {
dev_err(&pdev->dev, "failed to parse card name\n");
return ret;
}
ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing");
if (ret) {
dev_err(&pdev->dev, "failed to parse audio routing\n");
return ret;
}
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "failed to get dai and pcm info\n");
ret = -EINVAL;
return ret;
}
dailink->cpu_of_node = cpu_np;
dailink->platform_of_node = cpu_np;
of_node_put(cpu_np);
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "failed to get codec info\n");
ret = -EINVAL;
return ret;
}
dailink->codec_of_node = codec_np;
of_node_put(codec_np);
return 0;
}
static int atmel_asoc_wm8904_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &atmel_asoc_wm8904_card;
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
int id, ret;
card->dev = &pdev->dev;
ret = atmel_asoc_wm8904_dt_init(pdev);
if (ret) {
dev_err(&pdev->dev, "failed to init dt info\n");
return ret;
}
id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc");
ret = atmel_ssc_set_audio(id);
if (ret != 0) {
dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id);
return ret;
}
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed\n");
goto err_set_audio;
}
return 0;
err_set_audio:
atmel_ssc_put_audio(id);
return ret;
}
static int atmel_asoc_wm8904_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
int id;
id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc");
snd_soc_unregister_card(card);
atmel_ssc_put_audio(id);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = {
{ .compatible = "atmel,asoc-wm8904", },
{ }
};
#endif
static struct platform_driver atmel_asoc_wm8904_driver = {
.driver = {
.name = "atmel-wm8904-audio",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids),
},
.probe = atmel_asoc_wm8904_probe,
.remove = atmel_asoc_wm8904_remove,
};
module_platform_driver(atmel_asoc_wm8904_driver);
/* Module information */
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,325 @@
/*
* sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based
* ATMEL AT91SAM9G20ek board.
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* Based on ati_b1_wm8731.c by:
* Frank Mandarino <fmandarino@endrelia.com>
* Copyright 2006 Endrelia Technologies Inc.
* Based on corgi.c by:
* Copyright 2005 Wolfson Microelectronics PLC.
* Copyright 2005 Openedhand 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
#include "../codecs/wm8731.h"
#include "atmel-pcm.h"
#include "atmel_ssc_dai.h"
#define MCLK_RATE 12000000
/*
* As shipped the board does not have inputs. However, it is relatively
* straightforward to modify the board to hook them up so support is left
* in the driver.
*/
#undef ENABLE_MIC_INPUT
static struct clk *mclk;
static int at91sam9g20ek_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->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int ret;
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
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_NF | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops at91sam9g20ek_ops = {
.hw_params = at91sam9g20ek_hw_params,
};
static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
static int mclk_on;
int ret = 0;
switch (level) {
case SND_SOC_BIAS_ON:
case SND_SOC_BIAS_PREPARE:
if (!mclk_on)
ret = clk_enable(mclk);
if (ret == 0)
mclk_on = 1;
break;
case SND_SOC_BIAS_OFF:
case SND_SOC_BIAS_STANDBY:
if (mclk_on)
clk_disable(mclk);
mclk_on = 0;
break;
}
return ret;
}
static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
SND_SOC_DAPM_MIC("Int Mic", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
};
static const struct snd_soc_dapm_route intercon[] = {
/* speaker connected to LHPOUT */
{"Ext Spk", NULL, "LHPOUT"},
/* mic is connected to Mic Jack, with WM8731 Mic Bias */
{"MICIN", NULL, "Mic Bias"},
{"Mic Bias", NULL, "Int Mic"},
};
/*
* Logic for a wm8731 as connected on a at91sam9g20ek board.
*/
static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dapm_context *dapm = &codec->dapm;
int ret;
printk(KERN_DEBUG
"at91sam9g20ek_wm8731 "
": at91sam9g20ek_wm8731_init() called\n");
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK,
MCLK_RATE, SND_SOC_CLOCK_IN);
if (ret < 0) {
printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret);
return ret;
}
/* not connected */
snd_soc_dapm_nc_pin(dapm, "RLINEIN");
snd_soc_dapm_nc_pin(dapm, "LLINEIN");
#ifndef ENABLE_MIC_INPUT
snd_soc_dapm_nc_pin(&rtd->card->dapm, "Int Mic");
#endif
return 0;
}
static struct snd_soc_dai_link at91sam9g20ek_dai = {
.name = "WM8731",
.stream_name = "WM8731 PCM",
.cpu_dai_name = "at91rm9200_ssc.0",
.codec_dai_name = "wm8731-hifi",
.init = at91sam9g20ek_wm8731_init,
.platform_name = "at91rm9200_ssc.0",
.codec_name = "wm8731.0-001b",
.ops = &at91sam9g20ek_ops,
};
static struct snd_soc_card snd_soc_at91sam9g20ek = {
.name = "AT91SAMG20-EK",
.owner = THIS_MODULE,
.dai_link = &at91sam9g20ek_dai,
.num_links = 1,
.set_bias_level = at91sam9g20ek_set_bias_level,
.dapm_widgets = at91sam9g20ek_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets),
.dapm_routes = intercon,
.num_dapm_routes = ARRAY_SIZE(intercon),
};
static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct clk *pllb;
struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
int ret;
if (!np) {
if (!(machine_is_at91sam9g20ek() ||
machine_is_at91sam9g20ek_2mmc()))
return -ENODEV;
}
ret = atmel_ssc_set_audio(0);
if (ret) {
dev_err(&pdev->dev, "ssc channel is not valid\n");
return -EINVAL;
}
/*
* Codec MCLK is supplied by PCK0 - set it up.
*/
mclk = clk_get(NULL, "pck0");
if (IS_ERR(mclk)) {
printk(KERN_ERR "ASoC: Failed to get MCLK\n");
ret = PTR_ERR(mclk);
goto err;
}
pllb = clk_get(NULL, "pllb");
if (IS_ERR(pllb)) {
printk(KERN_ERR "ASoC: Failed to get PLLB\n");
ret = PTR_ERR(pllb);
goto err_mclk;
}
ret = clk_set_parent(mclk, pllb);
clk_put(pllb);
if (ret != 0) {
printk(KERN_ERR "ASoC: Failed to set MCLK parent\n");
goto err_mclk;
}
clk_set_rate(mclk, MCLK_RATE);
card->dev = &pdev->dev;
/* Parse device node info */
if (np) {
ret = snd_soc_of_parse_card_name(card, "atmel,model");
if (ret)
goto err;
ret = snd_soc_of_parse_audio_routing(card,
"atmel,audio-routing");
if (ret)
goto err;
/* Parse codec info */
at91sam9g20ek_dai.codec_name = NULL;
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "codec info missing\n");
return -EINVAL;
}
at91sam9g20ek_dai.codec_of_node = codec_np;
/* Parse dai and platform info */
at91sam9g20ek_dai.cpu_dai_name = NULL;
at91sam9g20ek_dai.platform_name = NULL;
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "dai and pcm info missing\n");
return -EINVAL;
}
at91sam9g20ek_dai.cpu_of_node = cpu_np;
at91sam9g20ek_dai.platform_of_node = cpu_np;
of_node_put(codec_np);
of_node_put(cpu_np);
}
ret = snd_soc_register_card(card);
if (ret) {
printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n");
}
return ret;
err_mclk:
clk_put(mclk);
mclk = NULL;
err:
atmel_ssc_put_audio(0);
return ret;
}
static int at91sam9g20ek_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
clk_disable(mclk);
mclk = NULL;
snd_soc_unregister_card(card);
atmel_ssc_put_audio(0);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = {
{ .compatible = "atmel,at91sam9g20ek-wm8731-audio", },
{ }
};
MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids);
#endif
static struct platform_driver at91sam9g20ek_audio_driver = {
.driver = {
.name = "at91sam9g20ek-audio",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
},
.probe = at91sam9g20ek_audio_probe,
.remove = at91sam9g20ek_audio_remove,
};
module_platform_driver(at91sam9g20ek_audio_driver);
/* Module information */
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
MODULE_ALIAS("platform:at91sam9g20ek-audio");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,208 @@
/*
* sam9x5_wm8731 -- SoC audio for AT91SAM9X5-based boards
* that are using WM8731 as codec.
*
* Copyright (C) 2011 Atmel,
* Nicolas Ferre <nicolas.ferre@atmel.com>
*
* Copyright (C) 2013 Paratronic,
* Richard Genoud <richard.genoud@gmail.com>
*
* Based on sam9g20_wm8731.c by:
* Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* 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/of.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <sound/soc-dapm.h>
#include "../codecs/wm8731.h"
#include "atmel_ssc_dai.h"
#define MCLK_RATE 12288000
#define DRV_NAME "sam9x5-snd-wm8731"
struct sam9x5_drvdata {
int ssc_id;
};
/*
* Logic for a wm8731 as connected on a at91sam9x5ek based board.
*/
static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct device *dev = rtd->dev;
int ret;
dev_dbg(dev, "ASoC: %s called\n", __func__);
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
MCLK_RATE, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(dev, "ASoC: Failed to set WM8731 SYSCLK: %d\n", ret);
return ret;
}
return 0;
}
/*
* Audio paths on at91sam9x5ek board:
*
* |A| ------------> | | ---R----> Headphone Jack
* |T| <----\ | WM | ---L--/
* |9| ---> CLK <--> | 8731 | <--R----- Line In Jack
* |1| <------------ | | <--L--/
*/
static const struct snd_soc_dapm_widget sam9x5_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_LINE("Line In Jack", NULL),
};
static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_card *card;
struct snd_soc_dai_link *dai;
struct sam9x5_drvdata *priv;
int ret;
if (!np) {
dev_err(&pdev->dev, "No device node supplied\n");
return -EINVAL;
}
card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL);
if (!dai || !card || !priv) {
ret = -ENOMEM;
goto out;
}
snd_soc_card_set_drvdata(card, priv);
card->dev = &pdev->dev;
card->owner = THIS_MODULE;
card->dai_link = dai;
card->num_links = 1;
card->dapm_widgets = sam9x5_dapm_widgets;
card->num_dapm_widgets = ARRAY_SIZE(sam9x5_dapm_widgets);
dai->name = "WM8731";
dai->stream_name = "WM8731 PCM";
dai->codec_dai_name = "wm8731-hifi";
dai->init = sam9x5_wm8731_init;
dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM;
ret = snd_soc_of_parse_card_name(card, "atmel,model");
if (ret) {
dev_err(&pdev->dev, "atmel,model node missing\n");
goto out;
}
ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing");
if (ret) {
dev_err(&pdev->dev, "atmel,audio-routing node missing\n");
goto out;
}
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "atmel,audio-codec node missing\n");
ret = -EINVAL;
goto out;
}
dai->codec_of_node = codec_np;
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "atmel,ssc-controller node missing\n");
ret = -EINVAL;
goto out;
}
dai->cpu_of_node = cpu_np;
dai->platform_of_node = cpu_np;
priv->ssc_id = of_alias_get_id(cpu_np, "ssc");
ret = atmel_ssc_set_audio(priv->ssc_id);
if (ret != 0) {
dev_err(&pdev->dev,
"ASoC: Failed to set SSC %d for audio: %d\n",
ret, priv->ssc_id);
goto out;
}
of_node_put(codec_np);
of_node_put(cpu_np);
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev,
"ASoC: Platform device allocation failed\n");
goto out_put_audio;
}
dev_dbg(&pdev->dev, "ASoC: %s ok\n", __func__);
return ret;
out_put_audio:
atmel_ssc_put_audio(priv->ssc_id);
out:
return ret;
}
static int sam9x5_wm8731_driver_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct sam9x5_drvdata *priv = card->drvdata;
snd_soc_unregister_card(card);
atmel_ssc_put_audio(priv->ssc_id);
return 0;
}
static const struct of_device_id sam9x5_wm8731_of_match[] = {
{ .compatible = "atmel,sam9x5-wm8731-audio", },
{},
};
MODULE_DEVICE_TABLE(of, sam9x5_wm8731_of_match);
static struct platform_driver sam9x5_wm8731_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(sam9x5_wm8731_of_match),
},
.probe = sam9x5_wm8731_driver_probe,
.remove = sam9x5_wm8731_driver_remove,
};
module_platform_driver(sam9x5_wm8731_driver);
/* Module information */
MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");
MODULE_AUTHOR("Richard Genoud <richard.genoud@gmail.com>");
MODULE_DESCRIPTION("ALSA SoC machine driver for AT91SAM9x5 - WM8731");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);

View file

@ -0,0 +1,151 @@
/*
* afeb9260.c -- SoC audio for AFEB9260
*
* Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org>
*
* 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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
#include <linux/gpio.h>
#include "../codecs/tlv320aic23.h"
#include "atmel-pcm.h"
#include "atmel_ssc_dai.h"
#define CODEC_CLOCK 12000000
static int afeb9260_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->codec_dai;
int err;
/* Set the codec system clock for DAC and ADC */
err =
snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
if (err < 0) {
printk(KERN_ERR "can't set codec system clock\n");
return err;
}
return err;
}
static struct snd_soc_ops afeb9260_ops = {
.hw_params = afeb9260_hw_params,
};
static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
};
static const struct snd_soc_dapm_route afeb9260_audio_map[] = {
{"Headphone Jack", NULL, "LHPOUT"},
{"Headphone Jack", NULL, "RHPOUT"},
{"LLINEIN", NULL, "Line In"},
{"RLINEIN", NULL, "Line In"},
{"MICIN", NULL, "Mic Jack"},
};
/* Digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link afeb9260_dai = {
.name = "TLV320AIC23",
.stream_name = "AIC23",
.cpu_dai_name = "atmel-ssc-dai.0",
.codec_dai_name = "tlv320aic23-hifi",
.platform_name = "atmel_pcm-audio",
.codec_name = "tlv320aic23-codec.0-001a",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &afeb9260_ops,
};
/* Audio machine driver */
static struct snd_soc_card snd_soc_machine_afeb9260 = {
.name = "AFEB9260",
.owner = THIS_MODULE,
.dai_link = &afeb9260_dai,
.num_links = 1,
.dapm_widgets = tlv320aic23_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets),
.dapm_routes = afeb9260_audio_map,
.num_dapm_routes = ARRAY_SIZE(afeb9260_audio_map),
};
static struct platform_device *afeb9260_snd_device;
static int __init afeb9260_soc_init(void)
{
int err;
struct device *dev;
if (!(machine_is_afeb9260()))
return -ENODEV;
afeb9260_snd_device = platform_device_alloc("soc-audio", -1);
if (!afeb9260_snd_device) {
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
return -ENOMEM;
}
platform_set_drvdata(afeb9260_snd_device, &snd_soc_machine_afeb9260);
err = platform_device_add(afeb9260_snd_device);
if (err)
goto err1;
dev = &afeb9260_snd_device->dev;
return 0;
err1:
platform_device_put(afeb9260_snd_device);
return err;
}
static void __exit afeb9260_soc_exit(void)
{
platform_device_unregister(afeb9260_snd_device);
}
module_init(afeb9260_soc_init);
module_exit(afeb9260_soc_exit);
MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>");
MODULE_DESCRIPTION("ALSA SoC for AFEB9260");
MODULE_LICENSE("GPL");

64
sound/soc/au1x/Kconfig Normal file
View file

@ -0,0 +1,64 @@
##
## Au1200/Au1550/Au1300 PSC + DBDMA
##
config SND_SOC_AU1XPSC
tristate "SoC Audio for Au12xx/Au13xx/Au1550"
depends on MIPS_ALCHEMY
help
This option enables support for the Programmable Serial
Controllers in AC97 and I2S mode, and the Descriptor-Based DMA
Controller (DBDMA) as found on the Au12xx/Au13xx/Au1550 SoC.
config SND_SOC_AU1XPSC_I2S
tristate
config SND_SOC_AU1XPSC_AC97
tristate
select AC97_BUS
select SND_AC97_CODEC
select SND_SOC_AC97_BUS
##
## Au1000/1500/1100 DMA + AC97C/I2SC
##
config SND_SOC_AU1XAUDIO
tristate "SoC Audio for Au1000/Au1500/Au1100"
depends on MIPS_ALCHEMY
help
This is a driver set for the AC97 unit and the
old DMA controller as found on the Au1000/Au1500/Au1100 chips.
config SND_SOC_AU1XAC97C
tristate
select AC97_BUS
select SND_AC97_CODEC
select SND_SOC_AC97_BUS
config SND_SOC_AU1XI2SC
tristate
##
## Boards
##
config SND_SOC_DB1000
tristate "DB1000 Audio support"
depends on SND_SOC_AU1XAUDIO
select SND_SOC_AU1XAC97C
select SND_SOC_AC97_CODEC
help
Select this option to enable AC97 audio on the early DB1x00 series
of boards (DB1000/DB1500/DB1100).
config SND_SOC_DB1200
tristate "DB1200/DB1300/DB1550 Audio support"
depends on SND_SOC_AU1XPSC
select SND_SOC_AU1XPSC_AC97
select SND_SOC_AC97_CODEC
select SND_SOC_WM9712
select SND_SOC_AU1XPSC_I2S
select SND_SOC_WM8731
help
Select this option to enable audio (AC97 and I2S) on the
Alchemy/AMD/RMI/NetLogic Db1200, Db1550 and Db1300 evaluation boards.
If you need Db1300 touchscreen support, you definitely want to say Y.

23
sound/soc/au1x/Makefile Normal file
View file

@ -0,0 +1,23 @@
# Au1200/Au1550 PSC audio
snd-soc-au1xpsc-dbdma-objs := dbdma2.o
snd-soc-au1xpsc-i2s-objs := psc-i2s.o
snd-soc-au1xpsc-ac97-objs := psc-ac97.o
# Au1000/1500/1100 Audio units
snd-soc-au1x-dma-objs := dma.o
snd-soc-au1x-ac97c-objs := ac97c.o
snd-soc-au1x-i2sc-objs := i2sc.o
obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o
obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o
obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o
obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o
obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o
obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
# Boards
snd-soc-db1000-objs := db1000.o
snd-soc-db1200-objs := db1200.o
obj-$(CONFIG_SND_SOC_DB1000) += snd-soc-db1000.o
obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o

348
sound/soc/au1x/ac97c.c Normal file
View file

@ -0,0 +1,348 @@
/*
* Au1000/Au1500/Au1100 AC97C controller driver for ASoC
*
* (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
*
* based on the old ALSA driver originally written by
* Charles Eidsness <charles@cooper-street.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include "psc.h"
/* register offsets and bits */
#define AC97_CONFIG 0x00
#define AC97_STATUS 0x04
#define AC97_DATA 0x08
#define AC97_CMDRESP 0x0c
#define AC97_ENABLE 0x10
#define CFG_RC(x) (((x) & 0x3ff) << 13) /* valid rx slots mask */
#define CFG_XS(x) (((x) & 0x3ff) << 3) /* valid tx slots mask */
#define CFG_SG (1 << 2) /* sync gate */
#define CFG_SN (1 << 1) /* sync control */
#define CFG_RS (1 << 0) /* acrst# control */
#define STAT_XU (1 << 11) /* tx underflow */
#define STAT_XO (1 << 10) /* tx overflow */
#define STAT_RU (1 << 9) /* rx underflow */
#define STAT_RO (1 << 8) /* rx overflow */
#define STAT_RD (1 << 7) /* codec ready */
#define STAT_CP (1 << 6) /* command pending */
#define STAT_TE (1 << 4) /* tx fifo empty */
#define STAT_TF (1 << 3) /* tx fifo full */
#define STAT_RE (1 << 1) /* rx fifo empty */
#define STAT_RF (1 << 0) /* rx fifo full */
#define CMD_SET_DATA(x) (((x) & 0xffff) << 16)
#define CMD_GET_DATA(x) ((x) & 0xffff)
#define CMD_READ (1 << 7)
#define CMD_WRITE (0 << 7)
#define CMD_IDX(x) ((x) & 0x7f)
#define EN_D (1 << 1) /* DISable bit */
#define EN_CE (1 << 0) /* clock enable bit */
/* how often to retry failed codec register reads/writes */
#define AC97_RW_RETRIES 5
#define AC97_RATES \
SNDRV_PCM_RATE_CONTINUOUS
#define AC97_FMTS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE)
/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only
* once AC97C on early Alchemy chips. The newer ones aren't so lucky.
*/
static struct au1xpsc_audio_data *ac97c_workdata;
#define ac97_to_ctx(x) ac97c_workdata
static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
{
return __raw_readl(ctx->mmio + reg);
}
static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
{
__raw_writel(v, ctx->mmio + reg);
wmb();
}
static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97,
unsigned short r)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
unsigned int tmo, retry;
unsigned long data;
data = ~0;
retry = AC97_RW_RETRIES;
do {
mutex_lock(&ctx->lock);
tmo = 5;
while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
udelay(21); /* wait an ac97 frame time */
if (!tmo) {
pr_debug("ac97rd timeout #1\n");
goto next;
}
WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ);
/* stupid errata: data is only valid for 21us, so
* poll, Forrest, poll...
*/
tmo = 0x10000;
while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
asm volatile ("nop");
data = RD(ctx, AC97_CMDRESP);
if (!tmo)
pr_debug("ac97rd timeout #2\n");
next:
mutex_unlock(&ctx->lock);
} while (--retry && !tmo);
pr_debug("AC97RD %04x %04lx %d\n", r, data, retry);
return retry ? data & 0xffff : 0xffff;
}
static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r,
unsigned short v)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
unsigned int tmo, retry;
retry = AC97_RW_RETRIES;
do {
mutex_lock(&ctx->lock);
for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
udelay(21);
if (!tmo) {
pr_debug("ac97wr timeout #1\n");
goto next;
}
WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v));
for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
udelay(21);
if (!tmo)
pr_debug("ac97wr timeout #2\n");
next:
mutex_unlock(&ctx->lock);
} while (--retry && !tmo);
pr_debug("AC97WR %04x %04x %d\n", r, v, retry);
}
static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN);
msleep(20);
WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG);
WR(ctx, AC97_CONFIG, ctx->cfg);
}
static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
int i;
WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS);
msleep(500);
WR(ctx, AC97_CONFIG, ctx->cfg);
/* wait for codec ready */
i = 50;
while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i)
msleep(20);
if (!i)
printk(KERN_ERR "ac97c: codec not ready after cold reset\n");
}
/* AC97 controller operations */
static struct snd_ac97_bus_ops ac97c_bus_ops = {
.read = au1xac97c_ac97_read,
.write = au1xac97c_ac97_write,
.reset = au1xac97c_ac97_cold_reset,
.warm_reset = au1xac97c_ac97_warm_reset,
};
static int alchemy_ac97c_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
return 0;
}
static const struct snd_soc_dai_ops alchemy_ac97c_ops = {
.startup = alchemy_ac97c_startup,
};
static int au1xac97c_dai_probe(struct snd_soc_dai *dai)
{
return ac97c_workdata ? 0 : -ENODEV;
}
static struct snd_soc_dai_driver au1xac97c_dai_driver = {
.name = "alchemy-ac97c",
.ac97_control = 1,
.probe = au1xac97c_dai_probe,
.playback = {
.rates = AC97_RATES,
.formats = AC97_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.capture = {
.rates = AC97_RATES,
.formats = AC97_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.ops = &alchemy_ac97c_ops,
};
static const struct snd_soc_component_driver au1xac97c_component = {
.name = "au1xac97c",
};
static int au1xac97c_drvprobe(struct platform_device *pdev)
{
int ret;
struct resource *iores, *dmares;
struct au1xpsc_audio_data *ctx;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->lock);
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores)
return -ENODEV;
if (!devm_request_mem_region(&pdev->dev, iores->start,
resource_size(iores),
pdev->name))
return -EBUSY;
ctx->mmio = devm_ioremap_nocache(&pdev->dev, iores->start,
resource_size(iores));
if (!ctx->mmio)
return -EBUSY;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmares)
return -EBUSY;
ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmares)
return -EBUSY;
ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;
/* switch it on */
WR(ctx, AC97_ENABLE, EN_D | EN_CE);
WR(ctx, AC97_ENABLE, EN_CE);
ctx->cfg = CFG_RC(3) | CFG_XS(3);
WR(ctx, AC97_CONFIG, ctx->cfg);
platform_set_drvdata(pdev, ctx);
ret = snd_soc_set_ac97_ops(&ac97c_bus_ops);
if (ret)
return ret;
ret = snd_soc_register_component(&pdev->dev, &au1xac97c_component,
&au1xac97c_dai_driver, 1);
if (ret)
return ret;
ac97c_workdata = ctx;
return 0;
}
static int au1xac97c_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */
ac97c_workdata = NULL; /* MDEV */
return 0;
}
#ifdef CONFIG_PM
static int au1xac97c_drvsuspend(struct device *dev)
{
struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */
return 0;
}
static int au1xac97c_drvresume(struct device *dev)
{
struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
WR(ctx, AC97_ENABLE, EN_D | EN_CE);
WR(ctx, AC97_ENABLE, EN_CE);
WR(ctx, AC97_CONFIG, ctx->cfg);
return 0;
}
static const struct dev_pm_ops au1xpscac97_pmops = {
.suspend = au1xac97c_drvsuspend,
.resume = au1xac97c_drvresume,
};
#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops)
#else
#define AU1XPSCAC97_PMOPS NULL
#endif
static struct platform_driver au1xac97c_driver = {
.driver = {
.name = "alchemy-ac97c",
.owner = THIS_MODULE,
.pm = AU1XPSCAC97_PMOPS,
},
.probe = au1xac97c_drvprobe,
.remove = au1xac97c_drvremove,
};
module_platform_driver(au1xac97c_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver");
MODULE_AUTHOR("Manuel Lauss");

65
sound/soc/au1x/db1000.c Normal file
View file

@ -0,0 +1,65 @@
/*
* DB1000/DB1500/DB1100 ASoC audio fabric support code.
*
* (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include <asm/mach-db1x00/bcsr.h>
#include "psc.h"
static struct snd_soc_dai_link db1000_ac97_dai = {
.name = "AC97",
.stream_name = "AC97 HiFi",
.codec_dai_name = "ac97-hifi",
.cpu_dai_name = "alchemy-ac97c",
.platform_name = "alchemy-pcm-dma.0",
.codec_name = "ac97-codec",
};
static struct snd_soc_card db1000_ac97 = {
.name = "DB1000_AC97",
.owner = THIS_MODULE,
.dai_link = &db1000_ac97_dai,
.num_links = 1,
};
static int db1000_audio_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &db1000_ac97;
card->dev = &pdev->dev;
return snd_soc_register_card(card);
}
static int db1000_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver db1000_audio_driver = {
.driver = {
.name = "db1000-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = db1000_audio_probe,
.remove = db1000_audio_remove,
};
module_platform_driver(db1000_audio_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DB1000/DB1500/DB1100 ASoC audio");
MODULE_AUTHOR("Manuel Lauss");

215
sound/soc/au1x/db1200.c Normal file
View file

@ -0,0 +1,215 @@
/*
* DB1200/DB1300/DB1550 ASoC audio fabric support code.
*
* (c) 2008-2011 Manuel Lauss <manuel.lauss@googlemail.com>
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include <asm/mach-au1x00/au1xxx_psc.h>
#include <asm/mach-au1x00/au1xxx_dbdma.h>
#include <asm/mach-db1x00/bcsr.h>
#include "../codecs/wm8731.h"
#include "psc.h"
static struct platform_device_id db1200_pids[] = {
{
.name = "db1200-ac97",
.driver_data = 0,
}, {
.name = "db1200-i2s",
.driver_data = 1,
}, {
.name = "db1300-ac97",
.driver_data = 2,
}, {
.name = "db1300-i2s",
.driver_data = 3,
}, {
.name = "db1550-ac97",
.driver_data = 4,
}, {
.name = "db1550-i2s",
.driver_data = 5,
},
{},
};
/*------------------------- AC97 PART ---------------------------*/
static struct snd_soc_dai_link db1200_ac97_dai = {
.name = "AC97",
.stream_name = "AC97 HiFi",
.codec_dai_name = "ac97-hifi",
.cpu_dai_name = "au1xpsc_ac97.1",
.platform_name = "au1xpsc-pcm.1",
.codec_name = "ac97-codec.1",
};
static struct snd_soc_card db1200_ac97_machine = {
.name = "DB1200_AC97",
.owner = THIS_MODULE,
.dai_link = &db1200_ac97_dai,
.num_links = 1,
};
static struct snd_soc_dai_link db1300_ac97_dai = {
.name = "AC97",
.stream_name = "AC97 HiFi",
.codec_dai_name = "wm9712-hifi",
.cpu_dai_name = "au1xpsc_ac97.1",
.platform_name = "au1xpsc-pcm.1",
.codec_name = "wm9712-codec.1",
};
static struct snd_soc_card db1300_ac97_machine = {
.name = "DB1300_AC97",
.owner = THIS_MODULE,
.dai_link = &db1300_ac97_dai,
.num_links = 1,
};
static struct snd_soc_card db1550_ac97_machine = {
.name = "DB1550_AC97",
.owner = THIS_MODULE,
.dai_link = &db1200_ac97_dai,
.num_links = 1,
};
/*------------------------- I2S PART ---------------------------*/
static int db1200_i2s_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int ret;
/* WM8731 has its own 12MHz crystal */
snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
12000000, SND_SOC_CLOCK_IN);
/* codec is bitclock and lrclk master */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
goto out;
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_LEFT_J |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
goto out;
ret = 0;
out:
return ret;
}
static struct snd_soc_ops db1200_i2s_wm8731_ops = {
.startup = db1200_i2s_startup,
};
static struct snd_soc_dai_link db1200_i2s_dai = {
.name = "WM8731",
.stream_name = "WM8731 PCM",
.codec_dai_name = "wm8731-hifi",
.cpu_dai_name = "au1xpsc_i2s.1",
.platform_name = "au1xpsc-pcm.1",
.codec_name = "wm8731.0-001b",
.ops = &db1200_i2s_wm8731_ops,
};
static struct snd_soc_card db1200_i2s_machine = {
.name = "DB1200_I2S",
.owner = THIS_MODULE,
.dai_link = &db1200_i2s_dai,
.num_links = 1,
};
static struct snd_soc_dai_link db1300_i2s_dai = {
.name = "WM8731",
.stream_name = "WM8731 PCM",
.codec_dai_name = "wm8731-hifi",
.cpu_dai_name = "au1xpsc_i2s.2",
.platform_name = "au1xpsc-pcm.2",
.codec_name = "wm8731.0-001b",
.ops = &db1200_i2s_wm8731_ops,
};
static struct snd_soc_card db1300_i2s_machine = {
.name = "DB1300_I2S",
.owner = THIS_MODULE,
.dai_link = &db1300_i2s_dai,
.num_links = 1,
};
static struct snd_soc_dai_link db1550_i2s_dai = {
.name = "WM8731",
.stream_name = "WM8731 PCM",
.codec_dai_name = "wm8731-hifi",
.cpu_dai_name = "au1xpsc_i2s.3",
.platform_name = "au1xpsc-pcm.3",
.codec_name = "wm8731.0-001b",
.ops = &db1200_i2s_wm8731_ops,
};
static struct snd_soc_card db1550_i2s_machine = {
.name = "DB1550_I2S",
.owner = THIS_MODULE,
.dai_link = &db1550_i2s_dai,
.num_links = 1,
};
/*------------------------- COMMON PART ---------------------------*/
static struct snd_soc_card *db1200_cards[] = {
&db1200_ac97_machine,
&db1200_i2s_machine,
&db1300_ac97_machine,
&db1300_i2s_machine,
&db1550_ac97_machine,
&db1550_i2s_machine,
};
static int db1200_audio_probe(struct platform_device *pdev)
{
const struct platform_device_id *pid = platform_get_device_id(pdev);
struct snd_soc_card *card;
card = db1200_cards[pid->driver_data];
card->dev = &pdev->dev;
return snd_soc_register_card(card);
}
static int db1200_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver db1200_audio_driver = {
.driver = {
.name = "db1200-ac97",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.id_table = db1200_pids,
.probe = db1200_audio_probe,
.remove = db1200_audio_remove,
};
module_platform_driver(db1200_audio_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DB1200/DB1300/DB1550 ASoC audio support");
MODULE_AUTHOR("Manuel Lauss");

376
sound/soc/au1x/dbdma2.c Normal file
View file

@ -0,0 +1,376 @@
/*
* Au12x0/Au1550 PSC ALSA ASoC audio support.
*
* (c) 2007-2008 MSC Vertriebsges.m.b.H.,
* Manuel Lauss <manuel.lauss@gmail.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.
*
* DMA glue for Au1x-PSC audio.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include <asm/mach-au1x00/au1xxx_dbdma.h>
#include <asm/mach-au1x00/au1xxx_psc.h>
#include "psc.h"
/*#define PCM_DEBUG*/
#define MSG(x...) printk(KERN_INFO "au1xpsc_pcm: " x)
#ifdef PCM_DEBUG
#define DBG MSG
#else
#define DBG(x...) do {} while (0)
#endif
struct au1xpsc_audio_dmadata {
/* DDMA control data */
unsigned int ddma_id; /* DDMA direction ID for this PSC */
u32 ddma_chan; /* DDMA context */
/* PCM context (for irq handlers) */
struct snd_pcm_substream *substream;
unsigned long curr_period; /* current segment DDMA is working on */
unsigned long q_period; /* queue period(s) */
dma_addr_t dma_area; /* address of queued DMA area */
dma_addr_t dma_area_s; /* start address of DMA area */
unsigned long pos; /* current byte position being played */
unsigned long periods; /* number of SG segments in total */
unsigned long period_bytes; /* size in bytes of one SG segment */
/* runtime data */
int msbits;
};
/*
* These settings are somewhat okay, at least on my machine audio plays
* almost skip-free. Especially the 64kB buffer seems to help a LOT.
*/
#define AU1XPSC_PERIOD_MIN_BYTES 1024
#define AU1XPSC_BUFFER_MIN_BYTES 65536
/* PCM hardware DMA capabilities - platform specific */
static const struct snd_pcm_hardware au1xpsc_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
.period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES,
.period_bytes_max = 4096 * 1024 - 1,
.periods_min = 2,
.periods_max = 4096, /* 2 to as-much-as-you-like */
.buffer_bytes_max = 4096 * 1024 - 1,
.fifo_size = 16, /* fifo entries of AC97/I2S PSC */
};
static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd)
{
au1xxx_dbdma_put_source(cd->ddma_chan, cd->dma_area,
cd->period_bytes, DDMA_FLAGS_IE);
/* update next-to-queue period */
++cd->q_period;
cd->dma_area += cd->period_bytes;
if (cd->q_period >= cd->periods) {
cd->q_period = 0;
cd->dma_area = cd->dma_area_s;
}
}
static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd)
{
au1xxx_dbdma_put_dest(cd->ddma_chan, cd->dma_area,
cd->period_bytes, DDMA_FLAGS_IE);
/* update next-to-queue period */
++cd->q_period;
cd->dma_area += cd->period_bytes;
if (cd->q_period >= cd->periods) {
cd->q_period = 0;
cd->dma_area = cd->dma_area_s;
}
}
static void au1x_pcm_dmatx_cb(int irq, void *dev_id)
{
struct au1xpsc_audio_dmadata *cd = dev_id;
cd->pos += cd->period_bytes;
if (++cd->curr_period >= cd->periods) {
cd->pos = 0;
cd->curr_period = 0;
}
snd_pcm_period_elapsed(cd->substream);
au1x_pcm_queue_tx(cd);
}
static void au1x_pcm_dmarx_cb(int irq, void *dev_id)
{
struct au1xpsc_audio_dmadata *cd = dev_id;
cd->pos += cd->period_bytes;
if (++cd->curr_period >= cd->periods) {
cd->pos = 0;
cd->curr_period = 0;
}
snd_pcm_period_elapsed(cd->substream);
au1x_pcm_queue_rx(cd);
}
static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd)
{
if (pcd->ddma_chan) {
au1xxx_dbdma_stop(pcd->ddma_chan);
au1xxx_dbdma_reset(pcd->ddma_chan);
au1xxx_dbdma_chan_free(pcd->ddma_chan);
pcd->ddma_chan = 0;
pcd->msbits = 0;
}
}
/* in case of missing DMA ring or changed TX-source / RX-dest bit widths,
* allocate (or reallocate) a 2-descriptor DMA ring with bit depth according
* to ALSA-supplied sample depth. This is due to limitations in the dbdma api
* (cannot adjust source/dest widths of already allocated descriptor ring).
*/
static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd,
int stype, int msbits)
{
/* DMA only in 8/16/32 bit widths */
if (msbits == 24)
msbits = 32;
/* check current config: correct bits and descriptors allocated? */
if ((pcd->ddma_chan) && (msbits == pcd->msbits))
goto out; /* all ok! */
au1x_pcm_dbdma_free(pcd);
if (stype == SNDRV_PCM_STREAM_CAPTURE)
pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id,
DSCR_CMD0_ALWAYS,
au1x_pcm_dmarx_cb, (void *)pcd);
else
pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS,
pcd->ddma_id,
au1x_pcm_dmatx_cb, (void *)pcd);
if (!pcd->ddma_chan)
return -ENOMEM;
au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits);
au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2);
pcd->msbits = msbits;
au1xxx_dbdma_stop(pcd->ddma_chan);
au1xxx_dbdma_reset(pcd->ddma_chan);
out:
return 0;
}
static inline struct au1xpsc_audio_dmadata *to_dmadata(struct snd_pcm_substream *ss)
{
struct snd_soc_pcm_runtime *rtd = ss->private_data;
struct au1xpsc_audio_dmadata *pcd =
snd_soc_platform_get_drvdata(rtd->platform);
return &pcd[ss->stream];
}
static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct au1xpsc_audio_dmadata *pcd;
int stype, ret;
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (ret < 0)
goto out;
stype = substream->stream;
pcd = to_dmadata(substream);
DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d "
"runtime->min_align %d\n",
(unsigned long)runtime->dma_area,
(unsigned long)runtime->dma_addr, runtime->dma_bytes,
runtime->min_align);
DBG("bits %d frags %d frag_bytes %d is_rx %d\n", params->msbits,
params_periods(params), params_period_bytes(params), stype);
ret = au1x_pcm_dbdma_realloc(pcd, stype, params->msbits);
if (ret) {
MSG("DDMA channel (re)alloc failed!\n");
goto out;
}
pcd->substream = substream;
pcd->period_bytes = params_period_bytes(params);
pcd->periods = params_periods(params);
pcd->dma_area_s = pcd->dma_area = runtime->dma_addr;
pcd->q_period = 0;
pcd->curr_period = 0;
pcd->pos = 0;
ret = 0;
out:
return ret;
}
static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream)
{
snd_pcm_lib_free_pages(substream);
return 0;
}
static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)
{
struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream);
au1xxx_dbdma_reset(pcd->ddma_chan);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
au1x_pcm_queue_rx(pcd);
au1x_pcm_queue_rx(pcd);
} else {
au1x_pcm_queue_tx(pcd);
au1x_pcm_queue_tx(pcd);
}
return 0;
}
static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
u32 c = to_dmadata(substream)->ddma_chan;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
au1xxx_dbdma_start(c);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
au1xxx_dbdma_stop(c);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t
au1xpsc_pcm_pointer(struct snd_pcm_substream *substream)
{
return bytes_to_frames(substream->runtime, to_dmadata(substream)->pos);
}
static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)
{
struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream);
struct snd_soc_pcm_runtime *rtd = substream->private_data;
int stype = substream->stream, *dmaids;
dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
if (!dmaids)
return -ENODEV; /* whoa, has ordering changed? */
pcd->ddma_id = dmaids[stype];
snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware);
return 0;
}
static int au1xpsc_pcm_close(struct snd_pcm_substream *substream)
{
au1x_pcm_dbdma_free(to_dmadata(substream));
return 0;
}
static struct snd_pcm_ops au1xpsc_pcm_ops = {
.open = au1xpsc_pcm_open,
.close = au1xpsc_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = au1xpsc_pcm_hw_params,
.hw_free = au1xpsc_pcm_hw_free,
.prepare = au1xpsc_pcm_prepare,
.trigger = au1xpsc_pcm_trigger,
.pointer = au1xpsc_pcm_pointer,
};
static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
snd_pcm_lib_preallocate_free_for_all(pcm);
}
static int au1xpsc_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1);
return 0;
}
/* au1xpsc audio platform */
static struct snd_soc_platform_driver au1xpsc_soc_platform = {
.ops = &au1xpsc_pcm_ops,
.pcm_new = au1xpsc_pcm_new,
.pcm_free = au1xpsc_pcm_free_dma_buffers,
};
static int au1xpsc_pcm_drvprobe(struct platform_device *pdev)
{
struct au1xpsc_audio_dmadata *dmadata;
dmadata = devm_kzalloc(&pdev->dev,
2 * sizeof(struct au1xpsc_audio_dmadata),
GFP_KERNEL);
if (!dmadata)
return -ENOMEM;
platform_set_drvdata(pdev, dmadata);
return snd_soc_register_platform(&pdev->dev, &au1xpsc_soc_platform);
}
static int au1xpsc_pcm_drvremove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static struct platform_driver au1xpsc_pcm_driver = {
.driver = {
.name = "au1xpsc-pcm",
.owner = THIS_MODULE,
},
.probe = au1xpsc_pcm_drvprobe,
.remove = au1xpsc_pcm_drvremove,
};
module_platform_driver(au1xpsc_pcm_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver");
MODULE_AUTHOR("Manuel Lauss");

344
sound/soc/au1x/dma.c Normal file
View file

@ -0,0 +1,344 @@
/*
* Au1000/Au1500/Au1100 Audio DMA support.
*
* (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
*
* copied almost verbatim from the old ALSA driver, written by
* Charles Eidsness <charles@cooper-street.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include <asm/mach-au1x00/au1000_dma.h>
#include "psc.h"
struct pcm_period {
u32 start;
u32 relative_end; /* relative to start of buffer */
struct pcm_period *next;
};
struct audio_stream {
struct snd_pcm_substream *substream;
int dma;
struct pcm_period *buffer;
unsigned int period_size;
unsigned int periods;
};
struct alchemy_pcm_ctx {
struct audio_stream stream[2]; /* playback & capture */
};
static void au1000_release_dma_link(struct audio_stream *stream)
{
struct pcm_period *pointer;
struct pcm_period *pointer_next;
stream->period_size = 0;
stream->periods = 0;
pointer = stream->buffer;
if (!pointer)
return;
do {
pointer_next = pointer->next;
kfree(pointer);
pointer = pointer_next;
} while (pointer != stream->buffer);
stream->buffer = NULL;
}
static int au1000_setup_dma_link(struct audio_stream *stream,
unsigned int period_bytes,
unsigned int periods)
{
struct snd_pcm_substream *substream = stream->substream;
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_period *pointer;
unsigned long dma_start;
int i;
dma_start = virt_to_phys(runtime->dma_area);
if (stream->period_size == period_bytes &&
stream->periods == periods)
return 0; /* not changed */
au1000_release_dma_link(stream);
stream->period_size = period_bytes;
stream->periods = periods;
stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL);
if (!stream->buffer)
return -ENOMEM;
pointer = stream->buffer;
for (i = 0; i < periods; i++) {
pointer->start = (u32)(dma_start + (i * period_bytes));
pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);
if (i < periods - 1) {
pointer->next = kmalloc(sizeof(struct pcm_period),
GFP_KERNEL);
if (!pointer->next) {
au1000_release_dma_link(stream);
return -ENOMEM;
}
pointer = pointer->next;
}
}
pointer->next = stream->buffer;
return 0;
}
static void au1000_dma_stop(struct audio_stream *stream)
{
if (stream->buffer)
disable_dma(stream->dma);
}
static void au1000_dma_start(struct audio_stream *stream)
{
if (!stream->buffer)
return;
init_dma(stream->dma);
if (get_dma_active_buffer(stream->dma) == 0) {
clear_dma_done0(stream->dma);
set_dma_addr0(stream->dma, stream->buffer->start);
set_dma_count0(stream->dma, stream->period_size >> 1);
set_dma_addr1(stream->dma, stream->buffer->next->start);
set_dma_count1(stream->dma, stream->period_size >> 1);
} else {
clear_dma_done1(stream->dma);
set_dma_addr1(stream->dma, stream->buffer->start);
set_dma_count1(stream->dma, stream->period_size >> 1);
set_dma_addr0(stream->dma, stream->buffer->next->start);
set_dma_count0(stream->dma, stream->period_size >> 1);
}
enable_dma_buffers(stream->dma);
start_dma(stream->dma);
}
static irqreturn_t au1000_dma_interrupt(int irq, void *ptr)
{
struct audio_stream *stream = (struct audio_stream *)ptr;
struct snd_pcm_substream *substream = stream->substream;
switch (get_dma_buffer_done(stream->dma)) {
case DMA_D0:
stream->buffer = stream->buffer->next;
clear_dma_done0(stream->dma);
set_dma_addr0(stream->dma, stream->buffer->next->start);
set_dma_count0(stream->dma, stream->period_size >> 1);
enable_dma_buffer0(stream->dma);
break;
case DMA_D1:
stream->buffer = stream->buffer->next;
clear_dma_done1(stream->dma);
set_dma_addr1(stream->dma, stream->buffer->next->start);
set_dma_count1(stream->dma, stream->period_size >> 1);
enable_dma_buffer1(stream->dma);
break;
case (DMA_D0 | DMA_D1):
pr_debug("DMA %d missed interrupt.\n", stream->dma);
au1000_dma_stop(stream);
au1000_dma_start(stream);
break;
case (~DMA_D0 & ~DMA_D1):
pr_debug("DMA %d empty irq.\n", stream->dma);
}
snd_pcm_period_elapsed(substream);
return IRQ_HANDLED;
}
static const struct snd_pcm_hardware alchemy_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
.period_bytes_min = 1024,
.period_bytes_max = 16 * 1024 - 1,
.periods_min = 4,
.periods_max = 255,
.buffer_bytes_max = 128 * 1024,
.fifo_size = 16,
};
static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss)
{
struct snd_soc_pcm_runtime *rtd = ss->private_data;
return snd_soc_platform_get_drvdata(rtd->platform);
}
static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss)
{
struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss);
return &(ctx->stream[ss->stream]);
}
static int alchemy_pcm_open(struct snd_pcm_substream *substream)
{
struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
struct snd_soc_pcm_runtime *rtd = substream->private_data;
int *dmaids, s = substream->stream;
char *name;
dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
if (!dmaids)
return -ENODEV; /* whoa, has ordering changed? */
/* DMA setup */
name = (s == SNDRV_PCM_STREAM_PLAYBACK) ? "audio-tx" : "audio-rx";
ctx->stream[s].dma = request_au1000_dma(dmaids[s], name,
au1000_dma_interrupt, 0,
&ctx->stream[s]);
set_dma_mode(ctx->stream[s].dma,
get_dma_mode(ctx->stream[s].dma) & ~DMA_NC);
ctx->stream[s].substream = substream;
ctx->stream[s].buffer = NULL;
snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware);
return 0;
}
static int alchemy_pcm_close(struct snd_pcm_substream *substream)
{
struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
int stype = substream->stream;
ctx->stream[stype].substream = NULL;
free_au1000_dma(ctx->stream[stype].dma);
return 0;
}
static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct audio_stream *stream = ss_to_as(substream);
int err;
err = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
if (err < 0)
return err;
err = au1000_setup_dma_link(stream,
params_period_bytes(hw_params),
params_periods(hw_params));
if (err)
snd_pcm_lib_free_pages(substream);
return err;
}
static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream)
{
struct audio_stream *stream = ss_to_as(substream);
au1000_release_dma_link(stream);
return snd_pcm_lib_free_pages(substream);
}
static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct audio_stream *stream = ss_to_as(substream);
int err = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
au1000_dma_start(stream);
break;
case SNDRV_PCM_TRIGGER_STOP:
au1000_dma_stop(stream);
break;
default:
err = -EINVAL;
break;
}
return err;
}
static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss)
{
struct audio_stream *stream = ss_to_as(ss);
long location;
location = get_dma_residue(stream->dma);
location = stream->buffer->relative_end - location;
if (location == -1)
location = 0;
return bytes_to_frames(ss->runtime, location);
}
static struct snd_pcm_ops alchemy_pcm_ops = {
.open = alchemy_pcm_open,
.close = alchemy_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = alchemy_pcm_hw_params,
.hw_free = alchemy_pcm_hw_free,
.trigger = alchemy_pcm_trigger,
.pointer = alchemy_pcm_pointer,
};
static void alchemy_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
snd_pcm_lib_preallocate_free_for_all(pcm);
}
static int alchemy_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_pcm *pcm = rtd->pcm;
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1);
return 0;
}
static struct snd_soc_platform_driver alchemy_pcm_soc_platform = {
.ops = &alchemy_pcm_ops,
.pcm_new = alchemy_pcm_new,
.pcm_free = alchemy_pcm_free_dma_buffers,
};
static int alchemy_pcm_drvprobe(struct platform_device *pdev)
{
struct alchemy_pcm_ctx *ctx;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
platform_set_drvdata(pdev, ctx);
return snd_soc_register_platform(&pdev->dev, &alchemy_pcm_soc_platform);
}
static int alchemy_pcm_drvremove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static struct platform_driver alchemy_pcmdma_driver = {
.driver = {
.name = "alchemy-pcm-dma",
.owner = THIS_MODULE,
},
.probe = alchemy_pcm_drvprobe,
.remove = alchemy_pcm_drvremove,
};
module_platform_driver(alchemy_pcmdma_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver");
MODULE_AUTHOR("Manuel Lauss");

324
sound/soc/au1x/i2sc.c Normal file
View file

@ -0,0 +1,324 @@
/*
* Au1000/Au1500/Au1100 I2S controller driver for ASoC
*
* (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
*
* Note: clock supplied to the I2S controller must be 256x samplerate.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include "psc.h"
#define I2S_RXTX 0x00
#define I2S_CFG 0x04
#define I2S_ENABLE 0x08
#define CFG_XU (1 << 25) /* tx underflow */
#define CFG_XO (1 << 24)
#define CFG_RU (1 << 23)
#define CFG_RO (1 << 22)
#define CFG_TR (1 << 21)
#define CFG_TE (1 << 20)
#define CFG_TF (1 << 19)
#define CFG_RR (1 << 18)
#define CFG_RF (1 << 17)
#define CFG_ICK (1 << 12) /* clock invert */
#define CFG_PD (1 << 11) /* set to make I2SDIO INPUT */
#define CFG_LB (1 << 10) /* loopback */
#define CFG_IC (1 << 9) /* word select invert */
#define CFG_FM_I2S (0 << 7) /* I2S format */
#define CFG_FM_LJ (1 << 7) /* left-justified */
#define CFG_FM_RJ (2 << 7) /* right-justified */
#define CFG_FM_MASK (3 << 7)
#define CFG_TN (1 << 6) /* tx fifo en */
#define CFG_RN (1 << 5) /* rx fifo en */
#define CFG_SZ_8 (0x08)
#define CFG_SZ_16 (0x10)
#define CFG_SZ_18 (0x12)
#define CFG_SZ_20 (0x14)
#define CFG_SZ_24 (0x18)
#define CFG_SZ_MASK (0x1f)
#define EN_D (1 << 1) /* DISable */
#define EN_CE (1 << 0) /* clock enable */
/* only limited by clock generator and board design */
#define AU1XI2SC_RATES \
SNDRV_PCM_RATE_CONTINUOUS
#define AU1XI2SC_FMTS \
(SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \
SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \
SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE | \
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \
SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \
SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE | \
0)
static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
{
return __raw_readl(ctx->mmio + reg);
}
static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
{
__raw_writel(v, ctx->mmio + reg);
wmb();
}
static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long c;
int ret;
ret = -EINVAL;
c = ctx->cfg;
c &= ~CFG_FM_MASK;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
c |= CFG_FM_I2S;
break;
case SND_SOC_DAIFMT_MSB:
c |= CFG_FM_RJ;
break;
case SND_SOC_DAIFMT_LSB:
c |= CFG_FM_LJ;
break;
default:
goto out;
}
c &= ~(CFG_IC | CFG_ICK); /* IB-IF */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
c |= CFG_IC | CFG_ICK;
break;
case SND_SOC_DAIFMT_NB_IF:
c |= CFG_IC;
break;
case SND_SOC_DAIFMT_IB_NF:
c |= CFG_ICK;
break;
case SND_SOC_DAIFMT_IB_IF:
break;
default:
goto out;
}
/* I2S controller only supports master */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */
break;
default:
goto out;
}
ret = 0;
ctx->cfg = c;
out:
return ret;
}
static int au1xi2s_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
int stype = SUBSTREAM_TYPE(substream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
/* power up */
WR(ctx, I2S_ENABLE, EN_D | EN_CE);
WR(ctx, I2S_ENABLE, EN_CE);
ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN;
WR(ctx, I2S_CFG, ctx->cfg);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN);
WR(ctx, I2S_CFG, ctx->cfg);
WR(ctx, I2S_ENABLE, EN_D); /* power off */
break;
default:
return -EINVAL;
}
return 0;
}
static unsigned long msbits_to_reg(int msbits)
{
switch (msbits) {
case 8:
return CFG_SZ_8;
case 16:
return CFG_SZ_16;
case 18:
return CFG_SZ_18;
case 20:
return CFG_SZ_20;
case 24:
return CFG_SZ_24;
}
return 0;
}
static int au1xi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
unsigned long v;
v = msbits_to_reg(params->msbits);
if (!v)
return -EINVAL;
ctx->cfg &= ~CFG_SZ_MASK;
ctx->cfg |= v;
return 0;
}
static int au1xi2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
return 0;
}
static const struct snd_soc_dai_ops au1xi2s_dai_ops = {
.startup = au1xi2s_startup,
.trigger = au1xi2s_trigger,
.hw_params = au1xi2s_hw_params,
.set_fmt = au1xi2s_set_fmt,
};
static struct snd_soc_dai_driver au1xi2s_dai_driver = {
.symmetric_rates = 1,
.playback = {
.rates = AU1XI2SC_RATES,
.formats = AU1XI2SC_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.capture = {
.rates = AU1XI2SC_RATES,
.formats = AU1XI2SC_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.ops = &au1xi2s_dai_ops,
};
static const struct snd_soc_component_driver au1xi2s_component = {
.name = "au1xi2s",
};
static int au1xi2s_drvprobe(struct platform_device *pdev)
{
struct resource *iores, *dmares;
struct au1xpsc_audio_data *ctx;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores)
return -ENODEV;
if (!devm_request_mem_region(&pdev->dev, iores->start,
resource_size(iores),
pdev->name))
return -EBUSY;
ctx->mmio = devm_ioremap_nocache(&pdev->dev, iores->start,
resource_size(iores));
if (!ctx->mmio)
return -EBUSY;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmares)
return -EBUSY;
ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmares)
return -EBUSY;
ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;
platform_set_drvdata(pdev, ctx);
return snd_soc_register_component(&pdev->dev, &au1xi2s_component,
&au1xi2s_dai_driver, 1);
}
static int au1xi2s_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */
return 0;
}
#ifdef CONFIG_PM
static int au1xi2s_drvsuspend(struct device *dev)
{
struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */
return 0;
}
static int au1xi2s_drvresume(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops au1xi2sc_pmops = {
.suspend = au1xi2s_drvsuspend,
.resume = au1xi2s_drvresume,
};
#define AU1XI2SC_PMOPS (&au1xi2sc_pmops)
#else
#define AU1XI2SC_PMOPS NULL
#endif
static struct platform_driver au1xi2s_driver = {
.driver = {
.name = "alchemy-i2sc",
.owner = THIS_MODULE,
.pm = AU1XI2SC_PMOPS,
},
.probe = au1xi2s_drvprobe,
.remove = au1xi2s_drvremove,
};
module_platform_driver(au1xi2s_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver");
MODULE_AUTHOR("Manuel Lauss");

505
sound/soc/au1x/psc-ac97.c Normal file
View file

@ -0,0 +1,505 @@
/*
* Au12x0/Au1550 PSC ALSA ASoC audio support.
*
* (c) 2007-2009 MSC Vertriebsges.m.b.H.,
* Manuel Lauss <manuel.lauss@gmail.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.
*
* Au1xxx-PSC AC97 glue.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/suspend.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include <asm/mach-au1x00/au1xxx_psc.h>
#include "psc.h"
/* how often to retry failed codec register reads/writes */
#define AC97_RW_RETRIES 5
#define AC97_DIR \
(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
#define AC97_RATES \
SNDRV_PCM_RATE_8000_48000
#define AC97_FMTS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE)
#define AC97PCR_START(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TS : PSC_AC97PCR_RS)
#define AC97PCR_STOP(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TP : PSC_AC97PCR_RP)
#define AC97PCR_CLRFIFO(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TC : PSC_AC97PCR_RC)
#define AC97STAT_BUSY(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97STAT_TB : PSC_AC97STAT_RB)
/* instance data. There can be only one, MacLeod!!!! */
static struct au1xpsc_audio_data *au1xpsc_ac97_workdata;
#if 0
/* this could theoretically work, but ac97->bus->card->private_data can be NULL
* when snd_ac97_mixer() is called; I don't know if the rest further down the
* chain are always valid either.
*/
static inline struct au1xpsc_audio_data *ac97_to_pscdata(struct snd_ac97 *x)
{
struct snd_soc_card *c = x->bus->card->private_data;
return snd_soc_dai_get_drvdata(c->rtd->cpu_dai);
}
#else
#define ac97_to_pscdata(x) au1xpsc_ac97_workdata
#endif
/* AC97 controller reads codec register */
static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
unsigned short reg)
{
struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
unsigned short retry, tmo;
unsigned long data;
__raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
wmb(); /* drain writebuffer */
retry = AC97_RW_RETRIES;
do {
mutex_lock(&pscdata->lock);
__raw_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg),
AC97_CDC(pscdata));
wmb(); /* drain writebuffer */
tmo = 20;
do {
udelay(21);
if (__raw_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)
break;
} while (--tmo);
data = __raw_readl(AC97_CDC(pscdata));
__raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
wmb(); /* drain writebuffer */
mutex_unlock(&pscdata->lock);
if (reg != ((data >> 16) & 0x7f))
tmo = 1; /* wrong register, try again */
} while (--retry && !tmo);
return retry ? data & 0xffff : 0xffff;
}
/* AC97 controller writes to codec register */
static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short val)
{
struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
unsigned int tmo, retry;
__raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
wmb(); /* drain writebuffer */
retry = AC97_RW_RETRIES;
do {
mutex_lock(&pscdata->lock);
__raw_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff),
AC97_CDC(pscdata));
wmb(); /* drain writebuffer */
tmo = 20;
do {
udelay(21);
if (__raw_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)
break;
} while (--tmo);
__raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
wmb(); /* drain writebuffer */
mutex_unlock(&pscdata->lock);
} while (--retry && !tmo);
}
/* AC97 controller asserts a warm reset */
static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97)
{
struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
__raw_writel(PSC_AC97RST_SNC, AC97_RST(pscdata));
wmb(); /* drain writebuffer */
msleep(10);
__raw_writel(0, AC97_RST(pscdata));
wmb(); /* drain writebuffer */
}
static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
{
struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
int i;
/* disable PSC during cold reset */
__raw_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
wmb(); /* drain writebuffer */
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata));
wmb(); /* drain writebuffer */
/* issue cold reset */
__raw_writel(PSC_AC97RST_RST, AC97_RST(pscdata));
wmb(); /* drain writebuffer */
msleep(500);
__raw_writel(0, AC97_RST(pscdata));
wmb(); /* drain writebuffer */
/* enable PSC */
__raw_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
wmb(); /* drain writebuffer */
/* wait for PSC to indicate it's ready */
i = 1000;
while (!((__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i))
msleep(1);
if (i == 0) {
printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n");
return;
}
/* enable the ac97 function */
__raw_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
wmb(); /* drain writebuffer */
/* wait for AC97 core to become ready */
i = 1000;
while (!((__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i))
msleep(1);
if (i == 0)
printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n");
}
/* AC97 controller operations */
static struct snd_ac97_bus_ops psc_ac97_ops = {
.read = au1xpsc_ac97_read,
.write = au1xpsc_ac97_write,
.reset = au1xpsc_ac97_cold_reset,
.warm_reset = au1xpsc_ac97_warm_reset,
};
static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
unsigned long r, ro, stat;
int chans, t, stype = substream->stream;
chans = params_channels(params);
r = ro = __raw_readl(AC97_CFG(pscdata));
stat = __raw_readl(AC97_STAT(pscdata));
/* already active? */
if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) {
/* reject parameters not currently set up */
if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) ||
(pscdata->rate != params_rate(params)))
return -EINVAL;
} else {
/* set sample bitdepth: REG[24:21]=(BITS-2)/2 */
r &= ~PSC_AC97CFG_LEN_MASK;
r |= PSC_AC97CFG_SET_LEN(params->msbits);
/* channels: enable slots for front L/R channel */
if (stype == SNDRV_PCM_STREAM_PLAYBACK) {
r &= ~PSC_AC97CFG_TXSLOT_MASK;
r |= PSC_AC97CFG_TXSLOT_ENA(3);
r |= PSC_AC97CFG_TXSLOT_ENA(4);
} else {
r &= ~PSC_AC97CFG_RXSLOT_MASK;
r |= PSC_AC97CFG_RXSLOT_ENA(3);
r |= PSC_AC97CFG_RXSLOT_ENA(4);
}
/* do we need to poke the hardware? */
if (!(r ^ ro))
goto out;
/* ac97 engine is about to be disabled */
mutex_lock(&pscdata->lock);
/* disable AC97 device controller first... */
__raw_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
wmb(); /* drain writebuffer */
/* ...wait for it... */
t = 100;
while ((__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR) && --t)
msleep(1);
if (!t)
printk(KERN_ERR "PSC-AC97: can't disable!\n");
/* ...write config... */
__raw_writel(r, AC97_CFG(pscdata));
wmb(); /* drain writebuffer */
/* ...enable the AC97 controller again... */
__raw_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
wmb(); /* drain writebuffer */
/* ...and wait for ready bit */
t = 100;
while ((!(__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && --t)
msleep(1);
if (!t)
printk(KERN_ERR "PSC-AC97: can't enable!\n");
mutex_unlock(&pscdata->lock);
pscdata->cfg = r;
pscdata->rate = params_rate(params);
}
out:
return 0;
}
static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
int ret, stype = substream->stream;
ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
__raw_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
wmb(); /* drain writebuffer */
__raw_writel(AC97PCR_START(stype), AC97_PCR(pscdata));
wmb(); /* drain writebuffer */
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
__raw_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata));
wmb(); /* drain writebuffer */
while (__raw_readl(AC97_STAT(pscdata)) & AC97STAT_BUSY(stype))
asm volatile ("nop");
__raw_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
wmb(); /* drain writebuffer */
break;
default:
ret = -EINVAL;
}
return ret;
}
static int au1xpsc_ac97_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]);
return 0;
}
static int au1xpsc_ac97_probe(struct snd_soc_dai *dai)
{
return au1xpsc_ac97_workdata ? 0 : -ENODEV;
}
static const struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = {
.startup = au1xpsc_ac97_startup,
.trigger = au1xpsc_ac97_trigger,
.hw_params = au1xpsc_ac97_hw_params,
};
static const struct snd_soc_dai_driver au1xpsc_ac97_dai_template = {
.ac97_control = 1,
.probe = au1xpsc_ac97_probe,
.playback = {
.rates = AC97_RATES,
.formats = AC97_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.capture = {
.rates = AC97_RATES,
.formats = AC97_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.ops = &au1xpsc_ac97_dai_ops,
};
static const struct snd_soc_component_driver au1xpsc_ac97_component = {
.name = "au1xpsc-ac97",
};
static int au1xpsc_ac97_drvprobe(struct platform_device *pdev)
{
int ret;
struct resource *iores, *dmares;
unsigned long sel;
struct au1xpsc_audio_data *wd;
wd = devm_kzalloc(&pdev->dev, sizeof(struct au1xpsc_audio_data),
GFP_KERNEL);
if (!wd)
return -ENOMEM;
mutex_init(&wd->lock);
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
wd->mmio = devm_ioremap_resource(&pdev->dev, iores);
if (IS_ERR(wd->mmio))
return PTR_ERR(wd->mmio);
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmares)
return -EBUSY;
wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmares)
return -EBUSY;
wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;
/* configuration: max dma trigger threshold, enable ac97 */
wd->cfg = PSC_AC97CFG_RT_FIFO8 | PSC_AC97CFG_TT_FIFO8 |
PSC_AC97CFG_DE_ENABLE;
/* preserve PSC clock source set up by platform */
sel = __raw_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
__raw_writel(0, PSC_SEL(wd));
wmb(); /* drain writebuffer */
__raw_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(wd));
wmb(); /* drain writebuffer */
/* name the DAI like this device instance ("au1xpsc-ac97.PSCINDEX") */
memcpy(&wd->dai_drv, &au1xpsc_ac97_dai_template,
sizeof(struct snd_soc_dai_driver));
wd->dai_drv.name = dev_name(&pdev->dev);
platform_set_drvdata(pdev, wd);
ret = snd_soc_set_ac97_ops(&psc_ac97_ops);
if (ret)
return ret;
ret = snd_soc_register_component(&pdev->dev, &au1xpsc_ac97_component,
&wd->dai_drv, 1);
if (ret)
return ret;
au1xpsc_ac97_workdata = wd;
return 0;
}
static int au1xpsc_ac97_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
/* disable PSC completely */
__raw_writel(0, AC97_CFG(wd));
wmb(); /* drain writebuffer */
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
au1xpsc_ac97_workdata = NULL; /* MDEV */
return 0;
}
#ifdef CONFIG_PM
static int au1xpsc_ac97_drvsuspend(struct device *dev)
{
struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
/* save interesting registers and disable PSC */
wd->pm[0] = __raw_readl(PSC_SEL(wd));
__raw_writel(0, AC97_CFG(wd));
wmb(); /* drain writebuffer */
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
return 0;
}
static int au1xpsc_ac97_drvresume(struct device *dev)
{
struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
/* restore PSC clock config */
__raw_writel(wd->pm[0] | PSC_SEL_PS_AC97MODE, PSC_SEL(wd));
wmb(); /* drain writebuffer */
/* after this point the ac97 core will cold-reset the codec.
* During cold-reset the PSC is reinitialized and the last
* configuration set up in hw_params() is restored.
*/
return 0;
}
static struct dev_pm_ops au1xpscac97_pmops = {
.suspend = au1xpsc_ac97_drvsuspend,
.resume = au1xpsc_ac97_drvresume,
};
#define AU1XPSCAC97_PMOPS &au1xpscac97_pmops
#else
#define AU1XPSCAC97_PMOPS NULL
#endif
static struct platform_driver au1xpsc_ac97_driver = {
.driver = {
.name = "au1xpsc_ac97",
.owner = THIS_MODULE,
.pm = AU1XPSCAC97_PMOPS,
},
.probe = au1xpsc_ac97_drvprobe,
.remove = au1xpsc_ac97_drvremove,
};
module_platform_driver(au1xpsc_ac97_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver");
MODULE_AUTHOR("Manuel Lauss");

433
sound/soc/au1x/psc-i2s.c Normal file
View file

@ -0,0 +1,433 @@
/*
* Au12x0/Au1550 PSC ALSA ASoC audio support.
*
* (c) 2007-2008 MSC Vertriebsges.m.b.H.,
* Manuel Lauss <manuel.lauss@gmail.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.
*
* Au1xxx-PSC I2S glue.
*
* NOTE: so far only PSC slave mode (bit- and frameclock) is supported.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include <asm/mach-au1x00/au1xxx_psc.h>
#include "psc.h"
/* supported I2S DAI hardware formats */
#define AU1XPSC_I2S_DAIFMT \
(SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \
SND_SOC_DAIFMT_NB_NF)
/* supported I2S direction */
#define AU1XPSC_I2S_DIR \
(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
#define AU1XPSC_I2S_RATES \
SNDRV_PCM_RATE_8000_192000
#define AU1XPSC_I2S_FMTS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
#define I2SSTAT_BUSY(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB)
#define I2SPCR_START(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TS : PSC_I2SPCR_RS)
#define I2SPCR_STOP(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TP : PSC_I2SPCR_RP)
#define I2SPCR_CLRFIFO(stype) \
((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)
static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long ct;
int ret;
ret = -EINVAL;
ct = pscdata->cfg;
ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ); /* left-justified */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
ct |= PSC_I2SCFG_XM; /* enable I2S mode */
break;
case SND_SOC_DAIFMT_MSB:
break;
case SND_SOC_DAIFMT_LSB:
ct |= PSC_I2SCFG_MLJ; /* LSB (right-) justified */
break;
default:
goto out;
}
ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI); /* IB-IF */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI;
break;
case SND_SOC_DAIFMT_NB_IF:
ct |= PSC_I2SCFG_BI;
break;
case SND_SOC_DAIFMT_IB_NF:
ct |= PSC_I2SCFG_WI;
break;
case SND_SOC_DAIFMT_IB_IF:
break;
default:
goto out;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */
ct |= PSC_I2SCFG_MS; /* PSC I2S slave mode */
break;
case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */
ct &= ~PSC_I2SCFG_MS; /* PSC I2S Master mode */
break;
default:
goto out;
}
pscdata->cfg = ct;
ret = 0;
out:
return ret;
}
static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
int cfgbits;
unsigned long stat;
/* check if the PSC is already streaming data */
stat = __raw_readl(I2S_STAT(pscdata));
if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) {
/* reject parameters not currently set up in hardware */
cfgbits = __raw_readl(I2S_CFG(pscdata));
if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) ||
(params_rate(params) != pscdata->rate))
return -EINVAL;
} else {
/* set sample bitdepth */
pscdata->cfg &= ~(0x1f << 4);
pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits);
/* remember current rate for other stream */
pscdata->rate = params_rate(params);
}
return 0;
}
/* Configure PSC late: on my devel systems the codec is I2S master and
* supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit. ASoC
* uses aggressive PM and switches the codec off when it is not in use
* which also means the PSC unit doesn't get any clocks and is therefore
* dead. That's why this chunk here gets called from the trigger callback
* because I can be reasonably certain the codec is driving the clocks.
*/
static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata)
{
unsigned long tmo;
/* bring PSC out of sleep, and configure I2S unit */
__raw_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
wmb(); /* drain writebuffer */
tmo = 1000000;
while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo)
tmo--;
if (!tmo)
goto psc_err;
__raw_writel(0, I2S_CFG(pscdata));
wmb(); /* drain writebuffer */
__raw_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata));
wmb(); /* drain writebuffer */
/* wait for I2S controller to become ready */
tmo = 1000000;
while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo)
tmo--;
if (tmo)
return 0;
psc_err:
__raw_writel(0, I2S_CFG(pscdata));
__raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
wmb(); /* drain writebuffer */
return -ETIMEDOUT;
}
static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype)
{
unsigned long tmo, stat;
int ret;
ret = 0;
/* if both TX and RX are idle, configure the PSC */
stat = __raw_readl(I2S_STAT(pscdata));
if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
ret = au1xpsc_i2s_configure(pscdata);
if (ret)
goto out;
}
__raw_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata));
wmb(); /* drain writebuffer */
__raw_writel(I2SPCR_START(stype), I2S_PCR(pscdata));
wmb(); /* drain writebuffer */
/* wait for start confirmation */
tmo = 1000000;
while (!(__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
tmo--;
if (!tmo) {
__raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
wmb(); /* drain writebuffer */
ret = -ETIMEDOUT;
}
out:
return ret;
}
static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype)
{
unsigned long tmo, stat;
__raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
wmb(); /* drain writebuffer */
/* wait for stop confirmation */
tmo = 1000000;
while ((__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
tmo--;
/* if both TX and RX are idle, disable PSC */
stat = __raw_readl(I2S_STAT(pscdata));
if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
__raw_writel(0, I2S_CFG(pscdata));
wmb(); /* drain writebuffer */
__raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
wmb(); /* drain writebuffer */
}
return 0;
}
static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
int ret, stype = substream->stream;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
ret = au1xpsc_i2s_start(pscdata, stype);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
ret = au1xpsc_i2s_stop(pscdata, stype);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]);
return 0;
}
static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = {
.startup = au1xpsc_i2s_startup,
.trigger = au1xpsc_i2s_trigger,
.hw_params = au1xpsc_i2s_hw_params,
.set_fmt = au1xpsc_i2s_set_fmt,
};
static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = {
.playback = {
.rates = AU1XPSC_I2S_RATES,
.formats = AU1XPSC_I2S_FMTS,
.channels_min = 2,
.channels_max = 8, /* 2 without external help */
},
.capture = {
.rates = AU1XPSC_I2S_RATES,
.formats = AU1XPSC_I2S_FMTS,
.channels_min = 2,
.channels_max = 8, /* 2 without external help */
},
.ops = &au1xpsc_i2s_dai_ops,
};
static const struct snd_soc_component_driver au1xpsc_i2s_component = {
.name = "au1xpsc-i2s",
};
static int au1xpsc_i2s_drvprobe(struct platform_device *pdev)
{
struct resource *iores, *dmares;
unsigned long sel;
int ret;
struct au1xpsc_audio_data *wd;
wd = devm_kzalloc(&pdev->dev, sizeof(struct au1xpsc_audio_data),
GFP_KERNEL);
if (!wd)
return -ENOMEM;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores)
return -ENODEV;
ret = -EBUSY;
if (!devm_request_mem_region(&pdev->dev, iores->start,
resource_size(iores),
pdev->name))
return -EBUSY;
wd->mmio = devm_ioremap(&pdev->dev, iores->start,
resource_size(iores));
if (!wd->mmio)
return -EBUSY;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmares)
return -EBUSY;
wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmares)
return -EBUSY;
wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;
/* preserve PSC clock source set up by platform (dev.platform_data
* is already occupied by soc layer)
*/
sel = __raw_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
__raw_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(wd));
__raw_writel(0, I2S_CFG(wd));
wmb(); /* drain writebuffer */
/* preconfigure: set max rx/tx fifo depths */
wd->cfg |= PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8;
/* don't wait for I2S core to become ready now; clocks may not
* be running yet; depending on clock input for PSC a wait might
* time out.
*/
/* name the DAI like this device instance ("au1xpsc-i2s.PSCINDEX") */
memcpy(&wd->dai_drv, &au1xpsc_i2s_dai_template,
sizeof(struct snd_soc_dai_driver));
wd->dai_drv.name = dev_name(&pdev->dev);
platform_set_drvdata(pdev, wd);
return snd_soc_register_component(&pdev->dev, &au1xpsc_i2s_component,
&wd->dai_drv, 1);
}
static int au1xpsc_i2s_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
__raw_writel(0, I2S_CFG(wd));
wmb(); /* drain writebuffer */
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
return 0;
}
#ifdef CONFIG_PM
static int au1xpsc_i2s_drvsuspend(struct device *dev)
{
struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
/* save interesting register and disable PSC */
wd->pm[0] = __raw_readl(PSC_SEL(wd));
__raw_writel(0, I2S_CFG(wd));
wmb(); /* drain writebuffer */
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
return 0;
}
static int au1xpsc_i2s_drvresume(struct device *dev)
{
struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
/* select I2S mode and PSC clock */
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
__raw_writel(0, PSC_SEL(wd));
wmb(); /* drain writebuffer */
__raw_writel(wd->pm[0], PSC_SEL(wd));
wmb(); /* drain writebuffer */
return 0;
}
static struct dev_pm_ops au1xpsci2s_pmops = {
.suspend = au1xpsc_i2s_drvsuspend,
.resume = au1xpsc_i2s_drvresume,
};
#define AU1XPSCI2S_PMOPS &au1xpsci2s_pmops
#else
#define AU1XPSCI2S_PMOPS NULL
#endif
static struct platform_driver au1xpsc_i2s_driver = {
.driver = {
.name = "au1xpsc_i2s",
.owner = THIS_MODULE,
.pm = AU1XPSCI2S_PMOPS,
},
.probe = au1xpsc_i2s_drvprobe,
.remove = au1xpsc_i2s_drvremove,
};
module_platform_driver(au1xpsc_i2s_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver");
MODULE_AUTHOR("Manuel Lauss");

42
sound/soc/au1x/psc.h Normal file
View file

@ -0,0 +1,42 @@
/*
* Alchemy ALSA ASoC audio support.
*
* (c) 2007-2011 MSC Vertriebsges.m.b.H.,
* Manuel Lauss <manuel.lauss@gmail.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.
*
*/
#ifndef _AU1X_PCM_H
#define _AU1X_PCM_H
struct au1xpsc_audio_data {
void __iomem *mmio;
unsigned long cfg;
unsigned long rate;
struct snd_soc_dai_driver dai_drv;
unsigned long pm[2];
struct mutex lock;
int dmaids[2];
};
/* easy access macros */
#define PSC_CTRL(x) ((x)->mmio + PSC_CTRL_OFFSET)
#define PSC_SEL(x) ((x)->mmio + PSC_SEL_OFFSET)
#define I2S_STAT(x) ((x)->mmio + PSC_I2SSTAT_OFFSET)
#define I2S_CFG(x) ((x)->mmio + PSC_I2SCFG_OFFSET)
#define I2S_PCR(x) ((x)->mmio + PSC_I2SPCR_OFFSET)
#define AC97_CFG(x) ((x)->mmio + PSC_AC97CFG_OFFSET)
#define AC97_CDC(x) ((x)->mmio + PSC_AC97CDC_OFFSET)
#define AC97_EVNT(x) ((x)->mmio + PSC_AC97EVNT_OFFSET)
#define AC97_PCR(x) ((x)->mmio + PSC_AC97PCR_OFFSET)
#define AC97_RST(x) ((x)->mmio + PSC_AC97RST_OFFSET)
#define AC97_STAT(x) ((x)->mmio + PSC_AC97STAT_OFFSET)
#endif

9
sound/soc/bcm/Kconfig Normal file
View file

@ -0,0 +1,9 @@
config SND_BCM2835_SOC_I2S
tristate "SoC Audio support for the Broadcom BCM2835 I2S module"
depends on ARCH_BCM2835 || COMPILE_TEST
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for codecs attached to
the BCM2835 I2S interface. You will also need
to select the audio interfaces to support below.

5
sound/soc/bcm/Makefile Normal file
View file

@ -0,0 +1,5 @@
# BCM2835 Platform Support
snd-soc-bcm2835-i2s-objs := bcm2835-i2s.o
obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o

879
sound/soc/bcm/bcm2835-i2s.c Normal file
View file

@ -0,0 +1,879 @@
/*
* ALSA SoC I2S Audio Layer for Broadcom BCM2835 SoC
*
* Author: Florian Meier <florian.meier@koalo.de>
* Copyright 2013
*
* Based on
* Raspberry Pi PCM I2S ALSA Driver
* Copyright (c) by Phil Poole 2013
*
* ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
* Vladimir Barinov, <vbarinov@embeddedalley.com>
* Copyright (C) 2007 MontaVista Software, Inc., <source@mvista.com>
*
* OMAP ALSA SoC DAI driver using McBSP port
* Copyright (C) 2008 Nokia Corporation
* Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
* Peter Ujfalusi <peter.ujfalusi@ti.com>
*
* Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
* Author: Timur Tabi <timur@freescale.com>
* Copyright 2007-2010 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
/* Clock registers */
#define BCM2835_CLK_PCMCTL_REG 0x00
#define BCM2835_CLK_PCMDIV_REG 0x04
/* Clock register settings */
#define BCM2835_CLK_PASSWD (0x5a000000)
#define BCM2835_CLK_PASSWD_MASK (0xff000000)
#define BCM2835_CLK_MASH(v) ((v) << 9)
#define BCM2835_CLK_FLIP BIT(8)
#define BCM2835_CLK_BUSY BIT(7)
#define BCM2835_CLK_KILL BIT(5)
#define BCM2835_CLK_ENAB BIT(4)
#define BCM2835_CLK_SRC(v) (v)
#define BCM2835_CLK_SHIFT (12)
#define BCM2835_CLK_DIVI(v) ((v) << BCM2835_CLK_SHIFT)
#define BCM2835_CLK_DIVF(v) (v)
#define BCM2835_CLK_DIVF_MASK (0xFFF)
enum {
BCM2835_CLK_MASH_0 = 0,
BCM2835_CLK_MASH_1,
BCM2835_CLK_MASH_2,
BCM2835_CLK_MASH_3,
};
enum {
BCM2835_CLK_SRC_GND = 0,
BCM2835_CLK_SRC_OSC,
BCM2835_CLK_SRC_DBG0,
BCM2835_CLK_SRC_DBG1,
BCM2835_CLK_SRC_PLLA,
BCM2835_CLK_SRC_PLLC,
BCM2835_CLK_SRC_PLLD,
BCM2835_CLK_SRC_HDMI,
};
/* Most clocks are not useable (freq = 0) */
static const unsigned int bcm2835_clk_freq[BCM2835_CLK_SRC_HDMI+1] = {
[BCM2835_CLK_SRC_GND] = 0,
[BCM2835_CLK_SRC_OSC] = 19200000,
[BCM2835_CLK_SRC_DBG0] = 0,
[BCM2835_CLK_SRC_DBG1] = 0,
[BCM2835_CLK_SRC_PLLA] = 0,
[BCM2835_CLK_SRC_PLLC] = 0,
[BCM2835_CLK_SRC_PLLD] = 500000000,
[BCM2835_CLK_SRC_HDMI] = 0,
};
/* I2S registers */
#define BCM2835_I2S_CS_A_REG 0x00
#define BCM2835_I2S_FIFO_A_REG 0x04
#define BCM2835_I2S_MODE_A_REG 0x08
#define BCM2835_I2S_RXC_A_REG 0x0c
#define BCM2835_I2S_TXC_A_REG 0x10
#define BCM2835_I2S_DREQ_A_REG 0x14
#define BCM2835_I2S_INTEN_A_REG 0x18
#define BCM2835_I2S_INTSTC_A_REG 0x1c
#define BCM2835_I2S_GRAY_REG 0x20
/* I2S register settings */
#define BCM2835_I2S_STBY BIT(25)
#define BCM2835_I2S_SYNC BIT(24)
#define BCM2835_I2S_RXSEX BIT(23)
#define BCM2835_I2S_RXF BIT(22)
#define BCM2835_I2S_TXE BIT(21)
#define BCM2835_I2S_RXD BIT(20)
#define BCM2835_I2S_TXD BIT(19)
#define BCM2835_I2S_RXR BIT(18)
#define BCM2835_I2S_TXW BIT(17)
#define BCM2835_I2S_CS_RXERR BIT(16)
#define BCM2835_I2S_CS_TXERR BIT(15)
#define BCM2835_I2S_RXSYNC BIT(14)
#define BCM2835_I2S_TXSYNC BIT(13)
#define BCM2835_I2S_DMAEN BIT(9)
#define BCM2835_I2S_RXTHR(v) ((v) << 7)
#define BCM2835_I2S_TXTHR(v) ((v) << 5)
#define BCM2835_I2S_RXCLR BIT(4)
#define BCM2835_I2S_TXCLR BIT(3)
#define BCM2835_I2S_TXON BIT(2)
#define BCM2835_I2S_RXON BIT(1)
#define BCM2835_I2S_EN (1)
#define BCM2835_I2S_CLKDIS BIT(28)
#define BCM2835_I2S_PDMN BIT(27)
#define BCM2835_I2S_PDME BIT(26)
#define BCM2835_I2S_FRXP BIT(25)
#define BCM2835_I2S_FTXP BIT(24)
#define BCM2835_I2S_CLKM BIT(23)
#define BCM2835_I2S_CLKI BIT(22)
#define BCM2835_I2S_FSM BIT(21)
#define BCM2835_I2S_FSI BIT(20)
#define BCM2835_I2S_FLEN(v) ((v) << 10)
#define BCM2835_I2S_FSLEN(v) (v)
#define BCM2835_I2S_CHWEX BIT(15)
#define BCM2835_I2S_CHEN BIT(14)
#define BCM2835_I2S_CHPOS(v) ((v) << 4)
#define BCM2835_I2S_CHWID(v) (v)
#define BCM2835_I2S_CH1(v) ((v) << 16)
#define BCM2835_I2S_CH2(v) (v)
#define BCM2835_I2S_TX_PANIC(v) ((v) << 24)
#define BCM2835_I2S_RX_PANIC(v) ((v) << 16)
#define BCM2835_I2S_TX(v) ((v) << 8)
#define BCM2835_I2S_RX(v) (v)
#define BCM2835_I2S_INT_RXERR BIT(3)
#define BCM2835_I2S_INT_TXERR BIT(2)
#define BCM2835_I2S_INT_RXR BIT(1)
#define BCM2835_I2S_INT_TXW BIT(0)
/* I2S DMA interface */
/* FIXME: Needs IOMMU support */
#define BCM2835_VCMMU_SHIFT (0x7E000000 - 0x20000000)
/* General device struct */
struct bcm2835_i2s_dev {
struct device *dev;
struct snd_dmaengine_dai_dma_data dma_data[2];
unsigned int fmt;
unsigned int bclk_ratio;
struct regmap *i2s_regmap;
struct regmap *clk_regmap;
};
static void bcm2835_i2s_start_clock(struct bcm2835_i2s_dev *dev)
{
/* Start the clock if in master mode */
unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
switch (master) {
case SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_CBS_CFM:
regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
BCM2835_CLK_PASSWD_MASK | BCM2835_CLK_ENAB,
BCM2835_CLK_PASSWD | BCM2835_CLK_ENAB);
break;
default:
break;
}
}
static void bcm2835_i2s_stop_clock(struct bcm2835_i2s_dev *dev)
{
uint32_t clkreg;
int timeout = 1000;
/* Stop clock */
regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
BCM2835_CLK_PASSWD_MASK | BCM2835_CLK_ENAB,
BCM2835_CLK_PASSWD);
/* Wait for the BUSY flag going down */
while (--timeout) {
regmap_read(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG, &clkreg);
if (!(clkreg & BCM2835_CLK_BUSY))
break;
}
if (!timeout) {
/* KILL the clock */
dev_err(dev->dev, "I2S clock didn't stop. Kill the clock!\n");
regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
BCM2835_CLK_KILL | BCM2835_CLK_PASSWD_MASK,
BCM2835_CLK_KILL | BCM2835_CLK_PASSWD);
}
}
static void bcm2835_i2s_clear_fifos(struct bcm2835_i2s_dev *dev,
bool tx, bool rx)
{
int timeout = 1000;
uint32_t syncval;
uint32_t csreg;
uint32_t i2s_active_state;
uint32_t clkreg;
uint32_t clk_active_state;
uint32_t off;
uint32_t clr;
off = tx ? BCM2835_I2S_TXON : 0;
off |= rx ? BCM2835_I2S_RXON : 0;
clr = tx ? BCM2835_I2S_TXCLR : 0;
clr |= rx ? BCM2835_I2S_RXCLR : 0;
/* Backup the current state */
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
i2s_active_state = csreg & (BCM2835_I2S_RXON | BCM2835_I2S_TXON);
regmap_read(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG, &clkreg);
clk_active_state = clkreg & BCM2835_CLK_ENAB;
/* Start clock if not running */
if (!clk_active_state) {
regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
BCM2835_CLK_PASSWD_MASK | BCM2835_CLK_ENAB,
BCM2835_CLK_PASSWD | BCM2835_CLK_ENAB);
}
/* Stop I2S module */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, off, 0);
/*
* Clear the FIFOs
* Requires at least 2 PCM clock cycles to take effect
*/
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, clr, clr);
/* Wait for 2 PCM clock cycles */
/*
* Toggle the SYNC flag. After 2 PCM clock cycles it can be read back
* FIXME: This does not seem to work for slave mode!
*/
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &syncval);
syncval &= BCM2835_I2S_SYNC;
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_SYNC, ~syncval);
/* Wait for the SYNC flag changing it's state */
while (--timeout) {
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
if ((csreg & BCM2835_I2S_SYNC) != syncval)
break;
}
if (!timeout)
dev_err(dev->dev, "I2S SYNC error!\n");
/* Stop clock if it was not running before */
if (!clk_active_state)
bcm2835_i2s_stop_clock(dev);
/* Restore I2S state */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_RXON | BCM2835_I2S_TXON, i2s_active_state);
}
static int bcm2835_i2s_set_dai_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
dev->fmt = fmt;
return 0;
}
static int bcm2835_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai,
unsigned int ratio)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
dev->bclk_ratio = ratio;
return 0;
}
static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
unsigned int sampling_rate = params_rate(params);
unsigned int data_length, data_delay, bclk_ratio;
unsigned int ch1pos, ch2pos, mode, format;
unsigned int mash = BCM2835_CLK_MASH_1;
unsigned int divi, divf, target_frequency;
int clk_src = -1;
unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
bool bit_master = (master == SND_SOC_DAIFMT_CBS_CFS
|| master == SND_SOC_DAIFMT_CBS_CFM);
bool frame_master = (master == SND_SOC_DAIFMT_CBS_CFS
|| master == SND_SOC_DAIFMT_CBM_CFS);
uint32_t csreg;
/*
* If a stream is already enabled,
* the registers are already set properly.
*/
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
if (csreg & (BCM2835_I2S_TXON | BCM2835_I2S_RXON))
return 0;
/*
* Adjust the data length according to the format.
* We prefill the half frame length with an integer
* divider of 2400 as explained at the clock settings.
* Maybe it is overwritten there, if the Integer mode
* does not apply.
*/
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
data_length = 16;
bclk_ratio = 40;
break;
case SNDRV_PCM_FORMAT_S32_LE:
data_length = 32;
bclk_ratio = 80;
break;
default:
return -EINVAL;
}
/* If bclk_ratio already set, use that one. */
if (dev->bclk_ratio)
bclk_ratio = dev->bclk_ratio;
/*
* Clock Settings
*
* The target frequency of the bit clock is
* sampling rate * frame length
*
* Integer mode:
* Sampling rates that are multiples of 8000 kHz
* can be driven by the oscillator of 19.2 MHz
* with an integer divider as long as the frame length
* is an integer divider of 19200000/8000=2400 as set up above.
* This is no longer possible if the sampling rate
* is too high (e.g. 192 kHz), because the oscillator is too slow.
*
* MASH mode:
* For all other sampling rates, it is not possible to
* have an integer divider. Approximate the clock
* with the MASH module that induces a slight frequency
* variance. To minimize that it is best to have the fastest
* clock here. That is PLLD with 500 MHz.
*/
target_frequency = sampling_rate * bclk_ratio;
clk_src = BCM2835_CLK_SRC_OSC;
mash = BCM2835_CLK_MASH_0;
if (bcm2835_clk_freq[clk_src] % target_frequency == 0
&& bit_master && frame_master) {
divi = bcm2835_clk_freq[clk_src] / target_frequency;
divf = 0;
} else {
uint64_t dividend;
if (!dev->bclk_ratio) {
/*
* Overwrite bclk_ratio, because the
* above trick is not needed or can
* not be used.
*/
bclk_ratio = 2 * data_length;
}
target_frequency = sampling_rate * bclk_ratio;
clk_src = BCM2835_CLK_SRC_PLLD;
mash = BCM2835_CLK_MASH_1;
dividend = bcm2835_clk_freq[clk_src];
dividend <<= BCM2835_CLK_SHIFT;
do_div(dividend, target_frequency);
divi = dividend >> BCM2835_CLK_SHIFT;
divf = dividend & BCM2835_CLK_DIVF_MASK;
}
/* Set clock divider */
regmap_write(dev->clk_regmap, BCM2835_CLK_PCMDIV_REG, BCM2835_CLK_PASSWD
| BCM2835_CLK_DIVI(divi)
| BCM2835_CLK_DIVF(divf));
/* Setup clock, but don't start it yet */
regmap_write(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG, BCM2835_CLK_PASSWD
| BCM2835_CLK_MASH(mash)
| BCM2835_CLK_SRC(clk_src));
/* Setup the frame format */
format = BCM2835_I2S_CHEN;
if (data_length > 24)
format |= BCM2835_I2S_CHWEX;
format |= BCM2835_I2S_CHWID((data_length-8)&0xf);
switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
data_delay = 1;
break;
default:
/*
* TODO
* Others are possible but are not implemented at the moment.
*/
dev_err(dev->dev, "%s:bad format\n", __func__);
return -EINVAL;
}
ch1pos = data_delay;
ch2pos = bclk_ratio / 2 + data_delay;
switch (params_channels(params)) {
case 2:
format = BCM2835_I2S_CH1(format) | BCM2835_I2S_CH2(format);
format |= BCM2835_I2S_CH1(BCM2835_I2S_CHPOS(ch1pos));
format |= BCM2835_I2S_CH2(BCM2835_I2S_CHPOS(ch2pos));
break;
default:
return -EINVAL;
}
/*
* Set format for both streams.
* We cannot set another frame length
* (and therefore word length) anyway,
* so the format will be the same.
*/
regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG, format);
regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG, format);
/* Setup the I2S mode */
mode = 0;
if (data_length <= 16) {
/*
* Use frame packed mode (2 channels per 32 bit word)
* We cannot set another frame length in the second stream
* (and therefore word length) anyway,
* so the format will be the same.
*/
mode |= BCM2835_I2S_FTXP | BCM2835_I2S_FRXP;
}
mode |= BCM2835_I2S_FLEN(bclk_ratio - 1);
mode |= BCM2835_I2S_FSLEN(bclk_ratio / 2);
/* Master or slave? */
switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* CPU is master */
break;
case SND_SOC_DAIFMT_CBM_CFS:
/*
* CODEC is bit clock master
* CPU is frame master
*/
mode |= BCM2835_I2S_CLKM;
break;
case SND_SOC_DAIFMT_CBS_CFM:
/*
* CODEC is frame master
* CPU is bit clock master
*/
mode |= BCM2835_I2S_FSM;
break;
case SND_SOC_DAIFMT_CBM_CFM:
/* CODEC is master */
mode |= BCM2835_I2S_CLKM;
mode |= BCM2835_I2S_FSM;
break;
default:
dev_err(dev->dev, "%s:bad master\n", __func__);
return -EINVAL;
}
/*
* Invert clocks?
*
* The BCM approach seems to be inverted to the classical I2S approach.
*/
switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
/* None. Therefore, both for BCM */
mode |= BCM2835_I2S_CLKI;
mode |= BCM2835_I2S_FSI;
break;
case SND_SOC_DAIFMT_IB_IF:
/* Both. Therefore, none for BCM */
break;
case SND_SOC_DAIFMT_NB_IF:
/*
* Invert only frame sync. Therefore,
* invert only bit clock for BCM
*/
mode |= BCM2835_I2S_CLKI;
break;
case SND_SOC_DAIFMT_IB_NF:
/*
* Invert only bit clock. Therefore,
* invert only frame sync for BCM
*/
mode |= BCM2835_I2S_FSI;
break;
default:
return -EINVAL;
}
regmap_write(dev->i2s_regmap, BCM2835_I2S_MODE_A_REG, mode);
/* Setup the DMA parameters */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_RXTHR(1)
| BCM2835_I2S_TXTHR(1)
| BCM2835_I2S_DMAEN, 0xffffffff);
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_DREQ_A_REG,
BCM2835_I2S_TX_PANIC(0x10)
| BCM2835_I2S_RX_PANIC(0x30)
| BCM2835_I2S_TX(0x30)
| BCM2835_I2S_RX(0x20), 0xffffffff);
/* Clear FIFOs */
bcm2835_i2s_clear_fifos(dev, true, true);
return 0;
}
static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
uint32_t cs_reg;
bcm2835_i2s_start_clock(dev);
/*
* Clear both FIFOs if the one that should be started
* is not empty at the moment. This should only happen
* after overrun. Otherwise, hw_params would have cleared
* the FIFO.
*/
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &cs_reg);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK
&& !(cs_reg & BCM2835_I2S_TXE))
bcm2835_i2s_clear_fifos(dev, true, false);
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
&& (cs_reg & BCM2835_I2S_RXD))
bcm2835_i2s_clear_fifos(dev, false, true);
return 0;
}
static void bcm2835_i2s_stop(struct bcm2835_i2s_dev *dev,
struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
uint32_t mask;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
mask = BCM2835_I2S_RXON;
else
mask = BCM2835_I2S_TXON;
regmap_update_bits(dev->i2s_regmap,
BCM2835_I2S_CS_A_REG, mask, 0);
/* Stop also the clock when not SND_SOC_DAIFMT_CONT */
if (!dai->active && !(dev->fmt & SND_SOC_DAIFMT_CONT))
bcm2835_i2s_stop_clock(dev);
}
static int bcm2835_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
uint32_t mask;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
bcm2835_i2s_start_clock(dev);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
mask = BCM2835_I2S_RXON;
else
mask = BCM2835_I2S_TXON;
regmap_update_bits(dev->i2s_regmap,
BCM2835_I2S_CS_A_REG, mask, mask);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
bcm2835_i2s_stop(dev, substream, dai);
break;
default:
return -EINVAL;
}
return 0;
}
static int bcm2835_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (dai->active)
return 0;
/* Should this still be running stop it */
bcm2835_i2s_stop_clock(dev);
/* Enable PCM block */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_EN, BCM2835_I2S_EN);
/*
* Disable STBY.
* Requires at least 4 PCM clock cycles to take effect.
*/
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_STBY, BCM2835_I2S_STBY);
return 0;
}
static void bcm2835_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
bcm2835_i2s_stop(dev, substream, dai);
/* If both streams are stopped, disable module and clock */
if (dai->active)
return;
/* Disable the module */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_EN, 0);
/*
* Stopping clock is necessary, because stop does
* not stop the clock when SND_SOC_DAIFMT_CONT
*/
bcm2835_i2s_stop_clock(dev);
}
static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = {
.startup = bcm2835_i2s_startup,
.shutdown = bcm2835_i2s_shutdown,
.prepare = bcm2835_i2s_prepare,
.trigger = bcm2835_i2s_trigger,
.hw_params = bcm2835_i2s_hw_params,
.set_fmt = bcm2835_i2s_set_dai_fmt,
.set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio
};
static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai,
&dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK],
&dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]);
return 0;
}
static struct snd_soc_dai_driver bcm2835_i2s_dai = {
.name = "bcm2835-i2s",
.probe = bcm2835_i2s_dai_probe,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S32_LE
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S32_LE
},
.ops = &bcm2835_i2s_dai_ops,
.symmetric_rates = 1
};
static bool bcm2835_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case BCM2835_I2S_CS_A_REG:
case BCM2835_I2S_FIFO_A_REG:
case BCM2835_I2S_INTSTC_A_REG:
case BCM2835_I2S_GRAY_REG:
return true;
default:
return false;
};
}
static bool bcm2835_i2s_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case BCM2835_I2S_FIFO_A_REG:
return true;
default:
return false;
};
}
static bool bcm2835_clk_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case BCM2835_CLK_PCMCTL_REG:
return true;
default:
return false;
};
}
static const struct regmap_config bcm2835_regmap_config[] = {
{
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = BCM2835_I2S_GRAY_REG,
.precious_reg = bcm2835_i2s_precious_reg,
.volatile_reg = bcm2835_i2s_volatile_reg,
.cache_type = REGCACHE_RBTREE,
},
{
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = BCM2835_CLK_PCMDIV_REG,
.volatile_reg = bcm2835_clk_volatile_reg,
.cache_type = REGCACHE_RBTREE,
},
};
static const struct snd_soc_component_driver bcm2835_i2s_component = {
.name = "bcm2835-i2s-comp",
};
static int bcm2835_i2s_probe(struct platform_device *pdev)
{
struct bcm2835_i2s_dev *dev;
int i;
int ret;
struct regmap *regmap[2];
struct resource *mem[2];
/* Request both ioareas */
for (i = 0; i <= 1; i++) {
void __iomem *base;
mem[i] = platform_get_resource(pdev, IORESOURCE_MEM, i);
base = devm_ioremap_resource(&pdev->dev, mem[i]);
if (IS_ERR(base))
return PTR_ERR(base);
regmap[i] = devm_regmap_init_mmio(&pdev->dev, base,
&bcm2835_regmap_config[i]);
if (IS_ERR(regmap[i]))
return PTR_ERR(regmap[i]);
}
dev = devm_kzalloc(&pdev->dev, sizeof(*dev),
GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->i2s_regmap = regmap[0];
dev->clk_regmap = regmap[1];
/* Set the DMA address */
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
(dma_addr_t)mem[0]->start + BCM2835_I2S_FIFO_A_REG
+ BCM2835_VCMMU_SHIFT;
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
(dma_addr_t)mem[0]->start + BCM2835_I2S_FIFO_A_REG
+ BCM2835_VCMMU_SHIFT;
/* Set the bus width */
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
DMA_SLAVE_BUSWIDTH_4_BYTES;
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr_width =
DMA_SLAVE_BUSWIDTH_4_BYTES;
/* Set burst */
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].maxburst = 2;
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].maxburst = 2;
/* BCLK ratio - use default */
dev->bclk_ratio = 0;
/* Store the pdev */
dev->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, dev);
ret = devm_snd_soc_register_component(&pdev->dev,
&bcm2835_i2s_component, &bcm2835_i2s_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
return ret;
}
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
return ret;
}
return 0;
}
static const struct of_device_id bcm2835_i2s_of_match[] = {
{ .compatible = "brcm,bcm2835-i2s", },
{},
};
static struct platform_driver bcm2835_i2s_driver = {
.probe = bcm2835_i2s_probe,
.driver = {
.name = "bcm2835-i2s",
.owner = THIS_MODULE,
.of_match_table = bcm2835_i2s_of_match,
},
};
module_platform_driver(bcm2835_i2s_driver);
MODULE_ALIAS("platform:bcm2835-i2s");
MODULE_DESCRIPTION("BCM2835 I2S interface");
MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
MODULE_LICENSE("GPL v2");

205
sound/soc/blackfin/Kconfig Normal file
View file

@ -0,0 +1,205 @@
config SND_BF5XX_I2S
tristate "SoC I2S Audio for the ADI Blackfin chip"
depends on BLACKFIN
select SND_BF5XX_SOC_SPORT if !BF60x
select SND_BF6XX_SOC_SPORT if BF60x
help
Say Y or M if you want to add support for codecs attached to
the Blackfin SPORT (synchronous serial ports) interface in I2S
mode (supports single stereo In/Out).
You will also need to select the audio interfaces to support below.
config SND_BF5XX_SOC_SSM2602
tristate "SoC SSM2602 Audio Codec Add-On Card support"
depends on SND_BF5XX_I2S && SND_SOC_I2C_AND_SPI
select SND_BF5XX_SOC_I2S if !BF60x
select SND_BF6XX_SOC_I2S if BF60x
select SND_SOC_SSM2602_SPI if SPI_MASTER
select SND_SOC_SSM2602_I2C if I2C
help
Say Y if you want to add support for the Analog Devices
SSM2602 Audio Codec Add-On Card.
config SND_SOC_BFIN_EVAL_ADAU1701
tristate "Support for the EVAL-ADAU1701MINIZ board on Blackfin eval boards"
depends on SND_BF5XX_I2S && I2C
select SND_BF5XX_SOC_I2S
select SND_SOC_ADAU1701
help
Say Y if you want to add support for the Analog Devices EVAL-ADAU1701MINIZ
board connected to one of the Blackfin evaluation boards like the
BF5XX-STAMP or BF5XX-EZKIT.
config SND_SOC_BFIN_EVAL_ADAU1373
tristate "Support for the EVAL-ADAU1373 board on Blackfin eval boards"
depends on SND_BF5XX_I2S && I2C
select SND_BF5XX_SOC_I2S
select SND_SOC_ADAU1373
help
Say Y if you want to add support for the Analog Devices EVAL-ADAU1373
board connected to one of the Blackfin evaluation boards like the
BF5XX-STAMP or BF5XX-EZKIT.
Note: This driver assumes that first ADAU1373 DAI is connected to the
first SPORT port on the BF5XX board.
config SND_SOC_BFIN_EVAL_ADAU1X61
tristate "Support for the EVAL-ADAU1X61 board on Blackfin eval boards"
depends on SND_BF5XX_I2S && I2C
select SND_BF5XX_SOC_I2S
select SND_SOC_ADAU1761_I2C
help
Say Y if you want to add support for the Analog Devices EVAL-ADAU1X61
board connected to one of the Blackfin evaluation boards like the
BF5XX-STAMP or BF5XX-EZKIT.
Note: This driver assumes that the ADAU1X61 is connected to the
first SPORT port on the BF5XX board.
config SND_SOC_BFIN_EVAL_ADAU1X81
tristate "Support for the EVAL-ADAU1X81 boards on Blackfin eval boards"
depends on SND_BF5XX_I2S && I2C
select SND_BF5XX_SOC_I2S
select SND_SOC_ADAU1781_I2C
help
Say Y if you want to add support for the Analog Devices EVAL-ADAU1X81
board connected to one of the Blackfin evaluation boards like the
BF5XX-STAMP or BF5XX-EZKIT.
Note: This driver assumes that the ADAU1X81 is connected to the
first SPORT port on the BF5XX board.
config SND_SOC_BFIN_EVAL_ADAV80X
tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards"
depends on SND_BF5XX_I2S && SND_SOC_I2C_AND_SPI
select SND_BF5XX_SOC_I2S
select SND_SOC_ADAV801 if SPI_MASTER
select SND_SOC_ADAV803 if I2C
help
Say Y if you want to add support for the Analog Devices EVAL-ADAV801 or
EVAL-ADAV803 board connected to one of the Blackfin evaluation boards
like the BF5XX-STAMP or BF5XX-EZKIT.
Note: This driver assumes that the ADAV80X digital record and playback
interfaces are connected to the first SPORT port on the BF5XX board.
config SND_BF5XX_SOC_AD1836
tristate "SoC AD1836 Audio support for BF5xx"
depends on SND_BF5XX_I2S && SPI_MASTER
select SND_BF5XX_SOC_I2S
select SND_SOC_AD1836
help
Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
config SND_BF5XX_SOC_AD193X
tristate "SoC AD193X Audio support for Blackfin"
depends on SND_BF5XX_I2S && SND_SOC_I2C_AND_SPI
select SND_BF5XX_SOC_I2S
select SND_SOC_AD193X_I2C if I2C
select SND_SOC_AD193X_SPI if SPI_MASTER
help
Say Y if you want to add support for AD193X codec on Blackfin.
This driver supports AD1936, AD1937, AD1938 and AD1939.
config SND_BF5XX_SOC_AD73311
tristate "SoC AD73311 Audio support for Blackfin"
depends on SND_BF5XX_I2S
select SND_BF5XX_SOC_I2S
select SND_SOC_AD73311
help
Say Y if you want to add support for AD73311 codec on Blackfin.
config SND_BFIN_AD73311_SE
int "PF pin for AD73311L Chip Select"
depends on SND_BF5XX_SOC_AD73311
default 4
help
Enter the GPIO used to control AD73311's SE pin. Acceptable
values are 0 to 7
config SND_BF5XX_AC97
tristate "SoC AC97 Audio for the ADI BF5xx chip"
depends on BLACKFIN
select AC97_BUS
select SND_SOC_AC97_BUS
select SND_BF5XX_SOC_SPORT
select SND_BF5XX_SOC_AC97
help
Say Y or M if you want to add support for codecs attached to
the Blackfin SPORT (synchronous serial ports) interface in slot 16
mode (pseudo AC97 interface).
You will also need to select the audio interfaces to support below.
Note:
AC97 codecs which do not implement the slot-16 mode will not function
properly with this driver. This driver is known to work with the
Analog Devices line of AC97 codecs.
config SND_BF5XX_MMAP_SUPPORT
bool "Enable MMAP Support"
depends on SND_BF5XX_AC97
default y
help
Say y if you want AC97 driver to support mmap mode.
We introduce an intermediate buffer to simulate mmap.
config SND_BF5XX_MULTICHAN_SUPPORT
bool "Enable Multichannel Support"
depends on SND_BF5XX_AC97
default n
help
Say y if you want AC97 driver to support up to 5.1 channel audio.
this mode will consume much more memory for DMA.
config SND_BF5XX_HAVE_COLD_RESET
bool "BOARD has COLD Reset GPIO"
depends on SND_BF5XX_AC97
default y if BFIN548_EZKIT
default n if !BFIN548_EZKIT
config SND_BF5XX_RESET_GPIO_NUM
int "Set a GPIO for cold reset"
depends on SND_BF5XX_HAVE_COLD_RESET
range 0 159
default 19 if BFIN548_EZKIT
default 5 if BFIN537_STAMP
default 0
help
Set the correct GPIO for RESET the sound chip.
config SND_BF5XX_SOC_AD1980
tristate "SoC AD1980/1 Audio support for BF5xx (Obsolete)"
depends on SND_BF5XX_AC97
select SND_BF5XX_SOC_AC97
select SND_SOC_AD1980
help
Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
Warning:
Because Analog Devices Inc. discontinued the ad1980 sound chip since
Sep. 2009, this ad1980 driver is not maintained, tested and supported
by ADI now.
config SND_BF5XX_SOC_SPORT
tristate
config SND_BF6XX_SOC_SPORT
tristate
config SND_BF5XX_SOC_I2S
tristate
config SND_BF6XX_SOC_I2S
tristate
config SND_BF5XX_SOC_AC97
tristate
config SND_BF5XX_SPORT_NUM
int "Set a SPORT for Sound chip"
depends on (SND_BF5XX_SOC_SPORT || SND_BF6XX_SOC_SPORT)
range 0 3 if BF54x
range 0 1 if !BF54x
default 0
help
Set the correct SPORT for sound chip.

View file

@ -0,0 +1,39 @@
# Blackfin Platform Support
snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o
snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o
snd-soc-bf5xx-sport-objs := bf5xx-sport.o
snd-soc-bf6xx-sport-objs := bf6xx-sport.o
snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o
snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o
snd-soc-bf6xx-i2s-objs := bf6xx-i2s.o
obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o
obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o
obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o
obj-$(CONFIG_SND_BF6XX_SOC_SPORT) += snd-soc-bf6xx-sport.o
obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o
obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o
obj-$(CONFIG_SND_BF6XX_SOC_I2S) += snd-soc-bf6xx-i2s.o
# Blackfin Machine Support
snd-ad1836-objs := bf5xx-ad1836.o
snd-ad1980-objs := bf5xx-ad1980.o
snd-ssm2602-objs := bf5xx-ssm2602.o
snd-ad73311-objs := bf5xx-ad73311.o
snd-ad193x-objs := bf5xx-ad193x.o
snd-soc-bfin-eval-adau1373-objs := bfin-eval-adau1373.o
snd-soc-bfin-eval-adau1x61-objs := bfin-eval-adau1x61.o
snd-soc-bfin-eval-adau1x81-objs := bfin-eval-adau1x81.o
snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o
snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o
obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o
obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o
obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) += snd-soc-bfin-eval-adau1373.o
obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1X61) += snd-soc-bfin-eval-adau1x61.o
obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1X81) += snd-soc-bfin-eval-adau1x81.o
obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) += snd-soc-bfin-eval-adau1701.o
obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAV80X) += snd-soc-bfin-eval-adav80x.o

View file

@ -0,0 +1,476 @@
/*
* File: sound/soc/blackfin/bf5xx-ac97-pcm.c
* Author: Cliff Cai <Cliff.Cai@analog.com>
*
* Created: Tue June 06 2008
* Description: DMA Driver for AC97 sound chip
*
* Modified:
* Copyright 2008 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/gfp.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <asm/dma.h>
#include "bf5xx-ac97.h"
#include "bf5xx-sport.h"
static unsigned int ac97_chan_mask[] = {
SP_FL, /* Mono */
SP_STEREO, /* Stereo */
SP_2DOT1, /* 2.1*/
SP_QUAD,/*Quadraquic*/
SP_FL | SP_FR | SP_FC | SP_SL | SP_SR,/*5 channels */
SP_5DOT1, /* 5.1 */
};
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
static void bf5xx_mmap_copy(struct snd_pcm_substream *substream,
snd_pcm_uframes_t count)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
bf5xx_pcm_to_ac97((struct ac97_frame *)sport->tx_dma_buf +
sport->tx_pos, (__u16 *)runtime->dma_area + sport->tx_pos *
runtime->channels, count, chan_mask);
sport->tx_pos += runtime->period_size;
if (sport->tx_pos >= runtime->buffer_size)
sport->tx_pos %= runtime->buffer_size;
sport->tx_delay_pos = sport->tx_pos;
} else {
bf5xx_ac97_to_pcm((struct ac97_frame *)sport->rx_dma_buf +
sport->rx_pos, (__u16 *)runtime->dma_area + sport->rx_pos *
runtime->channels, count);
sport->rx_pos += runtime->period_size;
if (sport->rx_pos >= runtime->buffer_size)
sport->rx_pos %= runtime->buffer_size;
}
}
#endif
static void bf5xx_dma_irq(void *data)
{
struct snd_pcm_substream *pcm = data;
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
struct snd_pcm_runtime *runtime = pcm->runtime;
struct sport_device *sport = runtime->private_data;
bf5xx_mmap_copy(pcm, runtime->period_size);
if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (sport->once == 0) {
snd_pcm_period_elapsed(pcm);
bf5xx_mmap_copy(pcm, runtime->period_size);
sport->once = 1;
}
}
#endif
snd_pcm_period_elapsed(pcm);
}
/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes.
* The total rx/tx buffer is for ac97 frame to hold all pcm data
* is 0x20000 * sizeof(struct ac97_frame) / 4.
*/
static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
#endif
SNDRV_PCM_INFO_BLOCK_TRANSFER,
.period_bytes_min = 32,
.period_bytes_max = 0x10000,
.periods_min = 1,
.periods_max = PAGE_SIZE/32,
.buffer_bytes_max = 0x20000, /* 128 kbytes */
.fifo_size = 16,
};
static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
size_t size = bf5xx_pcm_hardware.buffer_bytes_max
* sizeof(struct ac97_frame) / 4;
snd_pcm_lib_malloc_pages(substream, size);
return 0;
}
static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
{
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
sport->once = 0;
if (runtime->dma_area)
memset(runtime->dma_area, 0, runtime->buffer_size);
memset(sport->tx_dma_buf, 0, runtime->buffer_size *
sizeof(struct ac97_frame));
} else
memset(sport->rx_dma_buf, 0, runtime->buffer_size *
sizeof(struct ac97_frame));
#endif
snd_pcm_lib_free_pages(substream);
return 0;
}
static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
/* An intermediate buffer is introduced for implementing mmap for
* SPORT working in TMD mode(include AC97).
*/
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods,
runtime->period_size * sizeof(struct ac97_frame));
} else {
sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods,
runtime->period_size * sizeof(struct ac97_frame));
}
#else
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
sport_config_tx_dma(sport, runtime->dma_area, runtime->periods,
runtime->period_size * sizeof(struct ac97_frame));
} else {
sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
sport_config_rx_dma(sport, runtime->dma_area, runtime->periods,
runtime->period_size * sizeof(struct ac97_frame));
}
#endif
return 0;
}
static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
int ret = 0;
pr_debug("%s enter\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
bf5xx_mmap_copy(substream, runtime->period_size);
sport->tx_delay_pos = 0;
#endif
sport_tx_start(sport);
} else
sport_rx_start(sport);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
sport->tx_pos = 0;
#endif
sport_tx_stop(sport);
} else {
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
sport->rx_pos = 0;
#endif
sport_rx_stop(sport);
}
break;
default:
ret = -EINVAL;
}
return ret;
}
static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
unsigned int curr;
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
curr = sport->tx_delay_pos;
else
curr = sport->rx_pos;
#else
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame);
else
curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame);
#endif
return curr;
}
static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
pr_debug("%s enter\n", __func__);
snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
goto out;
if (sport_handle != NULL)
runtime->private_data = sport_handle;
else {
pr_err("sport_handle is NULL\n");
return -1;
}
return 0;
out:
return ret;
}
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
size_t size = vma->vm_end - vma->vm_start;
vma->vm_start = (unsigned long)runtime->dma_area;
vma->vm_end = vma->vm_start + size;
vma->vm_flags |= VM_SHARED;
return 0 ;
}
#else
static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos,
void __user *buf, snd_pcm_uframes_t count)
{
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
pr_debug("%s copy pos:0x%lx count:0x%lx\n",
substream->stream ? "Capture" : "Playback", pos, count);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos,
(__u16 *)buf, count, chan_mask);
else
bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos,
(__u16 *)buf, count);
return 0;
}
#endif
static struct snd_pcm_ops bf5xx_pcm_ac97_ops = {
.open = bf5xx_pcm_open,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = bf5xx_pcm_hw_params,
.hw_free = bf5xx_pcm_hw_free,
.prepare = bf5xx_pcm_prepare,
.trigger = bf5xx_pcm_trigger,
.pointer = bf5xx_pcm_pointer,
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
.mmap = bf5xx_pcm_mmap,
#else
.copy = bf5xx_pcm_copy,
#endif
};
static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
{
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
size_t size = bf5xx_pcm_hardware.buffer_bytes_max
* sizeof(struct ac97_frame) / 4;
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
if (!buf->area) {
pr_err("Failed to allocate dma memory\n");
pr_err("Please increase uncached DMA memory region\n");
return -ENOMEM;
}
buf->bytes = size;
pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
buf->area, buf->bytes);
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
sport_handle->tx_buf = buf->area;
else
sport_handle->rx_buf = buf->area;
/*
* Need to allocate local buffer when enable
* MMAP for SPORT working in TMD mode (include AC97).
*/
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (!sport_handle->tx_dma_buf) {
sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \
size, &sport_handle->tx_dma_phy, GFP_KERNEL);
if (!sport_handle->tx_dma_buf) {
pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n");
return -ENOMEM;
} else
memset(sport_handle->tx_dma_buf, 0, size);
} else
memset(sport_handle->tx_dma_buf, 0, size);
} else {
if (!sport_handle->rx_dma_buf) {
sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \
size, &sport_handle->rx_dma_phy, GFP_KERNEL);
if (!sport_handle->rx_dma_buf) {
pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n");
return -ENOMEM;
} else
memset(sport_handle->rx_dma_buf, 0, size);
} else
memset(sport_handle->rx_dma_buf, 0, size);
}
#endif
return 0;
}
static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
int stream;
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
size_t size = bf5xx_pcm_hardware.buffer_bytes_max *
sizeof(struct ac97_frame) / 4;
#endif
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
continue;
buf = &substream->dma_buffer;
if (!buf->area)
continue;
dma_free_coherent(NULL, buf->bytes, buf->area, 0);
buf->area = NULL;
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (sport_handle->tx_dma_buf)
dma_free_coherent(NULL, size, \
sport_handle->tx_dma_buf, 0);
sport_handle->tx_dma_buf = NULL;
} else {
if (sport_handle->rx_dma_buf)
dma_free_coherent(NULL, size, \
sport_handle->rx_dma_buf, 0);
sport_handle->rx_dma_buf = NULL;
}
#endif
}
}
static int bf5xx_pcm_ac97_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret;
pr_debug("%s enter\n", __func__);
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE);
if (ret)
goto out;
}
out:
return ret;
}
static struct snd_soc_platform_driver bf5xx_ac97_soc_platform = {
.ops = &bf5xx_pcm_ac97_ops,
.pcm_new = bf5xx_pcm_ac97_new,
.pcm_free = bf5xx_pcm_free_dma_buffers,
};
static int bf5xx_soc_platform_probe(struct platform_device *pdev)
{
return snd_soc_register_platform(&pdev->dev, &bf5xx_ac97_soc_platform);
}
static int bf5xx_soc_platform_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static struct platform_driver bf5xx_pcm_driver = {
.driver = {
.name = "bfin-ac97-pcm-audio",
.owner = THIS_MODULE,
},
.probe = bf5xx_soc_platform_probe,
.remove = bf5xx_soc_platform_remove,
};
module_platform_driver(bf5xx_pcm_driver);
MODULE_AUTHOR("Cliff Cai");
MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,389 @@
/*
* bf5xx-ac97.c -- AC97 support for the ADI blackfin chip.
*
* Author: Roy Huang
* Created: 11th. June 2007
* Copyright: Analog Device Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/irq.h>
#include <asm/portmux.h>
#include <linux/mutex.h>
#include <linux/gpio.h>
#include "bf5xx-sport.h"
#include "bf5xx-ac97.h"
/* Anomaly notes:
* 05000250 - AD1980 is running in TDM mode and RFS/TFS are generated by SPORT
* contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1,
* while the max AC97 data size is 13*16. The DIV is always larger
* than data size. AD73311 and ad2602 are not running in TDM mode.
* AD1836 and AD73322 depend on external RFS/TFS only. So, this
* anomaly does not affect blackfin sound drivers.
*/
static struct sport_device *ac97_sport_handle;
void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u16 *src,
size_t count, unsigned int chan_mask)
{
while (count--) {
dst->ac97_tag = TAG_VALID;
if (chan_mask & SP_FL) {
dst->ac97_pcm_r = *src++;
dst->ac97_tag |= TAG_PCM_RIGHT;
}
if (chan_mask & SP_FR) {
dst->ac97_pcm_l = *src++;
dst->ac97_tag |= TAG_PCM_LEFT;
}
#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
if (chan_mask & SP_SR) {
dst->ac97_sl = *src++;
dst->ac97_tag |= TAG_PCM_SL;
}
if (chan_mask & SP_SL) {
dst->ac97_sr = *src++;
dst->ac97_tag |= TAG_PCM_SR;
}
if (chan_mask & SP_LFE) {
dst->ac97_lfe = *src++;
dst->ac97_tag |= TAG_PCM_LFE;
}
if (chan_mask & SP_FC) {
dst->ac97_center = *src++;
dst->ac97_tag |= TAG_PCM_CENTER;
}
#endif
dst++;
}
}
EXPORT_SYMBOL(bf5xx_pcm_to_ac97);
void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u16 *dst,
size_t count)
{
while (count--) {
*(dst++) = src->ac97_pcm_l;
*(dst++) = src->ac97_pcm_r;
src++;
}
}
EXPORT_SYMBOL(bf5xx_ac97_to_pcm);
static unsigned int sport_tx_curr_frag(struct sport_device *sport)
{
return sport->tx_curr_frag = sport_curr_offset_tx(sport) /
sport->tx_fragsize;
}
static void enqueue_cmd(struct snd_ac97 *ac97, __u16 addr, __u16 data)
{
struct sport_device *sport = ac97_sport_handle;
int *cmd_count = sport->private_data;
int nextfrag = sport_tx_curr_frag(sport);
struct ac97_frame *nextwrite;
sport_incfrag(sport, &nextfrag, 1);
nextwrite = (struct ac97_frame *)(sport->tx_buf +
nextfrag * sport->tx_fragsize);
pr_debug("sport->tx_buf:%p, nextfrag:0x%x nextwrite:%p, cmd_count:%d\n",
sport->tx_buf, nextfrag, nextwrite, cmd_count[nextfrag]);
nextwrite[cmd_count[nextfrag]].ac97_tag |= TAG_CMD;
nextwrite[cmd_count[nextfrag]].ac97_addr = addr;
nextwrite[cmd_count[nextfrag]].ac97_data = data;
++cmd_count[nextfrag];
pr_debug("ac97_sport: Inserting %02x/%04x into fragment %d\n",
addr >> 8, data, nextfrag);
}
static unsigned short bf5xx_ac97_read(struct snd_ac97 *ac97,
unsigned short reg)
{
struct sport_device *sport_handle = ac97_sport_handle;
struct ac97_frame out_frame[2], in_frame[2];
pr_debug("%s enter 0x%x\n", __func__, reg);
/* When dma descriptor is enabled, the register should not be read */
if (sport_handle->tx_run || sport_handle->rx_run) {
pr_err("Could you send a mail to cliff.cai@analog.com "
"to report this?\n");
return -EFAULT;
}
memset(&out_frame, 0, 2 * sizeof(struct ac97_frame));
memset(&in_frame, 0, 2 * sizeof(struct ac97_frame));
out_frame[0].ac97_tag = TAG_VALID | TAG_CMD;
out_frame[0].ac97_addr = ((reg << 8) | 0x8000);
sport_send_and_recv(sport_handle, (unsigned char *)&out_frame,
(unsigned char *)&in_frame,
2 * sizeof(struct ac97_frame));
return in_frame[1].ac97_data;
}
void bf5xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short val)
{
struct sport_device *sport_handle = ac97_sport_handle;
pr_debug("%s enter 0x%x:0x%04x\n", __func__, reg, val);
if (sport_handle->tx_run) {
enqueue_cmd(ac97, (reg << 8), val); /* write */
enqueue_cmd(ac97, (reg << 8) | 0x8000, 0); /* read back */
} else {
struct ac97_frame frame;
memset(&frame, 0, sizeof(struct ac97_frame));
frame.ac97_tag = TAG_VALID | TAG_CMD;
frame.ac97_addr = (reg << 8);
frame.ac97_data = val;
sport_send_and_recv(sport_handle, (unsigned char *)&frame, \
NULL, sizeof(struct ac97_frame));
}
}
static void bf5xx_ac97_warm_reset(struct snd_ac97 *ac97)
{
struct sport_device *sport_handle = ac97_sport_handle;
u16 gpio = P_IDENT(sport_handle->pin_req[3]);
pr_debug("%s enter\n", __func__);
peripheral_free_list(sport_handle->pin_req);
gpio_request(gpio, "bf5xx-ac97");
gpio_direction_output(gpio, 1);
udelay(2);
gpio_set_value(gpio, 0);
udelay(1);
gpio_free(gpio);
peripheral_request_list(sport_handle->pin_req, "soc-audio");
}
static void bf5xx_ac97_cold_reset(struct snd_ac97 *ac97)
{
#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
pr_debug("%s enter\n", __func__);
/* It is specified for bf548-ezkit */
gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 0);
/* Keep reset pin low for 1 ms */
mdelay(1);
gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1);
/* Wait for bit clock recover */
mdelay(1);
#else
pr_info("%s: Not implemented\n", __func__);
#endif
}
static struct snd_ac97_bus_ops bf5xx_ac97_ops = {
.read = bf5xx_ac97_read,
.write = bf5xx_ac97_write,
.warm_reset = bf5xx_ac97_warm_reset,
.reset = bf5xx_ac97_cold_reset,
};
#ifdef CONFIG_PM
static int bf5xx_ac97_suspend(struct snd_soc_dai *dai)
{
struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
pr_debug("%s : sport %d\n", __func__, dai->id);
if (!dai->active)
return 0;
if (dai->capture_active)
sport_rx_stop(sport);
if (dai->playback_active)
sport_tx_stop(sport);
return 0;
}
static int bf5xx_ac97_resume(struct snd_soc_dai *dai)
{
int ret;
struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
pr_debug("%s : sport %d\n", __func__, dai->id);
if (!dai->active)
return 0;
#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
ret = sport_set_multichannel(sport, 16, 0x3FF, 0x3FF, 1);
#else
ret = sport_set_multichannel(sport, 16, 0x1F, 0x1F, 1);
#endif
if (ret) {
pr_err("SPORT is busy!\n");
return -EBUSY;
}
ret = sport_config_rx(sport, IRFS, 0xF, 0, (16*16-1));
if (ret) {
pr_err("SPORT is busy!\n");
return -EBUSY;
}
ret = sport_config_tx(sport, ITFS, 0xF, 0, (16*16-1));
if (ret) {
pr_err("SPORT is busy!\n");
return -EBUSY;
}
return 0;
}
#else
#define bf5xx_ac97_suspend NULL
#define bf5xx_ac97_resume NULL
#endif
static struct snd_soc_dai_driver bfin_ac97_dai = {
.ac97_control = 1,
.suspend = bf5xx_ac97_suspend,
.resume = bf5xx_ac97_resume,
.playback = {
.stream_name = "AC97 Playback",
.channels_min = 2,
#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
.channels_max = 6,
#else
.channels_max = 2,
#endif
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
};
static const struct snd_soc_component_driver bfin_ac97_component = {
.name = "bfin-ac97",
};
static int asoc_bfin_ac97_probe(struct platform_device *pdev)
{
struct sport_device *sport_handle;
int ret;
#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
/* Request PB3 as reset pin */
ret = devm_gpio_request_one(&pdev->dev,
CONFIG_SND_BF5XX_RESET_GPIO_NUM,
GPIOF_OUT_INIT_HIGH, "SND_AD198x RESET");
if (ret) {
dev_err(&pdev->dev,
"Failed to request GPIO_%d for reset: %d\n",
CONFIG_SND_BF5XX_RESET_GPIO_NUM, ret);
return ret;
}
#endif
sport_handle = sport_init(pdev, 2, sizeof(struct ac97_frame),
PAGE_SIZE);
if (!sport_handle) {
ret = -ENODEV;
goto sport_err;
}
/*SPORT works in TDM mode to simulate AC97 transfers*/
#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
ret = sport_set_multichannel(sport_handle, 16, 0x3FF, 0x3FF, 1);
#else
ret = sport_set_multichannel(sport_handle, 16, 0x1F, 0x1F, 1);
#endif
if (ret) {
pr_err("SPORT is busy!\n");
ret = -EBUSY;
goto sport_config_err;
}
ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1));
if (ret) {
pr_err("SPORT is busy!\n");
ret = -EBUSY;
goto sport_config_err;
}
ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1));
if (ret) {
pr_err("SPORT is busy!\n");
ret = -EBUSY;
goto sport_config_err;
}
ret = snd_soc_set_ac97_ops(&bf5xx_ac97_ops);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
goto sport_config_err;
}
ret = snd_soc_register_component(&pdev->dev, &bfin_ac97_component,
&bfin_ac97_dai, 1);
if (ret) {
pr_err("Failed to register DAI: %d\n", ret);
goto sport_config_err;
}
ac97_sport_handle = sport_handle;
return 0;
sport_config_err:
sport_done(sport_handle);
sport_err:
snd_soc_set_ac97_ops(NULL);
return ret;
}
static int asoc_bfin_ac97_remove(struct platform_device *pdev)
{
struct sport_device *sport_handle = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
sport_done(sport_handle);
snd_soc_set_ac97_ops(NULL);
return 0;
}
static struct platform_driver asoc_bfin_ac97_driver = {
.driver = {
.name = "bfin-ac97",
.owner = THIS_MODULE,
},
.probe = asoc_bfin_ac97_probe,
.remove = asoc_bfin_ac97_remove,
};
module_platform_driver(asoc_bfin_ac97_driver);
MODULE_AUTHOR("Roy Huang");
MODULE_DESCRIPTION("AC97 driver for ADI Blackfin");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,57 @@
/*
* sound/soc/blackfin/bf5xx-ac97.h
*
* 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 _BF5XX_AC97_H
#define _BF5XX_AC97_H
/* Frame format in memory, only support stereo currently */
struct ac97_frame {
u16 ac97_tag; /* slot 0 */
u16 ac97_addr; /* slot 1 */
u16 ac97_data; /* slot 2 */
u16 ac97_pcm_l; /*slot 3:front left*/
u16 ac97_pcm_r; /*slot 4:front left*/
#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
u16 ac97_mdm_l1;
u16 ac97_center; /*slot 6:center*/
u16 ac97_sl; /*slot 7:surround left*/
u16 ac97_sr; /*slot 8:surround right*/
u16 ac97_lfe; /*slot 9:lfe*/
#endif
} __attribute__ ((packed));
/* Speaker location */
#define SP_FL 0x0001
#define SP_FR 0x0010
#define SP_FC 0x0002
#define SP_LFE 0x0020
#define SP_SL 0x0004
#define SP_SR 0x0040
#define SP_STEREO (SP_FL | SP_FR)
#define SP_2DOT1 (SP_FL | SP_FR | SP_LFE)
#define SP_QUAD (SP_FL | SP_FR | SP_SL | SP_SR)
#define SP_5DOT1 (SP_FL | SP_FR | SP_FC | SP_LFE | SP_SL | SP_SR)
#define TAG_VALID 0x8000
#define TAG_CMD 0x6000
#define TAG_PCM_LEFT 0x1000
#define TAG_PCM_RIGHT 0x0800
#define TAG_PCM_MDM_L1 0x0400
#define TAG_PCM_CENTER 0x0200
#define TAG_PCM_SL 0x0100
#define TAG_PCM_SR 0x0080
#define TAG_PCM_LFE 0x0040
void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u16 *src, \
size_t count, unsigned int chan_mask);
void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u16 *dst, \
size_t count);
#endif

View file

@ -0,0 +1,119 @@
/*
* File: sound/soc/blackfin/bf5xx-ad1836.c
* Author: Barry Song <Barry.Song@analog.com>
*
* Created: Aug 4 2009
* Description: Board driver for ad1836 sound chip
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* 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/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <asm/blackfin.h>
#include <asm/cacheflush.h>
#include <asm/irq.h>
#include <asm/dma.h>
#include <asm/portmux.h>
#include "../codecs/ad1836.h"
static struct snd_soc_card bf5xx_ad1836;
static int bf5xx_ad1836_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
unsigned int channel_map[] = {0, 4, 1, 5, 2, 6, 3, 7};
int ret = 0;
/* set cpu DAI channel mapping */
ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map),
channel_map, ARRAY_SIZE(channel_map), channel_map);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xFF, 0xFF, 8, 32);
if (ret < 0)
return ret;
return 0;
}
#define BF5XX_AD1836_DAIFMT (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_IF | \
SND_SOC_DAIFMT_CBM_CFM)
static struct snd_soc_dai_link bf5xx_ad1836_dai = {
.name = "ad1836",
.stream_name = "AD1836",
.codec_dai_name = "ad1836-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.dai_fmt = BF5XX_AD1836_DAIFMT,
.init = bf5xx_ad1836_init,
};
static struct snd_soc_card bf5xx_ad1836 = {
.name = "bfin-ad1836",
.owner = THIS_MODULE,
.dai_link = &bf5xx_ad1836_dai,
.num_links = 1,
};
static int bf5xx_ad1836_driver_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &bf5xx_ad1836;
const char **link_name;
int ret;
link_name = pdev->dev.platform_data;
if (!link_name) {
dev_err(&pdev->dev, "No platform data supplied\n");
return -EINVAL;
}
bf5xx_ad1836_dai.cpu_dai_name = link_name[0];
bf5xx_ad1836_dai.codec_name = link_name[1];
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
static int bf5xx_ad1836_driver_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver bf5xx_ad1836_driver = {
.driver = {
.name = "bfin-snd-ad1836",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bf5xx_ad1836_driver_probe,
.remove = bf5xx_ad1836_driver_remove,
};
module_platform_driver(bf5xx_ad1836_driver);
/* Module information */
MODULE_AUTHOR("Barry Song");
MODULE_DESCRIPTION("ALSA SoC AD1836 board driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,131 @@
/*
* File: sound/soc/blackfin/bf5xx-ad193x.c
* Author: Barry Song <Barry.Song@analog.com>
*
* Created: Thur June 4 2009
* Description: Board driver for ad193x sound chip
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <asm/blackfin.h>
#include <asm/cacheflush.h>
#include <asm/irq.h>
#include <asm/dma.h>
#include <asm/portmux.h>
#include "../codecs/ad193x.h"
static struct snd_soc_card bf5xx_ad193x;
static int bf5xx_ad193x_link_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, 0, 24576000, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* set codec DAI slots, 8 channels, all channels are enabled */
ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 0xFF, 8, 32);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xFF, 0xFF, 8, 32);
if (ret < 0)
return ret;
return 0;
}
#define BF5XX_AD193X_DAIFMT (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_IF | \
SND_SOC_DAIFMT_CBM_CFM)
static struct snd_soc_dai_link bf5xx_ad193x_dai[] = {
{
.name = "ad193x",
.stream_name = "AD193X",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name ="ad193x-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "spi0.5",
.dai_fmt = BF5XX_AD193X_DAIFMT,
.init = bf5xx_ad193x_link_init,
},
{
.name = "ad193x",
.stream_name = "AD193X",
.cpu_dai_name = "bfin-i2s.1",
.codec_dai_name ="ad193x-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "spi0.5",
.dai_fmt = BF5XX_AD193X_DAIFMT,
.init = bf5xx_ad193x_link_init,
},
};
static struct snd_soc_card bf5xx_ad193x = {
.name = "bfin-ad193x",
.owner = THIS_MODULE,
.dai_link = &bf5xx_ad193x_dai[CONFIG_SND_BF5XX_SPORT_NUM],
.num_links = 1,
};
static struct platform_device *bfxx_ad193x_snd_device;
static int __init bf5xx_ad193x_init(void)
{
int ret;
bfxx_ad193x_snd_device = platform_device_alloc("soc-audio", -1);
if (!bfxx_ad193x_snd_device)
return -ENOMEM;
platform_set_drvdata(bfxx_ad193x_snd_device, &bf5xx_ad193x);
ret = platform_device_add(bfxx_ad193x_snd_device);
if (ret)
platform_device_put(bfxx_ad193x_snd_device);
return ret;
}
static void __exit bf5xx_ad193x_exit(void)
{
platform_device_unregister(bfxx_ad193x_snd_device);
}
module_init(bf5xx_ad193x_init);
module_exit(bf5xx_ad193x_exit);
/* Module information */
MODULE_AUTHOR("Barry Song");
MODULE_DESCRIPTION("ALSA SoC AD193X board driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,111 @@
/*
* File: sound/soc/blackfin/bf5xx-ad1980.c
* Author: Cliff Cai <Cliff.Cai@analog.com>
*
* Created: Tue June 06 2008
* Description: Board driver for AD1980/1 audio codec
*
* Modified:
* Copyright 2008 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* WARNING:
*
* Because Analog Devices Inc. discontinued the ad1980 sound chip since
* Sep. 2009, this ad1980 driver is not maintained, tested and supported
* by ADI now.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <asm/dma.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <linux/gpio.h>
#include <asm/portmux.h>
#include "../codecs/ad1980.h"
#include "bf5xx-ac97.h"
static struct snd_soc_card bf5xx_board;
static struct snd_soc_dai_link bf5xx_board_dai[] = {
{
.name = "AC97",
.stream_name = "AC97 HiFi",
.cpu_dai_name = "bfin-ac97.0",
.codec_dai_name = "ad1980-hifi",
.platform_name = "bfin-ac97-pcm-audio",
.codec_name = "ad1980",
},
{
.name = "AC97",
.stream_name = "AC97 HiFi",
.cpu_dai_name = "bfin-ac97.1",
.codec_dai_name = "ad1980-hifi",
.platform_name = "bfin-ac97-pcm-audio",
.codec_name = "ad1980",
},
};
static struct snd_soc_card bf5xx_board = {
.name = "bfin-ad1980",
.owner = THIS_MODULE,
.dai_link = &bf5xx_board_dai[CONFIG_SND_BF5XX_SPORT_NUM],
.num_links = 1,
};
static struct platform_device *bf5xx_board_snd_device;
static int __init bf5xx_board_init(void)
{
int ret;
bf5xx_board_snd_device = platform_device_alloc("soc-audio", -1);
if (!bf5xx_board_snd_device)
return -ENOMEM;
platform_set_drvdata(bf5xx_board_snd_device, &bf5xx_board);
ret = platform_device_add(bf5xx_board_snd_device);
if (ret)
platform_device_put(bf5xx_board_snd_device);
return ret;
}
static void __exit bf5xx_board_exit(void)
{
platform_device_unregister(bf5xx_board_snd_device);
}
module_init(bf5xx_board_init);
module_exit(bf5xx_board_exit);
/* Module information */
MODULE_AUTHOR("Cliff Cai");
MODULE_DESCRIPTION("ALSA SoC AD1980/1 BF5xx board (Obsolete)");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,212 @@
/*
* File: sound/soc/blackfin/bf5xx-ad73311.c
* Author: Cliff Cai <Cliff.Cai@analog.com>
*
* Created: Thur Sep 25 2008
* Description: Board driver for ad73311 sound chip
*
* Modified:
* Copyright 2008 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <asm/blackfin.h>
#include <asm/cacheflush.h>
#include <asm/irq.h>
#include <asm/dma.h>
#include <asm/portmux.h>
#include "../codecs/ad73311.h"
#include "bf5xx-sport.h"
#if CONFIG_SND_BF5XX_SPORT_NUM == 0
#define bfin_write_SPORT_TCR1 bfin_write_SPORT0_TCR1
#define bfin_read_SPORT_TCR1 bfin_read_SPORT0_TCR1
#define bfin_write_SPORT_TCR2 bfin_write_SPORT0_TCR2
#define bfin_write_SPORT_TX16 bfin_write_SPORT0_TX16
#define bfin_read_SPORT_STAT bfin_read_SPORT0_STAT
#else
#define bfin_write_SPORT_TCR1 bfin_write_SPORT1_TCR1
#define bfin_read_SPORT_TCR1 bfin_read_SPORT1_TCR1
#define bfin_write_SPORT_TCR2 bfin_write_SPORT1_TCR2
#define bfin_write_SPORT_TX16 bfin_write_SPORT1_TX16
#define bfin_read_SPORT_STAT bfin_read_SPORT1_STAT
#endif
#define GPIO_SE CONFIG_SND_BFIN_AD73311_SE
static struct snd_soc_card bf5xx_ad73311;
static int snd_ad73311_startup(void)
{
pr_debug("%s enter\n", __func__);
/* Pull up SE pin on AD73311L */
gpio_set_value(GPIO_SE, 1);
return 0;
}
static int snd_ad73311_configure(void)
{
unsigned short ctrl_regs[6];
unsigned short status = 0;
int count = 0;
/* DMCLK = MCLK = 16.384 MHz
* SCLK = DMCLK/8 = 2.048 MHz
* Sample Rate = DMCLK/2048 = 8 KHz
*/
ctrl_regs[0] = AD_CONTROL | AD_WRITE | CTRL_REG_B | REGB_MCDIV(0) | \
REGB_SCDIV(0) | REGB_DIRATE(0);
ctrl_regs[1] = AD_CONTROL | AD_WRITE | CTRL_REG_C | REGC_PUDEV | \
REGC_PUADC | REGC_PUDAC | REGC_PUREF | REGC_REFUSE ;
ctrl_regs[2] = AD_CONTROL | AD_WRITE | CTRL_REG_D | REGD_OGS(2) | \
REGD_IGS(2);
ctrl_regs[3] = AD_CONTROL | AD_WRITE | CTRL_REG_E | REGE_DA(0x1f);
ctrl_regs[4] = AD_CONTROL | AD_WRITE | CTRL_REG_F | REGF_SEEN ;
ctrl_regs[5] = AD_CONTROL | AD_WRITE | CTRL_REG_A | REGA_MODE_DATA;
local_irq_disable();
snd_ad73311_startup();
udelay(1);
bfin_write_SPORT_TCR1(TFSR);
bfin_write_SPORT_TCR2(0xF);
SSYNC();
/* SPORT Tx Register is a 8 x 16 FIFO, all the data can be put to
* FIFO before enable SPORT to transfer the data
*/
for (count = 0; count < 6; count++)
bfin_write_SPORT_TX16(ctrl_regs[count]);
SSYNC();
bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() | TSPEN);
SSYNC();
/* When TUVF is set, the data is already send out */
while (!(status & TUVF) && ++count < 10000) {
udelay(1);
status = bfin_read_SPORT_STAT();
SSYNC();
}
bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() & ~TSPEN);
SSYNC();
local_irq_enable();
if (count >= 10000) {
printk(KERN_ERR "ad73311: failed to configure codec\n");
return -1;
}
return 0;
}
static int bf5xx_probe(struct snd_soc_card *card)
{
int err;
if (gpio_request(GPIO_SE, "AD73311_SE")) {
printk(KERN_ERR "%s: Failed ro request GPIO_%d\n", __func__, GPIO_SE);
return -EBUSY;
}
gpio_direction_output(GPIO_SE, 0);
err = snd_ad73311_configure();
if (err < 0)
return -EFAULT;
return 0;
}
#define BF5XX_AD7311_DAI_FMT (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | \
SND_SOC_DAIFMT_CBM_CFM)
static struct snd_soc_dai_link bf5xx_ad73311_dai[] = {
{
.name = "ad73311",
.stream_name = "AD73311",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name = "ad73311-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "ad73311",
.dai_fmt = BF5XX_AD7311_DAI_FMT,
},
{
.name = "ad73311",
.stream_name = "AD73311",
.cpu_dai_name = "bfin-i2s.1",
.codec_dai_name = "ad73311-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "ad73311",
.dai_fmt = BF5XX_AD7311_DAI_FMT,
},
};
static struct snd_soc_card bf5xx_ad73311 = {
.name = "bfin-ad73311",
.owner = THIS_MODULE,
.probe = bf5xx_probe,
.dai_link = &bf5xx_ad73311_dai[CONFIG_SND_BF5XX_SPORT_NUM],
.num_links = 1,
};
static struct platform_device *bf5xx_ad73311_snd_device;
static int __init bf5xx_ad73311_init(void)
{
int ret;
pr_debug("%s enter\n", __func__);
bf5xx_ad73311_snd_device = platform_device_alloc("soc-audio", -1);
if (!bf5xx_ad73311_snd_device)
return -ENOMEM;
platform_set_drvdata(bf5xx_ad73311_snd_device, &bf5xx_ad73311);
ret = platform_device_add(bf5xx_ad73311_snd_device);
if (ret)
platform_device_put(bf5xx_ad73311_snd_device);
return ret;
}
static void __exit bf5xx_ad73311_exit(void)
{
pr_debug("%s enter\n", __func__);
platform_device_unregister(bf5xx_ad73311_snd_device);
}
module_init(bf5xx_ad73311_init);
module_exit(bf5xx_ad73311_exit);
/* Module information */
MODULE_AUTHOR("Cliff Cai");
MODULE_DESCRIPTION("ALSA SoC AD73311 Blackfin");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,368 @@
/*
* File: sound/soc/blackfin/bf5xx-i2s-pcm.c
* Author: Cliff Cai <Cliff.Cai@analog.com>
*
* Created: Tue June 06 2008
* Description: DMA driver for i2s codec
*
* Modified:
* Copyright 2008 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/gfp.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <asm/dma.h>
#include "bf5xx-sport.h"
#include "bf5xx-i2s-pcm.h"
static void bf5xx_dma_irq(void *data)
{
struct snd_pcm_substream *pcm = data;
snd_pcm_period_elapsed(pcm);
}
static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BLOCK_TRANSFER,
.period_bytes_min = 32,
.period_bytes_max = 0x10000,
.periods_min = 1,
.periods_max = PAGE_SIZE/32,
.buffer_bytes_max = 0x20000, /* 128 kbytes */
.fifo_size = 16,
};
static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
unsigned int buffer_size = params_buffer_bytes(params);
struct bf5xx_i2s_pcm_data *dma_data;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
if (dma_data->tdm_mode)
buffer_size = buffer_size / params_channels(params) * 8;
return snd_pcm_lib_malloc_pages(substream, buffer_size);
}
static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
{
snd_pcm_lib_free_pages(substream);
return 0;
}
static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
int period_bytes = frames_to_bytes(runtime, runtime->period_size);
struct bf5xx_i2s_pcm_data *dma_data;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
if (dma_data->tdm_mode)
period_bytes = period_bytes / runtime->channels * 8;
pr_debug("%s enter\n", __func__);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
sport_config_tx_dma(sport, runtime->dma_area,
runtime->periods, period_bytes);
} else {
sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
sport_config_rx_dma(sport, runtime->dma_area,
runtime->periods, period_bytes);
}
return 0;
}
static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
int ret = 0;
pr_debug("%s enter\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
sport_tx_start(sport);
else
sport_rx_start(sport);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
sport_tx_stop(sport);
else
sport_rx_stop(sport);
break;
default:
ret = -EINVAL;
}
return ret;
}
static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct sport_device *sport = runtime->private_data;
unsigned int diff;
snd_pcm_uframes_t frames;
struct bf5xx_i2s_pcm_data *dma_data;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
pr_debug("%s enter\n", __func__);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
diff = sport_curr_offset_tx(sport);
} else {
diff = sport_curr_offset_rx(sport);
}
/*
* TX at least can report one frame beyond the end of the
* buffer if we hit the wraparound case - clamp to within the
* buffer as the ALSA APIs require.
*/
if (diff == snd_pcm_lib_buffer_bytes(substream))
diff = 0;
frames = bytes_to_frames(substream->runtime, diff);
if (dma_data->tdm_mode)
frames = frames * runtime->channels / 8;
return frames;
}
static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dma_buffer *buf = &substream->dma_buffer;
struct bf5xx_i2s_pcm_data *dma_data;
int ret;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
pr_debug("%s enter\n", __func__);
snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
if (dma_data->tdm_mode)
runtime->hw.buffer_bytes_max /= 4;
else
runtime->hw.info |= SNDRV_PCM_INFO_MMAP;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
goto out;
if (sport_handle != NULL) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
sport_handle->tx_buf = buf->area;
else
sport_handle->rx_buf = buf->area;
runtime->private_data = sport_handle;
} else {
pr_err("sport_handle is NULL\n");
return -1;
}
return 0;
out:
return ret;
}
static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
size_t size = vma->vm_end - vma->vm_start;
vma->vm_start = (unsigned long)runtime->dma_area;
vma->vm_end = vma->vm_start + size;
vma->vm_flags |= VM_SHARED;
return 0 ;
}
static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int sample_size = runtime->sample_bits / 8;
struct bf5xx_i2s_pcm_data *dma_data;
unsigned int i;
void *src, *dst;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
if (dma_data->tdm_mode) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
src = buf;
dst = runtime->dma_area;
dst += pos * sample_size * 8;
while (count--) {
for (i = 0; i < runtime->channels; i++) {
memcpy(dst + dma_data->map[i] *
sample_size, src, sample_size);
src += sample_size;
}
dst += 8 * sample_size;
}
} else {
src = runtime->dma_area;
src += pos * sample_size * 8;
dst = buf;
while (count--) {
for (i = 0; i < runtime->channels; i++) {
memcpy(dst, src + dma_data->map[i] *
sample_size, sample_size);
dst += sample_size;
}
src += 8 * sample_size;
}
}
} else {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
src = buf;
dst = runtime->dma_area;
dst += frames_to_bytes(runtime, pos);
} else {
src = runtime->dma_area;
src += frames_to_bytes(runtime, pos);
dst = buf;
}
memcpy(dst, src, frames_to_bytes(runtime, count));
}
return 0;
}
static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int sample_size = runtime->sample_bits / 8;
void *buf = runtime->dma_area;
struct bf5xx_i2s_pcm_data *dma_data;
unsigned int offset, samples;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
if (dma_data->tdm_mode) {
offset = pos * 8 * sample_size;
samples = count * 8;
} else {
offset = frames_to_bytes(runtime, pos);
samples = count * runtime->channels;
}
snd_pcm_format_set_silence(runtime->format, buf + offset, samples);
return 0;
}
static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
.open = bf5xx_pcm_open,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = bf5xx_pcm_hw_params,
.hw_free = bf5xx_pcm_hw_free,
.prepare = bf5xx_pcm_prepare,
.trigger = bf5xx_pcm_trigger,
.pointer = bf5xx_pcm_pointer,
.mmap = bf5xx_pcm_mmap,
.copy = bf5xx_pcm_copy,
.silence = bf5xx_pcm_silence,
};
static int bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
int ret;
pr_debug("%s enter\n", __func__);
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
SNDRV_DMA_TYPE_DEV, card->dev, size, size);
}
static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = {
.ops = &bf5xx_pcm_i2s_ops,
.pcm_new = bf5xx_pcm_i2s_new,
};
static int bfin_i2s_soc_platform_probe(struct platform_device *pdev)
{
return snd_soc_register_platform(&pdev->dev, &bf5xx_i2s_soc_platform);
}
static int bfin_i2s_soc_platform_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static struct platform_driver bfin_i2s_pcm_driver = {
.driver = {
.name = "bfin-i2s-pcm-audio",
.owner = THIS_MODULE,
},
.probe = bfin_i2s_soc_platform_probe,
.remove = bfin_i2s_soc_platform_remove,
};
module_platform_driver(bfin_i2s_pcm_driver);
MODULE_AUTHOR("Cliff Cai");
MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,17 @@
/*
* 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 _BF5XX_TDM_PCM_H
#define _BF5XX_TDM_PCM_H
#define BFIN_TDM_DAI_MAX_SLOTS 8
struct bf5xx_i2s_pcm_data {
unsigned int map[BFIN_TDM_DAI_MAX_SLOTS];
bool tdm_mode;
};
#endif

View file

@ -0,0 +1,392 @@
/*
* File: sound/soc/blackfin/bf5xx-i2s.c
* Author: Cliff Cai <Cliff.Cai@analog.com>
*
* Created: Tue June 06 2008
* Description: Blackfin I2S CPU DAI driver
*
* Modified:
* Copyright 2008 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/irq.h>
#include <asm/portmux.h>
#include <linux/mutex.h>
#include <linux/gpio.h>
#include "bf5xx-sport.h"
#include "bf5xx-i2s-pcm.h"
struct bf5xx_i2s_port {
u16 tcr1;
u16 rcr1;
u16 tcr2;
u16 rcr2;
int configured;
unsigned int slots;
unsigned int tx_mask;
unsigned int rx_mask;
struct bf5xx_i2s_pcm_data tx_dma_data;
struct bf5xx_i2s_pcm_data rx_dma_data;
};
static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
int ret = 0;
/* interface format:support I2S,slave mode */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
bf5xx_i2s->tcr1 |= TFSR | TCKFE;
bf5xx_i2s->rcr1 |= RFSR | RCKFE;
bf5xx_i2s->tcr2 |= TSFSE;
bf5xx_i2s->rcr2 |= RSFSE;
break;
case SND_SOC_DAIFMT_DSP_A:
bf5xx_i2s->tcr1 |= TFSR;
bf5xx_i2s->rcr1 |= RFSR;
break;
case SND_SOC_DAIFMT_LEFT_J:
ret = -EINVAL;
break;
default:
dev_err(cpu_dai->dev, "%s: Unknown DAI format type\n",
__func__);
ret = -EINVAL;
break;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
break;
case SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_CBM_CFS:
case SND_SOC_DAIFMT_CBS_CFM:
ret = -EINVAL;
break;
default:
dev_err(cpu_dai->dev, "%s: Unknown DAI master type\n",
__func__);
ret = -EINVAL;
break;
}
return ret;
}
static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
int ret = 0;
bf5xx_i2s->tcr2 &= ~0x1f;
bf5xx_i2s->rcr2 &= ~0x1f;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
bf5xx_i2s->tcr2 |= 7;
bf5xx_i2s->rcr2 |= 7;
sport_handle->wdsize = 1;
break;
case SNDRV_PCM_FORMAT_S16_LE:
bf5xx_i2s->tcr2 |= 15;
bf5xx_i2s->rcr2 |= 15;
sport_handle->wdsize = 2;
break;
case SNDRV_PCM_FORMAT_S24_LE:
bf5xx_i2s->tcr2 |= 23;
bf5xx_i2s->rcr2 |= 23;
sport_handle->wdsize = 3;
break;
case SNDRV_PCM_FORMAT_S32_LE:
bf5xx_i2s->tcr2 |= 31;
bf5xx_i2s->rcr2 |= 31;
sport_handle->wdsize = 4;
break;
}
if (!bf5xx_i2s->configured) {
/*
* TX and RX are not independent,they are enabled at the
* same time, even if only one side is running. So, we
* need to configure both of them at the time when the first
* stream is opened.
*
* CPU DAI:slave mode.
*/
bf5xx_i2s->configured = 1;
ret = sport_config_rx(sport_handle, bf5xx_i2s->rcr1,
bf5xx_i2s->rcr2, 0, 0);
if (ret) {
dev_err(dai->dev, "SPORT is busy!\n");
return -EBUSY;
}
ret = sport_config_tx(sport_handle, bf5xx_i2s->tcr1,
bf5xx_i2s->tcr2, 0, 0);
if (ret) {
dev_err(dai->dev, "SPORT is busy!\n");
return -EBUSY;
}
}
return 0;
}
static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
dev_dbg(dai->dev, "%s enter\n", __func__);
/* No active stream, SPORT is allowed to be configured again. */
if (!dai->active)
bf5xx_i2s->configured = 0;
}
static int bf5xx_i2s_set_channel_map(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
unsigned int tx_mapped = 0, rx_mapped = 0;
unsigned int slot;
int i;
if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) ||
(rx_num > BFIN_TDM_DAI_MAX_SLOTS))
return -EINVAL;
for (i = 0; i < tx_num; i++) {
slot = tx_slot[i];
if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
(!(tx_mapped & (1 << slot)))) {
bf5xx_i2s->tx_dma_data.map[i] = slot;
tx_mapped |= 1 << slot;
} else
return -EINVAL;
}
for (i = 0; i < rx_num; i++) {
slot = rx_slot[i];
if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
(!(rx_mapped & (1 << slot)))) {
bf5xx_i2s->rx_dma_data.map[i] = slot;
rx_mapped |= 1 << slot;
} else
return -EINVAL;
}
return 0;
}
static int bf5xx_i2s_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int width)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
if (slots % 8 != 0 || slots > 8)
return -EINVAL;
if (width != 32)
return -EINVAL;
bf5xx_i2s->slots = slots;
bf5xx_i2s->tx_mask = tx_mask;
bf5xx_i2s->rx_mask = rx_mask;
bf5xx_i2s->tx_dma_data.tdm_mode = slots != 0;
bf5xx_i2s->rx_dma_data.tdm_mode = slots != 0;
return sport_set_multichannel(sport_handle, slots, tx_mask, rx_mask, 0);
}
#ifdef CONFIG_PM
static int bf5xx_i2s_suspend(struct snd_soc_dai *dai)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
dev_dbg(dai->dev, "%s : sport %d\n", __func__, dai->id);
if (dai->capture_active)
sport_rx_stop(sport_handle);
if (dai->playback_active)
sport_tx_stop(sport_handle);
return 0;
}
static int bf5xx_i2s_resume(struct snd_soc_dai *dai)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
int ret;
dev_dbg(dai->dev, "%s : sport %d\n", __func__, dai->id);
ret = sport_config_rx(sport_handle, bf5xx_i2s->rcr1,
bf5xx_i2s->rcr2, 0, 0);
if (ret) {
dev_err(dai->dev, "SPORT is busy!\n");
return -EBUSY;
}
ret = sport_config_tx(sport_handle, bf5xx_i2s->tcr1,
bf5xx_i2s->tcr2, 0, 0);
if (ret) {
dev_err(dai->dev, "SPORT is busy!\n");
return -EBUSY;
}
return sport_set_multichannel(sport_handle, bf5xx_i2s->slots,
bf5xx_i2s->tx_mask, bf5xx_i2s->rx_mask, 0);
}
#else
#define bf5xx_i2s_suspend NULL
#define bf5xx_i2s_resume NULL
#endif
static int bf5xx_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
unsigned int i;
for (i = 0; i < BFIN_TDM_DAI_MAX_SLOTS; i++) {
bf5xx_i2s->tx_dma_data.map[i] = i;
bf5xx_i2s->rx_dma_data.map[i] = i;
}
dai->playback_dma_data = &bf5xx_i2s->tx_dma_data;
dai->capture_dma_data = &bf5xx_i2s->rx_dma_data;
return 0;
}
#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
SNDRV_PCM_RATE_96000)
#define BF5XX_I2S_FORMATS \
(SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops bf5xx_i2s_dai_ops = {
.shutdown = bf5xx_i2s_shutdown,
.hw_params = bf5xx_i2s_hw_params,
.set_fmt = bf5xx_i2s_set_dai_fmt,
.set_tdm_slot = bf5xx_i2s_set_tdm_slot,
.set_channel_map = bf5xx_i2s_set_channel_map,
};
static struct snd_soc_dai_driver bf5xx_i2s_dai = {
.probe = bf5xx_i2s_dai_probe,
.suspend = bf5xx_i2s_suspend,
.resume = bf5xx_i2s_resume,
.playback = {
.channels_min = 2,
.channels_max = 8,
.rates = BF5XX_I2S_RATES,
.formats = BF5XX_I2S_FORMATS,},
.capture = {
.channels_min = 2,
.channels_max = 8,
.rates = BF5XX_I2S_RATES,
.formats = BF5XX_I2S_FORMATS,},
.ops = &bf5xx_i2s_dai_ops,
};
static const struct snd_soc_component_driver bf5xx_i2s_component = {
.name = "bf5xx-i2s",
};
static int bf5xx_i2s_probe(struct platform_device *pdev)
{
struct sport_device *sport_handle;
int ret;
/* configure SPORT for I2S */
sport_handle = sport_init(pdev, 4, 8 * sizeof(u32),
sizeof(struct bf5xx_i2s_port));
if (!sport_handle)
return -ENODEV;
/* register with the ASoC layers */
ret = snd_soc_register_component(&pdev->dev, &bf5xx_i2s_component,
&bf5xx_i2s_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Failed to register DAI: %d\n", ret);
sport_done(sport_handle);
return ret;
}
return 0;
}
static int bf5xx_i2s_remove(struct platform_device *pdev)
{
struct sport_device *sport_handle = platform_get_drvdata(pdev);
dev_dbg(&pdev->dev, "%s enter\n", __func__);
snd_soc_unregister_component(&pdev->dev);
sport_done(sport_handle);
return 0;
}
static struct platform_driver bfin_i2s_driver = {
.probe = bf5xx_i2s_probe,
.remove = bf5xx_i2s_remove,
.driver = {
.name = "bfin-i2s",
.owner = THIS_MODULE,
},
};
module_platform_driver(bfin_i2s_driver);
/* Module information */
MODULE_AUTHOR("Cliff Cai");
MODULE_DESCRIPTION("I2S driver for ADI Blackfin");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,174 @@
/*
* File: bf5xx_sport.h
* Based on:
* Author: Roy Huang <roy.huang@analog.com>
*
* Created:
* Description:
*
* Copyright 2004-2007 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __BF5XX_SPORT_H__
#define __BF5XX_SPORT_H__
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <asm/dma.h>
#include <asm/bfin_sport.h>
#define DESC_ELEMENT_COUNT 9
struct sport_device {
int num;
int dma_rx_chan;
int dma_tx_chan;
int err_irq;
const unsigned short *pin_req;
struct sport_register *regs;
unsigned char *rx_buf;
unsigned char *tx_buf;
unsigned int rx_fragsize;
unsigned int tx_fragsize;
unsigned int rx_frags;
unsigned int tx_frags;
unsigned int wdsize;
/* for dummy dma transfer */
void *dummy_buf;
unsigned int dummy_count;
/* DMA descriptor ring head of current audio stream*/
struct dmasg *dma_rx_desc;
struct dmasg *dma_tx_desc;
unsigned int rx_desc_bytes;
unsigned int tx_desc_bytes;
unsigned int rx_run:1; /* rx is running */
unsigned int tx_run:1; /* tx is running */
struct dmasg *dummy_rx_desc;
struct dmasg *dummy_tx_desc;
struct dmasg *curr_rx_desc;
struct dmasg *curr_tx_desc;
int rx_curr_frag;
int tx_curr_frag;
unsigned int rcr1;
unsigned int rcr2;
int rx_tdm_count;
unsigned int tcr1;
unsigned int tcr2;
int tx_tdm_count;
void (*rx_callback)(void *data);
void *rx_data;
void (*tx_callback)(void *data);
void *tx_data;
void (*err_callback)(void *data);
void *err_data;
unsigned char *tx_dma_buf;
unsigned char *rx_dma_buf;
#ifdef CONFIG_SND_BF5XX_MMAP_SUPPORT
dma_addr_t tx_dma_phy;
dma_addr_t rx_dma_phy;
int tx_pos;/*pcm sample count*/
int rx_pos;
unsigned int tx_buffer_size;
unsigned int rx_buffer_size;
int tx_delay_pos;
int once;
#endif
void *private_data;
};
struct sport_param {
int num;
int dma_rx_chan;
int dma_tx_chan;
int err_irq;
const unsigned short *pin_req;
struct sport_register *regs;
unsigned int wdsize;
unsigned int dummy_count;
void *private_data;
};
struct sport_device *sport_init(struct platform_device *pdev,
unsigned int wdsize, unsigned int dummy_count, size_t priv_size);
void sport_done(struct sport_device *sport);
/* first use these ...*/
/* note: multichannel is in units of 8 channels, tdm_count is number of channels
* NOT / 8 ! all channels are enabled by default */
int sport_set_multichannel(struct sport_device *sport, int tdm_count,
u32 tx_mask, u32 rx_mask, int packed);
int sport_config_rx(struct sport_device *sport,
unsigned int rcr1, unsigned int rcr2,
unsigned int clkdiv, unsigned int fsdiv);
int sport_config_tx(struct sport_device *sport,
unsigned int tcr1, unsigned int tcr2,
unsigned int clkdiv, unsigned int fsdiv);
/* ... then these: */
/* buffer size (in bytes) == fragcount * fragsize_bytes */
/* this is not a very general api, it sets the dma to 2d autobuffer mode */
int sport_config_rx_dma(struct sport_device *sport, void *buf,
int fragcount, size_t fragsize_bytes);
int sport_config_tx_dma(struct sport_device *sport, void *buf,
int fragcount, size_t fragsize_bytes);
int sport_tx_start(struct sport_device *sport);
int sport_tx_stop(struct sport_device *sport);
int sport_rx_start(struct sport_device *sport);
int sport_rx_stop(struct sport_device *sport);
/* for use in interrupt handler */
unsigned long sport_curr_offset_rx(struct sport_device *sport);
unsigned long sport_curr_offset_tx(struct sport_device *sport);
void sport_incfrag(struct sport_device *sport, int *frag, int tx);
void sport_decfrag(struct sport_device *sport, int *frag, int tx);
int sport_set_rx_callback(struct sport_device *sport,
void (*rx_callback)(void *), void *rx_data);
int sport_set_tx_callback(struct sport_device *sport,
void (*tx_callback)(void *), void *tx_data);
int sport_set_err_callback(struct sport_device *sport,
void (*err_callback)(void *), void *err_data);
int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
u8 *in_data, int len);
#endif /* BF53X_SPORT_H */

View file

@ -0,0 +1,126 @@
/*
* File: sound/soc/blackfin/bf5xx-ssm2602.c
* Author: Cliff Cai <Cliff.Cai@analog.com>
*
* Created: Tue June 06 2008
* Description: board driver for SSM2602 sound chip
*
* Modified:
* Copyright 2008 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <asm/dma.h>
#include <asm/portmux.h>
#include <linux/gpio.h>
#include "../codecs/ssm2602.h"
#include "bf5xx-sport.h"
static struct snd_soc_card bf5xx_ssm2602;
static int bf5xx_ssm2602_dai_init(struct snd_soc_pcm_runtime *rtd)
{
/*
* If you are using a crystal source which frequency is not 12MHz
* then modify the below case statement with frequency of the crystal.
*
* If you are using the SPORT to generate clocking then this is
* where to do it.
*/
return snd_soc_dai_set_sysclk(rtd->codec_dai, SSM2602_SYSCLK, 12000000,
SND_SOC_CLOCK_IN);
}
/* CODEC is master for BCLK and LRC in this configuration. */
#define BF5XX_SSM2602_DAIFMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \
SND_SOC_DAIFMT_CBM_CFM)
static struct snd_soc_dai_link bf5xx_ssm2602_dai[] = {
{
.name = "ssm2602",
.stream_name = "SSM2602",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name = "ssm2602-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "ssm2602.0-001b",
.init = bf5xx_ssm2602_dai_init,
.dai_fmt = BF5XX_SSM2602_DAIFMT,
},
{
.name = "ssm2602",
.stream_name = "SSM2602",
.cpu_dai_name = "bfin-i2s.1",
.codec_dai_name = "ssm2602-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "ssm2602.0-001b",
.init = bf5xx_ssm2602_dai_init,
.dai_fmt = BF5XX_SSM2602_DAIFMT,
},
};
static struct snd_soc_card bf5xx_ssm2602 = {
.name = "bfin-ssm2602",
.owner = THIS_MODULE,
.dai_link = &bf5xx_ssm2602_dai[CONFIG_SND_BF5XX_SPORT_NUM],
.num_links = 1,
};
static struct platform_device *bf5xx_ssm2602_snd_device;
static int __init bf5xx_ssm2602_init(void)
{
int ret;
pr_debug("%s enter\n", __func__);
bf5xx_ssm2602_snd_device = platform_device_alloc("soc-audio", -1);
if (!bf5xx_ssm2602_snd_device)
return -ENOMEM;
platform_set_drvdata(bf5xx_ssm2602_snd_device, &bf5xx_ssm2602);
ret = platform_device_add(bf5xx_ssm2602_snd_device);
if (ret)
platform_device_put(bf5xx_ssm2602_snd_device);
return ret;
}
static void __exit bf5xx_ssm2602_exit(void)
{
pr_debug("%s enter\n", __func__);
platform_device_unregister(bf5xx_ssm2602_snd_device);
}
module_init(bf5xx_ssm2602_init);
module_exit(bf5xx_ssm2602_exit);
/* Module information */
MODULE_AUTHOR("Cliff Cai");
MODULE_DESCRIPTION("ALSA SoC SSM2602 BF527-EZKIT");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,240 @@
/*
* bf6xx-i2s.c - Analog Devices BF6XX i2s interface driver
*
* Copyright (c) 2012 Analog Devices Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "bf6xx-sport.h"
struct sport_params param;
static int bfin_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct sport_device *sport = snd_soc_dai_get_drvdata(cpu_dai);
struct device *dev = &sport->pdev->dev;
int ret = 0;
param.spctl &= ~(SPORT_CTL_OPMODE | SPORT_CTL_CKRE | SPORT_CTL_FSR
| SPORT_CTL_LFS | SPORT_CTL_LAFS);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
param.spctl |= SPORT_CTL_OPMODE | SPORT_CTL_CKRE
| SPORT_CTL_LFS;
break;
case SND_SOC_DAIFMT_DSP_A:
param.spctl |= SPORT_CTL_FSR;
break;
case SND_SOC_DAIFMT_LEFT_J:
param.spctl |= SPORT_CTL_OPMODE | SPORT_CTL_LFS
| SPORT_CTL_LAFS;
break;
default:
dev_err(dev, "%s: Unknown DAI format type\n", __func__);
ret = -EINVAL;
break;
}
param.spctl &= ~(SPORT_CTL_ICLK | SPORT_CTL_IFS);
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
break;
case SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_CBM_CFS:
case SND_SOC_DAIFMT_CBS_CFM:
ret = -EINVAL;
break;
default:
dev_err(dev, "%s: Unknown DAI master type\n", __func__);
ret = -EINVAL;
break;
}
return ret;
}
static int bfin_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
struct device *dev = &sport->pdev->dev;
int ret = 0;
param.spctl &= ~SPORT_CTL_SLEN;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
param.spctl |= 0x70;
sport->wdsize = 1;
break;
case SNDRV_PCM_FORMAT_S16_LE:
param.spctl |= 0xf0;
sport->wdsize = 2;
break;
case SNDRV_PCM_FORMAT_S24_LE:
param.spctl |= 0x170;
sport->wdsize = 3;
break;
case SNDRV_PCM_FORMAT_S32_LE:
param.spctl |= 0x1f0;
sport->wdsize = 4;
break;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = sport_set_tx_params(sport, &param);
if (ret) {
dev_err(dev, "SPORT tx is busy!\n");
return ret;
}
} else {
ret = sport_set_rx_params(sport, &param);
if (ret) {
dev_err(dev, "SPORT rx is busy!\n");
return ret;
}
}
return 0;
}
#ifdef CONFIG_PM
static int bfin_i2s_suspend(struct snd_soc_dai *dai)
{
struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
if (dai->capture_active)
sport_rx_stop(sport);
if (dai->playback_active)
sport_tx_stop(sport);
return 0;
}
static int bfin_i2s_resume(struct snd_soc_dai *dai)
{
struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
struct device *dev = &sport->pdev->dev;
int ret;
ret = sport_set_tx_params(sport, &param);
if (ret) {
dev_err(dev, "SPORT tx is busy!\n");
return ret;
}
ret = sport_set_rx_params(sport, &param);
if (ret) {
dev_err(dev, "SPORT rx is busy!\n");
return ret;
}
return 0;
}
#else
#define bfin_i2s_suspend NULL
#define bfin_i2s_resume NULL
#endif
#define BFIN_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
SNDRV_PCM_RATE_96000)
#define BFIN_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_ops bfin_i2s_dai_ops = {
.hw_params = bfin_i2s_hw_params,
.set_fmt = bfin_i2s_set_dai_fmt,
};
static struct snd_soc_dai_driver bfin_i2s_dai = {
.suspend = bfin_i2s_suspend,
.resume = bfin_i2s_resume,
.playback = {
.channels_min = 1,
.channels_max = 2,
.rates = BFIN_I2S_RATES,
.formats = BFIN_I2S_FORMATS,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rates = BFIN_I2S_RATES,
.formats = BFIN_I2S_FORMATS,
},
.ops = &bfin_i2s_dai_ops,
};
static const struct snd_soc_component_driver bfin_i2s_component = {
.name = "bfin-i2s",
};
static int bfin_i2s_probe(struct platform_device *pdev)
{
struct sport_device *sport;
struct device *dev = &pdev->dev;
int ret;
sport = sport_create(pdev);
if (!sport)
return -ENODEV;
/* register with the ASoC layers */
ret = snd_soc_register_component(dev, &bfin_i2s_component,
&bfin_i2s_dai, 1);
if (ret) {
dev_err(dev, "Failed to register DAI: %d\n", ret);
sport_delete(sport);
return ret;
}
platform_set_drvdata(pdev, sport);
return 0;
}
static int bfin_i2s_remove(struct platform_device *pdev)
{
struct sport_device *sport = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
sport_delete(sport);
return 0;
}
static struct platform_driver bfin_i2s_driver = {
.probe = bfin_i2s_probe,
.remove = bfin_i2s_remove,
.driver = {
.name = "bfin-i2s",
.owner = THIS_MODULE,
},
};
module_platform_driver(bfin_i2s_driver);
MODULE_DESCRIPTION("Analog Devices BF6XX i2s interface driver");
MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,429 @@
/*
* bf6xx_sport.c Analog Devices BF6XX SPORT driver
*
* Copyright (c) 2012 Analog Devices Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/blackfin.h>
#include <asm/dma.h>
#include <asm/portmux.h>
#include "bf6xx-sport.h"
int sport_set_tx_params(struct sport_device *sport,
struct sport_params *params)
{
if (sport->tx_regs->spctl & SPORT_CTL_SPENPRI)
return -EBUSY;
sport->tx_regs->spctl = params->spctl | SPORT_CTL_SPTRAN;
sport->tx_regs->div = params->div;
SSYNC();
return 0;
}
EXPORT_SYMBOL(sport_set_tx_params);
int sport_set_rx_params(struct sport_device *sport,
struct sport_params *params)
{
if (sport->rx_regs->spctl & SPORT_CTL_SPENPRI)
return -EBUSY;
sport->rx_regs->spctl = params->spctl & ~SPORT_CTL_SPTRAN;
sport->rx_regs->div = params->div;
SSYNC();
return 0;
}
EXPORT_SYMBOL(sport_set_rx_params);
static int compute_wdsize(size_t wdsize)
{
switch (wdsize) {
case 1:
return WDSIZE_8 | PSIZE_8;
case 2:
return WDSIZE_16 | PSIZE_16;
default:
return WDSIZE_32 | PSIZE_32;
}
}
void sport_tx_start(struct sport_device *sport)
{
set_dma_next_desc_addr(sport->tx_dma_chan, sport->tx_desc);
set_dma_config(sport->tx_dma_chan, DMAFLOW_LIST | DI_EN
| compute_wdsize(sport->wdsize) | NDSIZE_6);
enable_dma(sport->tx_dma_chan);
sport->tx_regs->spctl |= SPORT_CTL_SPENPRI;
SSYNC();
}
EXPORT_SYMBOL(sport_tx_start);
void sport_rx_start(struct sport_device *sport)
{
set_dma_next_desc_addr(sport->rx_dma_chan, sport->rx_desc);
set_dma_config(sport->rx_dma_chan, DMAFLOW_LIST | DI_EN | WNR
| compute_wdsize(sport->wdsize) | NDSIZE_6);
enable_dma(sport->rx_dma_chan);
sport->rx_regs->spctl |= SPORT_CTL_SPENPRI;
SSYNC();
}
EXPORT_SYMBOL(sport_rx_start);
void sport_tx_stop(struct sport_device *sport)
{
sport->tx_regs->spctl &= ~SPORT_CTL_SPENPRI;
SSYNC();
disable_dma(sport->tx_dma_chan);
}
EXPORT_SYMBOL(sport_tx_stop);
void sport_rx_stop(struct sport_device *sport)
{
sport->rx_regs->spctl &= ~SPORT_CTL_SPENPRI;
SSYNC();
disable_dma(sport->rx_dma_chan);
}
EXPORT_SYMBOL(sport_rx_stop);
void sport_set_tx_callback(struct sport_device *sport,
void (*tx_callback)(void *), void *tx_data)
{
sport->tx_callback = tx_callback;
sport->tx_data = tx_data;
}
EXPORT_SYMBOL(sport_set_tx_callback);
void sport_set_rx_callback(struct sport_device *sport,
void (*rx_callback)(void *), void *rx_data)
{
sport->rx_callback = rx_callback;
sport->rx_data = rx_data;
}
EXPORT_SYMBOL(sport_set_rx_callback);
static void setup_desc(struct dmasg *desc, void *buf, int fragcount,
size_t fragsize, unsigned int cfg,
unsigned int count, size_t wdsize)
{
int i;
for (i = 0; i < fragcount; ++i) {
desc[i].next_desc_addr = &(desc[i + 1]);
desc[i].start_addr = (unsigned long)buf + i*fragsize;
desc[i].cfg = cfg;
desc[i].x_count = count;
desc[i].x_modify = wdsize;
desc[i].y_count = 0;
desc[i].y_modify = 0;
}
/* make circular */
desc[fragcount-1].next_desc_addr = desc;
}
int sport_config_tx_dma(struct sport_device *sport, void *buf,
int fragcount, size_t fragsize)
{
unsigned int count;
unsigned int cfg;
dma_addr_t addr;
count = fragsize/sport->wdsize;
if (sport->tx_desc)
dma_free_coherent(NULL, sport->tx_desc_size,
sport->tx_desc, 0);
sport->tx_desc = dma_alloc_coherent(NULL,
fragcount * sizeof(struct dmasg), &addr, 0);
sport->tx_desc_size = fragcount * sizeof(struct dmasg);
if (!sport->tx_desc)
return -ENOMEM;
sport->tx_buf = buf;
sport->tx_fragsize = fragsize;
sport->tx_frags = fragcount;
cfg = DMAFLOW_LIST | DI_EN | compute_wdsize(sport->wdsize) | NDSIZE_6;
setup_desc(sport->tx_desc, buf, fragcount, fragsize,
cfg|DMAEN, count, sport->wdsize);
return 0;
}
EXPORT_SYMBOL(sport_config_tx_dma);
int sport_config_rx_dma(struct sport_device *sport, void *buf,
int fragcount, size_t fragsize)
{
unsigned int count;
unsigned int cfg;
dma_addr_t addr;
count = fragsize/sport->wdsize;
if (sport->rx_desc)
dma_free_coherent(NULL, sport->rx_desc_size,
sport->rx_desc, 0);
sport->rx_desc = dma_alloc_coherent(NULL,
fragcount * sizeof(struct dmasg), &addr, 0);
sport->rx_desc_size = fragcount * sizeof(struct dmasg);
if (!sport->rx_desc)
return -ENOMEM;
sport->rx_buf = buf;
sport->rx_fragsize = fragsize;
sport->rx_frags = fragcount;
cfg = DMAFLOW_LIST | DI_EN | compute_wdsize(sport->wdsize)
| WNR | NDSIZE_6;
setup_desc(sport->rx_desc, buf, fragcount, fragsize,
cfg|DMAEN, count, sport->wdsize);
return 0;
}
EXPORT_SYMBOL(sport_config_rx_dma);
unsigned long sport_curr_offset_tx(struct sport_device *sport)
{
unsigned long curr = get_dma_curr_addr(sport->tx_dma_chan);
return (unsigned char *)curr - sport->tx_buf;
}
EXPORT_SYMBOL(sport_curr_offset_tx);
unsigned long sport_curr_offset_rx(struct sport_device *sport)
{
unsigned long curr = get_dma_curr_addr(sport->rx_dma_chan);
return (unsigned char *)curr - sport->rx_buf;
}
EXPORT_SYMBOL(sport_curr_offset_rx);
static irqreturn_t sport_tx_irq(int irq, void *dev_id)
{
struct sport_device *sport = dev_id;
static unsigned long status;
status = get_dma_curr_irqstat(sport->tx_dma_chan);
if (status & (DMA_DONE|DMA_ERR)) {
clear_dma_irqstat(sport->tx_dma_chan);
SSYNC();
}
if (sport->tx_callback)
sport->tx_callback(sport->tx_data);
return IRQ_HANDLED;
}
static irqreturn_t sport_rx_irq(int irq, void *dev_id)
{
struct sport_device *sport = dev_id;
unsigned long status;
status = get_dma_curr_irqstat(sport->rx_dma_chan);
if (status & (DMA_DONE|DMA_ERR)) {
clear_dma_irqstat(sport->rx_dma_chan);
SSYNC();
}
if (sport->rx_callback)
sport->rx_callback(sport->rx_data);
return IRQ_HANDLED;
}
static irqreturn_t sport_err_irq(int irq, void *dev_id)
{
struct sport_device *sport = dev_id;
struct device *dev = &sport->pdev->dev;
if (sport->tx_regs->spctl & SPORT_CTL_DERRPRI)
dev_err(dev, "sport error: TUVF\n");
if (sport->rx_regs->spctl & SPORT_CTL_DERRPRI)
dev_err(dev, "sport error: ROVF\n");
return IRQ_HANDLED;
}
static int sport_get_resource(struct sport_device *sport)
{
struct platform_device *pdev = sport->pdev;
struct device *dev = &pdev->dev;
struct bfin_snd_platform_data *pdata = dev->platform_data;
struct resource *res;
if (!pdata) {
dev_err(dev, "No platform data\n");
return -ENODEV;
}
sport->pin_req = pdata->pin_req;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "No tx MEM resource\n");
return -ENODEV;
}
sport->tx_regs = (struct sport_register *)res->start;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res) {
dev_err(dev, "No rx MEM resource\n");
return -ENODEV;
}
sport->rx_regs = (struct sport_register *)res->start;
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!res) {
dev_err(dev, "No tx DMA resource\n");
return -ENODEV;
}
sport->tx_dma_chan = res->start;
res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!res) {
dev_err(dev, "No rx DMA resource\n");
return -ENODEV;
}
sport->rx_dma_chan = res->start;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "No tx error irq resource\n");
return -ENODEV;
}
sport->tx_err_irq = res->start;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
if (!res) {
dev_err(dev, "No rx error irq resource\n");
return -ENODEV;
}
sport->rx_err_irq = res->start;
return 0;
}
static int sport_request_resource(struct sport_device *sport)
{
struct device *dev = &sport->pdev->dev;
int ret;
ret = peripheral_request_list(sport->pin_req, "soc-audio");
if (ret) {
dev_err(dev, "Unable to request sport pin\n");
return ret;
}
ret = request_dma(sport->tx_dma_chan, "SPORT TX Data");
if (ret) {
dev_err(dev, "Unable to allocate DMA channel for sport tx\n");
goto err_tx_dma;
}
set_dma_callback(sport->tx_dma_chan, sport_tx_irq, sport);
ret = request_dma(sport->rx_dma_chan, "SPORT RX Data");
if (ret) {
dev_err(dev, "Unable to allocate DMA channel for sport rx\n");
goto err_rx_dma;
}
set_dma_callback(sport->rx_dma_chan, sport_rx_irq, sport);
ret = request_irq(sport->tx_err_irq, sport_err_irq,
0, "SPORT TX ERROR", sport);
if (ret) {
dev_err(dev, "Unable to allocate tx error IRQ for sport\n");
goto err_tx_irq;
}
ret = request_irq(sport->rx_err_irq, sport_err_irq,
0, "SPORT RX ERROR", sport);
if (ret) {
dev_err(dev, "Unable to allocate rx error IRQ for sport\n");
goto err_rx_irq;
}
return 0;
err_rx_irq:
free_irq(sport->tx_err_irq, sport);
err_tx_irq:
free_dma(sport->rx_dma_chan);
err_rx_dma:
free_dma(sport->tx_dma_chan);
err_tx_dma:
peripheral_free_list(sport->pin_req);
return ret;
}
static void sport_free_resource(struct sport_device *sport)
{
free_irq(sport->rx_err_irq, sport);
free_irq(sport->tx_err_irq, sport);
free_dma(sport->rx_dma_chan);
free_dma(sport->tx_dma_chan);
peripheral_free_list(sport->pin_req);
}
struct sport_device *sport_create(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sport_device *sport;
int ret;
sport = kzalloc(sizeof(*sport), GFP_KERNEL);
if (!sport) {
dev_err(dev, "Unable to allocate memory for sport device\n");
return NULL;
}
sport->pdev = pdev;
ret = sport_get_resource(sport);
if (ret) {
kfree(sport);
return NULL;
}
ret = sport_request_resource(sport);
if (ret) {
kfree(sport);
return NULL;
}
dev_dbg(dev, "SPORT create success\n");
return sport;
}
EXPORT_SYMBOL(sport_create);
void sport_delete(struct sport_device *sport)
{
if (sport->tx_desc)
dma_free_coherent(NULL, sport->tx_desc_size,
sport->tx_desc, 0);
if (sport->rx_desc)
dma_free_coherent(NULL, sport->rx_desc_size,
sport->rx_desc, 0);
sport_free_resource(sport);
kfree(sport);
}
EXPORT_SYMBOL(sport_delete);
MODULE_DESCRIPTION("Analog Devices BF6XX SPORT driver");
MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,82 @@
/*
* bf6xx_sport - Analog Devices BF6XX SPORT driver
*
* Copyright (c) 2012 Analog Devices Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _BF6XX_SPORT_H_
#define _BF6XX_SPORT_H_
#include <linux/platform_device.h>
#include <asm/bfin_sport3.h>
struct sport_device {
struct platform_device *pdev;
const unsigned short *pin_req;
struct sport_register *tx_regs;
struct sport_register *rx_regs;
int tx_dma_chan;
int rx_dma_chan;
int tx_err_irq;
int rx_err_irq;
void (*tx_callback)(void *data);
void *tx_data;
void (*rx_callback)(void *data);
void *rx_data;
struct dmasg *tx_desc;
struct dmasg *rx_desc;
unsigned int tx_desc_size;
unsigned int rx_desc_size;
unsigned char *tx_buf;
unsigned char *rx_buf;
unsigned int tx_fragsize;
unsigned int rx_fragsize;
unsigned int tx_frags;
unsigned int rx_frags;
unsigned int wdsize;
};
struct sport_params {
u32 spctl;
u32 div;
};
struct sport_device *sport_create(struct platform_device *pdev);
void sport_delete(struct sport_device *sport);
int sport_set_tx_params(struct sport_device *sport,
struct sport_params *params);
int sport_set_rx_params(struct sport_device *sport,
struct sport_params *params);
void sport_tx_start(struct sport_device *sport);
void sport_rx_start(struct sport_device *sport);
void sport_tx_stop(struct sport_device *sport);
void sport_rx_stop(struct sport_device *sport);
void sport_set_tx_callback(struct sport_device *sport,
void (*tx_callback)(void *), void *tx_data);
void sport_set_rx_callback(struct sport_device *sport,
void (*rx_callback)(void *), void *rx_data);
int sport_config_tx_dma(struct sport_device *sport, void *buf,
int fragcount, size_t fragsize);
int sport_config_rx_dma(struct sport_device *sport, void *buf,
int fragcount, size_t fragsize);
unsigned long sport_curr_offset_tx(struct sport_device *sport);
unsigned long sport_curr_offset_rx(struct sport_device *sport);
#endif

View file

@ -0,0 +1,184 @@
/*
* Machine driver for EVAL-ADAU1373 on Analog Devices bfin
* evaluation boards.
*
* Copyright 2011 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "../codecs/adau1373.h"
static const struct snd_soc_dapm_widget bfin_eval_adau1373_dapm_widgets[] = {
SND_SOC_DAPM_LINE("Line In1", NULL),
SND_SOC_DAPM_LINE("Line In2", NULL),
SND_SOC_DAPM_LINE("Line In3", NULL),
SND_SOC_DAPM_LINE("Line In4", NULL),
SND_SOC_DAPM_LINE("Line Out1", NULL),
SND_SOC_DAPM_LINE("Line Out2", NULL),
SND_SOC_DAPM_LINE("Stereo Out", NULL),
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_HP("Earpiece", NULL),
SND_SOC_DAPM_SPK("Speaker", NULL),
};
static const struct snd_soc_dapm_route bfin_eval_adau1373_dapm_routes[] = {
{ "AIN1L", NULL, "Line In1" },
{ "AIN1R", NULL, "Line In1" },
{ "AIN2L", NULL, "Line In2" },
{ "AIN2R", NULL, "Line In2" },
{ "AIN3L", NULL, "Line In3" },
{ "AIN3R", NULL, "Line In3" },
{ "AIN4L", NULL, "Line In4" },
{ "AIN4R", NULL, "Line In4" },
/* MICBIAS can be connected via a jumper to the line-in jack, since w
don't know which one is going to be used, just power both. */
{ "Line In1", NULL, "MICBIAS1" },
{ "Line In2", NULL, "MICBIAS1" },
{ "Line In3", NULL, "MICBIAS1" },
{ "Line In4", NULL, "MICBIAS1" },
{ "Line In1", NULL, "MICBIAS2" },
{ "Line In2", NULL, "MICBIAS2" },
{ "Line In3", NULL, "MICBIAS2" },
{ "Line In4", NULL, "MICBIAS2" },
{ "Line Out1", NULL, "LOUT1L" },
{ "Line Out1", NULL, "LOUT1R" },
{ "Line Out2", NULL, "LOUT2L" },
{ "Line Out2", NULL, "LOUT2R" },
{ "Headphone", NULL, "HPL" },
{ "Headphone", NULL, "HPR" },
{ "Earpiece", NULL, "EP" },
{ "Speaker", NULL, "SPKL" },
{ "Stereo Out", NULL, "SPKR" },
};
static int bfin_eval_adau1373_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->codec_dai;
int ret;
int pll_rate;
switch (params_rate(params)) {
case 48000:
case 8000:
case 12000:
case 16000:
case 24000:
case 32000:
pll_rate = 48000 * 1024;
break;
case 44100:
case 7350:
case 11025:
case 14700:
case 22050:
case 29400:
pll_rate = 44100 * 1024;
break;
default:
return -EINVAL;
}
ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1,
ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate);
if (ret)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate,
SND_SOC_CLOCK_IN);
return ret;
}
static int bfin_eval_adau1373_codec_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int pll_rate = 48000 * 1024;
int ret;
ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1,
ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate);
if (ret)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate,
SND_SOC_CLOCK_IN);
return ret;
}
static struct snd_soc_ops bfin_eval_adau1373_ops = {
.hw_params = bfin_eval_adau1373_hw_params,
};
static struct snd_soc_dai_link bfin_eval_adau1373_dai = {
.name = "adau1373",
.stream_name = "adau1373",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name = "adau1373-aif1",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "adau1373.0-001a",
.ops = &bfin_eval_adau1373_ops,
.init = bfin_eval_adau1373_codec_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
};
static struct snd_soc_card bfin_eval_adau1373 = {
.name = "bfin-eval-adau1373",
.owner = THIS_MODULE,
.dai_link = &bfin_eval_adau1373_dai,
.num_links = 1,
.dapm_widgets = bfin_eval_adau1373_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(bfin_eval_adau1373_dapm_widgets),
.dapm_routes = bfin_eval_adau1373_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(bfin_eval_adau1373_dapm_routes),
};
static int bfin_eval_adau1373_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &bfin_eval_adau1373;
card->dev = &pdev->dev;
return snd_soc_register_card(&bfin_eval_adau1373);
}
static int bfin_eval_adau1373_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver bfin_eval_adau1373_driver = {
.driver = {
.name = "bfin-eval-adau1373",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bfin_eval_adau1373_probe,
.remove = bfin_eval_adau1373_remove,
};
module_platform_driver(bfin_eval_adau1373_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("ALSA SoC bfin adau1373 driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bfin-eval-adau1373");

View file

@ -0,0 +1,124 @@
/*
* Machine driver for EVAL-ADAU1701MINIZ on Analog Devices bfin
* evaluation boards.
*
* Copyright 2011 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "../codecs/adau1701.h"
static const struct snd_soc_dapm_widget bfin_eval_adau1701_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Speaker", NULL),
SND_SOC_DAPM_LINE("Line Out", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
};
static const struct snd_soc_dapm_route bfin_eval_adau1701_dapm_routes[] = {
{ "Speaker", NULL, "OUT0" },
{ "Speaker", NULL, "OUT1" },
{ "Line Out", NULL, "OUT2" },
{ "Line Out", NULL, "OUT3" },
{ "IN0", NULL, "Line In" },
{ "IN1", NULL, "Line In" },
};
static int bfin_eval_adau1701_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->codec_dai;
int ret;
ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1701_CLK_SRC_OSC, 12288000,
SND_SOC_CLOCK_IN);
return ret;
}
static struct snd_soc_ops bfin_eval_adau1701_ops = {
.hw_params = bfin_eval_adau1701_hw_params,
};
#define BFIN_EVAL_ADAU1701_DAI_FMT (SND_SOC_DAIFMT_I2S | \
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM)
static struct snd_soc_dai_link bfin_eval_adau1701_dai[] = {
{
.name = "adau1701",
.stream_name = "adau1701",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name = "adau1701",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "adau1701.0-0034",
.ops = &bfin_eval_adau1701_ops,
.dai_fmt = BFIN_EVAL_ADAU1701_DAI_FMT,
},
{
.name = "adau1701",
.stream_name = "adau1701",
.cpu_dai_name = "bfin-i2s.1",
.codec_dai_name = "adau1701",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "adau1701.0-0034",
.ops = &bfin_eval_adau1701_ops,
.dai_fmt = BFIN_EVAL_ADAU1701_DAI_FMT,
},
};
static struct snd_soc_card bfin_eval_adau1701 = {
.name = "bfin-eval-adau1701",
.owner = THIS_MODULE,
.dai_link = &bfin_eval_adau1701_dai[CONFIG_SND_BF5XX_SPORT_NUM],
.num_links = 1,
.dapm_widgets = bfin_eval_adau1701_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(bfin_eval_adau1701_dapm_widgets),
.dapm_routes = bfin_eval_adau1701_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(bfin_eval_adau1701_dapm_routes),
};
static int bfin_eval_adau1701_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &bfin_eval_adau1701;
card->dev = &pdev->dev;
return snd_soc_register_card(&bfin_eval_adau1701);
}
static int bfin_eval_adau1701_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver bfin_eval_adau1701_driver = {
.driver = {
.name = "bfin-eval-adau1701",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bfin_eval_adau1701_probe,
.remove = bfin_eval_adau1701_remove,
};
module_platform_driver(bfin_eval_adau1701_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("ALSA SoC bfin ADAU1701 driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bfin-eval-adau1701");

View file

@ -0,0 +1,142 @@
/*
* Machine driver for EVAL-ADAU1x61MINIZ on Analog Devices bfin
* evaluation boards.
*
* Copyright 2011-2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "../codecs/adau17x1.h"
static const struct snd_soc_dapm_widget bfin_eval_adau1x61_dapm_widgets[] = {
SND_SOC_DAPM_LINE("In 1", NULL),
SND_SOC_DAPM_LINE("In 2", NULL),
SND_SOC_DAPM_LINE("In 3-4", NULL),
SND_SOC_DAPM_LINE("Diff Out L", NULL),
SND_SOC_DAPM_LINE("Diff Out R", NULL),
SND_SOC_DAPM_LINE("Stereo Out", NULL),
SND_SOC_DAPM_HP("Capless HP Out", NULL),
};
static const struct snd_soc_dapm_route bfin_eval_adau1x61_dapm_routes[] = {
{ "LAUX", NULL, "In 3-4" },
{ "RAUX", NULL, "In 3-4" },
{ "LINP", NULL, "In 1" },
{ "LINN", NULL, "In 1"},
{ "RINP", NULL, "In 2" },
{ "RINN", NULL, "In 2" },
{ "In 1", NULL, "MICBIAS" },
{ "In 2", NULL, "MICBIAS" },
{ "Capless HP Out", NULL, "LHP" },
{ "Capless HP Out", NULL, "RHP" },
{ "Diff Out L", NULL, "LOUT" },
{ "Diff Out R", NULL, "ROUT" },
{ "Stereo Out", NULL, "LOUT" },
{ "Stereo Out", NULL, "ROUT" },
};
static int bfin_eval_adau1x61_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->codec_dai;
int pll_rate;
int ret;
switch (params_rate(params)) {
case 48000:
case 8000:
case 12000:
case 16000:
case 24000:
case 32000:
case 96000:
pll_rate = 48000 * 1024;
break;
case 44100:
case 7350:
case 11025:
case 14700:
case 22050:
case 29400:
case 88200:
pll_rate = 44100 * 1024;
break;
default:
return -EINVAL;
}
ret = snd_soc_dai_set_pll(codec_dai, ADAU17X1_PLL,
ADAU17X1_PLL_SRC_MCLK, 12288000, pll_rate);
if (ret)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, ADAU17X1_CLK_SRC_PLL, pll_rate,
SND_SOC_CLOCK_IN);
return ret;
}
static const struct snd_soc_ops bfin_eval_adau1x61_ops = {
.hw_params = bfin_eval_adau1x61_hw_params,
};
static struct snd_soc_dai_link bfin_eval_adau1x61_dai = {
.name = "adau1x61",
.stream_name = "adau1x61",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name = "adau-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "adau1761.0-0038",
.ops = &bfin_eval_adau1x61_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
};
static struct snd_soc_card bfin_eval_adau1x61 = {
.name = "bfin-eval-adau1x61",
.driver_name = "eval-adau1x61",
.dai_link = &bfin_eval_adau1x61_dai,
.num_links = 1,
.dapm_widgets = bfin_eval_adau1x61_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(bfin_eval_adau1x61_dapm_widgets),
.dapm_routes = bfin_eval_adau1x61_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(bfin_eval_adau1x61_dapm_routes),
.fully_routed = true,
};
static int bfin_eval_adau1x61_probe(struct platform_device *pdev)
{
bfin_eval_adau1x61.dev = &pdev->dev;
return devm_snd_soc_register_card(&pdev->dev, &bfin_eval_adau1x61);
}
static struct platform_driver bfin_eval_adau1x61_driver = {
.driver = {
.name = "bfin-eval-adau1x61",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bfin_eval_adau1x61_probe,
};
module_platform_driver(bfin_eval_adau1x61_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("ALSA SoC bfin adau1x61 driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bfin-eval-adau1x61");

View file

@ -0,0 +1,130 @@
/*
* Machine driver for EVAL-ADAU1x81 on Analog Devices bfin
* evaluation boards.
*
* Copyright 2011-2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "../codecs/adau17x1.h"
static const struct snd_soc_dapm_widget bfin_eval_adau1x81_dapm_widgets[] = {
SND_SOC_DAPM_LINE("Stereo In", NULL),
SND_SOC_DAPM_LINE("Beep", NULL),
SND_SOC_DAPM_SPK("Speaker", NULL),
SND_SOC_DAPM_HP("Headphone", NULL),
};
static const struct snd_soc_dapm_route bfin_eval_adau1x81_dapm_routes[] = {
{ "BEEP", NULL, "Beep" },
{ "LMIC", NULL, "Stereo In" },
{ "LMIC", NULL, "Stereo In" },
{ "Headphone", NULL, "AOUTL" },
{ "Headphone", NULL, "AOUTR" },
{ "Speaker", NULL, "SP" },
};
static int bfin_eval_adau1x81_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->codec_dai;
int pll_rate;
int ret;
switch (params_rate(params)) {
case 48000:
case 8000:
case 12000:
case 16000:
case 24000:
case 32000:
case 96000:
pll_rate = 48000 * 1024;
break;
case 44100:
case 7350:
case 11025:
case 14700:
case 22050:
case 29400:
case 88200:
pll_rate = 44100 * 1024;
break;
default:
return -EINVAL;
}
ret = snd_soc_dai_set_pll(codec_dai, ADAU17X1_PLL,
ADAU17X1_PLL_SRC_MCLK, 12288000, pll_rate);
if (ret)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, ADAU17X1_CLK_SRC_PLL, pll_rate,
SND_SOC_CLOCK_IN);
return ret;
}
static const struct snd_soc_ops bfin_eval_adau1x81_ops = {
.hw_params = bfin_eval_adau1x81_hw_params,
};
static struct snd_soc_dai_link bfin_eval_adau1x81_dai = {
.name = "adau1x81",
.stream_name = "adau1x81",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name = "adau-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.codec_name = "adau1781.0-0038",
.ops = &bfin_eval_adau1x81_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
};
static struct snd_soc_card bfin_eval_adau1x81 = {
.name = "bfin-eval-adau1x81",
.driver_name = "eval-adau1x81",
.dai_link = &bfin_eval_adau1x81_dai,
.num_links = 1,
.dapm_widgets = bfin_eval_adau1x81_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(bfin_eval_adau1x81_dapm_widgets),
.dapm_routes = bfin_eval_adau1x81_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(bfin_eval_adau1x81_dapm_routes),
.fully_routed = true,
};
static int bfin_eval_adau1x81_probe(struct platform_device *pdev)
{
bfin_eval_adau1x81.dev = &pdev->dev;
return devm_snd_soc_register_card(&pdev->dev, &bfin_eval_adau1x81);
}
static struct platform_driver bfin_eval_adau1x81_driver = {
.driver = {
.name = "bfin-eval-adau1x81",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bfin_eval_adau1x81_probe,
};
module_platform_driver(bfin_eval_adau1x81_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("ALSA SoC bfin adau1x81 driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bfin-eval-adau1x81");

View file

@ -0,0 +1,156 @@
/*
* Machine driver for EVAL-ADAV801 and EVAL-ADAV803 on Analog Devices bfin
* evaluation boards.
*
* Copyright 2011 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include "../codecs/adav80x.h"
static const struct snd_soc_dapm_widget bfin_eval_adav80x_dapm_widgets[] = {
SND_SOC_DAPM_LINE("Line Out", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
};
static const struct snd_soc_dapm_route bfin_eval_adav80x_dapm_routes[] = {
{ "Line Out", NULL, "VOUTL" },
{ "Line Out", NULL, "VOUTR" },
{ "VINL", NULL, "Line In" },
{ "VINR", NULL, "Line In" },
};
static int bfin_eval_adav80x_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->codec_dai;
int ret;
ret = snd_soc_dai_set_pll(codec_dai, ADAV80X_PLL1, ADAV80X_PLL_SRC_XTAL,
27000000, params_rate(params) * 256);
if (ret)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_PLL1,
params_rate(params) * 256, SND_SOC_CLOCK_IN);
return ret;
}
static int bfin_eval_adav80x_codec_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *codec_dai = rtd->codec_dai;
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK1, 0,
SND_SOC_CLOCK_OUT);
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK2, 0,
SND_SOC_CLOCK_OUT);
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK3, 0,
SND_SOC_CLOCK_OUT);
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_XTAL, 2700000, 0);
return 0;
}
static struct snd_soc_ops bfin_eval_adav80x_ops = {
.hw_params = bfin_eval_adav80x_hw_params,
};
static struct snd_soc_dai_link bfin_eval_adav80x_dais[] = {
{
.name = "adav80x",
.stream_name = "ADAV80x HiFi",
.cpu_dai_name = "bfin-i2s.0",
.codec_dai_name = "adav80x-hifi",
.platform_name = "bfin-i2s-pcm-audio",
.init = bfin_eval_adav80x_codec_init,
.ops = &bfin_eval_adav80x_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
},
};
static struct snd_soc_card bfin_eval_adav80x = {
.name = "bfin-eval-adav80x",
.owner = THIS_MODULE,
.dai_link = bfin_eval_adav80x_dais,
.num_links = ARRAY_SIZE(bfin_eval_adav80x_dais),
.dapm_widgets = bfin_eval_adav80x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(bfin_eval_adav80x_dapm_widgets),
.dapm_routes = bfin_eval_adav80x_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(bfin_eval_adav80x_dapm_routes),
};
enum bfin_eval_adav80x_type {
BFIN_EVAL_ADAV801,
BFIN_EVAL_ADAV803,
};
static int bfin_eval_adav80x_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &bfin_eval_adav80x;
const char *codec_name;
switch (platform_get_device_id(pdev)->driver_data) {
case BFIN_EVAL_ADAV801:
codec_name = "spi0.1";
break;
case BFIN_EVAL_ADAV803:
codec_name = "adav803.0-0034";
break;
default:
return -EINVAL;
}
bfin_eval_adav80x_dais[0].codec_name = codec_name;
card->dev = &pdev->dev;
return snd_soc_register_card(&bfin_eval_adav80x);
}
static int bfin_eval_adav80x_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct platform_device_id bfin_eval_adav80x_ids[] = {
{ "bfin-eval-adav801", BFIN_EVAL_ADAV801 },
{ "bfin-eval-adav803", BFIN_EVAL_ADAV803 },
{ },
};
MODULE_DEVICE_TABLE(platform, bfin_eval_adav80x_ids);
static struct platform_driver bfin_eval_adav80x_driver = {
.driver = {
.name = "bfin-eval-adav80x",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bfin_eval_adav80x_probe,
.remove = bfin_eval_adav80x_remove,
.id_table = bfin_eval_adav80x_ids,
};
module_platform_driver(bfin_eval_adav80x_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("ALSA SoC bfin adav80x driver");
MODULE_LICENSE("GPL");

42
sound/soc/cirrus/Kconfig Normal file
View file

@ -0,0 +1,42 @@
config SND_EP93XX_SOC
tristate "SoC Audio support for the Cirrus Logic EP93xx series"
depends on ARCH_EP93XX || COMPILE_TEST
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for codecs attached to
the EP93xx I2S or AC97 interfaces.
config SND_EP93XX_SOC_I2S
tristate
config SND_EP93XX_SOC_AC97
tristate
select AC97_BUS
select SND_SOC_AC97_BUS
config SND_EP93XX_SOC_SNAPPERCL15
tristate "SoC Audio support for Bluewater Systems Snapper CL15 module"
depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15
select SND_EP93XX_SOC_I2S
select SND_SOC_TLV320AIC23_I2C
help
Say Y or M here if you want to add support for I2S audio on the
Bluewater Systems Snapper CL15 module.
config SND_EP93XX_SOC_SIMONE
tristate "SoC Audio support for Simplemachines Sim.One board"
depends on SND_EP93XX_SOC && MACH_SIM_ONE
select SND_EP93XX_SOC_AC97
select SND_SOC_AC97_CODEC
help
Say Y or M here if you want to add support for AC97 audio on the
Simplemachines Sim.One board.
config SND_EP93XX_SOC_EDB93XX
tristate "SoC Audio support for Cirrus Logic EDB93xx boards"
depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A)
select SND_EP93XX_SOC_I2S
select SND_SOC_CS4271
help
Say Y or M here if you want to add support for I2S audio on the
Cirrus Logic EDB93xx boards.

17
sound/soc/cirrus/Makefile Normal file
View file

@ -0,0 +1,17 @@
# EP93xx Platform Support
snd-soc-ep93xx-objs := ep93xx-pcm.o
snd-soc-ep93xx-i2s-objs := ep93xx-i2s.o
snd-soc-ep93xx-ac97-objs := ep93xx-ac97.o
obj-$(CONFIG_SND_EP93XX_SOC) += snd-soc-ep93xx.o
obj-$(CONFIG_SND_EP93XX_SOC_I2S) += snd-soc-ep93xx-i2s.o
obj-$(CONFIG_SND_EP93XX_SOC_AC97) += snd-soc-ep93xx-ac97.o
# EP93XX Machine Support
snd-soc-snappercl15-objs := snappercl15.o
snd-soc-simone-objs := simone.o
snd-soc-edb93xx-objs := edb93xx.o
obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o
obj-$(CONFIG_SND_EP93XX_SOC_SIMONE) += snd-soc-simone.o
obj-$(CONFIG_SND_EP93XX_SOC_EDB93XX) += snd-soc-edb93xx.o

127
sound/soc/cirrus/edb93xx.c Normal file
View file

@ -0,0 +1,127 @@
/*
* SoC audio for EDB93xx
*
* Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This driver support CS4271 codec being master or slave, working
* in control port mode, connected either via SPI or I2C.
* The data format accepted is I2S or left-justified.
* DAPM support not implemented.
*/
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
static int edb93xx_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->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int err;
unsigned int mclk_rate;
unsigned int rate = params_rate(params);
/*
* According to CS4271 datasheet we use MCLK/LRCK=256 for
* rates below 50kHz and 128 for higher sample rates
*/
if (rate < 50000)
mclk_rate = rate * 64 * 4;
else
mclk_rate = rate * 64 * 2;
err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate,
SND_SOC_CLOCK_IN);
if (err)
return err;
return snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate,
SND_SOC_CLOCK_OUT);
}
static struct snd_soc_ops edb93xx_ops = {
.hw_params = edb93xx_hw_params,
};
static struct snd_soc_dai_link edb93xx_dai = {
.name = "CS4271",
.stream_name = "CS4271 HiFi",
.platform_name = "ep93xx-i2s",
.cpu_dai_name = "ep93xx-i2s",
.codec_name = "spi0.0",
.codec_dai_name = "cs4271-hifi",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
SND_SOC_DAIFMT_CBS_CFS,
.ops = &edb93xx_ops,
};
static struct snd_soc_card snd_soc_edb93xx = {
.name = "EDB93XX",
.owner = THIS_MODULE,
.dai_link = &edb93xx_dai,
.num_links = 1,
};
static int edb93xx_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snd_soc_edb93xx;
int ret;
ret = ep93xx_i2s_acquire();
if (ret)
return ret;
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
ep93xx_i2s_release();
}
return ret;
}
static int edb93xx_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
ep93xx_i2s_release();
return 0;
}
static struct platform_driver edb93xx_driver = {
.driver = {
.name = "edb93xx-audio",
.owner = THIS_MODULE,
},
.probe = edb93xx_probe,
.remove = edb93xx_remove,
};
module_platform_driver(edb93xx_driver);
MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>");
MODULE_DESCRIPTION("ALSA SoC EDB93xx");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:edb93xx-audio");

View file

@ -0,0 +1,451 @@
/*
* ASoC driver for Cirrus Logic EP93xx AC97 controller.
*
* Copyright (c) 2010 Mika Westerberg
*
* Based on s3c-ac97 ASoC driver by Jaswinder Singh.
*
* 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/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/ac97_codec.h>
#include <sound/soc.h>
#include <linux/platform_data/dma-ep93xx.h>
#include "ep93xx-pcm.h"
/*
* Per channel (1-4) registers.
*/
#define AC97CH(n) (((n) - 1) * 0x20)
#define AC97DR(n) (AC97CH(n) + 0x0000)
#define AC97RXCR(n) (AC97CH(n) + 0x0004)
#define AC97RXCR_REN BIT(0)
#define AC97RXCR_RX3 BIT(3)
#define AC97RXCR_RX4 BIT(4)
#define AC97RXCR_CM BIT(15)
#define AC97TXCR(n) (AC97CH(n) + 0x0008)
#define AC97TXCR_TEN BIT(0)
#define AC97TXCR_TX3 BIT(3)
#define AC97TXCR_TX4 BIT(4)
#define AC97TXCR_CM BIT(15)
#define AC97SR(n) (AC97CH(n) + 0x000c)
#define AC97SR_TXFE BIT(1)
#define AC97SR_TXUE BIT(6)
#define AC97RISR(n) (AC97CH(n) + 0x0010)
#define AC97ISR(n) (AC97CH(n) + 0x0014)
#define AC97IE(n) (AC97CH(n) + 0x0018)
/*
* Global AC97 controller registers.
*/
#define AC97S1DATA 0x0080
#define AC97S2DATA 0x0084
#define AC97S12DATA 0x0088
#define AC97RGIS 0x008c
#define AC97GIS 0x0090
#define AC97IM 0x0094
/*
* Common bits for RGIS, GIS and IM registers.
*/
#define AC97_SLOT2RXVALID BIT(1)
#define AC97_CODECREADY BIT(5)
#define AC97_SLOT2TXCOMPLETE BIT(6)
#define AC97EOI 0x0098
#define AC97EOI_WINT BIT(0)
#define AC97EOI_CODECREADY BIT(1)
#define AC97GCR 0x009c
#define AC97GCR_AC97IFE BIT(0)
#define AC97RESET 0x00a0
#define AC97RESET_TIMEDRESET BIT(0)
#define AC97SYNC 0x00a4
#define AC97SYNC_TIMEDSYNC BIT(0)
#define AC97_TIMEOUT msecs_to_jiffies(5)
/**
* struct ep93xx_ac97_info - EP93xx AC97 controller info structure
* @lock: mutex serializing access to the bus (slot 1 & 2 ops)
* @dev: pointer to the platform device dev structure
* @regs: mapped AC97 controller registers
* @done: bus ops wait here for an interrupt
*/
struct ep93xx_ac97_info {
struct mutex lock;
struct device *dev;
void __iomem *regs;
struct completion done;
struct snd_dmaengine_dai_dma_data dma_params_rx;
struct snd_dmaengine_dai_dma_data dma_params_tx;
};
/* currently ALSA only supports a single AC97 device */
static struct ep93xx_ac97_info *ep93xx_ac97_info;
static struct ep93xx_dma_data ep93xx_ac97_pcm_out = {
.name = "ac97-pcm-out",
.port = EP93XX_DMA_AAC1,
.direction = DMA_MEM_TO_DEV,
};
static struct ep93xx_dma_data ep93xx_ac97_pcm_in = {
.name = "ac97-pcm-in",
.port = EP93XX_DMA_AAC1,
.direction = DMA_DEV_TO_MEM,
};
static inline unsigned ep93xx_ac97_read_reg(struct ep93xx_ac97_info *info,
unsigned reg)
{
return __raw_readl(info->regs + reg);
}
static inline void ep93xx_ac97_write_reg(struct ep93xx_ac97_info *info,
unsigned reg, unsigned val)
{
__raw_writel(val, info->regs + reg);
}
static unsigned short ep93xx_ac97_read(struct snd_ac97 *ac97,
unsigned short reg)
{
struct ep93xx_ac97_info *info = ep93xx_ac97_info;
unsigned short val;
mutex_lock(&info->lock);
ep93xx_ac97_write_reg(info, AC97S1DATA, reg);
ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2RXVALID);
if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) {
dev_warn(info->dev, "timeout reading register %x\n", reg);
mutex_unlock(&info->lock);
return -ETIMEDOUT;
}
val = (unsigned short)ep93xx_ac97_read_reg(info, AC97S2DATA);
mutex_unlock(&info->lock);
return val;
}
static void ep93xx_ac97_write(struct snd_ac97 *ac97,
unsigned short reg,
unsigned short val)
{
struct ep93xx_ac97_info *info = ep93xx_ac97_info;
mutex_lock(&info->lock);
/*
* Writes to the codec need to be done so that slot 2 is filled in
* before slot 1.
*/
ep93xx_ac97_write_reg(info, AC97S2DATA, val);
ep93xx_ac97_write_reg(info, AC97S1DATA, reg);
ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2TXCOMPLETE);
if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
dev_warn(info->dev, "timeout writing register %x\n", reg);
mutex_unlock(&info->lock);
}
static void ep93xx_ac97_warm_reset(struct snd_ac97 *ac97)
{
struct ep93xx_ac97_info *info = ep93xx_ac97_info;
mutex_lock(&info->lock);
/*
* We are assuming that before this functions gets called, the codec
* BIT_CLK is stopped by forcing the codec into powerdown mode. We can
* control the SYNC signal directly via AC97SYNC register. Using
* TIMEDSYNC the controller will keep the SYNC high > 1us.
*/
ep93xx_ac97_write_reg(info, AC97SYNC, AC97SYNC_TIMEDSYNC);
ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY);
if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
dev_warn(info->dev, "codec warm reset timeout\n");
mutex_unlock(&info->lock);
}
static void ep93xx_ac97_cold_reset(struct snd_ac97 *ac97)
{
struct ep93xx_ac97_info *info = ep93xx_ac97_info;
mutex_lock(&info->lock);
/*
* For doing cold reset, we disable the AC97 controller interface, clear
* WINT and CODECREADY bits, and finally enable the interface again.
*/
ep93xx_ac97_write_reg(info, AC97GCR, 0);
ep93xx_ac97_write_reg(info, AC97EOI, AC97EOI_CODECREADY | AC97EOI_WINT);
ep93xx_ac97_write_reg(info, AC97GCR, AC97GCR_AC97IFE);
/*
* Now, assert the reset and wait for the codec to become ready.
*/
ep93xx_ac97_write_reg(info, AC97RESET, AC97RESET_TIMEDRESET);
ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY);
if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
dev_warn(info->dev, "codec cold reset timeout\n");
/*
* Give the codec some time to come fully out from the reset. This way
* we ensure that the subsequent reads/writes will work.
*/
usleep_range(15000, 20000);
mutex_unlock(&info->lock);
}
static irqreturn_t ep93xx_ac97_interrupt(int irq, void *dev_id)
{
struct ep93xx_ac97_info *info = dev_id;
unsigned status, mask;
/*
* Just mask out the interrupt and wake up the waiting thread.
* Interrupts are cleared via reading/writing to slot 1 & 2 registers by
* the waiting thread.
*/
status = ep93xx_ac97_read_reg(info, AC97GIS);
mask = ep93xx_ac97_read_reg(info, AC97IM);
mask &= ~status;
ep93xx_ac97_write_reg(info, AC97IM, mask);
complete(&info->done);
return IRQ_HANDLED;
}
static struct snd_ac97_bus_ops ep93xx_ac97_ops = {
.read = ep93xx_ac97_read,
.write = ep93xx_ac97_write,
.reset = ep93xx_ac97_cold_reset,
.warm_reset = ep93xx_ac97_warm_reset,
};
static int ep93xx_ac97_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai);
unsigned v = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/*
* Enable compact mode, TX slots 3 & 4, and the TX FIFO
* itself.
*/
v |= AC97TXCR_CM;
v |= AC97TXCR_TX3 | AC97TXCR_TX4;
v |= AC97TXCR_TEN;
ep93xx_ac97_write_reg(info, AC97TXCR(1), v);
} else {
/*
* Enable compact mode, RX slots 3 & 4, and the RX FIFO
* itself.
*/
v |= AC97RXCR_CM;
v |= AC97RXCR_RX3 | AC97RXCR_RX4;
v |= AC97RXCR_REN;
ep93xx_ac97_write_reg(info, AC97RXCR(1), v);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/*
* As per Cirrus EP93xx errata described below:
*
* http://www.cirrus.com/en/pubs/errata/ER667E2B.pdf
*
* we will wait for the TX FIFO to be empty before
* clearing the TEN bit.
*/
unsigned long timeout = jiffies + AC97_TIMEOUT;
do {
v = ep93xx_ac97_read_reg(info, AC97SR(1));
if (time_after(jiffies, timeout)) {
dev_warn(info->dev, "TX timeout\n");
break;
}
} while (!(v & (AC97SR_TXFE | AC97SR_TXUE)));
/* disable the TX FIFO */
ep93xx_ac97_write_reg(info, AC97TXCR(1), 0);
} else {
/* disable the RX FIFO */
ep93xx_ac97_write_reg(info, AC97RXCR(1), 0);
}
break;
default:
dev_warn(info->dev, "unknown command %d\n", cmd);
return -EINVAL;
}
return 0;
}
static int ep93xx_ac97_dai_probe(struct snd_soc_dai *dai)
{
struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai);
info->dma_params_tx.filter_data = &ep93xx_ac97_pcm_out;
info->dma_params_rx.filter_data = &ep93xx_ac97_pcm_in;
dai->playback_dma_data = &info->dma_params_tx;
dai->capture_dma_data = &info->dma_params_rx;
return 0;
}
static const struct snd_soc_dai_ops ep93xx_ac97_dai_ops = {
.trigger = ep93xx_ac97_trigger,
};
static struct snd_soc_dai_driver ep93xx_ac97_dai = {
.name = "ep93xx-ac97",
.id = 0,
.ac97_control = 1,
.probe = ep93xx_ac97_dai_probe,
.playback = {
.stream_name = "AC97 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &ep93xx_ac97_dai_ops,
};
static const struct snd_soc_component_driver ep93xx_ac97_component = {
.name = "ep93xx-ac97",
};
static int ep93xx_ac97_probe(struct platform_device *pdev)
{
struct ep93xx_ac97_info *info;
struct resource *res;
unsigned int irq;
int ret;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
info->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(info->regs))
return PTR_ERR(info->regs);
irq = platform_get_irq(pdev, 0);
if (!irq)
return -ENODEV;
ret = devm_request_irq(&pdev->dev, irq, ep93xx_ac97_interrupt,
IRQF_TRIGGER_HIGH, pdev->name, info);
if (ret)
goto fail;
dev_set_drvdata(&pdev->dev, info);
mutex_init(&info->lock);
init_completion(&info->done);
info->dev = &pdev->dev;
ep93xx_ac97_info = info;
platform_set_drvdata(pdev, info);
ret = snd_soc_set_ac97_ops(&ep93xx_ac97_ops);
if (ret)
goto fail;
ret = snd_soc_register_component(&pdev->dev, &ep93xx_ac97_component,
&ep93xx_ac97_dai, 1);
if (ret)
goto fail;
ret = devm_ep93xx_pcm_platform_register(&pdev->dev);
if (ret)
goto fail_unregister;
return 0;
fail_unregister:
snd_soc_unregister_component(&pdev->dev);
fail:
ep93xx_ac97_info = NULL;
snd_soc_set_ac97_ops(NULL);
return ret;
}
static int ep93xx_ac97_remove(struct platform_device *pdev)
{
struct ep93xx_ac97_info *info = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
/* disable the AC97 controller */
ep93xx_ac97_write_reg(info, AC97GCR, 0);
ep93xx_ac97_info = NULL;
snd_soc_set_ac97_ops(NULL);
return 0;
}
static struct platform_driver ep93xx_ac97_driver = {
.probe = ep93xx_ac97_probe,
.remove = ep93xx_ac97_remove,
.driver = {
.name = "ep93xx-ac97",
.owner = THIS_MODULE,
},
};
module_platform_driver(ep93xx_ac97_driver);
MODULE_DESCRIPTION("EP93xx AC97 ASoC Driver");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ep93xx-ac97");

View file

@ -0,0 +1,463 @@
/*
* linux/sound/soc/ep93xx-i2s.c
* EP93xx I2S driver
*
* Copyright (C) 2010 Ryan Mallon
*
* Based on the original driver by:
* Copyright (C) 2007 Chase Douglas <chasedouglas@gmail>
* Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
*
* 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/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <mach/hardware.h>
#include <mach/ep93xx-regs.h>
#include <linux/platform_data/dma-ep93xx.h>
#include "ep93xx-pcm.h"
#define EP93XX_I2S_TXCLKCFG 0x00
#define EP93XX_I2S_RXCLKCFG 0x04
#define EP93XX_I2S_GLCTRL 0x0C
#define EP93XX_I2S_TXLINCTRLDATA 0x28
#define EP93XX_I2S_TXCTRL 0x2C
#define EP93XX_I2S_TXWRDLEN 0x30
#define EP93XX_I2S_TX0EN 0x34
#define EP93XX_I2S_RXLINCTRLDATA 0x58
#define EP93XX_I2S_RXCTRL 0x5C
#define EP93XX_I2S_RXWRDLEN 0x60
#define EP93XX_I2S_RX0EN 0x64
#define EP93XX_I2S_WRDLEN_16 (0 << 0)
#define EP93XX_I2S_WRDLEN_24 (1 << 0)
#define EP93XX_I2S_WRDLEN_32 (2 << 0)
#define EP93XX_I2S_LINCTRLDATA_R_JUST (1 << 2) /* Right justify */
#define EP93XX_I2S_CLKCFG_LRS (1 << 0) /* lrclk polarity */
#define EP93XX_I2S_CLKCFG_CKP (1 << 1) /* Bit clock polarity */
#define EP93XX_I2S_CLKCFG_REL (1 << 2) /* First bit transition */
#define EP93XX_I2S_CLKCFG_MASTER (1 << 3) /* Master mode */
#define EP93XX_I2S_CLKCFG_NBCG (1 << 4) /* Not bit clock gating */
struct ep93xx_i2s_info {
struct clk *mclk;
struct clk *sclk;
struct clk *lrclk;
void __iomem *regs;
struct snd_dmaengine_dai_dma_data dma_params_rx;
struct snd_dmaengine_dai_dma_data dma_params_tx;
};
static struct ep93xx_dma_data ep93xx_i2s_dma_data[] = {
[SNDRV_PCM_STREAM_PLAYBACK] = {
.name = "i2s-pcm-out",
.port = EP93XX_DMA_I2S1,
.direction = DMA_MEM_TO_DEV,
},
[SNDRV_PCM_STREAM_CAPTURE] = {
.name = "i2s-pcm-in",
.port = EP93XX_DMA_I2S1,
.direction = DMA_DEV_TO_MEM,
},
};
static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info,
unsigned reg, unsigned val)
{
__raw_writel(val, info->regs + reg);
}
static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info,
unsigned reg)
{
return __raw_readl(info->regs + reg);
}
static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream)
{
unsigned base_reg;
int i;
if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
(ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
/* Enable clocks */
clk_enable(info->mclk);
clk_enable(info->sclk);
clk_enable(info->lrclk);
/* Enable i2s */
ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1);
}
/* Enable fifos */
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
base_reg = EP93XX_I2S_TX0EN;
else
base_reg = EP93XX_I2S_RX0EN;
for (i = 0; i < 3; i++)
ep93xx_i2s_write_reg(info, base_reg + (i * 4), 1);
}
static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream)
{
unsigned base_reg;
int i;
/* Disable fifos */
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
base_reg = EP93XX_I2S_TX0EN;
else
base_reg = EP93XX_I2S_RX0EN;
for (i = 0; i < 3; i++)
ep93xx_i2s_write_reg(info, base_reg + (i * 4), 0);
if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
(ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
/* Disable i2s */
ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0);
/* Disable clocks */
clk_disable(info->lrclk);
clk_disable(info->sclk);
clk_disable(info->mclk);
}
}
static int ep93xx_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
info->dma_params_tx.filter_data =
&ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_PLAYBACK];
info->dma_params_rx.filter_data =
&ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_CAPTURE];
dai->playback_dma_data = &info->dma_params_tx;
dai->capture_dma_data = &info->dma_params_rx;
return 0;
}
static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
ep93xx_i2s_disable(info, substream->stream);
}
static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
unsigned int clk_cfg, lin_ctrl;
clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG);
lin_ctrl = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXLINCTRLDATA);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
clk_cfg |= EP93XX_I2S_CLKCFG_REL;
lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
break;
case SND_SOC_DAIFMT_LEFT_J:
clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
break;
case SND_SOC_DAIFMT_RIGHT_J:
clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
lin_ctrl |= EP93XX_I2S_LINCTRLDATA_R_JUST;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* CPU is master */
clk_cfg |= EP93XX_I2S_CLKCFG_MASTER;
break;
case SND_SOC_DAIFMT_CBM_CFM:
/* Codec is master */
clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
/* Negative bit clock, lrclk low on left word */
clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL);
break;
case SND_SOC_DAIFMT_NB_IF:
/* Negative bit clock, lrclk low on right word */
clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP;
clk_cfg |= EP93XX_I2S_CLKCFG_REL;
break;
case SND_SOC_DAIFMT_IB_NF:
/* Positive bit clock, lrclk low on left word */
clk_cfg |= EP93XX_I2S_CLKCFG_CKP;
clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
break;
case SND_SOC_DAIFMT_IB_IF:
/* Positive bit clock, lrclk low on right word */
clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL;
break;
}
/* Write new register values */
ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg);
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg);
ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, lin_ctrl);
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, lin_ctrl);
return 0;
}
static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
unsigned word_len, div, sdiv, lrdiv;
int err;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
word_len = EP93XX_I2S_WRDLEN_16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
word_len = EP93XX_I2S_WRDLEN_24;
break;
case SNDRV_PCM_FORMAT_S32_LE:
word_len = EP93XX_I2S_WRDLEN_32;
break;
default:
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len);
else
ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len);
/*
* EP93xx I2S module can be setup so SCLK / LRCLK value can be
* 32, 64, 128. MCLK / SCLK value can be 2 and 4.
* We set LRCLK equal to `rate' and minimum SCLK / LRCLK
* value is 64, because our sample size is 32 bit * 2 channels.
* I2S standard permits us to transmit more bits than
* the codec uses.
*/
div = clk_get_rate(info->mclk) / params_rate(params);
sdiv = 4;
if (div > (256 + 512) / 2) {
lrdiv = 128;
} else {
lrdiv = 64;
if (div < (128 + 256) / 2)
sdiv = 2;
}
err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv);
if (err)
return err;
err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv);
if (err)
return err;
ep93xx_i2s_enable(info, substream->stream);
return 0;
}
static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
unsigned int freq, int dir)
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
if (dir == SND_SOC_CLOCK_IN || clk_id != 0)
return -EINVAL;
return clk_set_rate(info->mclk, freq);
}
#ifdef CONFIG_PM
static int ep93xx_i2s_suspend(struct snd_soc_dai *dai)
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
if (!dai->active)
return 0;
ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK);
ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE);
return 0;
}
static int ep93xx_i2s_resume(struct snd_soc_dai *dai)
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
if (!dai->active)
return 0;
ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK);
ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE);
return 0;
}
#else
#define ep93xx_i2s_suspend NULL
#define ep93xx_i2s_resume NULL
#endif
static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = {
.shutdown = ep93xx_i2s_shutdown,
.hw_params = ep93xx_i2s_hw_params,
.set_sysclk = ep93xx_i2s_set_sysclk,
.set_fmt = ep93xx_i2s_set_dai_fmt,
};
#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver ep93xx_i2s_dai = {
.symmetric_rates= 1,
.probe = ep93xx_i2s_dai_probe,
.suspend = ep93xx_i2s_suspend,
.resume = ep93xx_i2s_resume,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = EP93XX_I2S_FORMATS,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = EP93XX_I2S_FORMATS,
},
.ops = &ep93xx_i2s_dai_ops,
};
static const struct snd_soc_component_driver ep93xx_i2s_component = {
.name = "ep93xx-i2s",
};
static int ep93xx_i2s_probe(struct platform_device *pdev)
{
struct ep93xx_i2s_info *info;
struct resource *res;
int err;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
info->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(info->regs))
return PTR_ERR(info->regs);
info->mclk = clk_get(&pdev->dev, "mclk");
if (IS_ERR(info->mclk)) {
err = PTR_ERR(info->mclk);
goto fail;
}
info->sclk = clk_get(&pdev->dev, "sclk");
if (IS_ERR(info->sclk)) {
err = PTR_ERR(info->sclk);
goto fail_put_mclk;
}
info->lrclk = clk_get(&pdev->dev, "lrclk");
if (IS_ERR(info->lrclk)) {
err = PTR_ERR(info->lrclk);
goto fail_put_sclk;
}
dev_set_drvdata(&pdev->dev, info);
err = snd_soc_register_component(&pdev->dev, &ep93xx_i2s_component,
&ep93xx_i2s_dai, 1);
if (err)
goto fail_put_lrclk;
err = devm_ep93xx_pcm_platform_register(&pdev->dev);
if (err)
goto fail_unregister;
return 0;
fail_unregister:
snd_soc_unregister_component(&pdev->dev);
fail_put_lrclk:
clk_put(info->lrclk);
fail_put_sclk:
clk_put(info->sclk);
fail_put_mclk:
clk_put(info->mclk);
fail:
return err;
}
static int ep93xx_i2s_remove(struct platform_device *pdev)
{
struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
clk_put(info->lrclk);
clk_put(info->sclk);
clk_put(info->mclk);
return 0;
}
static struct platform_driver ep93xx_i2s_driver = {
.probe = ep93xx_i2s_probe,
.remove = ep93xx_i2s_remove,
.driver = {
.name = "ep93xx-i2s",
.owner = THIS_MODULE,
},
};
module_platform_driver(ep93xx_i2s_driver);
MODULE_ALIAS("platform:ep93xx-i2s");
MODULE_AUTHOR("Ryan Mallon");
MODULE_DESCRIPTION("EP93XX I2S driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,71 @@
/*
* linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface
*
* Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
* Copyright (C) 2006 Applied Data Systems
*
* Rewritten for the SoC audio subsystem (Based on PXA2xx code):
* Copyright (c) 2008 Ryan Mallon
*
* 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/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <linux/platform_data/dma-ep93xx.h>
#include "ep93xx-pcm.h"
static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER),
.buffer_bytes_max = 131072,
.period_bytes_min = 32,
.period_bytes_max = 32768,
.periods_min = 1,
.periods_max = 32,
.fifo_size = 32,
};
static bool ep93xx_pcm_dma_filter(struct dma_chan *chan, void *filter_param)
{
struct ep93xx_dma_data *data = filter_param;
if (data->direction == ep93xx_dma_chan_direction(chan)) {
chan->private = data;
return true;
}
return false;
}
static const struct snd_dmaengine_pcm_config ep93xx_dmaengine_pcm_config = {
.pcm_hardware = &ep93xx_pcm_hardware,
.compat_filter_fn = ep93xx_pcm_dma_filter,
.prealloc_buffer_size = 131072,
};
int devm_ep93xx_pcm_platform_register(struct device *dev)
{
return devm_snd_dmaengine_pcm_register(dev,
&ep93xx_dmaengine_pcm_config,
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE |
SND_DMAENGINE_PCM_FLAG_NO_DT |
SND_DMAENGINE_PCM_FLAG_COMPAT);
}
EXPORT_SYMBOL_GPL(devm_ep93xx_pcm_platform_register);
MODULE_AUTHOR("Ryan Mallon");
MODULE_DESCRIPTION("EP93xx ALSA PCM interface");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EP93XX_PCM_H__
#define __EP93XX_PCM_H__
int devm_ep93xx_pcm_platform_register(struct device *dev);
#endif

88
sound/soc/cirrus/simone.c Normal file
View file

@ -0,0 +1,88 @@
/*
* simone.c -- ASoC audio for Simplemachines Sim.One board
*
* Copyright (c) 2010 Mika Westerberg
*
* Based on snappercl15 machine driver by Ryan Mallon.
*
* 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/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
static struct snd_soc_dai_link simone_dai = {
.name = "AC97",
.stream_name = "AC97 HiFi",
.cpu_dai_name = "ep93xx-ac97",
.codec_dai_name = "ac97-hifi",
.codec_name = "ac97-codec",
.platform_name = "ep93xx-ac97",
};
static struct snd_soc_card snd_soc_simone = {
.name = "Sim.One",
.owner = THIS_MODULE,
.dai_link = &simone_dai,
.num_links = 1,
};
static struct platform_device *simone_snd_ac97_device;
static int simone_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snd_soc_simone;
int ret;
simone_snd_ac97_device = platform_device_register_simple("ac97-codec",
-1, NULL, 0);
if (IS_ERR(simone_snd_ac97_device))
return PTR_ERR(simone_snd_ac97_device);
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
platform_device_unregister(simone_snd_ac97_device);
}
return ret;
}
static int simone_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
platform_device_unregister(simone_snd_ac97_device);
return 0;
}
static struct platform_driver simone_driver = {
.driver = {
.name = "simone-audio",
.owner = THIS_MODULE,
},
.probe = simone_probe,
.remove = simone_remove,
};
module_platform_driver(simone_driver);
MODULE_DESCRIPTION("ALSA SoC Simplemachines Sim.One");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:simone-audio");

View file

@ -0,0 +1,137 @@
/*
* snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module
*
* Copyright (C) 2008 Bluewater Systems Ltd
* Author: Ryan Mallon
*
* 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/platform_device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
#include "../codecs/tlv320aic23.h"
#define CODEC_CLOCK 5644800
static int snappercl15_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->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int err;
err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK,
SND_SOC_CLOCK_IN);
if (err)
return err;
err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK,
SND_SOC_CLOCK_OUT);
if (err)
return err;
return 0;
}
static struct snd_soc_ops snappercl15_ops = {
.hw_params = snappercl15_hw_params,
};
static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
};
static const struct snd_soc_dapm_route audio_map[] = {
{"Headphone Jack", NULL, "LHPOUT"},
{"Headphone Jack", NULL, "RHPOUT"},
{"LLINEIN", NULL, "Line In"},
{"RLINEIN", NULL, "Line In"},
{"MICIN", NULL, "Mic Jack"},
};
static struct snd_soc_dai_link snappercl15_dai = {
.name = "tlv320aic23",
.stream_name = "AIC23",
.cpu_dai_name = "ep93xx-i2s",
.codec_dai_name = "tlv320aic23-hifi",
.codec_name = "tlv320aic23-codec.0-001a",
.platform_name = "ep93xx-i2s",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
SND_SOC_DAIFMT_CBS_CFS,
.ops = &snappercl15_ops,
};
static struct snd_soc_card snd_soc_snappercl15 = {
.name = "Snapper CL15",
.owner = THIS_MODULE,
.dai_link = &snappercl15_dai,
.num_links = 1,
.dapm_widgets = tlv320aic23_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
static int snappercl15_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snd_soc_snappercl15;
int ret;
ret = ep93xx_i2s_acquire();
if (ret)
return ret;
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
ep93xx_i2s_release();
}
return ret;
}
static int snappercl15_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
ep93xx_i2s_release();
return 0;
}
static struct platform_driver snappercl15_driver = {
.driver = {
.name = "snappercl15-audio",
.owner = THIS_MODULE,
},
.probe = snappercl15_probe,
.remove = snappercl15_remove,
};
module_platform_driver(snappercl15_driver);
MODULE_AUTHOR("Ryan Mallon");
MODULE_DESCRIPTION("ALSA SoC Snapper CL15");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:snappercl15-audio");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,96 @@
/*
* 88pm860x-codec.h -- 88PM860x ALSA SoC Audio Driver
*
* Copyright 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.
*/
#ifndef __88PM860X_H
#define __88PM860X_H
#define PM860X_PCM_IFACE_1 0xb0
#define PM860X_PCM_IFACE_2 0xb1
#define PM860X_PCM_IFACE_3 0xb2
#define PM860X_PCM_RATE 0xb3
#define PM860X_EC_PATH 0xb4
#define PM860X_SIDETONE_L_GAIN 0xb5
#define PM860X_SIDETONE_R_GAIN 0xb6
#define PM860X_SIDETONE_SHIFT 0xb7
#define PM860X_ADC_OFFSET_1 0xb8
#define PM860X_ADC_OFFSET_2 0xb9
#define PM860X_DMIC_DELAY 0xba
#define PM860X_I2S_IFACE_1 0xbb
#define PM860X_I2S_IFACE_2 0xbc
#define PM860X_I2S_IFACE_3 0xbd
#define PM860X_I2S_IFACE_4 0xbe
#define PM860X_EQUALIZER_N0_1 0xbf
#define PM860X_EQUALIZER_N0_2 0xc0
#define PM860X_EQUALIZER_N1_1 0xc1
#define PM860X_EQUALIZER_N1_2 0xc2
#define PM860X_EQUALIZER_D1_1 0xc3
#define PM860X_EQUALIZER_D1_2 0xc4
#define PM860X_LOFI_GAIN_LEFT 0xc5
#define PM860X_LOFI_GAIN_RIGHT 0xc6
#define PM860X_HIFIL_GAIN_LEFT 0xc7
#define PM860X_HIFIL_GAIN_RIGHT 0xc8
#define PM860X_HIFIR_GAIN_LEFT 0xc9
#define PM860X_HIFIR_GAIN_RIGHT 0xca
#define PM860X_DAC_OFFSET 0xcb
#define PM860X_OFFSET_LEFT_1 0xcc
#define PM860X_OFFSET_LEFT_2 0xcd
#define PM860X_OFFSET_RIGHT_1 0xce
#define PM860X_OFFSET_RIGHT_2 0xcf
#define PM860X_ADC_ANA_1 0xd0
#define PM860X_ADC_ANA_2 0xd1
#define PM860X_ADC_ANA_3 0xd2
#define PM860X_ADC_ANA_4 0xd3
#define PM860X_ANA_TO_ANA 0xd4
#define PM860X_HS1_CTRL 0xd5
#define PM860X_HS2_CTRL 0xd6
#define PM860X_LO1_CTRL 0xd7
#define PM860X_LO2_CTRL 0xd8
#define PM860X_EAR_CTRL_1 0xd9
#define PM860X_EAR_CTRL_2 0xda
#define PM860X_AUDIO_SUPPLIES_1 0xdb
#define PM860X_AUDIO_SUPPLIES_2 0xdc
#define PM860X_ADC_EN_1 0xdd
#define PM860X_ADC_EN_2 0xde
#define PM860X_DAC_EN_1 0xdf
#define PM860X_DAC_EN_2 0xe1
#define PM860X_AUDIO_CAL_1 0xe2
#define PM860X_AUDIO_CAL_2 0xe3
#define PM860X_AUDIO_CAL_3 0xe4
#define PM860X_AUDIO_CAL_4 0xe5
#define PM860X_AUDIO_CAL_5 0xe6
#define PM860X_ANA_INPUT_SEL_1 0xe7
#define PM860X_ANA_INPUT_SEL_2 0xe8
#define PM860X_PCM_IFACE_4 0xe9
#define PM860X_I2S_IFACE_5 0xea
#define PM860X_SHORTS 0x3b
#define PM860X_PLL_ADJ_1 0x3c
#define PM860X_PLL_ADJ_2 0x3d
/* bits definition */
#define PM860X_CLK_DIR_IN 0
#define PM860X_CLK_DIR_OUT 1
#define PM860X_DET_HEADSET (1 << 0)
#define PM860X_DET_MIC (1 << 1)
#define PM860X_DET_HOOK (1 << 2)
#define PM860X_SHORT_HEADSET (1 << 3)
#define PM860X_SHORT_LINEOUT (1 << 4)
#define PM860X_DET_MASK 0x1F
extern int pm860x_hs_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *,
int, int, int, int);
extern int pm860x_mic_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *,
int);
#endif /* __88PM860X_H */

836
sound/soc/codecs/Kconfig Executable file
View file

@ -0,0 +1,836 @@
# Helper to resolve issues with configs that have SPI enabled but I2C
# modular, meaning we can't build the codec driver in with I2C support.
# We use an ordered list of conditional defaults to pick the appropriate
# setting - SPI can't be modular so that case doesn't need to be covered.
config SND_SOC_I2C_AND_SPI
tristate
default m if I2C=m
default y if I2C=y
default y if SPI_MASTER=y
menu "CODEC drivers"
config SND_SOC_ALL_CODECS
tristate "Build all ASoC CODEC drivers"
depends on COMPILE_TEST
select SND_SOC_88PM860X if MFD_88PM860X
select SND_SOC_L3
select SND_SOC_AB8500_CODEC if ABX500_CORE
select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS
select SND_SOC_AD1836 if SPI_MASTER
select SND_SOC_AD193X_SPI if SPI_MASTER
select SND_SOC_AD193X_I2C if I2C
select SND_SOC_AD1980 if SND_SOC_AC97_BUS
select SND_SOC_AD73311
select SND_SOC_ADAU1373 if I2C
select SND_SOC_ADAU1761_I2C if I2C
select SND_SOC_ADAU1761_SPI if SPI
select SND_SOC_ADAU1781_I2C if I2C
select SND_SOC_ADAU1781_SPI if SPI
select SND_SOC_ADAV801 if SPI_MASTER
select SND_SOC_ADAV803 if I2C
select SND_SOC_ADAU1977_SPI if SPI_MASTER
select SND_SOC_ADAU1977_I2C if I2C
select SND_SOC_ADAU1701 if I2C
select SND_SOC_ADS117X
select SND_SOC_AK4104 if SPI_MASTER
select SND_SOC_AK4535 if I2C
select SND_SOC_AK4554
select SND_SOC_AK4641 if I2C
select SND_SOC_AK4642 if I2C
select SND_SOC_AK4671 if I2C
select SND_SOC_AK5386
select SND_SOC_ALC5623 if I2C
select SND_SOC_ALC5632 if I2C
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS35L32 if I2C
select SND_SOC_CS42L51_I2C if I2C
select SND_SOC_CS42L52 if I2C && INPUT
select SND_SOC_CS42L56 if I2C && INPUT
select SND_SOC_CS42L73 if I2C
select SND_SOC_CS4265 if I2C
select SND_SOC_CS4270 if I2C
select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI
select SND_SOC_CS42XX8_I2C if I2C
select SND_SOC_LARGO if MFD_LARGO
select SND_SOC_CX20442 if TTY
select SND_SOC_DA7210 if I2C
select SND_SOC_DA7213 if I2C
select SND_SOC_DA732X if I2C
select SND_SOC_DA9055 if I2C
select SND_SOC_DMIC
select SND_SOC_BT_SCO
select SND_SOC_ES8328_SPI if SPI_MASTER
select SND_SOC_ES8328_I2C if I2C
select SND_SOC_ISABELLE if I2C
select SND_SOC_JZ4740_CODEC
select SND_SOC_LM4857 if I2C
select SND_SOC_LM49453 if I2C
select SND_SOC_MAX98088 if I2C
select SND_SOC_MAX98090 if I2C
select SND_SOC_MAX98095 if I2C
select SND_SOC_MAX9850 if I2C
select SND_SOC_MAX9768 if I2C
select SND_SOC_MAX9877 if I2C
select SND_SOC_MC13783 if MFD_MC13XXX
select SND_SOC_ML26124 if I2C
select SND_SOC_HDMI_CODEC
select SND_SOC_PCM1681 if I2C
select SND_SOC_PCM1792A if SPI_MASTER
select SND_SOC_PCM3008
select SND_SOC_PCM512x_I2C if I2C
select SND_SOC_PCM512x_SPI if SPI_MASTER
select SND_SOC_RT286 if I2C
select SND_SOC_RT5631 if I2C
select SND_SOC_RT5640 if I2C
select SND_SOC_RT5645 if I2C
select SND_SOC_RT5651 if I2C
select SND_SOC_RT5670 if I2C
select SND_SOC_RT5677 if I2C
select SND_SOC_SGTL5000 if I2C
select SND_SOC_SI476X if MFD_SI476X_CORE
select SND_SOC_SIRF_AUDIO_CODEC
select SND_SOC_SN95031 if INTEL_SCU_IPC
select SND_SOC_SPDIF
select SND_SOC_SSM2518 if I2C
select SND_SOC_SSM2602_SPI if SPI_MASTER
select SND_SOC_SSM2602_I2C if I2C
select SND_SOC_SSM4567 if I2C
select SND_SOC_STA32X if I2C
select SND_SOC_STA350 if I2C
select SND_SOC_STA529 if I2C
select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
select SND_SOC_TAS2552 if I2C
select SND_SOC_TAS5086 if I2C
select SND_SOC_TLV320AIC23_I2C if I2C
select SND_SOC_TLV320AIC23_SPI if SPI_MASTER
select SND_SOC_TLV320AIC26 if SPI_MASTER
select SND_SOC_TLV320AIC31XX if I2C
select SND_SOC_TLV320AIC32X4 if I2C
select SND_SOC_TLV320AIC3X if I2C
select SND_SOC_TPA6130A2 if I2C
select SND_SOC_TLV320DAC33 if I2C
select SND_SOC_TWL4030 if TWL4030_CORE
select SND_SOC_TWL6040 if TWL6040_CORE
select SND_SOC_UDA134X
select SND_SOC_UDA1380 if I2C
select SND_SOC_WL1273 if MFD_WL1273_CORE
select SND_SOC_WM0010 if SPI_MASTER
select SND_SOC_WM1250_EV1 if I2C
select SND_SOC_WM2000 if I2C
select SND_SOC_WM2200 if I2C
select SND_SOC_WM5100 if I2C
select SND_SOC_WM5102 if MFD_WM5102
select SND_SOC_WM5110 if MFD_WM5110
select SND_SOC_WM8350 if MFD_WM8350
select SND_SOC_WM8400 if MFD_WM8400
select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8523 if I2C
select SND_SOC_WM8580 if I2C
select SND_SOC_WM8711 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8727
select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8737 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8741 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8770 if SPI_MASTER
select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8782
select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8900 if I2C
select SND_SOC_WM8903 if I2C
select SND_SOC_WM8904 if I2C
select SND_SOC_WM8940 if I2C
select SND_SOC_WM8955 if I2C
select SND_SOC_WM8960 if I2C
select SND_SOC_WM8961 if I2C
select SND_SOC_WM8962 if I2C && INPUT
select SND_SOC_WM8971 if I2C
select SND_SOC_WM8974 if I2C
select SND_SOC_WM8978 if I2C
select SND_SOC_WM8983 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8990 if I2C
select SND_SOC_WM8991 if I2C
select SND_SOC_WM8993 if I2C
select SND_SOC_WM8994 if MFD_WM8994
select SND_SOC_WM8995 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8996 if I2C
select SND_SOC_WM8997 if MFD_WM8997
select SND_SOC_WM9081 if I2C
select SND_SOC_WM9090 if I2C
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
help
Normally ASoC codec drivers are only built if a machine driver which
uses them is also built since they are only usable with a machine
driver. Selecting this option will allow these drivers to be built
without an explicit machine driver for test and development purposes.
Support for the bus types used to access the codecs to be built must
be selected separately.
If unsure select "N".
config SND_SOC_88PM860X
tristate
config SND_SOC_ARIZONA
tristate
default y if SND_SOC_WM5102=y
default y if SND_SOC_WM5110=y
default y if SND_SOC_WM8997=y
default y if SND_SOC_LARGO=y
default m if SND_SOC_WM5102=m
default m if SND_SOC_WM5110=m
default m if SND_SOC_WM8997=m
default m if SND_SOC_LARGO=m
config SND_SOC_WM_HUBS
tristate
default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y
default m if SND_SOC_WM8993=m || SND_SOC_WM8994=m
config SND_SOC_WM_ADSP
tristate
default y if SND_SOC_WM5102=y
default y if SND_SOC_WM5110=y
default y if SND_SOC_WM2200=y
default y if SND_SOC_LARGO=y
default m if SND_SOC_WM5102=m
default m if SND_SOC_WM5110=m
default m if SND_SOC_WM2200=m
default m if SND_SOC_LARGO=m
config SND_SOC_AB8500_CODEC
tristate
config SND_SOC_AC97_CODEC
tristate
select SND_AC97_CODEC
config SND_SOC_AD1836
tristate
config SND_SOC_AD193X
tristate
config SND_SOC_AD193X_SPI
tristate
select SND_SOC_AD193X
config SND_SOC_AD193X_I2C
tristate
select SND_SOC_AD193X
config SND_SOC_AD1980
tristate
config SND_SOC_AD73311
tristate
config SND_SOC_ADAU1373
tristate
config SND_SOC_ADAU1701
tristate "Analog Devices ADAU1701 CODEC"
depends on I2C
select SND_SOC_SIGMADSP_I2C
config SND_SOC_ADAU17X1
tristate
select SND_SOC_SIGMADSP_REGMAP
config SND_SOC_ADAU1761
tristate
select SND_SOC_ADAU17X1
config SND_SOC_ADAU1761_I2C
tristate
select SND_SOC_ADAU1761
select REGMAP_I2C
config SND_SOC_ADAU1761_SPI
tristate
select SND_SOC_ADAU1761
select REGMAP_SPI
config SND_SOC_ADAU1781
select SND_SOC_ADAU17X1
tristate
config SND_SOC_ADAU1781_I2C
tristate
select SND_SOC_ADAU1781
select REGMAP_I2C
config SND_SOC_ADAU1781_SPI
tristate
select SND_SOC_ADAU1781
select REGMAP_SPI
config SND_SOC_ADAU1977
tristate
config SND_SOC_ADAU1977_SPI
tristate
select SND_SOC_ADAU1977
select REGMAP_SPI
config SND_SOC_ADAU1977_I2C
tristate
select SND_SOC_ADAU1977
select REGMAP_I2C
config SND_SOC_ADAV80X
tristate
config SND_SOC_ADAV801
tristate
select SND_SOC_ADAV80X
config SND_SOC_ADAV803
tristate
select SND_SOC_ADAV80X
config SND_SOC_ADS117X
tristate
config SND_SOC_AK4104
tristate "AKM AK4104 CODEC"
depends on SPI_MASTER
config SND_SOC_AK4535
tristate
config SND_SOC_AK4554
tristate "AKM AK4554 CODEC"
config SND_SOC_AK4641
tristate
config SND_SOC_AK4642
tristate "AKM AK4642 CODEC"
depends on I2C
config SND_SOC_AK4671
tristate
config SND_SOC_AK5386
tristate "AKM AK5638 CODEC"
config SND_SOC_ALC5623
tristate "Realtek ALC5623 CODEC"
depends on I2C
config SND_SOC_ALC5632
tristate
config SND_SOC_CQ0093VC
tristate
config SND_SOC_CS35L32
tristate "Cirrus Logic CS35L32 CODEC"
depends on I2C
config SND_SOC_CS42L51
tristate
config SND_SOC_CS42L51_I2C
tristate
select SND_SOC_CS42L51
config SND_SOC_CS42L52
tristate "Cirrus Logic CS42L52 CODEC"
depends on I2C && INPUT
config SND_SOC_CS42L56
tristate "Cirrus Logic CS42L56 CODEC"
depends on I2C && INPUT
config SND_SOC_CS42L73
tristate "Cirrus Logic CS42L73 CODEC"
depends on I2C
config SND_SOC_CS4265
tristate "Cirrus Logic CS4265 CODEC"
depends on I2C
select REGMAP_I2C
# Cirrus Logic CS4270 Codec
config SND_SOC_CS4270
tristate "Cirrus Logic CS4270 CODEC"
depends on I2C
# Cirrus Logic CS4270 Codec VD = 3.3V Errata
# Select if you are affected by the errata where the part will not function
# if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will
# not select any sample rates that require MCLK to be divided by 1.5.
config SND_SOC_CS4270_VD33_ERRATA
bool
depends on SND_SOC_CS4270
config SND_SOC_CS4271
tristate "Cirrus Logic CS4271 CODEC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_CS42XX8
tristate
config SND_SOC_CS42XX8_I2C
tristate "Cirrus Logic CS42448/CS42888 CODEC (I2C)"
depends on I2C
select SND_SOC_CS42XX8
select REGMAP_I2C
config SND_SOC_LARGO
tristate
config SND_SOC_CX20442
tristate
depends on TTY
config SND_SOC_COD3026X
tristate
default n
config SND_SOC_COD9002X
tristate
default y
config SND_SOC_JZ4740_CODEC
select REGMAP_MMIO
tristate
config SND_SOC_L3
tristate
config SND_SOC_DA7210
tristate
config SND_SOC_DA7213
tristate
config SND_SOC_DA732X
tristate
config SND_SOC_DA9055
tristate
config SND_SOC_BT_SCO
tristate
config SND_SOC_DMIC
tristate
config SND_SOC_HDMI_CODEC
tristate "HDMI stub CODEC"
config SND_SOC_ES8328
tristate "Everest Semi ES8328 CODEC"
config SND_SOC_ES8328_I2C
tristate
select SND_SOC_ES8328
config SND_SOC_ES8328_SPI
tristate
select SND_SOC_ES8328
config SND_SOC_ISABELLE
tristate
config SND_SOC_LM49453
tristate
config SND_SOC_MAX98088
tristate
config SND_SOC_MAX98090
tristate
config SND_SOC_MAX98095
tristate
config SND_SOC_MAX9850
tristate
config SND_SOC_PCM1681
tristate "Texas Instruments PCM1681 CODEC"
depends on I2C
config SND_SOC_PCM1792A
tristate "Texas Instruments PCM1792A CODEC"
depends on SPI_MASTER
config SND_SOC_PCM3008
tristate
config SND_SOC_PCM512x
tristate
config SND_SOC_PCM512x_I2C
tristate "Texas Instruments PCM512x CODECs - I2C"
depends on I2C
select SND_SOC_PCM512x
select REGMAP_I2C
config SND_SOC_PCM512x_SPI
tristate "Texas Instruments PCM512x CODECs - SPI"
depends on SPI_MASTER
select SND_SOC_PCM512x
select REGMAP_SPI
config SND_SOC_RL6231
tristate
default y if SND_SOC_RT5640=y
default y if SND_SOC_RT5645=y
default y if SND_SOC_RT5651=y
default y if SND_SOC_RT5670=y
default y if SND_SOC_RT5677=y
default m if SND_SOC_RT5640=m
default m if SND_SOC_RT5645=m
default m if SND_SOC_RT5651=m
default m if SND_SOC_RT5670=m
default m if SND_SOC_RT5677=m
config SND_SOC_RT286
tristate
depends on I2C
config SND_SOC_RT5631
tristate
config SND_SOC_RT5640
tristate
config SND_SOC_RT5645
tristate
config SND_SOC_RT5651
tristate
config SND_SOC_RT5670
tristate
config SND_SOC_RT5677
tristate
#Freescale sgtl5000 codec
config SND_SOC_SGTL5000
tristate "Freescale SGTL5000 CODEC"
depends on I2C
config SND_SOC_SI476X
tristate
config SND_SOC_SIGMADSP
tristate
select CRC32
config SND_SOC_SIGMADSP_I2C
tristate
select SND_SOC_SIGMADSP
config SND_SOC_SIGMADSP_REGMAP
tristate
select SND_SOC_SIGMADSP
config SND_SOC_SIRF_AUDIO_CODEC
tristate "SiRF SoC internal audio codec"
select REGMAP_MMIO
config SND_SOC_SN95031
tristate
config SND_SOC_SPDIF
tristate "S/PDIF CODEC"
config SND_SOC_EXYNOS_AUDMIXER
tristate
select REGMAP_MMIO
select REGMAP_I2C
config SND_SOC_SSM2518
tristate
config SND_SOC_SSM2602
tristate
config SND_SOC_SSM2602_SPI
tristate "Analog Devices SSM2602 CODEC - SPI"
depends on SPI_MASTER
select SND_SOC_SSM2602
select REGMAP_SPI
config SND_SOC_SSM2602_I2C
tristate "Analog Devices SSM2602 CODEC - I2C"
depends on I2C
select SND_SOC_SSM2602
select REGMAP_I2C
config SND_SOC_SSM4567
tristate "Analog Devices ssm4567 amplifier driver support"
depends on I2C
config SND_SOC_STA32X
tristate
config SND_SOC_STA350
tristate "STA350 speaker amplifier"
depends on I2C
config SND_SOC_STA529
tristate
config SND_SOC_STAC9766
tristate
config SND_SOC_TAS2552
tristate "Texas Instruments TAS2552 Mono Audio amplifier"
depends on I2C
config SND_SOC_TAS5086
tristate "Texas Instruments TAS5086 speaker amplifier"
depends on I2C
config SND_SOC_TLV320AIC23
tristate
config SND_SOC_TLV320AIC23_I2C
tristate
select SND_SOC_TLV320AIC23
config SND_SOC_TLV320AIC23_SPI
tristate
select SND_SOC_TLV320AIC23
config SND_SOC_TLV320AIC26
tristate
depends on SPI
config SND_SOC_TLV320AIC31XX
tristate "Texas Instruments TLV320AIC31xx CODECs"
depends on I2C
select REGMAP_I2C
config SND_SOC_TLV320AIC32X4
tristate
config SND_SOC_TLV320AIC3X
tristate "Texas Instruments TLV320AIC3x CODECs"
depends on I2C
config SND_SOC_TLV320DAC33
tristate
config SND_SOC_TWL4030
select MFD_TWL4030_AUDIO
tristate
config SND_SOC_TWL6040
tristate
config SND_SOC_UDA134X
tristate
config SND_SOC_UDA1380
tristate
config SND_SOC_WL1273
tristate
config SND_SOC_WM0010
tristate
config SND_SOC_WM1250_EV1
tristate
config SND_SOC_WM2000
tristate
config SND_SOC_WM2200
tristate
config SND_SOC_WM5100
tristate
config SND_SOC_WM5102
tristate
config SND_SOC_WM5110
tristate
config SND_SOC_WM8350
tristate
config SND_SOC_WM8400
tristate
config SND_SOC_WM8510
tristate "Wolfson Microelectronics WM8510 CODEC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8523
tristate "Wolfson Microelectronics WM8523 DAC"
depends on I2C
config SND_SOC_WM8580
tristate "Wolfson Microelectronics WM8523 CODEC"
depends on I2C
config SND_SOC_WM8711
tristate "Wolfson Microelectronics WM8711 CODEC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8727
tristate
config SND_SOC_WM8728
tristate "Wolfson Microelectronics WM8728 DAC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8731
tristate "Wolfson Microelectronics WM8731 CODEC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8737
tristate "Wolfson Microelectronics WM8737 ADC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8741
tristate "Wolfson Microelectronics WM8737 DAC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8750
tristate "Wolfson Microelectronics WM8750 CODEC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8753
tristate "Wolfson Microelectronics WM8753 CODEC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8770
tristate "Wolfson Microelectronics WM8770 CODEC"
depends on SPI_MASTER
config SND_SOC_WM8776
tristate "Wolfson Microelectronics WM8776 CODEC"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8782
tristate
config SND_SOC_WM8804
tristate "Wolfson Microelectronics WM8804 S/PDIF transceiver"
depends on SND_SOC_I2C_AND_SPI
config SND_SOC_WM8900
tristate
config SND_SOC_WM8903
tristate "Wolfson Microelectronics WM8903 CODEC"
depends on I2C
config SND_SOC_WM8904
tristate
config SND_SOC_WM8940
tristate
config SND_SOC_WM8955
tristate
config SND_SOC_WM8960
tristate
config SND_SOC_WM8961
tristate
config SND_SOC_WM8962
tristate "Wolfson Microelectronics WM8962 CODEC"
depends on I2C && INPUT
config SND_SOC_WM8971
tristate
config SND_SOC_WM8974
tristate
config SND_SOC_WM8978
tristate "Wolfson Microelectronics WM8978 codec"
depends on I2C
config SND_SOC_WM8983
tristate
config SND_SOC_WM8985
tristate
config SND_SOC_WM8988
tristate
config SND_SOC_WM8990
tristate
config SND_SOC_WM8991
tristate
config SND_SOC_WM8993
tristate
config SND_SOC_WM8994
tristate
config SND_SOC_WM8995
tristate
config SND_SOC_WM8996
tristate
config SND_SOC_WM8997
tristate
config SND_SOC_WM9081
tristate
config SND_SOC_WM9090
tristate
config SND_SOC_WM9705
tristate
config SND_SOC_WM9712
tristate
config SND_SOC_WM9713
tristate
config SND_SOC_DUMMY_CODEC
tristate
# Amp
config SND_SOC_LM4857
tristate
config SND_SOC_MAX9768
tristate
config SND_SOC_MAX9877
tristate
config SND_SOC_MC13783
tristate
config SND_SOC_ML26124
tristate
config SND_SOC_TPA6130A2
tristate "Texas Instruments TPA6130A2 headphone amplifier"
depends on I2C
endmenu

355
sound/soc/codecs/Makefile Executable file
View file

@ -0,0 +1,355 @@
snd-soc-88pm860x-objs := 88pm860x-codec.o
snd-soc-ab8500-codec-objs := ab8500-codec.o
snd-soc-ac97-objs := ac97.o
snd-soc-ad1836-objs := ad1836.o
snd-soc-ad193x-objs := ad193x.o
snd-soc-ad193x-spi-objs := ad193x-spi.o
snd-soc-ad193x-i2c-objs := ad193x-i2c.o
snd-soc-ad1980-objs := ad1980.o
snd-soc-ad73311-objs := ad73311.o
snd-soc-adau1373-objs := adau1373.o
snd-soc-adau1701-objs := adau1701.o
snd-soc-adau17x1-objs := adau17x1.o
snd-soc-adau1761-objs := adau1761.o
snd-soc-adau1761-i2c-objs := adau1761-i2c.o
snd-soc-adau1761-spi-objs := adau1761-spi.o
snd-soc-adau1781-objs := adau1781.o
snd-soc-adau1781-i2c-objs := adau1781-i2c.o
snd-soc-adau1781-spi-objs := adau1781-spi.o
snd-soc-adau1977-objs := adau1977.o
snd-soc-adau1977-spi-objs := adau1977-spi.o
snd-soc-adau1977-i2c-objs := adau1977-i2c.o
snd-soc-adav80x-objs := adav80x.o
snd-soc-adav801-objs := adav801.o
snd-soc-adav803-objs := adav803.o
snd-soc-ads117x-objs := ads117x.o
snd-soc-ak4104-objs := ak4104.o
snd-soc-ak4535-objs := ak4535.o
snd-soc-ak4554-objs := ak4554.o
snd-soc-ak4641-objs := ak4641.o
snd-soc-ak4642-objs := ak4642.o
snd-soc-ak4671-objs := ak4671.o
snd-soc-ak5386-objs := ak5386.o
snd-soc-arizona-objs := arizona.o
snd-soc-cod3026-objs := cod3026x.o
snd-soc-cod9002-objs := cod9002x.o
snd-soc-cq93vc-objs := cq93vc.o
snd-soc-cs35l32-objs := cs35l32.o
snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
snd-soc-cs42l52-objs := cs42l52.o
snd-soc-cs42l56-objs := cs42l56.o
snd-soc-cs42l73-objs := cs42l73.o
snd-soc-cs4265-objs := cs4265.o
snd-soc-cs4270-objs := cs4270.o
snd-soc-cs4271-objs := cs4271.o
snd-soc-cs42xx8-objs := cs42xx8.o
snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o
snd-soc-largo-objs := largo.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
snd-soc-da7213-objs := da7213.o
snd-soc-da732x-objs := da732x.o
snd-soc-da9055-objs := da9055.o
snd-soc-bt-sco-objs := bt-sco.o
snd-soc-dmic-objs := dmic.o
snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
snd-soc-es8328-spi-objs := es8328-spi.o
snd-soc-isabelle-objs := isabelle.o
snd-soc-jz4740-codec-objs := jz4740.o
snd-soc-l3-objs := l3.o
snd-soc-lm4857-objs := lm4857.o
snd-soc-lm49453-objs := lm49453.o
snd-soc-max9768-objs := max9768.o
snd-soc-max98088-objs := max98088.o
snd-soc-max98090-objs := max98090.o
snd-soc-max98095-objs := max98095.o
snd-soc-max9850-objs := max9850.o
snd-soc-mc13783-objs := mc13783.o
snd-soc-ml26124-objs := ml26124.o
snd-soc-hdmi-codec-objs := hdmi.o
snd-soc-pcm1681-objs := pcm1681.o
snd-soc-pcm1792a-codec-objs := pcm1792a.o
snd-soc-pcm3008-objs := pcm3008.o
snd-soc-pcm512x-objs := pcm512x.o
snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o
snd-soc-pcm512x-spi-objs := pcm512x-spi.o
snd-soc-rl6231-objs := rl6231.o
snd-soc-rt286-objs := rt286.o
snd-soc-rt5631-objs := rt5631.o
snd-soc-rt5640-objs := rt5640.o
snd-soc-rt5645-objs := rt5645.o
snd-soc-rt5651-objs := rt5651.o
snd-soc-rt5670-objs := rt5670.o
snd-soc-rt5677-objs := rt5677.o
snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
snd-soc-alc5632-objs := alc5632.o
snd-soc-sigmadsp-objs := sigmadsp.o
snd-soc-sigmadsp-i2c-objs := sigmadsp-i2c.o
snd-soc-sigmadsp-regmap-objs := sigmadsp-regmap.o
snd-soc-si476x-objs := si476x.o
snd-soc-sirf-audio-codec-objs := sirf-audio-codec.o
snd-soc-sn95031-objs := sn95031.o
snd-soc-spdif-tx-objs := spdif_transmitter.o
snd-soc-spdif-rx-objs := spdif_receiver.o
snd-soc-audmixer-objs := exynos-audmixer.o
snd-soc-ssm2518-objs := ssm2518.o
snd-soc-ssm2602-objs := ssm2602.o
snd-soc-ssm2602-spi-objs := ssm2602-spi.o
snd-soc-ssm2602-i2c-objs := ssm2602-i2c.o
snd-soc-ssm4567-objs := ssm4567.o
snd-soc-sta32x-objs := sta32x.o
snd-soc-sta350-objs := sta350.o
snd-soc-sta529-objs := sta529.o
snd-soc-stac9766-objs := stac9766.o
snd-soc-tas5086-objs := tas5086.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o
snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o
snd-soc-tlv320aic3x-objs := tlv320aic3x.o
snd-soc-tlv320dac33-objs := tlv320dac33.o
snd-soc-twl4030-objs := twl4030.o
snd-soc-twl6040-objs := twl6040.o
snd-soc-uda134x-objs := uda134x.o
snd-soc-uda1380-objs := uda1380.o
snd-soc-wl1273-objs := wl1273.o
snd-soc-wm-adsp-objs := wm_adsp.o
snd-soc-wm0010-objs := wm0010.o
snd-soc-wm1250-ev1-objs := wm1250-ev1.o
snd-soc-wm2000-objs := wm2000.o
snd-soc-wm2200-objs := wm2200.o
snd-soc-wm5100-objs := wm5100.o wm5100-tables.o
snd-soc-wm5102-objs := wm5102.o
snd-soc-wm5110-objs := wm5110.o
snd-soc-wm8350-objs := wm8350.o
snd-soc-wm8400-objs := wm8400.o
snd-soc-wm8510-objs := wm8510.o
snd-soc-wm8523-objs := wm8523.o
snd-soc-wm8580-objs := wm8580.o
snd-soc-wm8711-objs := wm8711.o
snd-soc-wm8727-objs := wm8727.o
snd-soc-wm8728-objs := wm8728.o
snd-soc-wm8731-objs := wm8731.o
snd-soc-wm8737-objs := wm8737.o
snd-soc-wm8741-objs := wm8741.o
snd-soc-wm8750-objs := wm8750.o
snd-soc-wm8753-objs := wm8753.o
snd-soc-wm8770-objs := wm8770.o
snd-soc-wm8776-objs := wm8776.o
snd-soc-wm8782-objs := wm8782.o
snd-soc-wm8804-objs := wm8804.o
snd-soc-wm8900-objs := wm8900.o
snd-soc-wm8903-objs := wm8903.o
snd-soc-wm8904-objs := wm8904.o
snd-soc-wm8996-objs := wm8996.o
snd-soc-wm8940-objs := wm8940.o
snd-soc-wm8955-objs := wm8955.o
snd-soc-wm8960-objs := wm8960.o
snd-soc-wm8961-objs := wm8961.o
snd-soc-wm8962-objs := wm8962.o
snd-soc-wm8971-objs := wm8971.o
snd-soc-wm8974-objs := wm8974.o
snd-soc-wm8978-objs := wm8978.o
snd-soc-wm8983-objs := wm8983.o
snd-soc-wm8985-objs := wm8985.o
snd-soc-wm8988-objs := wm8988.o
snd-soc-wm8990-objs := wm8990.o
snd-soc-wm8991-objs := wm8991.o
snd-soc-wm8993-objs := wm8993.o
snd-soc-wm8994-objs := wm8994.o wm8958-dsp2.o
snd-soc-wm8995-objs := wm8995.o
snd-soc-wm8997-objs := wm8997.o
snd-soc-wm9081-objs := wm9081.o
snd-soc-wm9090-objs := wm9090.o
snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
snd-soc-dummy-objs := dummy_codec.o
# Amp
snd-soc-max9877-objs := max9877.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
snd-soc-tas2552-objs := tas2552.o
obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o
obj-$(CONFIG_SND_SOC_AB8500_CODEC) += snd-soc-ab8500-codec.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o
obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o
obj-$(CONFIG_SND_SOC_AD193X_I2C) += snd-soc-ad193x-i2c.o
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o
obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o
obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o
obj-$(CONFIG_SND_SOC_ADAU1761) += snd-soc-adau1761.o
obj-$(CONFIG_SND_SOC_ADAU1761_I2C) += snd-soc-adau1761-i2c.o
obj-$(CONFIG_SND_SOC_ADAU1761_SPI) += snd-soc-adau1761-spi.o
obj-$(CONFIG_SND_SOC_ADAU1781) += snd-soc-adau1781.o
obj-$(CONFIG_SND_SOC_ADAU1781_I2C) += snd-soc-adau1781-i2c.o
obj-$(CONFIG_SND_SOC_ADAU1781_SPI) += snd-soc-adau1781-spi.o
obj-$(CONFIG_SND_SOC_ADAU1977) += snd-soc-adau1977.o
obj-$(CONFIG_SND_SOC_ADAU1977_SPI) += snd-soc-adau1977-spi.o
obj-$(CONFIG_SND_SOC_ADAU1977_I2C) += snd-soc-adau1977-i2c.o
obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o
obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o
obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o
obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
obj-$(CONFIG_SND_SOC_AK4554) += snd-soc-ak4554.o
obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o
obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o
obj-$(CONFIG_SND_SOC_COD3026X) += snd-soc-cod3026.o
obj-$(CONFIG_SND_SOC_COD9002X) += snd-soc-cod9002.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o
obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o
obj-$(CONFIG_SND_SOC_CS42L56) += snd-soc-cs42l56.o
obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o
obj-$(CONFIG_SND_SOC_CS4265) += snd-soc-cs4265.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o
obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o
obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o
obj-$(CONFIG_SND_SOC_LARGO) += snd-soc-largo.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o
obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o
obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o
obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o
obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o
obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o
obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o
obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o
obj-$(CONFIG_SND_SOC_MAX98090) += snd-soc-max98090.o
obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o
obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o
obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o
obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o
obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o
obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o
obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o
obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o
obj-$(CONFIG_SND_SOC_RL6231) += snd-soc-rl6231.o
obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o
obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o
obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o
obj-$(CONFIG_SND_SOC_RT5645) += snd-soc-rt5645.o
obj-$(CONFIG_SND_SOC_RT5651) += snd-soc-rt5651.o
obj-$(CONFIG_SND_SOC_RT5670) += snd-soc-rt5670.o
obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o
obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o
obj-$(CONFIG_SND_SOC_SIGMADSP_I2C) += snd-soc-sigmadsp-i2c.o
obj-$(CONFIG_SND_SOC_SIGMADSP_REGMAP) += snd-soc-sigmadsp-regmap.o
obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o
obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o
obj-$(CONFIG_SND_SOC_EXYNOS_AUDMIXER) += snd-soc-audmixer.o
obj-$(CONFIG_SND_SOC_SSM2518) += snd-soc-ssm2518.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
obj-$(CONFIG_SND_SOC_SSM2602_SPI) += snd-soc-ssm2602-spi.o
obj-$(CONFIG_SND_SOC_SSM2602_I2C) += snd-soc-ssm2602-i2c.o
obj-$(CONFIG_SND_SOC_SSM4567) += snd-soc-ssm4567.o
obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o
obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o
obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o
obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o
obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o
obj-$(CONFIG_SND_SOC_TLV320AIC23_SPI) += snd-soc-tlv320aic23-spi.o
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
obj-$(CONFIG_SND_SOC_TLV320AIC31XX) += snd-soc-tlv320aic31xx.o
obj-$(CONFIG_SND_SOC_TLV320AIC32X4) += snd-soc-tlv320aic32x4.o
obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o
obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o
obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o
obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o
obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o
obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o
obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o
obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
obj-$(CONFIG_SND_SOC_WM2200) += snd-soc-wm2200.o
obj-$(CONFIG_SND_SOC_WM5100) += snd-soc-wm5100.o
obj-$(CONFIG_SND_SOC_WM5102) += snd-soc-wm5102.o
obj-$(CONFIG_SND_SOC_WM5110) += snd-soc-wm5110.o
obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o
obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o
obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o
obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o
obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o
obj-$(CONFIG_SND_SOC_WM8727) += snd-soc-wm8727.o
obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o
obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o
obj-$(CONFIG_SND_SOC_WM8737) += snd-soc-wm8737.o
obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o
obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o
obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o
obj-$(CONFIG_SND_SOC_WM8782) += snd-soc-wm8782.o
obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o
obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
obj-$(CONFIG_SND_SOC_WM8904) += snd-soc-wm8904.o
obj-$(CONFIG_SND_SOC_WM8996) += snd-soc-wm8996.o
obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o
obj-$(CONFIG_SND_SOC_WM8955) += snd-soc-wm8955.o
obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o
obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o
obj-$(CONFIG_SND_SOC_WM8962) += snd-soc-wm8962.o
obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o
obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o
obj-$(CONFIG_SND_SOC_WM8983) += snd-soc-wm8983.o
obj-$(CONFIG_SND_SOC_WM8985) += snd-soc-wm8985.o
obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o
obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o
obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.o
obj-$(CONFIG_SND_SOC_WM8995) += snd-soc-wm8995.o
obj-$(CONFIG_SND_SOC_WM8997) += snd-soc-wm8997.o
obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o
obj-$(CONFIG_SND_SOC_WM9090) += snd-soc-wm9090.o
obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
obj-$(CONFIG_SND_SOC_DUMMY_CODEC) += snd-soc-dummy.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,592 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Kristoffer Karlsson <kristoffer.karlsson@stericsson.com>,
* Roger Nilsson <roger.xr.nilsson@stericsson.com>,
* for ST-Ericsson.
*
* Based on the early work done by:
* Mikko J. Lehto <mikko.lehto@symbio.com>,
* Mikko Sarmanne <mikko.sarmanne@symbio.com>,
* for ST-Ericsson.
*
* License terms:
*
* 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 AB8500_CODEC_REGISTERS_H
#define AB8500_CODEC_REGISTERS_H
#define AB8500_SUPPORTED_RATE (SNDRV_PCM_RATE_48000)
#define AB8500_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE)
/* AB8500 interface slot offset definitions */
#define AB8500_AD_DATA0_OFFSET 0
#define AB8500_DA_DATA0_OFFSET 8
#define AB8500_AD_DATA1_OFFSET 16
#define AB8500_DA_DATA1_OFFSET 24
/* AB8500 audio bank (0x0d) register definitions */
#define AB8500_POWERUP 0x00
#define AB8500_AUDSWRESET 0x01
#define AB8500_ADPATHENA 0x02
#define AB8500_DAPATHENA 0x03
#define AB8500_ANACONF1 0x04
#define AB8500_ANACONF2 0x05
#define AB8500_DIGMICCONF 0x06
#define AB8500_ANACONF3 0x07
#define AB8500_ANACONF4 0x08
#define AB8500_DAPATHCONF 0x09
#define AB8500_MUTECONF 0x0A
#define AB8500_SHORTCIRCONF 0x0B
#define AB8500_ANACONF5 0x0C
#define AB8500_ENVCPCONF 0x0D
#define AB8500_SIGENVCONF 0x0E
#define AB8500_PWMGENCONF1 0x0F
#define AB8500_PWMGENCONF2 0x10
#define AB8500_PWMGENCONF3 0x11
#define AB8500_PWMGENCONF4 0x12
#define AB8500_PWMGENCONF5 0x13
#define AB8500_ANAGAIN1 0x14
#define AB8500_ANAGAIN2 0x15
#define AB8500_ANAGAIN3 0x16
#define AB8500_ANAGAIN4 0x17
#define AB8500_DIGLINHSLGAIN 0x18
#define AB8500_DIGLINHSRGAIN 0x19
#define AB8500_ADFILTCONF 0x1A
#define AB8500_DIGIFCONF1 0x1B
#define AB8500_DIGIFCONF2 0x1C
#define AB8500_DIGIFCONF3 0x1D
#define AB8500_DIGIFCONF4 0x1E
#define AB8500_ADSLOTSEL1 0x1F
#define AB8500_ADSLOTSEL2 0x20
#define AB8500_ADSLOTSEL3 0x21
#define AB8500_ADSLOTSEL4 0x22
#define AB8500_ADSLOTSEL5 0x23
#define AB8500_ADSLOTSEL6 0x24
#define AB8500_ADSLOTSEL7 0x25
#define AB8500_ADSLOTSEL8 0x26
#define AB8500_ADSLOTSEL9 0x27
#define AB8500_ADSLOTSEL10 0x28
#define AB8500_ADSLOTSEL11 0x29
#define AB8500_ADSLOTSEL12 0x2A
#define AB8500_ADSLOTSEL13 0x2B
#define AB8500_ADSLOTSEL14 0x2C
#define AB8500_ADSLOTSEL15 0x2D
#define AB8500_ADSLOTSEL16 0x2E
#define AB8500_ADSLOTSEL(slot) (AB8500_ADSLOTSEL1 + (slot >> 1))
#define AB8500_ADSLOTHIZCTRL1 0x2F
#define AB8500_ADSLOTHIZCTRL2 0x30
#define AB8500_ADSLOTHIZCTRL3 0x31
#define AB8500_ADSLOTHIZCTRL4 0x32
#define AB8500_DASLOTCONF1 0x33
#define AB8500_DASLOTCONF2 0x34
#define AB8500_DASLOTCONF3 0x35
#define AB8500_DASLOTCONF4 0x36
#define AB8500_DASLOTCONF5 0x37
#define AB8500_DASLOTCONF6 0x38
#define AB8500_DASLOTCONF7 0x39
#define AB8500_DASLOTCONF8 0x3A
#define AB8500_CLASSDCONF1 0x3B
#define AB8500_CLASSDCONF2 0x3C
#define AB8500_CLASSDCONF3 0x3D
#define AB8500_DMICFILTCONF 0x3E
#define AB8500_DIGMULTCONF1 0x3F
#define AB8500_DIGMULTCONF2 0x40
#define AB8500_ADDIGGAIN1 0x41
#define AB8500_ADDIGGAIN2 0x42
#define AB8500_ADDIGGAIN3 0x43
#define AB8500_ADDIGGAIN4 0x44
#define AB8500_ADDIGGAIN5 0x45
#define AB8500_ADDIGGAIN6 0x46
#define AB8500_DADIGGAIN1 0x47
#define AB8500_DADIGGAIN2 0x48
#define AB8500_DADIGGAIN3 0x49
#define AB8500_DADIGGAIN4 0x4A
#define AB8500_DADIGGAIN5 0x4B
#define AB8500_DADIGGAIN6 0x4C
#define AB8500_ADDIGLOOPGAIN1 0x4D
#define AB8500_ADDIGLOOPGAIN2 0x4E
#define AB8500_HSLEARDIGGAIN 0x4F
#define AB8500_HSRDIGGAIN 0x50
#define AB8500_SIDFIRGAIN1 0x51
#define AB8500_SIDFIRGAIN2 0x52
#define AB8500_ANCCONF1 0x53
#define AB8500_ANCCONF2 0x54
#define AB8500_ANCCONF3 0x55
#define AB8500_ANCCONF4 0x56
#define AB8500_ANCCONF5 0x57
#define AB8500_ANCCONF6 0x58
#define AB8500_ANCCONF7 0x59
#define AB8500_ANCCONF8 0x5A
#define AB8500_ANCCONF9 0x5B
#define AB8500_ANCCONF10 0x5C
#define AB8500_ANCCONF11 0x5D
#define AB8500_ANCCONF12 0x5E
#define AB8500_ANCCONF13 0x5F
#define AB8500_ANCCONF14 0x60
#define AB8500_SIDFIRADR 0x61
#define AB8500_SIDFIRCOEF1 0x62
#define AB8500_SIDFIRCOEF2 0x63
#define AB8500_SIDFIRCONF 0x64
#define AB8500_AUDINTMASK1 0x65
#define AB8500_AUDINTSOURCE1 0x66
#define AB8500_AUDINTMASK2 0x67
#define AB8500_AUDINTSOURCE2 0x68
#define AB8500_FIFOCONF1 0x69
#define AB8500_FIFOCONF2 0x6A
#define AB8500_FIFOCONF3 0x6B
#define AB8500_FIFOCONF4 0x6C
#define AB8500_FIFOCONF5 0x6D
#define AB8500_FIFOCONF6 0x6E
#define AB8500_AUDREV 0x6F
#define AB8500_FIRST_REG AB8500_POWERUP
#define AB8500_LAST_REG AB8500_AUDREV
#define AB8500_CACHEREGNUM (AB8500_LAST_REG + 1)
#define AB8500_MASK_ALL 0xFF
#define AB8500_MASK_SLOT(slot) ((slot & 1) ? 0xF0 : 0x0F)
#define AB8500_MASK_NONE 0x00
/* AB8500_POWERUP */
#define AB8500_POWERUP_POWERUP 7
#define AB8500_POWERUP_ENANA 3
/* AB8500_AUDSWRESET */
#define AB8500_AUDSWRESET_SWRESET 7
/* AB8500_ADPATHENA */
#define AB8500_ADPATHENA_ENAD12 7
#define AB8500_ADPATHENA_ENAD34 5
#define AB8500_ADPATHENA_ENAD5768 3
/* AB8500_DAPATHENA */
#define AB8500_DAPATHENA_ENDA1 7
#define AB8500_DAPATHENA_ENDA2 6
#define AB8500_DAPATHENA_ENDA3 5
#define AB8500_DAPATHENA_ENDA4 4
#define AB8500_DAPATHENA_ENDA5 3
#define AB8500_DAPATHENA_ENDA6 2
/* AB8500_ANACONF1 */
#define AB8500_ANACONF1_HSLOWPOW 7
#define AB8500_ANACONF1_DACLOWPOW1 6
#define AB8500_ANACONF1_DACLOWPOW0 5
#define AB8500_ANACONF1_EARDACLOWPOW 4
#define AB8500_ANACONF1_EARSELCM 2
#define AB8500_ANACONF1_HSHPEN 1
#define AB8500_ANACONF1_EARDRVLOWPOW 0
/* AB8500_ANACONF2 */
#define AB8500_ANACONF2_ENMIC1 7
#define AB8500_ANACONF2_ENMIC2 6
#define AB8500_ANACONF2_ENLINL 5
#define AB8500_ANACONF2_ENLINR 4
#define AB8500_ANACONF2_MUTMIC1 3
#define AB8500_ANACONF2_MUTMIC2 2
#define AB8500_ANACONF2_MUTLINL 1
#define AB8500_ANACONF2_MUTLINR 0
/* AB8500_DIGMICCONF */
#define AB8500_DIGMICCONF_ENDMIC1 7
#define AB8500_DIGMICCONF_ENDMIC2 6
#define AB8500_DIGMICCONF_ENDMIC3 5
#define AB8500_DIGMICCONF_ENDMIC4 4
#define AB8500_DIGMICCONF_ENDMIC5 3
#define AB8500_DIGMICCONF_ENDMIC6 2
#define AB8500_DIGMICCONF_HSFADSPEED 0
/* AB8500_ANACONF3 */
#define AB8500_ANACONF3_MIC1SEL 7
#define AB8500_ANACONF3_LINRSEL 6
#define AB8500_ANACONF3_ENDRVHSL 5
#define AB8500_ANACONF3_ENDRVHSR 4
#define AB8500_ANACONF3_ENADCMIC 2
#define AB8500_ANACONF3_ENADCLINL 1
#define AB8500_ANACONF3_ENADCLINR 0
/* AB8500_ANACONF4 */
#define AB8500_ANACONF4_DISPDVSS 7
#define AB8500_ANACONF4_ENEAR 6
#define AB8500_ANACONF4_ENHSL 5
#define AB8500_ANACONF4_ENHSR 4
#define AB8500_ANACONF4_ENHFL 3
#define AB8500_ANACONF4_ENHFR 2
#define AB8500_ANACONF4_ENVIB1 1
#define AB8500_ANACONF4_ENVIB2 0
/* AB8500_DAPATHCONF */
#define AB8500_DAPATHCONF_ENDACEAR 6
#define AB8500_DAPATHCONF_ENDACHSL 5
#define AB8500_DAPATHCONF_ENDACHSR 4
#define AB8500_DAPATHCONF_ENDACHFL 3
#define AB8500_DAPATHCONF_ENDACHFR 2
#define AB8500_DAPATHCONF_ENDACVIB1 1
#define AB8500_DAPATHCONF_ENDACVIB2 0
/* AB8500_MUTECONF */
#define AB8500_MUTECONF_MUTEAR 6
#define AB8500_MUTECONF_MUTHSL 5
#define AB8500_MUTECONF_MUTHSR 4
#define AB8500_MUTECONF_MUTDACEAR 2
#define AB8500_MUTECONF_MUTDACHSL 1
#define AB8500_MUTECONF_MUTDACHSR 0
/* AB8500_SHORTCIRCONF */
#define AB8500_SHORTCIRCONF_ENSHORTPWD 7
#define AB8500_SHORTCIRCONF_EARSHORTDIS 6
#define AB8500_SHORTCIRCONF_HSSHORTDIS 5
#define AB8500_SHORTCIRCONF_HSPULLDEN 4
#define AB8500_SHORTCIRCONF_HSOSCEN 2
#define AB8500_SHORTCIRCONF_HSFADDIS 1
#define AB8500_SHORTCIRCONF_HSZCDDIS 0
/* Zero cross should be disabled */
/* AB8500_ANACONF5 */
#define AB8500_ANACONF5_ENCPHS 7
#define AB8500_ANACONF5_HSLDACTOLOL 5
#define AB8500_ANACONF5_HSRDACTOLOR 4
#define AB8500_ANACONF5_ENLOL 3
#define AB8500_ANACONF5_ENLOR 2
#define AB8500_ANACONF5_HSAUTOEN 0
/* AB8500_ENVCPCONF */
#define AB8500_ENVCPCONF_ENVDETHTHRE 4
#define AB8500_ENVCPCONF_ENVDETLTHRE 0
#define AB8500_ENVCPCONF_ENVDETHTHRE_MAX 0x0F
#define AB8500_ENVCPCONF_ENVDETLTHRE_MAX 0x0F
/* AB8500_SIGENVCONF */
#define AB8500_SIGENVCONF_CPLVEN 5
#define AB8500_SIGENVCONF_ENVDETCPEN 4
#define AB8500_SIGENVCONF_ENVDETTIME 0
#define AB8500_SIGENVCONF_ENVDETTIME_MAX 0x0F
/* AB8500_PWMGENCONF1 */
#define AB8500_PWMGENCONF1_PWMTOVIB1 7
#define AB8500_PWMGENCONF1_PWMTOVIB2 6
#define AB8500_PWMGENCONF1_PWM1CTRL 5
#define AB8500_PWMGENCONF1_PWM2CTRL 4
#define AB8500_PWMGENCONF1_PWM1NCTRL 3
#define AB8500_PWMGENCONF1_PWM1PCTRL 2
#define AB8500_PWMGENCONF1_PWM2NCTRL 1
#define AB8500_PWMGENCONF1_PWM2PCTRL 0
/* AB8500_PWMGENCONF2 */
/* AB8500_PWMGENCONF3 */
/* AB8500_PWMGENCONF4 */
/* AB8500_PWMGENCONF5 */
#define AB8500_PWMGENCONFX_PWMVIBXPOL 7
#define AB8500_PWMGENCONFX_PWMVIBXDUTCYC 0
#define AB8500_PWMGENCONFX_PWMVIBXDUTCYC_MAX 0x64
/* AB8500_ANAGAIN1 */
/* AB8500_ANAGAIN2 */
#define AB8500_ANAGAINX_ENSEMICX 7
#define AB8500_ANAGAINX_LOWPOWMICX 6
#define AB8500_ANAGAINX_MICXGAIN 0
#define AB8500_ANAGAINX_MICXGAIN_MAX 0x1F
/* AB8500_ANAGAIN3 */
#define AB8500_ANAGAIN3_HSLGAIN 4
#define AB8500_ANAGAIN3_HSRGAIN 0
#define AB8500_ANAGAIN3_HSXGAIN_MAX 0x0F
/* AB8500_ANAGAIN4 */
#define AB8500_ANAGAIN4_LINLGAIN 4
#define AB8500_ANAGAIN4_LINRGAIN 0
#define AB8500_ANAGAIN4_LINXGAIN_MAX 0x0F
/* AB8500_DIGLINHSLGAIN */
/* AB8500_DIGLINHSRGAIN */
#define AB8500_DIGLINHSXGAIN_LINTOHSXGAIN 0
#define AB8500_DIGLINHSXGAIN_LINTOHSXGAIN_MAX 0x13
/* AB8500_ADFILTCONF */
#define AB8500_ADFILTCONF_AD1NH 7
#define AB8500_ADFILTCONF_AD2NH 6
#define AB8500_ADFILTCONF_AD3NH 5
#define AB8500_ADFILTCONF_AD4NH 4
#define AB8500_ADFILTCONF_AD1VOICE 3
#define AB8500_ADFILTCONF_AD2VOICE 2
#define AB8500_ADFILTCONF_AD3VOICE 1
#define AB8500_ADFILTCONF_AD4VOICE 0
/* AB8500_DIGIFCONF1 */
#define AB8500_DIGIFCONF1_ENMASTGEN 7
#define AB8500_DIGIFCONF1_IF1BITCLKOS1 6
#define AB8500_DIGIFCONF1_IF1BITCLKOS0 5
#define AB8500_DIGIFCONF1_ENFSBITCLK1 4
#define AB8500_DIGIFCONF1_IF0BITCLKOS1 2
#define AB8500_DIGIFCONF1_IF0BITCLKOS0 1
#define AB8500_DIGIFCONF1_ENFSBITCLK0 0
/* AB8500_DIGIFCONF2 */
#define AB8500_DIGIFCONF2_FSYNC0P 6
#define AB8500_DIGIFCONF2_BITCLK0P 5
#define AB8500_DIGIFCONF2_IF0DEL 4
#define AB8500_DIGIFCONF2_IF0FORMAT1 3
#define AB8500_DIGIFCONF2_IF0FORMAT0 2
#define AB8500_DIGIFCONF2_IF0WL1 1
#define AB8500_DIGIFCONF2_IF0WL0 0
/* AB8500_DIGIFCONF3 */
#define AB8500_DIGIFCONF3_IF0DATOIF1AD 7
#define AB8500_DIGIFCONF3_IF0CLKTOIF1CLK 6
#define AB8500_DIGIFCONF3_IF1MASTER 5
#define AB8500_DIGIFCONF3_IF1DATOIF0AD 3
#define AB8500_DIGIFCONF3_IF1CLKTOIF0CLK 2
#define AB8500_DIGIFCONF3_IF0MASTER 1
#define AB8500_DIGIFCONF3_IF0BFIFOEN 0
/* AB8500_DIGIFCONF4 */
#define AB8500_DIGIFCONF4_FSYNC1P 6
#define AB8500_DIGIFCONF4_BITCLK1P 5
#define AB8500_DIGIFCONF4_IF1DEL 4
#define AB8500_DIGIFCONF4_IF1FORMAT1 3
#define AB8500_DIGIFCONF4_IF1FORMAT0 2
#define AB8500_DIGIFCONF4_IF1WL1 1
#define AB8500_DIGIFCONF4_IF1WL0 0
/* AB8500_ADSLOTSELX */
#define AB8500_AD_OUT1 0x0
#define AB8500_AD_OUT2 0x1
#define AB8500_AD_OUT3 0x2
#define AB8500_AD_OUT4 0x3
#define AB8500_AD_OUT5 0x4
#define AB8500_AD_OUT6 0x5
#define AB8500_AD_OUT7 0x6
#define AB8500_AD_OUT8 0x7
#define AB8500_ZEROES 0x8
#define AB8500_TRISTATE 0xF
#define AB8500_ADSLOTSELX_EVEN_SHIFT 0
#define AB8500_ADSLOTSELX_ODD_SHIFT 4
#define AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(out, slot) \
((out) << (((slot) & 1) ? \
AB8500_ADSLOTSELX_ODD_SHIFT : AB8500_ADSLOTSELX_EVEN_SHIFT))
/* AB8500_ADSLOTHIZCTRL1 */
/* AB8500_ADSLOTHIZCTRL2 */
/* AB8500_ADSLOTHIZCTRL3 */
/* AB8500_ADSLOTHIZCTRL4 */
/* AB8500_DASLOTCONF1 */
#define AB8500_DASLOTCONF1_DA12VOICE 7
#define AB8500_DASLOTCONF1_SWAPDA12_34 6
#define AB8500_DASLOTCONF1_DAI7TOADO1 5
/* AB8500_DASLOTCONF2 */
#define AB8500_DASLOTCONF2_DAI8TOADO2 5
/* AB8500_DASLOTCONF3 */
#define AB8500_DASLOTCONF3_DA34VOICE 7
#define AB8500_DASLOTCONF3_DAI7TOADO3 5
/* AB8500_DASLOTCONF4 */
#define AB8500_DASLOTCONF4_DAI8TOADO4 5
/* AB8500_DASLOTCONF5 */
#define AB8500_DASLOTCONF5_DA56VOICE 7
#define AB8500_DASLOTCONF5_DAI7TOADO5 5
/* AB8500_DASLOTCONF6 */
#define AB8500_DASLOTCONF6_DAI8TOADO6 5
/* AB8500_DASLOTCONF7 */
#define AB8500_DASLOTCONF7_DAI8TOADO7 5
/* AB8500_DASLOTCONF8 */
#define AB8500_DASLOTCONF8_DAI7TOADO8 5
#define AB8500_DASLOTCONFX_SLTODAX_SHIFT 0
#define AB8500_DASLOTCONFX_SLTODAX_MASK 0x1F
/* AB8500_CLASSDCONF1 */
#define AB8500_CLASSDCONF1_PARLHF 7
#define AB8500_CLASSDCONF1_PARLVIB 6
#define AB8500_CLASSDCONF1_VIB1SWAPEN 3
#define AB8500_CLASSDCONF1_VIB2SWAPEN 2
#define AB8500_CLASSDCONF1_HFLSWAPEN 1
#define AB8500_CLASSDCONF1_HFRSWAPEN 0
/* AB8500_CLASSDCONF2 */
#define AB8500_CLASSDCONF2_FIRBYP3 7
#define AB8500_CLASSDCONF2_FIRBYP2 6
#define AB8500_CLASSDCONF2_FIRBYP1 5
#define AB8500_CLASSDCONF2_FIRBYP0 4
#define AB8500_CLASSDCONF2_HIGHVOLEN3 3
#define AB8500_CLASSDCONF2_HIGHVOLEN2 2
#define AB8500_CLASSDCONF2_HIGHVOLEN1 1
#define AB8500_CLASSDCONF2_HIGHVOLEN0 0
/* AB8500_CLASSDCONF3 */
#define AB8500_CLASSDCONF3_DITHHPGAIN 4
#define AB8500_CLASSDCONF3_DITHHPGAIN_MAX 0x0A
#define AB8500_CLASSDCONF3_DITHWGAIN 0
#define AB8500_CLASSDCONF3_DITHWGAIN_MAX 0x0A
/* AB8500_DMICFILTCONF */
#define AB8500_DMICFILTCONF_ANCINSEL 7
#define AB8500_DMICFILTCONF_DA3TOEAR 6
#define AB8500_DMICFILTCONF_DMIC1SINC3 5
#define AB8500_DMICFILTCONF_DMIC2SINC3 4
#define AB8500_DMICFILTCONF_DMIC3SINC3 3
#define AB8500_DMICFILTCONF_DMIC4SINC3 2
#define AB8500_DMICFILTCONF_DMIC5SINC3 1
#define AB8500_DMICFILTCONF_DMIC6SINC3 0
/* AB8500_DIGMULTCONF1 */
#define AB8500_DIGMULTCONF1_DATOHSLEN 7
#define AB8500_DIGMULTCONF1_DATOHSREN 6
#define AB8500_DIGMULTCONF1_AD1SEL 5
#define AB8500_DIGMULTCONF1_AD2SEL 4
#define AB8500_DIGMULTCONF1_AD3SEL 3
#define AB8500_DIGMULTCONF1_AD5SEL 2
#define AB8500_DIGMULTCONF1_AD6SEL 1
#define AB8500_DIGMULTCONF1_ANCSEL 0
/* AB8500_DIGMULTCONF2 */
#define AB8500_DIGMULTCONF2_DATOHFREN 7
#define AB8500_DIGMULTCONF2_DATOHFLEN 6
#define AB8500_DIGMULTCONF2_HFRSEL 5
#define AB8500_DIGMULTCONF2_HFLSEL 4
#define AB8500_DIGMULTCONF2_FIRSID1SEL 2
#define AB8500_DIGMULTCONF2_FIRSID2SEL 0
/* AB8500_ADDIGGAIN1 */
/* AB8500_ADDIGGAIN2 */
/* AB8500_ADDIGGAIN3 */
/* AB8500_ADDIGGAIN4 */
/* AB8500_ADDIGGAIN5 */
/* AB8500_ADDIGGAIN6 */
#define AB8500_ADDIGGAINX_FADEDISADX 6
#define AB8500_ADDIGGAINX_ADXGAIN_MAX 0x3F
/* AB8500_DADIGGAIN1 */
/* AB8500_DADIGGAIN2 */
/* AB8500_DADIGGAIN3 */
/* AB8500_DADIGGAIN4 */
/* AB8500_DADIGGAIN5 */
/* AB8500_DADIGGAIN6 */
#define AB8500_DADIGGAINX_FADEDISDAX 6
#define AB8500_DADIGGAINX_DAXGAIN_MAX 0x3F
/* AB8500_ADDIGLOOPGAIN1 */
/* AB8500_ADDIGLOOPGAIN2 */
#define AB8500_ADDIGLOOPGAINX_FADEDISADXL 6
#define AB8500_ADDIGLOOPGAINX_ADXLBGAIN_MAX 0x3F
/* AB8500_HSLEARDIGGAIN */
#define AB8500_HSLEARDIGGAIN_HSSINC1 7
#define AB8500_HSLEARDIGGAIN_FADEDISHSL 4
#define AB8500_HSLEARDIGGAIN_HSLDGAIN_MAX 0x09
/* AB8500_HSRDIGGAIN */
#define AB8500_HSRDIGGAIN_FADESPEED 6
#define AB8500_HSRDIGGAIN_FADEDISHSR 4
#define AB8500_HSRDIGGAIN_HSRDGAIN_MAX 0x09
/* AB8500_SIDFIRGAIN1 */
/* AB8500_SIDFIRGAIN2 */
#define AB8500_SIDFIRGAINX_FIRSIDXGAIN_MAX 0x1F
/* AB8500_ANCCONF1 */
#define AB8500_ANCCONF1_ANCIIRUPDATE 3
#define AB8500_ANCCONF1_ENANC 2
#define AB8500_ANCCONF1_ANCIIRINIT 1
#define AB8500_ANCCONF1_ANCFIRUPDATE 0
/* AB8500_ANCCONF2 */
#define AB8500_ANCCONF2_SHIFT 5
#define AB8500_ANCCONF2_MIN -0x10
#define AB8500_ANCCONF2_MAX 0xF
/* AB8500_ANCCONF3 */
#define AB8500_ANCCONF3_SHIFT 5
#define AB8500_ANCCONF3_MIN -0x10
#define AB8500_ANCCONF3_MAX 0xF
/* AB8500_ANCCONF4 */
#define AB8500_ANCCONF4_SHIFT 5
#define AB8500_ANCCONF4_MIN -0x10
#define AB8500_ANCCONF4_MAX 0xF
/* AB8500_ANC_FIR_COEFFS */
#define AB8500_ANC_FIR_COEFF_MIN -0x8000
#define AB8500_ANC_FIR_COEFF_MAX 0x7FFF
#define AB8500_ANC_FIR_COEFFS 15
/* AB8500_ANC_IIR_COEFFS */
#define AB8500_ANC_IIR_COEFF_MIN -0x800000
#define AB8500_ANC_IIR_COEFF_MAX 0x7FFFFF
#define AB8500_ANC_IIR_COEFFS 24
/* AB8500_ANC_WARP_DELAY */
#define AB8500_ANC_WARP_DELAY_SHIFT 16
#define AB8500_ANC_WARP_DELAY_MIN 0x0000
#define AB8500_ANC_WARP_DELAY_MAX 0xFFFF
/* AB8500_ANCCONF11 */
/* AB8500_ANCCONF12 */
/* AB8500_ANCCONF13 */
/* AB8500_ANCCONF14 */
/* AB8500_SIDFIRADR */
#define AB8500_SIDFIRADR_FIRSIDSET 7
#define AB8500_SIDFIRADR_ADDRESS_SHIFT 0
#define AB8500_SIDFIRADR_ADDRESS_MAX 0x7F
/* AB8500_SIDFIRCOEF1 */
/* AB8500_SIDFIRCOEF2 */
#define AB8500_SID_FIR_COEFF_MIN 0
#define AB8500_SID_FIR_COEFF_MAX 0xFFFF
#define AB8500_SID_FIR_COEFFS 128
/* AB8500_SIDFIRCONF */
#define AB8500_SIDFIRCONF_ENFIRSIDS 2
#define AB8500_SIDFIRCONF_FIRSIDSTOIF1 1
#define AB8500_SIDFIRCONF_FIRSIDBUSY 0
/* AB8500_AUDINTMASK1 */
/* AB8500_AUDINTSOURCE1 */
/* AB8500_AUDINTMASK2 */
/* AB8500_AUDINTSOURCE2 */
/* AB8500_FIFOCONF1 */
#define AB8500_FIFOCONF1_BFIFOMASK 0x80
#define AB8500_FIFOCONF1_BFIFO19M2 0x40
#define AB8500_FIFOCONF1_BFIFOINT_SHIFT 0
#define AB8500_FIFOCONF1_BFIFOINT_MAX 0x3F
/* AB8500_FIFOCONF2 */
#define AB8500_FIFOCONF2_BFIFOTX_SHIFT 0
#define AB8500_FIFOCONF2_BFIFOTX_MAX 0xFF
/* AB8500_FIFOCONF3 */
#define AB8500_FIFOCONF3_BFIFOEXSL_SHIFT 5
#define AB8500_FIFOCONF3_BFIFOEXSL_MAX 0x5
#define AB8500_FIFOCONF3_PREBITCLK0_SHIFT 2
#define AB8500_FIFOCONF3_PREBITCLK0_MAX 0x7
#define AB8500_FIFOCONF3_BFIFOMAST_SHIFT 1
#define AB8500_FIFOCONF3_BFIFORUN_SHIFT 0
/* AB8500_FIFOCONF4 */
#define AB8500_FIFOCONF4_BFIFOFRAMSW_SHIFT 0
#define AB8500_FIFOCONF4_BFIFOFRAMSW_MAX 0xFF
/* AB8500_FIFOCONF5 */
#define AB8500_FIFOCONF5_BFIFOWAKEUP_SHIFT 0
#define AB8500_FIFOCONF5_BFIFOWAKEUP_MAX 0xFF
/* AB8500_FIFOCONF6 */
#define AB8500_FIFOCONF6_BFIFOSAMPLE_SHIFT 0
#define AB8500_FIFOCONF6_BFIFOSAMPLE_MAX 0xFF
/* AB8500_AUDREV */
#endif

149
sound/soc/codecs/ac97.c Normal file
View file

@ -0,0 +1,149 @@
/*
* ac97.c -- ALSA Soc AC97 codec support
*
* Copyright 2005 Wolfson Microelectronics PLC.
* Author: Liam Girdwood <lrg@slimlogic.co.uk>
*
* 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.
*
* Generic AC97 support.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
static const struct snd_soc_dapm_widget ac97_widgets[] = {
SND_SOC_DAPM_INPUT("RX"),
SND_SOC_DAPM_OUTPUT("TX"),
};
static const struct snd_soc_dapm_route ac97_routes[] = {
{ "AC97 Capture", NULL, "RX" },
{ "TX", NULL, "AC97 Playback" },
};
static int ac97_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_codec *codec = dai->codec;
int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
return snd_ac97_set_rate(codec->ac97, reg, substream->runtime->rate);
}
#define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
SNDRV_PCM_RATE_48000)
static const struct snd_soc_dai_ops ac97_dai_ops = {
.prepare = ac97_prepare,
};
static struct snd_soc_dai_driver ac97_dai = {
.name = "ac97-hifi",
.ac97_control = 1,
.playback = {
.stream_name = "AC97 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = STD_AC97_RATES,
.formats = SND_SOC_STD_AC97_FMTS,},
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = STD_AC97_RATES,
.formats = SND_SOC_STD_AC97_FMTS,},
.ops = &ac97_dai_ops,
};
static int ac97_soc_probe(struct snd_soc_codec *codec)
{
struct snd_ac97_bus *ac97_bus;
struct snd_ac97_template ac97_template;
int ret;
/* add codec as bus device for standard ac97 */
ret = snd_ac97_bus(codec->component.card->snd_card, 0, soc_ac97_ops,
NULL, &ac97_bus);
if (ret < 0)
return ret;
memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
ret = snd_ac97_mixer(ac97_bus, &ac97_template, &codec->ac97);
if (ret < 0)
return ret;
return 0;
}
#ifdef CONFIG_PM
static int ac97_soc_suspend(struct snd_soc_codec *codec)
{
snd_ac97_suspend(codec->ac97);
return 0;
}
static int ac97_soc_resume(struct snd_soc_codec *codec)
{
snd_ac97_resume(codec->ac97);
return 0;
}
#else
#define ac97_soc_suspend NULL
#define ac97_soc_resume NULL
#endif
static struct snd_soc_codec_driver soc_codec_dev_ac97 = {
.probe = ac97_soc_probe,
.suspend = ac97_soc_suspend,
.resume = ac97_soc_resume,
.dapm_widgets = ac97_widgets,
.num_dapm_widgets = ARRAY_SIZE(ac97_widgets),
.dapm_routes = ac97_routes,
.num_dapm_routes = ARRAY_SIZE(ac97_routes),
};
static int ac97_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev,
&soc_codec_dev_ac97, &ac97_dai, 1);
}
static int ac97_remove(struct platform_device *pdev)
{
snd_soc_unregister_codec(&pdev->dev);
return 0;
}
static struct platform_driver ac97_codec_driver = {
.driver = {
.name = "ac97-codec",
.owner = THIS_MODULE,
},
.probe = ac97_probe,
.remove = ac97_remove,
};
module_platform_driver(ac97_codec_driver);
MODULE_DESCRIPTION("Soc Generic AC97 driver");
MODULE_AUTHOR("Liam Girdwood");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ac97-codec");

418
sound/soc/codecs/ad1836.c Normal file
View file

@ -0,0 +1,418 @@
/*
* Audio Codec driver supporting:
* AD1835A, AD1836, AD1837A, AD1838A, AD1839A
*
* Copyright 2009-2011 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include "ad1836.h"
enum ad1836_type {
AD1835,
AD1836,
AD1838,
};
/* codec private data */
struct ad1836_priv {
enum ad1836_type type;
struct regmap *regmap;
};
/*
* AD1836 volume/mute/de-emphasis etc. controls
*/
static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"};
static SOC_ENUM_SINGLE_DECL(ad1836_deemp_enum,
AD1836_DAC_CTRL1, 8, ad1836_deemp);
#define AD1836_DAC_VOLUME(x) \
SOC_DOUBLE_R("DAC" #x " Playback Volume", AD1836_DAC_L_VOL(x), \
AD1836_DAC_R_VOL(x), 0, 0x3FF, 0)
#define AD1836_DAC_SWITCH(x) \
SOC_DOUBLE("DAC" #x " Playback Switch", AD1836_DAC_CTRL2, \
AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
#define AD1836_ADC_SWITCH(x) \
SOC_DOUBLE("ADC" #x " Capture Switch", AD1836_ADC_CTRL2, \
AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
static const struct snd_kcontrol_new ad183x_dac_controls[] = {
AD1836_DAC_VOLUME(1),
AD1836_DAC_SWITCH(1),
AD1836_DAC_VOLUME(2),
AD1836_DAC_SWITCH(2),
AD1836_DAC_VOLUME(3),
AD1836_DAC_SWITCH(3),
AD1836_DAC_VOLUME(4),
AD1836_DAC_SWITCH(4),
};
static const struct snd_soc_dapm_widget ad183x_dac_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
SND_SOC_DAPM_OUTPUT("DAC4OUT"),
};
static const struct snd_soc_dapm_route ad183x_dac_routes[] = {
{ "DAC1OUT", NULL, "DAC" },
{ "DAC2OUT", NULL, "DAC" },
{ "DAC3OUT", NULL, "DAC" },
{ "DAC4OUT", NULL, "DAC" },
};
static const struct snd_kcontrol_new ad183x_adc_controls[] = {
AD1836_ADC_SWITCH(1),
AD1836_ADC_SWITCH(2),
AD1836_ADC_SWITCH(3),
};
static const struct snd_soc_dapm_widget ad183x_adc_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("ADC1IN"),
SND_SOC_DAPM_INPUT("ADC2IN"),
};
static const struct snd_soc_dapm_route ad183x_adc_routes[] = {
{ "ADC", NULL, "ADC1IN" },
{ "ADC", NULL, "ADC2IN" },
};
static const struct snd_kcontrol_new ad183x_controls[] = {
/* ADC high-pass filter */
SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1,
AD1836_ADC_HIGHPASS_FILTER, 1, 0),
/* DAC de-emphasis */
SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum),
};
static const struct snd_soc_dapm_widget ad183x_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1,
AD1836_DAC_POWERDOWN, 1),
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1,
AD1836_ADC_POWERDOWN, 1, NULL, 0),
};
static const struct snd_soc_dapm_route ad183x_dapm_routes[] = {
{ "DAC", NULL, "ADC_PWR" },
{ "ADC", NULL, "ADC_PWR" },
};
static const DECLARE_TLV_DB_SCALE(ad1836_in_tlv, 0, 300, 0);
static const struct snd_kcontrol_new ad1836_controls[] = {
SOC_DOUBLE_TLV("ADC2 Capture Volume", AD1836_ADC_CTRL1, 3, 0, 4, 0,
ad1836_in_tlv),
};
/*
* DAI ops entries
*/
static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
/* at present, we support adc aux mode to interface with
* blackfin sport tdm mode
*/
case SND_SOC_DAIFMT_DSP_A:
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_IF:
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
/* ALCLK,ABCLK are both output, AD1836 can only be master */
case SND_SOC_DAIFMT_CBM_CFM:
break;
default:
return -EINVAL;
}
return 0;
}
static int ad1836_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(dai->codec);
int word_len = 0;
/* bit size */
switch (params_width(params)) {
case 16:
word_len = AD1836_WORD_LEN_16;
break;
case 20:
word_len = AD1836_WORD_LEN_20;
break;
case 24:
case 32:
word_len = AD1836_WORD_LEN_24;
break;
default:
return -EINVAL;
}
regmap_update_bits(ad1836->regmap, AD1836_DAC_CTRL1,
AD1836_DAC_WORD_LEN_MASK,
word_len << AD1836_DAC_WORD_LEN_OFFSET);
regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_WORD_LEN_MASK,
word_len << AD1836_ADC_WORD_OFFSET);
return 0;
}
static const struct snd_soc_dai_ops ad1836_dai_ops = {
.hw_params = ad1836_hw_params,
.set_fmt = ad1836_set_dai_fmt,
};
#define AD183X_DAI(_name, num_dacs, num_adcs) \
{ \
.name = _name "-hifi", \
.playback = { \
.stream_name = "Playback", \
.channels_min = 2, \
.channels_max = (num_dacs) * 2, \
.rates = SNDRV_PCM_RATE_48000, \
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
}, \
.capture = { \
.stream_name = "Capture", \
.channels_min = 2, \
.channels_max = (num_adcs) * 2, \
.rates = SNDRV_PCM_RATE_48000, \
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
}, \
.ops = &ad1836_dai_ops, \
}
static struct snd_soc_dai_driver ad183x_dais[] = {
[AD1835] = AD183X_DAI("ad1835", 4, 1),
[AD1836] = AD183X_DAI("ad1836", 3, 2),
[AD1838] = AD183X_DAI("ad1838", 3, 1),
};
#ifdef CONFIG_PM
static int ad1836_suspend(struct snd_soc_codec *codec)
{
struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
/* reset clock control mode */
return regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_SERFMT_MASK, 0);
}
static int ad1836_resume(struct snd_soc_codec *codec)
{
struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
/* restore clock control mode */
return regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_SERFMT_MASK, AD1836_ADC_AUX);
}
#else
#define ad1836_suspend NULL
#define ad1836_resume NULL
#endif
static int ad1836_probe(struct snd_soc_codec *codec)
{
struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
struct snd_soc_dapm_context *dapm = &codec->dapm;
int num_dacs, num_adcs;
int ret = 0;
int i;
num_dacs = ad183x_dais[ad1836->type].playback.channels_max / 2;
num_adcs = ad183x_dais[ad1836->type].capture.channels_max / 2;
/* default setting for ad1836 */
/* de-emphasis: 48kHz, power-on dac */
regmap_write(ad1836->regmap, AD1836_DAC_CTRL1, 0x300);
/* unmute dac channels */
regmap_write(ad1836->regmap, AD1836_DAC_CTRL2, 0x0);
/* high-pass filter enable, power-on adc */
regmap_write(ad1836->regmap, AD1836_ADC_CTRL1, 0x100);
/* unmute adc channles, adc aux mode */
regmap_write(ad1836->regmap, AD1836_ADC_CTRL2, 0x180);
/* volume */
for (i = 1; i <= num_dacs; ++i) {
regmap_write(ad1836->regmap, AD1836_DAC_L_VOL(i), 0x3FF);
regmap_write(ad1836->regmap, AD1836_DAC_R_VOL(i), 0x3FF);
}
if (ad1836->type == AD1836) {
/* left/right diff:PGA/MUX */
regmap_write(ad1836->regmap, AD1836_ADC_CTRL3, 0x3A);
ret = snd_soc_add_codec_controls(codec, ad1836_controls,
ARRAY_SIZE(ad1836_controls));
if (ret)
return ret;
} else {
regmap_write(ad1836->regmap, AD1836_ADC_CTRL3, 0x00);
}
ret = snd_soc_add_codec_controls(codec, ad183x_dac_controls, num_dacs * 2);
if (ret)
return ret;
ret = snd_soc_add_codec_controls(codec, ad183x_adc_controls, num_adcs);
if (ret)
return ret;
ret = snd_soc_dapm_new_controls(dapm, ad183x_dac_dapm_widgets, num_dacs);
if (ret)
return ret;
ret = snd_soc_dapm_new_controls(dapm, ad183x_adc_dapm_widgets, num_adcs);
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(dapm, ad183x_dac_routes, num_dacs);
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(dapm, ad183x_adc_routes, num_adcs);
if (ret)
return ret;
return ret;
}
/* power down chip */
static int ad1836_remove(struct snd_soc_codec *codec)
{
struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
/* reset clock control mode */
return regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_SERFMT_MASK, 0);
}
static struct snd_soc_codec_driver soc_codec_dev_ad1836 = {
.probe = ad1836_probe,
.remove = ad1836_remove,
.suspend = ad1836_suspend,
.resume = ad1836_resume,
.controls = ad183x_controls,
.num_controls = ARRAY_SIZE(ad183x_controls),
.dapm_widgets = ad183x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad183x_dapm_widgets),
.dapm_routes = ad183x_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(ad183x_dapm_routes),
};
static const struct reg_default ad1836_reg_defaults[] = {
{ AD1836_DAC_CTRL1, 0x0000 },
{ AD1836_DAC_CTRL2, 0x0000 },
{ AD1836_DAC_L_VOL(0), 0x0000 },
{ AD1836_DAC_R_VOL(0), 0x0000 },
{ AD1836_DAC_L_VOL(1), 0x0000 },
{ AD1836_DAC_R_VOL(1), 0x0000 },
{ AD1836_DAC_L_VOL(2), 0x0000 },
{ AD1836_DAC_R_VOL(2), 0x0000 },
{ AD1836_DAC_L_VOL(3), 0x0000 },
{ AD1836_DAC_R_VOL(3), 0x0000 },
{ AD1836_ADC_CTRL1, 0x0000 },
{ AD1836_ADC_CTRL2, 0x0000 },
{ AD1836_ADC_CTRL3, 0x0000 },
};
static const struct regmap_config ad1836_regmap_config = {
.val_bits = 12,
.reg_bits = 4,
.read_flag_mask = 0x08,
.max_register = AD1836_ADC_CTRL3,
.reg_defaults = ad1836_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(ad1836_reg_defaults),
.cache_type = REGCACHE_RBTREE,
};
static int ad1836_spi_probe(struct spi_device *spi)
{
struct ad1836_priv *ad1836;
int ret;
ad1836 = devm_kzalloc(&spi->dev, sizeof(struct ad1836_priv),
GFP_KERNEL);
if (ad1836 == NULL)
return -ENOMEM;
ad1836->regmap = devm_regmap_init_spi(spi, &ad1836_regmap_config);
if (IS_ERR(ad1836->regmap))
return PTR_ERR(ad1836->regmap);
ad1836->type = spi_get_device_id(spi)->driver_data;
spi_set_drvdata(spi, ad1836);
ret = snd_soc_register_codec(&spi->dev,
&soc_codec_dev_ad1836, &ad183x_dais[ad1836->type], 1);
return ret;
}
static int ad1836_spi_remove(struct spi_device *spi)
{
snd_soc_unregister_codec(&spi->dev);
return 0;
}
static const struct spi_device_id ad1836_ids[] = {
{ "ad1835", AD1835 },
{ "ad1836", AD1836 },
{ "ad1837", AD1835 },
{ "ad1838", AD1838 },
{ "ad1839", AD1838 },
{ },
};
MODULE_DEVICE_TABLE(spi, ad1836_ids);
static struct spi_driver ad1836_spi_driver = {
.driver = {
.name = "ad1836",
.owner = THIS_MODULE,
},
.probe = ad1836_spi_probe,
.remove = ad1836_spi_remove,
.id_table = ad1836_ids,
};
module_spi_driver(ad1836_spi_driver);
MODULE_DESCRIPTION("ASoC ad1836 driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");

51
sound/soc/codecs/ad1836.h Normal file
View file

@ -0,0 +1,51 @@
/*
* Audio Codec driver supporting:
* AD1835A, AD1836, AD1837A, AD1838A, AD1839A
*
* Copyright 2009-2011 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#ifndef __AD1836_H__
#define __AD1836_H__
#define AD1836_DAC_CTRL1 0
#define AD1836_DAC_POWERDOWN 2
#define AD1836_DAC_SERFMT_MASK 0xE0
#define AD1836_DAC_SERFMT_PCK256 (0x4 << 5)
#define AD1836_DAC_SERFMT_PCK128 (0x5 << 5)
#define AD1836_DAC_WORD_LEN_MASK 0x18
#define AD1836_DAC_WORD_LEN_OFFSET 3
#define AD1836_DAC_CTRL2 1
/* These macros are one-based. So AD183X_MUTE_LEFT(1) will return the mute bit
* for the first ADC/DAC */
#define AD1836_MUTE_LEFT(x) (((x) * 2) - 2)
#define AD1836_MUTE_RIGHT(x) (((x) * 2) - 1)
#define AD1836_DAC_L_VOL(x) ((x) * 2)
#define AD1836_DAC_R_VOL(x) (1 + ((x) * 2))
#define AD1836_ADC_CTRL1 12
#define AD1836_ADC_POWERDOWN 7
#define AD1836_ADC_HIGHPASS_FILTER 8
#define AD1836_ADC_CTRL2 13
#define AD1836_ADC_WORD_LEN_MASK 0x30
#define AD1836_ADC_WORD_OFFSET 4
#define AD1836_ADC_SERFMT_MASK (7 << 6)
#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6)
#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6)
#define AD1836_ADC_AUX (0x6 << 6)
#define AD1836_ADC_CTRL3 14
#define AD1836_NUM_REGS 16
#define AD1836_WORD_LEN_24 0x0
#define AD1836_WORD_LEN_20 0x1
#define AD1836_WORD_LEN_16 0x2
#endif

View file

@ -0,0 +1,54 @@
/*
* AD1936/AD1937 audio driver
*
* Copyright 2014 Analog Devices Inc.
*
* Licensed under the GPL-2.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "ad193x.h"
static const struct i2c_device_id ad193x_id[] = {
{ "ad1936", 0 },
{ "ad1937", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ad193x_id);
static int ad193x_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct regmap_config config;
config = ad193x_regmap_config;
config.val_bits = 8;
config.reg_bits = 8;
return ad193x_probe(&client->dev, devm_regmap_init_i2c(client, &config));
}
static int ad193x_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
return 0;
}
static struct i2c_driver ad193x_i2c_driver = {
.driver = {
.name = "ad193x",
},
.probe = ad193x_i2c_probe,
.remove = ad193x_i2c_remove,
.id_table = ad193x_id,
};
module_i2c_driver(ad193x_i2c_driver);
MODULE_DESCRIPTION("ASoC AD1936/AD1937 audio CODEC driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,48 @@
/*
* AD1938/AD1939 audio driver
*
* Copyright 2014 Analog Devices Inc.
*
* Licensed under the GPL-2.
*/
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "ad193x.h"
static int ad193x_spi_probe(struct spi_device *spi)
{
struct regmap_config config;
config = ad193x_regmap_config;
config.val_bits = 8;
config.reg_bits = 16;
config.read_flag_mask = 0x09;
config.write_flag_mask = 0x08;
return ad193x_probe(&spi->dev, devm_regmap_init_spi(spi, &config));
}
static int ad193x_spi_remove(struct spi_device *spi)
{
snd_soc_unregister_codec(&spi->dev);
return 0;
}
static struct spi_driver ad193x_spi_driver = {
.driver = {
.name = "ad193x",
.owner = THIS_MODULE,
},
.probe = ad193x_spi_probe,
.remove = ad193x_spi_remove,
};
module_spi_driver(ad193x_spi_driver);
MODULE_DESCRIPTION("ASoC AD1938/AD1939 audio CODEC driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");

388
sound/soc/codecs/ad193x.c Normal file
View file

@ -0,0 +1,388 @@
/*
* AD193X Audio Codec driver supporting AD1936/7/8/9
*
* Copyright 2010 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "ad193x.h"
/* codec private data */
struct ad193x_priv {
struct regmap *regmap;
int sysclk;
};
/*
* AD193X volume/mute/de-emphasis etc. controls
*/
static const char * const ad193x_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"};
static SOC_ENUM_SINGLE_DECL(ad193x_deemp_enum, AD193X_DAC_CTRL2, 1,
ad193x_deemp);
static const DECLARE_TLV_DB_MINMAX(adau193x_tlv, -9563, 0);
static const struct snd_kcontrol_new ad193x_snd_controls[] = {
/* DAC volume control */
SOC_DOUBLE_R_TLV("DAC1 Volume", AD193X_DAC_L1_VOL,
AD193X_DAC_R1_VOL, 0, 0xFF, 1, adau193x_tlv),
SOC_DOUBLE_R_TLV("DAC2 Volume", AD193X_DAC_L2_VOL,
AD193X_DAC_R2_VOL, 0, 0xFF, 1, adau193x_tlv),
SOC_DOUBLE_R_TLV("DAC3 Volume", AD193X_DAC_L3_VOL,
AD193X_DAC_R3_VOL, 0, 0xFF, 1, adau193x_tlv),
SOC_DOUBLE_R_TLV("DAC4 Volume", AD193X_DAC_L4_VOL,
AD193X_DAC_R4_VOL, 0, 0xFF, 1, adau193x_tlv),
/* ADC switch control */
SOC_DOUBLE("ADC1 Switch", AD193X_ADC_CTRL0, AD193X_ADCL1_MUTE,
AD193X_ADCR1_MUTE, 1, 1),
SOC_DOUBLE("ADC2 Switch", AD193X_ADC_CTRL0, AD193X_ADCL2_MUTE,
AD193X_ADCR2_MUTE, 1, 1),
/* DAC switch control */
SOC_DOUBLE("DAC1 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL1_MUTE,
AD193X_DACR1_MUTE, 1, 1),
SOC_DOUBLE("DAC2 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL2_MUTE,
AD193X_DACR2_MUTE, 1, 1),
SOC_DOUBLE("DAC3 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL3_MUTE,
AD193X_DACR3_MUTE, 1, 1),
SOC_DOUBLE("DAC4 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL4_MUTE,
AD193X_DACR4_MUTE, 1, 1),
/* ADC high-pass filter */
SOC_SINGLE("ADC High Pass Filter Switch", AD193X_ADC_CTRL0,
AD193X_ADC_HIGHPASS_FILTER, 1, 0),
/* DAC de-emphasis */
SOC_ENUM("Playback Deemphasis", ad193x_deemp_enum),
};
static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", AD193X_DAC_CTRL0, 0, 1),
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0),
SND_SOC_DAPM_SUPPLY("SYSCLK", AD193X_PLL_CLK_CTRL0, 7, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
SND_SOC_DAPM_OUTPUT("DAC4OUT"),
SND_SOC_DAPM_INPUT("ADC1IN"),
SND_SOC_DAPM_INPUT("ADC2IN"),
};
static const struct snd_soc_dapm_route audio_paths[] = {
{ "DAC", NULL, "SYSCLK" },
{ "ADC", NULL, "SYSCLK" },
{ "DAC", NULL, "ADC_PWR" },
{ "ADC", NULL, "ADC_PWR" },
{ "DAC1OUT", NULL, "DAC" },
{ "DAC2OUT", NULL, "DAC" },
{ "DAC3OUT", NULL, "DAC" },
{ "DAC4OUT", NULL, "DAC" },
{ "ADC", NULL, "ADC1IN" },
{ "ADC", NULL, "ADC2IN" },
{ "SYSCLK", NULL, "PLL_PWR" },
};
/*
* DAI ops entries
*/
static int ad193x_mute(struct snd_soc_dai *dai, int mute)
{
struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(dai->codec);
if (mute)
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2,
AD193X_DAC_MASTER_MUTE,
AD193X_DAC_MASTER_MUTE);
else
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2,
AD193X_DAC_MASTER_MUTE, 0);
return 0;
}
static int ad193x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int width)
{
struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(dai->codec);
unsigned int channels;
switch (slots) {
case 2:
channels = AD193X_2_CHANNELS;
break;
case 4:
channels = AD193X_4_CHANNELS;
break;
case 8:
channels = AD193X_8_CHANNELS;
break;
case 16:
channels = AD193X_16_CHANNELS;
break;
default:
return -EINVAL;
}
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL1,
AD193X_DAC_CHAN_MASK, channels << AD193X_DAC_CHAN_SHFT);
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL2,
AD193X_ADC_CHAN_MASK, channels << AD193X_ADC_CHAN_SHFT);
return 0;
}
static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec_dai->codec);
unsigned int adc_serfmt = 0;
unsigned int adc_fmt = 0;
unsigned int dac_fmt = 0;
/* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S
* with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A)
*/
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
adc_serfmt |= AD193X_ADC_SERFMT_TDM;
break;
case SND_SOC_DAIFMT_DSP_A:
adc_serfmt |= AD193X_ADC_SERFMT_AUX;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
break;
case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */
adc_fmt |= AD193X_ADC_LEFT_HIGH;
dac_fmt |= AD193X_DAC_LEFT_HIGH;
break;
case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */
adc_fmt |= AD193X_ADC_BCLK_INV;
dac_fmt |= AD193X_DAC_BCLK_INV;
break;
case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */
adc_fmt |= AD193X_ADC_LEFT_HIGH;
adc_fmt |= AD193X_ADC_BCLK_INV;
dac_fmt |= AD193X_DAC_LEFT_HIGH;
dac_fmt |= AD193X_DAC_BCLK_INV;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */
adc_fmt |= AD193X_ADC_LCR_MASTER;
adc_fmt |= AD193X_ADC_BCLK_MASTER;
dac_fmt |= AD193X_DAC_LCR_MASTER;
dac_fmt |= AD193X_DAC_BCLK_MASTER;
break;
case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */
adc_fmt |= AD193X_ADC_LCR_MASTER;
dac_fmt |= AD193X_DAC_LCR_MASTER;
break;
case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */
adc_fmt |= AD193X_ADC_BCLK_MASTER;
dac_fmt |= AD193X_DAC_BCLK_MASTER;
break;
case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */
break;
default:
return -EINVAL;
}
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL1,
AD193X_ADC_SERFMT_MASK, adc_serfmt);
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL2,
AD193X_ADC_FMT_MASK, adc_fmt);
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL1,
AD193X_DAC_FMT_MASK, dac_fmt);
return 0;
}
static int ad193x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
switch (freq) {
case 12288000:
case 18432000:
case 24576000:
case 36864000:
ad193x->sysclk = freq;
return 0;
}
return -EINVAL;
}
static int ad193x_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
int word_len = 0, master_rate = 0;
struct snd_soc_codec *codec = dai->codec;
struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
/* bit size */
switch (params_width(params)) {
case 16:
word_len = 3;
break;
case 20:
word_len = 1;
break;
case 24:
case 32:
word_len = 0;
break;
}
switch (ad193x->sysclk) {
case 12288000:
master_rate = AD193X_PLL_INPUT_256;
break;
case 18432000:
master_rate = AD193X_PLL_INPUT_384;
break;
case 24576000:
master_rate = AD193X_PLL_INPUT_512;
break;
case 36864000:
master_rate = AD193X_PLL_INPUT_768;
break;
}
regmap_update_bits(ad193x->regmap, AD193X_PLL_CLK_CTRL0,
AD193X_PLL_INPUT_MASK, master_rate);
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2,
AD193X_DAC_WORD_LEN_MASK,
word_len << AD193X_DAC_WORD_LEN_SHFT);
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL1,
AD193X_ADC_WORD_LEN_MASK, word_len);
return 0;
}
static const struct snd_soc_dai_ops ad193x_dai_ops = {
.hw_params = ad193x_hw_params,
.digital_mute = ad193x_mute,
.set_tdm_slot = ad193x_set_tdm_slot,
.set_sysclk = ad193x_set_dai_sysclk,
.set_fmt = ad193x_set_dai_fmt,
};
/* codec DAI instance */
static struct snd_soc_dai_driver ad193x_dai = {
.name = "ad193x-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 4,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
},
.ops = &ad193x_dai_ops,
};
static int ad193x_codec_probe(struct snd_soc_codec *codec)
{
struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
/* default setting for ad193x */
/* unmute dac channels */
regmap_write(ad193x->regmap, AD193X_DAC_CHNL_MUTE, 0x0);
/* de-emphasis: 48kHz, powedown dac */
regmap_write(ad193x->regmap, AD193X_DAC_CTRL2, 0x1A);
/* powerdown dac, dac in tdm mode */
regmap_write(ad193x->regmap, AD193X_DAC_CTRL0, 0x41);
/* high-pass filter enable */
regmap_write(ad193x->regmap, AD193X_ADC_CTRL0, 0x3);
/* sata delay=1, adc aux mode */
regmap_write(ad193x->regmap, AD193X_ADC_CTRL1, 0x43);
/* pll input: mclki/xi */
regmap_write(ad193x->regmap, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */
regmap_write(ad193x->regmap, AD193X_PLL_CLK_CTRL1, 0x04);
return 0;
}
static struct snd_soc_codec_driver soc_codec_dev_ad193x = {
.probe = ad193x_codec_probe,
.controls = ad193x_snd_controls,
.num_controls = ARRAY_SIZE(ad193x_snd_controls),
.dapm_widgets = ad193x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad193x_dapm_widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
};
static bool adau193x_reg_volatile(struct device *dev, unsigned int reg)
{
return false;
}
const struct regmap_config ad193x_regmap_config = {
.max_register = AD193X_NUM_REGS - 1,
.volatile_reg = adau193x_reg_volatile,
};
EXPORT_SYMBOL_GPL(ad193x_regmap_config);
int ad193x_probe(struct device *dev, struct regmap *regmap)
{
struct ad193x_priv *ad193x;
if (IS_ERR(regmap))
return PTR_ERR(regmap);
ad193x = devm_kzalloc(dev, sizeof(*ad193x), GFP_KERNEL);
if (ad193x == NULL)
return -ENOMEM;
ad193x->regmap = regmap;
dev_set_drvdata(dev, ad193x);
return snd_soc_register_codec(dev, &soc_codec_dev_ad193x,
&ad193x_dai, 1);
}
EXPORT_SYMBOL_GPL(ad193x_probe);
MODULE_DESCRIPTION("ASoC ad193x driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");

92
sound/soc/codecs/ad193x.h Normal file
View file

@ -0,0 +1,92 @@
/*
* AD193X Audio Codec driver
*
* Copyright 2010 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#ifndef __AD193X_H__
#define __AD193X_H__
#include <linux/regmap.h>
struct device;
extern const struct regmap_config ad193x_regmap_config;
int ad193x_probe(struct device *dev, struct regmap *regmap);
#define AD193X_PLL_CLK_CTRL0 0x00
#define AD193X_PLL_POWERDOWN 0x01
#define AD193X_PLL_INPUT_MASK 0x6
#define AD193X_PLL_INPUT_256 (0 << 1)
#define AD193X_PLL_INPUT_384 (1 << 1)
#define AD193X_PLL_INPUT_512 (2 << 1)
#define AD193X_PLL_INPUT_768 (3 << 1)
#define AD193X_PLL_CLK_CTRL1 0x01
#define AD193X_DAC_CTRL0 0x02
#define AD193X_DAC_POWERDOWN 0x01
#define AD193X_DAC_SERFMT_MASK 0xC0
#define AD193X_DAC_SERFMT_STEREO (0 << 6)
#define AD193X_DAC_SERFMT_TDM (1 << 6)
#define AD193X_DAC_CTRL1 0x03
#define AD193X_DAC_CHAN_SHFT 1
#define AD193X_DAC_CHAN_MASK (3 << AD193X_DAC_CHAN_SHFT)
#define AD193X_DAC_LCR_MASTER (1 << 4)
#define AD193X_DAC_BCLK_MASTER (1 << 5)
#define AD193X_DAC_LEFT_HIGH (1 << 3)
#define AD193X_DAC_BCLK_INV (1 << 7)
#define AD193X_DAC_FMT_MASK (AD193X_DAC_LCR_MASTER | \
AD193X_DAC_BCLK_MASTER | AD193X_DAC_LEFT_HIGH | AD193X_DAC_BCLK_INV)
#define AD193X_DAC_CTRL2 0x04
#define AD193X_DAC_WORD_LEN_SHFT 3
#define AD193X_DAC_WORD_LEN_MASK 0x18
#define AD193X_DAC_MASTER_MUTE 1
#define AD193X_DAC_CHNL_MUTE 0x05
#define AD193X_DACL1_MUTE 0
#define AD193X_DACR1_MUTE 1
#define AD193X_DACL2_MUTE 2
#define AD193X_DACR2_MUTE 3
#define AD193X_DACL3_MUTE 4
#define AD193X_DACR3_MUTE 5
#define AD193X_DACL4_MUTE 6
#define AD193X_DACR4_MUTE 7
#define AD193X_DAC_L1_VOL 0x06
#define AD193X_DAC_R1_VOL 0x07
#define AD193X_DAC_L2_VOL 0x08
#define AD193X_DAC_R2_VOL 0x09
#define AD193X_DAC_L3_VOL 0x0a
#define AD193X_DAC_R3_VOL 0x0b
#define AD193X_DAC_L4_VOL 0x0c
#define AD193X_DAC_R4_VOL 0x0d
#define AD193X_ADC_CTRL0 0x0e
#define AD193X_ADC_POWERDOWN 0x01
#define AD193X_ADC_HIGHPASS_FILTER 1
#define AD193X_ADCL1_MUTE 2
#define AD193X_ADCR1_MUTE 3
#define AD193X_ADCL2_MUTE 4
#define AD193X_ADCR2_MUTE 5
#define AD193X_ADC_CTRL1 0x0f
#define AD193X_ADC_SERFMT_MASK 0x60
#define AD193X_ADC_SERFMT_STEREO (0 << 5)
#define AD193X_ADC_SERFMT_TDM (1 << 5)
#define AD193X_ADC_SERFMT_AUX (2 << 5)
#define AD193X_ADC_WORD_LEN_MASK 0x3
#define AD193X_ADC_CTRL2 0x10
#define AD193X_ADC_CHAN_SHFT 4
#define AD193X_ADC_CHAN_MASK (3 << AD193X_ADC_CHAN_SHFT)
#define AD193X_ADC_LCR_MASTER (1 << 3)
#define AD193X_ADC_BCLK_MASTER (1 << 6)
#define AD193X_ADC_LEFT_HIGH (1 << 2)
#define AD193X_ADC_BCLK_INV (1 << 1)
#define AD193X_ADC_FMT_MASK (AD193X_ADC_LCR_MASTER | \
AD193X_ADC_BCLK_MASTER | AD193X_ADC_LEFT_HIGH | AD193X_ADC_BCLK_INV)
#define AD193X_2_CHANNELS 0
#define AD193X_4_CHANNELS 1
#define AD193X_8_CHANNELS 2
#define AD193X_16_CHANNELS 3
#define AD193X_NUM_REGS 17
#endif

326
sound/soc/codecs/ad1980.c Normal file
View file

@ -0,0 +1,326 @@
/*
* ad1980.c -- ALSA Soc AD1980 codec support
*
* Copyright: Analog Device Inc.
* Author: Roy Huang <roy.huang@analog.com>
* Cliff Cai <cliff.cai@analog.com>
*
* 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.
*/
/*
* WARNING:
*
* Because Analog Devices Inc. discontinued the ad1980 sound chip since
* Sep. 2009, this ad1980 driver is not maintained, tested and supported
* by ADI now.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include "ad1980.h"
/*
* AD1980 register cache
*/
static const u16 ad1980_reg[] = {
0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */
0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */
0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */
0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */
0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */
0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */
0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */
0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */
0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */
0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */
0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */
};
static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line",
"Stereo Mix", "Mono Mix", "Phone"};
static SOC_ENUM_DOUBLE_DECL(ad1980_cap_src,
AC97_REC_SEL, 8, 0, ad1980_rec_sel);
static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = {
SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0),
SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1),
SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1),
SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1),
SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1),
SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0),
SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0),
SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1),
SOC_DOUBLE("Center/LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 0, 31, 1),
SOC_DOUBLE("Center/LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 7, 1, 1),
SOC_ENUM("Capture Source", ad1980_cap_src),
SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0),
};
static const struct snd_soc_dapm_widget ad1980_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("MIC1"),
SND_SOC_DAPM_INPUT("MIC2"),
SND_SOC_DAPM_INPUT("CD_L"),
SND_SOC_DAPM_INPUT("CD_R"),
SND_SOC_DAPM_INPUT("AUX_L"),
SND_SOC_DAPM_INPUT("AUX_R"),
SND_SOC_DAPM_INPUT("LINE_IN_L"),
SND_SOC_DAPM_INPUT("LINE_IN_R"),
SND_SOC_DAPM_OUTPUT("LFE_OUT"),
SND_SOC_DAPM_OUTPUT("CENTER_OUT"),
SND_SOC_DAPM_OUTPUT("LINE_OUT_L"),
SND_SOC_DAPM_OUTPUT("LINE_OUT_R"),
SND_SOC_DAPM_OUTPUT("MONO_OUT"),
SND_SOC_DAPM_OUTPUT("HP_OUT_L"),
SND_SOC_DAPM_OUTPUT("HP_OUT_R"),
};
static const struct snd_soc_dapm_route ad1980_dapm_routes[] = {
{ "Capture", NULL, "MIC1" },
{ "Capture", NULL, "MIC2" },
{ "Capture", NULL, "CD_L" },
{ "Capture", NULL, "CD_R" },
{ "Capture", NULL, "AUX_L" },
{ "Capture", NULL, "AUX_R" },
{ "Capture", NULL, "LINE_IN_L" },
{ "Capture", NULL, "LINE_IN_R" },
{ "LFE_OUT", NULL, "Playback" },
{ "CENTER_OUT", NULL, "Playback" },
{ "LINE_OUT_L", NULL, "Playback" },
{ "LINE_OUT_R", NULL, "Playback" },
{ "MONO_OUT", NULL, "Playback" },
{ "HP_OUT_L", NULL, "Playback" },
{ "HP_OUT_R", NULL, "Playback" },
};
static unsigned int ac97_read(struct snd_soc_codec *codec,
unsigned int reg)
{
u16 *cache = codec->reg_cache;
switch (reg) {
case AC97_RESET:
case AC97_INT_PAGING:
case AC97_POWERDOWN:
case AC97_EXTENDED_STATUS:
case AC97_VENDOR_ID1:
case AC97_VENDOR_ID2:
return soc_ac97_ops->read(codec->ac97, reg);
default:
reg = reg >> 1;
if (reg >= ARRAY_SIZE(ad1980_reg))
return -EINVAL;
return cache[reg];
}
}
static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
unsigned int val)
{
u16 *cache = codec->reg_cache;
soc_ac97_ops->write(codec->ac97, reg, val);
reg = reg >> 1;
if (reg < ARRAY_SIZE(ad1980_reg))
cache[reg] = val;
return 0;
}
static struct snd_soc_dai_driver ad1980_dai = {
.name = "ad1980-hifi",
.ac97_control = 1,
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 6,
.rates = SNDRV_PCM_RATE_48000,
.formats = SND_SOC_STD_AC97_FMTS, },
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.formats = SND_SOC_STD_AC97_FMTS, },
};
static int ad1980_reset(struct snd_soc_codec *codec, int try_warm)
{
unsigned int retry_cnt = 0;
do {
if (try_warm && soc_ac97_ops->warm_reset) {
soc_ac97_ops->warm_reset(codec->ac97);
if (ac97_read(codec, AC97_RESET) == 0x0090)
return 1;
}
soc_ac97_ops->reset(codec->ac97);
/*
* Set bit 16slot in register 74h, then every slot will has only
* 16 bits. This command is sent out in 20bit mode, in which
* case the first nibble of data is eaten by the addr. (Tag is
* always 16 bit)
*/
ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900);
if (ac97_read(codec, AC97_RESET) == 0x0090)
return 0;
} while (retry_cnt++ < 10);
printk(KERN_ERR "AD1980 AC97 reset failed\n");
return -EIO;
}
static int ad1980_soc_probe(struct snd_soc_codec *codec)
{
int ret;
u16 vendor_id2;
u16 ext_status;
printk(KERN_INFO "AD1980 SoC Audio Codec\n");
ret = snd_soc_new_ac97_codec(codec, soc_ac97_ops, 0);
if (ret < 0) {
printk(KERN_ERR "ad1980: failed to register AC97 codec\n");
return ret;
}
ret = ad1980_reset(codec, 0);
if (ret < 0) {
printk(KERN_ERR "Failed to reset AD1980: AC97 link error\n");
goto reset_err;
}
/* Read out vendor ID to make sure it is ad1980 */
if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144) {
ret = -ENODEV;
goto reset_err;
}
vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2);
if (vendor_id2 != 0x5370) {
if (vendor_id2 != 0x5374) {
ret = -ENODEV;
goto reset_err;
} else {
printk(KERN_WARNING "ad1980: "
"Found AD1981 - only 2/2 IN/OUT Channels "
"supported\n");
}
}
/* unmute captures and playbacks volume */
ac97_write(codec, AC97_MASTER, 0x0000);
ac97_write(codec, AC97_PCM, 0x0000);
ac97_write(codec, AC97_REC_GAIN, 0x0000);
ac97_write(codec, AC97_CENTER_LFE_MASTER, 0x0000);
ac97_write(codec, AC97_SURROUND_MASTER, 0x0000);
/*power on LFE/CENTER/Surround DACs*/
ext_status = ac97_read(codec, AC97_EXTENDED_STATUS);
ac97_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800);
snd_soc_add_codec_controls(codec, ad1980_snd_ac97_controls,
ARRAY_SIZE(ad1980_snd_ac97_controls));
return 0;
reset_err:
snd_soc_free_ac97_codec(codec);
return ret;
}
static int ad1980_soc_remove(struct snd_soc_codec *codec)
{
snd_soc_free_ac97_codec(codec);
return 0;
}
static struct snd_soc_codec_driver soc_codec_dev_ad1980 = {
.probe = ad1980_soc_probe,
.remove = ad1980_soc_remove,
.reg_cache_size = ARRAY_SIZE(ad1980_reg),
.reg_word_size = sizeof(u16),
.reg_cache_default = ad1980_reg,
.reg_cache_step = 2,
.write = ac97_write,
.read = ac97_read,
.dapm_widgets = ad1980_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad1980_dapm_widgets),
.dapm_routes = ad1980_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(ad1980_dapm_routes),
};
static int ad1980_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev,
&soc_codec_dev_ad1980, &ad1980_dai, 1);
}
static int ad1980_remove(struct platform_device *pdev)
{
snd_soc_unregister_codec(&pdev->dev);
return 0;
}
static struct platform_driver ad1980_codec_driver = {
.driver = {
.name = "ad1980",
.owner = THIS_MODULE,
},
.probe = ad1980_probe,
.remove = ad1980_remove,
};
module_platform_driver(ad1980_codec_driver);
MODULE_DESCRIPTION("ASoC ad1980 driver (Obsolete)");
MODULE_AUTHOR("Roy Huang, Cliff Cai");
MODULE_LICENSE("GPL");

26
sound/soc/codecs/ad1980.h Normal file
View file

@ -0,0 +1,26 @@
/*
* ad1980.h -- ad1980 Soc Audio driver
*
* WARNING:
*
* Because Analog Devices Inc. discontinued the ad1980 sound chip since
* Sep. 2009, this ad1980 driver is not maintained, tested and supported
* by ADI now.
*/
#ifndef _AD1980_H
#define _AD1980_H
/* Bit definition of Power-Down Control/Status Register */
#define ADC 0x0001
#define DAC 0x0002
#define ANL 0x0004
#define REF 0x0008
#define PR0 0x0100
#define PR1 0x0200
#define PR2 0x0400
#define PR3 0x0800
#define PR4 0x1000
#define PR5 0x2000
#define PR6 0x4000
#endif

View file

@ -0,0 +1,90 @@
/*
* ad73311.c -- ALSA Soc AD73311 codec support
*
* Copyright: Analog Device Inc.
* Author: Cliff Cai <cliff.cai@analog.com>
*
* 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/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include "ad73311.h"
static const struct snd_soc_dapm_widget ad73311_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("VINP"),
SND_SOC_DAPM_INPUT("VINN"),
SND_SOC_DAPM_OUTPUT("VOUTN"),
SND_SOC_DAPM_OUTPUT("VOUTP"),
};
static const struct snd_soc_dapm_route ad73311_dapm_routes[] = {
{ "Capture", NULL, "VINP" },
{ "Capture", NULL, "VINN" },
{ "VOUTN", NULL, "Playback" },
{ "VOUTP", NULL, "Playback" },
};
static struct snd_soc_dai_driver ad73311_dai = {
.name = "ad73311-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
};
static struct snd_soc_codec_driver soc_codec_dev_ad73311 = {
.dapm_widgets = ad73311_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad73311_dapm_widgets),
.dapm_routes = ad73311_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(ad73311_dapm_routes),
};
static int ad73311_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev,
&soc_codec_dev_ad73311, &ad73311_dai, 1);
}
static int ad73311_remove(struct platform_device *pdev)
{
snd_soc_unregister_codec(&pdev->dev);
return 0;
}
static struct platform_driver ad73311_codec_driver = {
.driver = {
.name = "ad73311",
.owner = THIS_MODULE,
},
.probe = ad73311_probe,
.remove = ad73311_remove,
};
module_platform_driver(ad73311_codec_driver);
MODULE_DESCRIPTION("ASoC ad73311 driver");
MODULE_AUTHOR("Cliff Cai ");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,88 @@
/*
* File: sound/soc/codec/ad73311.h
* Based on:
* Author: Cliff Cai <cliff.cai@analog.com>
*
* Created: Thur Sep 25, 2008
* Description: definitions for AD73311 registers
*
*
* Modified:
* Copyright 2006 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __AD73311_H__
#define __AD73311_H__
#define AD_CONTROL 0x8000
#define AD_DATA 0x0000
#define AD_READ 0x4000
#define AD_WRITE 0x0000
/* Control register A */
#define CTRL_REG_A (0 << 8)
#define REGA_MODE_PRO 0x00
#define REGA_MODE_DATA 0x01
#define REGA_MODE_MIXED 0x03
#define REGA_DLB 0x04
#define REGA_SLB 0x08
#define REGA_DEVC(x) ((x & 0x7) << 4)
#define REGA_RESET 0x80
/* Control register B */
#define CTRL_REG_B (1 << 8)
#define REGB_DIRATE(x) (x & 0x3)
#define REGB_SCDIV(x) ((x & 0x3) << 2)
#define REGB_MCDIV(x) ((x & 0x7) << 4)
#define REGB_CEE (1 << 7)
/* Control register C */
#define CTRL_REG_C (2 << 8)
#define REGC_PUDEV (1 << 0)
#define REGC_PUADC (1 << 3)
#define REGC_PUDAC (1 << 4)
#define REGC_PUREF (1 << 5)
#define REGC_REFUSE (1 << 6)
/* Control register D */
#define CTRL_REG_D (3 << 8)
#define REGD_IGS(x) (x & 0x7)
#define REGD_RMOD (1 << 3)
#define REGD_OGS(x) ((x & 0x7) << 4)
#define REGD_MUTE (1 << 7)
/* Control register E */
#define CTRL_REG_E (4 << 8)
#define REGE_DA(x) (x & 0x1f)
#define REGE_IBYP (1 << 5)
/* Control register F */
#define CTRL_REG_F (5 << 8)
#define REGF_SEEN (1 << 5)
#define REGF_INV (1 << 6)
#define REGF_ALB (1 << 7)
#endif

1549
sound/soc/codecs/adau1373.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
#ifndef __ADAU1373_H__
#define __ADAU1373_H__
enum adau1373_pll_src {
ADAU1373_PLL_SRC_MCLK1 = 0,
ADAU1373_PLL_SRC_BCLK1 = 1,
ADAU1373_PLL_SRC_BCLK2 = 2,
ADAU1373_PLL_SRC_BCLK3 = 3,
ADAU1373_PLL_SRC_LRCLK1 = 4,
ADAU1373_PLL_SRC_LRCLK2 = 5,
ADAU1373_PLL_SRC_LRCLK3 = 6,
ADAU1373_PLL_SRC_GPIO1 = 7,
ADAU1373_PLL_SRC_GPIO2 = 8,
ADAU1373_PLL_SRC_GPIO3 = 9,
ADAU1373_PLL_SRC_GPIO4 = 10,
ADAU1373_PLL_SRC_MCLK2 = 11,
};
enum adau1373_pll {
ADAU1373_PLL1 = 0,
ADAU1373_PLL2 = 1,
};
enum adau1373_clk_src {
ADAU1373_CLK_SRC_PLL1 = 0,
ADAU1373_CLK_SRC_PLL2 = 1,
};
#endif

761
sound/soc/codecs/adau1701.c Normal file
View file

@ -0,0 +1,761 @@
/*
* Driver for ADAU1701 SigmaDSP processor
*
* Copyright 2011 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
* based on an inital version by Cliff Cai <cliff.cai@analog.com>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "sigmadsp.h"
#include "adau1701.h"
#define ADAU1701_DSPCTRL 0x081c
#define ADAU1701_SEROCTL 0x081e
#define ADAU1701_SERICTL 0x081f
#define ADAU1701_AUXNPOW 0x0822
#define ADAU1701_PINCONF_0 0x0820
#define ADAU1701_PINCONF_1 0x0821
#define ADAU1701_AUXNPOW 0x0822
#define ADAU1701_OSCIPOW 0x0826
#define ADAU1701_DACSET 0x0827
#define ADAU1701_MAX_REGISTER 0x0828
#define ADAU1701_DSPCTRL_CR (1 << 2)
#define ADAU1701_DSPCTRL_DAM (1 << 3)
#define ADAU1701_DSPCTRL_ADM (1 << 4)
#define ADAU1701_DSPCTRL_SR_48 0x00
#define ADAU1701_DSPCTRL_SR_96 0x01
#define ADAU1701_DSPCTRL_SR_192 0x02
#define ADAU1701_DSPCTRL_SR_MASK 0x03
#define ADAU1701_SEROCTL_INV_LRCLK 0x2000
#define ADAU1701_SEROCTL_INV_BCLK 0x1000
#define ADAU1701_SEROCTL_MASTER 0x0800
#define ADAU1701_SEROCTL_OBF16 0x0000
#define ADAU1701_SEROCTL_OBF8 0x0200
#define ADAU1701_SEROCTL_OBF4 0x0400
#define ADAU1701_SEROCTL_OBF2 0x0600
#define ADAU1701_SEROCTL_OBF_MASK 0x0600
#define ADAU1701_SEROCTL_OLF1024 0x0000
#define ADAU1701_SEROCTL_OLF512 0x0080
#define ADAU1701_SEROCTL_OLF256 0x0100
#define ADAU1701_SEROCTL_OLF_MASK 0x0180
#define ADAU1701_SEROCTL_MSB_DEALY1 0x0000
#define ADAU1701_SEROCTL_MSB_DEALY0 0x0004
#define ADAU1701_SEROCTL_MSB_DEALY8 0x0008
#define ADAU1701_SEROCTL_MSB_DEALY12 0x000c
#define ADAU1701_SEROCTL_MSB_DEALY16 0x0010
#define ADAU1701_SEROCTL_MSB_DEALY_MASK 0x001c
#define ADAU1701_SEROCTL_WORD_LEN_24 0x0000
#define ADAU1701_SEROCTL_WORD_LEN_20 0x0001
#define ADAU1701_SEROCTL_WORD_LEN_16 0x0002
#define ADAU1701_SEROCTL_WORD_LEN_MASK 0x0003
#define ADAU1701_AUXNPOW_VBPD 0x40
#define ADAU1701_AUXNPOW_VRPD 0x20
#define ADAU1701_SERICTL_I2S 0
#define ADAU1701_SERICTL_LEFTJ 1
#define ADAU1701_SERICTL_TDM 2
#define ADAU1701_SERICTL_RIGHTJ_24 3
#define ADAU1701_SERICTL_RIGHTJ_20 4
#define ADAU1701_SERICTL_RIGHTJ_18 5
#define ADAU1701_SERICTL_RIGHTJ_16 6
#define ADAU1701_SERICTL_MODE_MASK 7
#define ADAU1701_SERICTL_INV_BCLK BIT(3)
#define ADAU1701_SERICTL_INV_LRCLK BIT(4)
#define ADAU1701_OSCIPOW_OPD 0x04
#define ADAU1701_DACSET_DACINIT 1
#define ADAU1707_CLKDIV_UNSET (-1U)
#define ADAU1701_FIRMWARE "adau1701.bin"
struct adau1701 {
int gpio_nreset;
int gpio_pll_mode[2];
unsigned int dai_fmt;
unsigned int pll_clkdiv;
unsigned int sysclk;
struct regmap *regmap;
u8 pin_config[12];
};
static const struct snd_kcontrol_new adau1701_controls[] = {
SOC_SINGLE("Master Capture Switch", ADAU1701_DSPCTRL, 4, 1, 0),
};
static const struct snd_soc_dapm_widget adau1701_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC0", "Playback", ADAU1701_AUXNPOW, 3, 1),
SND_SOC_DAPM_DAC("DAC1", "Playback", ADAU1701_AUXNPOW, 2, 1),
SND_SOC_DAPM_DAC("DAC2", "Playback", ADAU1701_AUXNPOW, 1, 1),
SND_SOC_DAPM_DAC("DAC3", "Playback", ADAU1701_AUXNPOW, 0, 1),
SND_SOC_DAPM_ADC("ADC", "Capture", ADAU1701_AUXNPOW, 7, 1),
SND_SOC_DAPM_OUTPUT("OUT0"),
SND_SOC_DAPM_OUTPUT("OUT1"),
SND_SOC_DAPM_OUTPUT("OUT2"),
SND_SOC_DAPM_OUTPUT("OUT3"),
SND_SOC_DAPM_INPUT("IN0"),
SND_SOC_DAPM_INPUT("IN1"),
};
static const struct snd_soc_dapm_route adau1701_dapm_routes[] = {
{ "OUT0", NULL, "DAC0" },
{ "OUT1", NULL, "DAC1" },
{ "OUT2", NULL, "DAC2" },
{ "OUT3", NULL, "DAC3" },
{ "ADC", NULL, "IN0" },
{ "ADC", NULL, "IN1" },
};
static unsigned int adau1701_register_size(struct device *dev,
unsigned int reg)
{
switch (reg) {
case ADAU1701_PINCONF_0:
case ADAU1701_PINCONF_1:
return 3;
case ADAU1701_DSPCTRL:
case ADAU1701_SEROCTL:
case ADAU1701_AUXNPOW:
case ADAU1701_OSCIPOW:
case ADAU1701_DACSET:
return 2;
case ADAU1701_SERICTL:
return 1;
}
dev_err(dev, "Unsupported register address: %d\n", reg);
return 0;
}
static bool adau1701_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADAU1701_DACSET:
return true;
default:
return false;
}
}
static int adau1701_reg_write(void *context, unsigned int reg,
unsigned int value)
{
struct i2c_client *client = context;
unsigned int i;
unsigned int size;
uint8_t buf[5];
int ret;
size = adau1701_register_size(&client->dev, reg);
if (size == 0)
return -EINVAL;
buf[0] = reg >> 8;
buf[1] = reg & 0xff;
for (i = size + 1; i >= 2; --i) {
buf[i] = value;
value >>= 8;
}
ret = i2c_master_send(client, buf, size + 2);
if (ret == size + 2)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
static int adau1701_reg_read(void *context, unsigned int reg,
unsigned int *value)
{
int ret;
unsigned int i;
unsigned int size;
uint8_t send_buf[2], recv_buf[3];
struct i2c_client *client = context;
struct i2c_msg msgs[2];
size = adau1701_register_size(&client->dev, reg);
if (size == 0)
return -EINVAL;
send_buf[0] = reg >> 8;
send_buf[1] = reg & 0xff;
msgs[0].addr = client->addr;
msgs[0].len = sizeof(send_buf);
msgs[0].buf = send_buf;
msgs[0].flags = 0;
msgs[1].addr = client->addr;
msgs[1].len = size;
msgs[1].buf = recv_buf;
msgs[1].flags = I2C_M_RD;
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret < 0)
return ret;
else if (ret != ARRAY_SIZE(msgs))
return -EIO;
*value = 0;
for (i = 0; i < size; i++) {
*value <<= 8;
*value |= recv_buf[i];
}
return 0;
}
static int adau1701_reset(struct snd_soc_codec *codec, unsigned int clkdiv)
{
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
struct i2c_client *client = to_i2c_client(codec->dev);
int ret;
if (clkdiv != ADAU1707_CLKDIV_UNSET &&
gpio_is_valid(adau1701->gpio_pll_mode[0]) &&
gpio_is_valid(adau1701->gpio_pll_mode[1])) {
switch (clkdiv) {
case 64:
gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 0);
gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 0);
break;
case 256:
gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 0);
gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 1);
break;
case 384:
gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 1);
gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 0);
break;
case 0: /* fallback */
case 512:
gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 1);
gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 1);
break;
}
}
adau1701->pll_clkdiv = clkdiv;
if (gpio_is_valid(adau1701->gpio_nreset)) {
gpio_set_value_cansleep(adau1701->gpio_nreset, 0);
/* minimum reset time is 20ns */
udelay(1);
gpio_set_value_cansleep(adau1701->gpio_nreset, 1);
/* power-up time may be as long as 85ms */
mdelay(85);
}
/*
* Postpone the firmware download to a point in time when we
* know the correct PLL setup
*/
if (clkdiv != ADAU1707_CLKDIV_UNSET) {
ret = process_sigma_firmware(client, ADAU1701_FIRMWARE);
if (ret) {
dev_warn(codec->dev, "Failed to load firmware\n");
return ret;
}
}
regmap_write(adau1701->regmap, ADAU1701_DACSET, ADAU1701_DACSET_DACINIT);
regmap_write(adau1701->regmap, ADAU1701_DSPCTRL, ADAU1701_DSPCTRL_CR);
regcache_mark_dirty(adau1701->regmap);
regcache_sync(adau1701->regmap);
return 0;
}
static int adau1701_set_capture_pcm_format(struct snd_soc_codec *codec,
struct snd_pcm_hw_params *params)
{
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
unsigned int mask = ADAU1701_SEROCTL_WORD_LEN_MASK;
unsigned int val;
switch (params_width(params)) {
case 16:
val = ADAU1701_SEROCTL_WORD_LEN_16;
break;
case 20:
val = ADAU1701_SEROCTL_WORD_LEN_20;
break;
case 24:
val = ADAU1701_SEROCTL_WORD_LEN_24;
break;
default:
return -EINVAL;
}
if (adau1701->dai_fmt == SND_SOC_DAIFMT_RIGHT_J) {
switch (params_width(params)) {
case 16:
val |= ADAU1701_SEROCTL_MSB_DEALY16;
break;
case 20:
val |= ADAU1701_SEROCTL_MSB_DEALY12;
break;
case 24:
val |= ADAU1701_SEROCTL_MSB_DEALY8;
break;
}
mask |= ADAU1701_SEROCTL_MSB_DEALY_MASK;
}
regmap_update_bits(adau1701->regmap, ADAU1701_SEROCTL, mask, val);
return 0;
}
static int adau1701_set_playback_pcm_format(struct snd_soc_codec *codec,
struct snd_pcm_hw_params *params)
{
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
unsigned int val;
if (adau1701->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
return 0;
switch (params_width(params)) {
case 16:
val = ADAU1701_SERICTL_RIGHTJ_16;
break;
case 20:
val = ADAU1701_SERICTL_RIGHTJ_20;
break;
case 24:
val = ADAU1701_SERICTL_RIGHTJ_24;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1701->regmap, ADAU1701_SERICTL,
ADAU1701_SERICTL_MODE_MASK, val);
return 0;
}
static int adau1701_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct snd_soc_codec *codec = dai->codec;
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
unsigned int clkdiv = adau1701->sysclk / params_rate(params);
unsigned int val;
int ret;
/*
* If the mclk/lrclk ratio changes, the chip needs updated PLL
* mode GPIO settings, and a full reset cycle, including a new
* firmware upload.
*/
if (clkdiv != adau1701->pll_clkdiv) {
ret = adau1701_reset(codec, clkdiv);
if (ret < 0)
return ret;
}
switch (params_rate(params)) {
case 192000:
val = ADAU1701_DSPCTRL_SR_192;
break;
case 96000:
val = ADAU1701_DSPCTRL_SR_96;
break;
case 48000:
val = ADAU1701_DSPCTRL_SR_48;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL,
ADAU1701_DSPCTRL_SR_MASK, val);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return adau1701_set_playback_pcm_format(codec, params);
else
return adau1701_set_capture_pcm_format(codec, params);
}
static int adau1701_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
unsigned int serictl = 0x00, seroctl = 0x00;
bool invert_lrclk;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
/* master, 64-bits per sample, 1 frame per sample */
seroctl |= ADAU1701_SEROCTL_MASTER | ADAU1701_SEROCTL_OBF16
| ADAU1701_SEROCTL_OLF1024;
break;
case SND_SOC_DAIFMT_CBS_CFS:
break;
default:
return -EINVAL;
}
/* clock inversion */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
invert_lrclk = false;
break;
case SND_SOC_DAIFMT_NB_IF:
invert_lrclk = true;
break;
case SND_SOC_DAIFMT_IB_NF:
invert_lrclk = false;
serictl |= ADAU1701_SERICTL_INV_BCLK;
seroctl |= ADAU1701_SEROCTL_INV_BCLK;
break;
case SND_SOC_DAIFMT_IB_IF:
invert_lrclk = true;
serictl |= ADAU1701_SERICTL_INV_BCLK;
seroctl |= ADAU1701_SEROCTL_INV_BCLK;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
break;
case SND_SOC_DAIFMT_LEFT_J:
serictl |= ADAU1701_SERICTL_LEFTJ;
seroctl |= ADAU1701_SEROCTL_MSB_DEALY0;
invert_lrclk = !invert_lrclk;
break;
case SND_SOC_DAIFMT_RIGHT_J:
serictl |= ADAU1701_SERICTL_RIGHTJ_24;
seroctl |= ADAU1701_SEROCTL_MSB_DEALY8;
invert_lrclk = !invert_lrclk;
break;
default:
return -EINVAL;
}
if (invert_lrclk) {
seroctl |= ADAU1701_SEROCTL_INV_LRCLK;
serictl |= ADAU1701_SERICTL_INV_LRCLK;
}
adau1701->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
regmap_write(adau1701->regmap, ADAU1701_SERICTL, serictl);
regmap_update_bits(adau1701->regmap, ADAU1701_SEROCTL,
~ADAU1701_SEROCTL_WORD_LEN_MASK, seroctl);
return 0;
}
static int adau1701_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
unsigned int mask = ADAU1701_AUXNPOW_VBPD | ADAU1701_AUXNPOW_VRPD;
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
/* Enable VREF and VREF buffer */
regmap_update_bits(adau1701->regmap,
ADAU1701_AUXNPOW, mask, 0x00);
break;
case SND_SOC_BIAS_OFF:
/* Disable VREF and VREF buffer */
regmap_update_bits(adau1701->regmap,
ADAU1701_AUXNPOW, mask, mask);
break;
}
codec->dapm.bias_level = level;
return 0;
}
static int adau1701_digital_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
unsigned int mask = ADAU1701_DSPCTRL_DAM;
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
unsigned int val;
if (mute)
val = 0;
else
val = mask;
regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL, mask, val);
return 0;
}
static int adau1701_set_sysclk(struct snd_soc_codec *codec, int clk_id,
int source, unsigned int freq, int dir)
{
unsigned int val;
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
switch (clk_id) {
case ADAU1701_CLK_SRC_OSC:
val = 0x0;
break;
case ADAU1701_CLK_SRC_MCLK:
val = ADAU1701_OSCIPOW_OPD;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1701->regmap, ADAU1701_OSCIPOW,
ADAU1701_OSCIPOW_OPD, val);
adau1701->sysclk = freq;
return 0;
}
#define ADAU1701_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \
SNDRV_PCM_RATE_192000)
#define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE)
static const struct snd_soc_dai_ops adau1701_dai_ops = {
.set_fmt = adau1701_set_dai_fmt,
.hw_params = adau1701_hw_params,
.digital_mute = adau1701_digital_mute,
};
static struct snd_soc_dai_driver adau1701_dai = {
.name = "adau1701",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = ADAU1701_RATES,
.formats = ADAU1701_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 8,
.rates = ADAU1701_RATES,
.formats = ADAU1701_FORMATS,
},
.ops = &adau1701_dai_ops,
.symmetric_rates = 1,
};
#ifdef CONFIG_OF
static const struct of_device_id adau1701_dt_ids[] = {
{ .compatible = "adi,adau1701", },
{ }
};
MODULE_DEVICE_TABLE(of, adau1701_dt_ids);
#endif
static int adau1701_probe(struct snd_soc_codec *codec)
{
int i, ret;
unsigned int val;
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
/*
* Let the pll_clkdiv variable default to something that won't happen
* at runtime. That way, we can postpone the firmware download from
* adau1701_reset() to a point in time when we know the correct PLL
* mode parameters.
*/
adau1701->pll_clkdiv = ADAU1707_CLKDIV_UNSET;
/* initalize with pre-configured pll mode settings */
ret = adau1701_reset(codec, adau1701->pll_clkdiv);
if (ret < 0)
return ret;
/* set up pin config */
val = 0;
for (i = 0; i < 6; i++)
val |= adau1701->pin_config[i] << (i * 4);
regmap_write(adau1701->regmap, ADAU1701_PINCONF_0, val);
val = 0;
for (i = 0; i < 6; i++)
val |= adau1701->pin_config[i + 6] << (i * 4);
regmap_write(adau1701->regmap, ADAU1701_PINCONF_1, val);
return 0;
}
static struct snd_soc_codec_driver adau1701_codec_drv = {
.probe = adau1701_probe,
.set_bias_level = adau1701_set_bias_level,
.idle_bias_off = true,
.controls = adau1701_controls,
.num_controls = ARRAY_SIZE(adau1701_controls),
.dapm_widgets = adau1701_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1701_dapm_widgets),
.dapm_routes = adau1701_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1701_dapm_routes),
.set_sysclk = adau1701_set_sysclk,
};
static const struct regmap_config adau1701_regmap = {
.reg_bits = 16,
.val_bits = 32,
.max_register = ADAU1701_MAX_REGISTER,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = adau1701_volatile_reg,
.reg_write = adau1701_reg_write,
.reg_read = adau1701_reg_read,
};
static int adau1701_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct adau1701 *adau1701;
struct device *dev = &client->dev;
int gpio_nreset = -EINVAL;
int gpio_pll_mode[2] = { -EINVAL, -EINVAL };
int ret;
adau1701 = devm_kzalloc(dev, sizeof(*adau1701), GFP_KERNEL);
if (!adau1701)
return -ENOMEM;
adau1701->regmap = devm_regmap_init(dev, NULL, client,
&adau1701_regmap);
if (IS_ERR(adau1701->regmap))
return PTR_ERR(adau1701->regmap);
if (dev->of_node) {
gpio_nreset = of_get_named_gpio(dev->of_node, "reset-gpio", 0);
if (gpio_nreset < 0 && gpio_nreset != -ENOENT)
return gpio_nreset;
gpio_pll_mode[0] = of_get_named_gpio(dev->of_node,
"adi,pll-mode-gpios", 0);
if (gpio_pll_mode[0] < 0 && gpio_pll_mode[0] != -ENOENT)
return gpio_pll_mode[0];
gpio_pll_mode[1] = of_get_named_gpio(dev->of_node,
"adi,pll-mode-gpios", 1);
if (gpio_pll_mode[1] < 0 && gpio_pll_mode[1] != -ENOENT)
return gpio_pll_mode[1];
of_property_read_u32(dev->of_node, "adi,pll-clkdiv",
&adau1701->pll_clkdiv);
of_property_read_u8_array(dev->of_node, "adi,pin-config",
adau1701->pin_config,
ARRAY_SIZE(adau1701->pin_config));
}
if (gpio_is_valid(gpio_nreset)) {
ret = devm_gpio_request_one(dev, gpio_nreset, GPIOF_OUT_INIT_LOW,
"ADAU1701 Reset");
if (ret < 0)
return ret;
}
if (gpio_is_valid(gpio_pll_mode[0]) &&
gpio_is_valid(gpio_pll_mode[1])) {
ret = devm_gpio_request_one(dev, gpio_pll_mode[0],
GPIOF_OUT_INIT_LOW,
"ADAU1701 PLL mode 0");
if (ret < 0)
return ret;
ret = devm_gpio_request_one(dev, gpio_pll_mode[1],
GPIOF_OUT_INIT_LOW,
"ADAU1701 PLL mode 1");
if (ret < 0)
return ret;
}
adau1701->gpio_nreset = gpio_nreset;
adau1701->gpio_pll_mode[0] = gpio_pll_mode[0];
adau1701->gpio_pll_mode[1] = gpio_pll_mode[1];
i2c_set_clientdata(client, adau1701);
ret = snd_soc_register_codec(&client->dev, &adau1701_codec_drv,
&adau1701_dai, 1);
return ret;
}
static int adau1701_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
return 0;
}
static const struct i2c_device_id adau1701_i2c_id[] = {
{ "adau1401", 0 },
{ "adau1401a", 0 },
{ "adau1701", 0 },
{ "adau1702", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1701_i2c_id);
static struct i2c_driver adau1701_i2c_driver = {
.driver = {
.name = "adau1701",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(adau1701_dt_ids),
},
.probe = adau1701_i2c_probe,
.remove = adau1701_i2c_remove,
.id_table = adau1701_i2c_id,
};
module_i2c_driver(adau1701_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1701 SigmaDSP driver");
MODULE_AUTHOR("Cliff Cai <cliff.cai@analog.com>");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,17 @@
/*
* header file for ADAU1701 SigmaDSP processor
*
* Copyright 2011 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#ifndef _ADAU1701_H
#define _ADAU1701_H
enum adau1701_clk_src {
ADAU1701_CLK_SRC_OSC,
ADAU1701_CLK_SRC_MCLK,
};
#endif

View file

@ -0,0 +1,60 @@
/*
* Driver for ADAU1761/ADAU1461/ADAU1761/ADAU1961 codec
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "adau1761.h"
static int adau1761_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct regmap_config config;
config = adau1761_regmap_config;
config.val_bits = 8;
config.reg_bits = 16;
return adau1761_probe(&client->dev,
devm_regmap_init_i2c(client, &config),
id->driver_data, NULL);
}
static int adau1761_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
return 0;
}
static const struct i2c_device_id adau1761_i2c_ids[] = {
{ "adau1361", ADAU1361 },
{ "adau1461", ADAU1761 },
{ "adau1761", ADAU1761 },
{ "adau1961", ADAU1361 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1761_i2c_ids);
static struct i2c_driver adau1761_i2c_driver = {
.driver = {
.name = "adau1761",
.owner = THIS_MODULE,
},
.probe = adau1761_i2c_probe,
.remove = adau1761_i2c_remove,
.id_table = adau1761_i2c_ids,
};
module_i2c_driver(adau1761_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC I2C driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,77 @@
/*
* Driver for ADAU1761/ADAU1461/ADAU1761/ADAU1961 codec
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <sound/soc.h>
#include "adau1761.h"
static void adau1761_spi_switch_mode(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
/*
* To get the device into SPI mode CLATCH has to be pulled low three
* times. Do this by issuing three dummy reads.
*/
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
}
static int adau1761_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct regmap_config config;
if (!id)
return -EINVAL;
config = adau1761_regmap_config;
config.val_bits = 8;
config.reg_bits = 24;
config.read_flag_mask = 0x1;
return adau1761_probe(&spi->dev,
devm_regmap_init_spi(spi, &config),
id->driver_data, adau1761_spi_switch_mode);
}
static int adau1761_spi_remove(struct spi_device *spi)
{
snd_soc_unregister_codec(&spi->dev);
return 0;
}
static const struct spi_device_id adau1761_spi_id[] = {
{ "adau1361", ADAU1361 },
{ "adau1461", ADAU1761 },
{ "adau1761", ADAU1761 },
{ "adau1961", ADAU1361 },
{ }
};
MODULE_DEVICE_TABLE(spi, adau1761_spi_id);
static struct spi_driver adau1761_spi_driver = {
.driver = {
.name = "adau1761",
.owner = THIS_MODULE,
},
.probe = adau1761_spi_probe,
.remove = adau1761_spi_remove,
.id_table = adau1761_spi_id,
};
module_spi_driver(adau1761_spi_driver);
MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC SPI driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

807
sound/soc/codecs/adau1761.c Normal file
View file

@ -0,0 +1,807 @@
/*
* Driver for ADAU1761/ADAU1461/ADAU1761/ADAU1961 codec
*
* Copyright 2011-2013 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/platform_data/adau17x1.h>
#include "adau17x1.h"
#include "adau1761.h"
#define ADAU1761_DIGMIC_JACKDETECT 0x4008
#define ADAU1761_REC_MIXER_LEFT0 0x400a
#define ADAU1761_REC_MIXER_LEFT1 0x400b
#define ADAU1761_REC_MIXER_RIGHT0 0x400c
#define ADAU1761_REC_MIXER_RIGHT1 0x400d
#define ADAU1761_LEFT_DIFF_INPUT_VOL 0x400e
#define ADAU1761_RIGHT_DIFF_INPUT_VOL 0x400f
#define ADAU1761_PLAY_LR_MIXER_LEFT 0x4020
#define ADAU1761_PLAY_MIXER_LEFT0 0x401c
#define ADAU1761_PLAY_MIXER_LEFT1 0x401d
#define ADAU1761_PLAY_MIXER_RIGHT0 0x401e
#define ADAU1761_PLAY_MIXER_RIGHT1 0x401f
#define ADAU1761_PLAY_LR_MIXER_RIGHT 0x4021
#define ADAU1761_PLAY_MIXER_MONO 0x4022
#define ADAU1761_PLAY_HP_LEFT_VOL 0x4023
#define ADAU1761_PLAY_HP_RIGHT_VOL 0x4024
#define ADAU1761_PLAY_LINE_LEFT_VOL 0x4025
#define ADAU1761_PLAY_LINE_RIGHT_VOL 0x4026
#define ADAU1761_PLAY_MONO_OUTPUT_VOL 0x4027
#define ADAU1761_POP_CLICK_SUPPRESS 0x4028
#define ADAU1761_JACK_DETECT_PIN 0x4031
#define ADAU1761_DEJITTER 0x4036
#define ADAU1761_CLK_ENABLE0 0x40f9
#define ADAU1761_CLK_ENABLE1 0x40fa
#define ADAU1761_DIGMIC_JACKDETECT_ACTIVE_LOW BIT(0)
#define ADAU1761_DIGMIC_JACKDETECT_DIGMIC BIT(5)
#define ADAU1761_DIFF_INPUT_VOL_LDEN BIT(0)
#define ADAU1761_PLAY_MONO_OUTPUT_VOL_MODE_HP BIT(0)
#define ADAU1761_PLAY_MONO_OUTPUT_VOL_UNMUTE BIT(1)
#define ADAU1761_PLAY_HP_RIGHT_VOL_MODE_HP BIT(0)
#define ADAU1761_PLAY_LINE_LEFT_VOL_MODE_HP BIT(0)
#define ADAU1761_PLAY_LINE_RIGHT_VOL_MODE_HP BIT(0)
#define ADAU1761_FIRMWARE "adau1761.bin"
static const struct reg_default adau1761_reg_defaults[] = {
{ ADAU1761_DEJITTER, 0x03 },
{ ADAU1761_DIGMIC_JACKDETECT, 0x00 },
{ ADAU1761_REC_MIXER_LEFT0, 0x00 },
{ ADAU1761_REC_MIXER_LEFT1, 0x00 },
{ ADAU1761_REC_MIXER_RIGHT0, 0x00 },
{ ADAU1761_REC_MIXER_RIGHT1, 0x00 },
{ ADAU1761_LEFT_DIFF_INPUT_VOL, 0x00 },
{ ADAU1761_RIGHT_DIFF_INPUT_VOL, 0x00 },
{ ADAU1761_PLAY_LR_MIXER_LEFT, 0x00 },
{ ADAU1761_PLAY_MIXER_LEFT0, 0x00 },
{ ADAU1761_PLAY_MIXER_LEFT1, 0x00 },
{ ADAU1761_PLAY_MIXER_RIGHT0, 0x00 },
{ ADAU1761_PLAY_MIXER_RIGHT1, 0x00 },
{ ADAU1761_PLAY_LR_MIXER_RIGHT, 0x00 },
{ ADAU1761_PLAY_MIXER_MONO, 0x00 },
{ ADAU1761_PLAY_HP_LEFT_VOL, 0x00 },
{ ADAU1761_PLAY_HP_RIGHT_VOL, 0x00 },
{ ADAU1761_PLAY_LINE_LEFT_VOL, 0x00 },
{ ADAU1761_PLAY_LINE_RIGHT_VOL, 0x00 },
{ ADAU1761_PLAY_MONO_OUTPUT_VOL, 0x00 },
{ ADAU1761_POP_CLICK_SUPPRESS, 0x00 },
{ ADAU1761_JACK_DETECT_PIN, 0x00 },
{ ADAU1761_CLK_ENABLE0, 0x00 },
{ ADAU1761_CLK_ENABLE1, 0x00 },
{ ADAU17X1_CLOCK_CONTROL, 0x00 },
{ ADAU17X1_PLL_CONTROL, 0x00 },
{ ADAU17X1_REC_POWER_MGMT, 0x00 },
{ ADAU17X1_MICBIAS, 0x00 },
{ ADAU17X1_SERIAL_PORT0, 0x00 },
{ ADAU17X1_SERIAL_PORT1, 0x00 },
{ ADAU17X1_CONVERTER0, 0x00 },
{ ADAU17X1_CONVERTER1, 0x00 },
{ ADAU17X1_LEFT_INPUT_DIGITAL_VOL, 0x00 },
{ ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, 0x00 },
{ ADAU17X1_ADC_CONTROL, 0x00 },
{ ADAU17X1_PLAY_POWER_MGMT, 0x00 },
{ ADAU17X1_DAC_CONTROL0, 0x00 },
{ ADAU17X1_DAC_CONTROL1, 0x00 },
{ ADAU17X1_DAC_CONTROL2, 0x00 },
{ ADAU17X1_SERIAL_PORT_PAD, 0xaa },
{ ADAU17X1_CONTROL_PORT_PAD0, 0xaa },
{ ADAU17X1_CONTROL_PORT_PAD1, 0x00 },
{ ADAU17X1_DSP_SAMPLING_RATE, 0x01 },
{ ADAU17X1_SERIAL_INPUT_ROUTE, 0x00 },
{ ADAU17X1_SERIAL_OUTPUT_ROUTE, 0x00 },
{ ADAU17X1_DSP_ENABLE, 0x00 },
{ ADAU17X1_DSP_RUN, 0x00 },
{ ADAU17X1_SERIAL_SAMPLING_RATE, 0x00 },
};
static const DECLARE_TLV_DB_SCALE(adau1761_sing_in_tlv, -1500, 300, 1);
static const DECLARE_TLV_DB_SCALE(adau1761_diff_in_tlv, -1200, 75, 0);
static const DECLARE_TLV_DB_SCALE(adau1761_out_tlv, -5700, 100, 0);
static const DECLARE_TLV_DB_SCALE(adau1761_sidetone_tlv, -1800, 300, 1);
static const DECLARE_TLV_DB_SCALE(adau1761_boost_tlv, -600, 600, 1);
static const DECLARE_TLV_DB_SCALE(adau1761_pga_boost_tlv, -2000, 2000, 1);
static const unsigned int adau1761_bias_select_values[] = {
0, 2, 3,
};
static const char * const adau1761_bias_select_text[] = {
"Normal operation", "Enhanced performance", "Power saving",
};
static const char * const adau1761_bias_select_extreme_text[] = {
"Normal operation", "Extreme power saving", "Enhanced performance",
"Power saving",
};
static SOC_ENUM_SINGLE_DECL(adau1761_adc_bias_enum,
ADAU17X1_REC_POWER_MGMT, 3, adau1761_bias_select_extreme_text);
static SOC_ENUM_SINGLE_DECL(adau1761_hp_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 6, adau1761_bias_select_extreme_text);
static SOC_ENUM_SINGLE_DECL(adau1761_dac_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 4, adau1761_bias_select_extreme_text);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1761_playback_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 2, 0x3, adau1761_bias_select_text,
adau1761_bias_select_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1761_capture_bias_enum,
ADAU17X1_REC_POWER_MGMT, 1, 0x3, adau1761_bias_select_text,
adau1761_bias_select_values);
static const struct snd_kcontrol_new adau1761_jack_detect_controls[] = {
SOC_SINGLE("Speaker Auto-mute Switch", ADAU1761_DIGMIC_JACKDETECT,
4, 1, 0),
};
static const struct snd_kcontrol_new adau1761_differential_mode_controls[] = {
SOC_DOUBLE_R_TLV("Capture Volume", ADAU1761_LEFT_DIFF_INPUT_VOL,
ADAU1761_RIGHT_DIFF_INPUT_VOL, 2, 0x3f, 0,
adau1761_diff_in_tlv),
SOC_DOUBLE_R("Capture Switch", ADAU1761_LEFT_DIFF_INPUT_VOL,
ADAU1761_RIGHT_DIFF_INPUT_VOL, 1, 1, 0),
SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1761_REC_MIXER_LEFT1,
ADAU1761_REC_MIXER_RIGHT1, 3, 2, 0, adau1761_pga_boost_tlv),
};
static const struct snd_kcontrol_new adau1761_single_mode_controls[] = {
SOC_SINGLE_TLV("Input 1 Capture Volume", ADAU1761_REC_MIXER_LEFT0,
4, 7, 0, adau1761_sing_in_tlv),
SOC_SINGLE_TLV("Input 2 Capture Volume", ADAU1761_REC_MIXER_LEFT0,
1, 7, 0, adau1761_sing_in_tlv),
SOC_SINGLE_TLV("Input 3 Capture Volume", ADAU1761_REC_MIXER_RIGHT0,
4, 7, 0, adau1761_sing_in_tlv),
SOC_SINGLE_TLV("Input 4 Capture Volume", ADAU1761_REC_MIXER_RIGHT0,
1, 7, 0, adau1761_sing_in_tlv),
};
static const struct snd_kcontrol_new adau1761_controls[] = {
SOC_DOUBLE_R_TLV("Aux Capture Volume", ADAU1761_REC_MIXER_LEFT1,
ADAU1761_REC_MIXER_RIGHT1, 0, 7, 0, adau1761_sing_in_tlv),
SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1761_PLAY_HP_LEFT_VOL,
ADAU1761_PLAY_HP_RIGHT_VOL, 2, 0x3f, 0, adau1761_out_tlv),
SOC_DOUBLE_R("Headphone Playback Switch", ADAU1761_PLAY_HP_LEFT_VOL,
ADAU1761_PLAY_HP_RIGHT_VOL, 1, 1, 0),
SOC_DOUBLE_R_TLV("Lineout Playback Volume", ADAU1761_PLAY_LINE_LEFT_VOL,
ADAU1761_PLAY_LINE_RIGHT_VOL, 2, 0x3f, 0, adau1761_out_tlv),
SOC_DOUBLE_R("Lineout Playback Switch", ADAU1761_PLAY_LINE_LEFT_VOL,
ADAU1761_PLAY_LINE_RIGHT_VOL, 1, 1, 0),
SOC_ENUM("ADC Bias", adau1761_adc_bias_enum),
SOC_ENUM("DAC Bias", adau1761_dac_bias_enum),
SOC_ENUM("Capture Bias", adau1761_capture_bias_enum),
SOC_ENUM("Playback Bias", adau1761_playback_bias_enum),
SOC_ENUM("Headphone Bias", adau1761_hp_bias_enum),
};
static const struct snd_kcontrol_new adau1761_mono_controls[] = {
SOC_SINGLE_TLV("Mono Playback Volume", ADAU1761_PLAY_MONO_OUTPUT_VOL,
2, 0x3f, 0, adau1761_out_tlv),
SOC_SINGLE("Mono Playback Switch", ADAU1761_PLAY_MONO_OUTPUT_VOL,
1, 1, 0),
};
static const struct snd_kcontrol_new adau1761_left_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
ADAU1761_PLAY_MIXER_LEFT0, 5, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
ADAU1761_PLAY_MIXER_LEFT0, 6, 1, 0),
SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
ADAU1761_PLAY_MIXER_LEFT0, 1, 8, 0, adau1761_sidetone_tlv),
SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
ADAU1761_PLAY_MIXER_LEFT1, 4, 8, 0, adau1761_sidetone_tlv),
SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
ADAU1761_PLAY_MIXER_LEFT1, 0, 8, 0, adau1761_sidetone_tlv),
};
static const struct snd_kcontrol_new adau1761_right_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
ADAU1761_PLAY_MIXER_RIGHT0, 5, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
ADAU1761_PLAY_MIXER_RIGHT0, 6, 1, 0),
SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
ADAU1761_PLAY_MIXER_RIGHT0, 1, 8, 0, adau1761_sidetone_tlv),
SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
ADAU1761_PLAY_MIXER_RIGHT1, 4, 8, 0, adau1761_sidetone_tlv),
SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
ADAU1761_PLAY_MIXER_RIGHT1, 0, 8, 0, adau1761_sidetone_tlv),
};
static const struct snd_kcontrol_new adau1761_left_lr_mixer_controls[] = {
SOC_DAPM_SINGLE_TLV("Left Volume",
ADAU1761_PLAY_LR_MIXER_LEFT, 1, 2, 0, adau1761_boost_tlv),
SOC_DAPM_SINGLE_TLV("Right Volume",
ADAU1761_PLAY_LR_MIXER_LEFT, 3, 2, 0, adau1761_boost_tlv),
};
static const struct snd_kcontrol_new adau1761_right_lr_mixer_controls[] = {
SOC_DAPM_SINGLE_TLV("Left Volume",
ADAU1761_PLAY_LR_MIXER_RIGHT, 1, 2, 0, adau1761_boost_tlv),
SOC_DAPM_SINGLE_TLV("Right Volume",
ADAU1761_PLAY_LR_MIXER_RIGHT, 3, 2, 0, adau1761_boost_tlv),
};
static const char * const adau1761_input_mux_text[] = {
"ADC", "DMIC",
};
static SOC_ENUM_SINGLE_DECL(adau1761_input_mux_enum,
ADAU17X1_ADC_CONTROL, 2, adau1761_input_mux_text);
static const struct snd_kcontrol_new adau1761_input_mux_control =
SOC_DAPM_ENUM("Input Select", adau1761_input_mux_enum);
static int adau1761_dejitter_fixup(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct adau *adau = snd_soc_codec_get_drvdata(w->codec);
/* After any power changes have been made the dejitter circuit
* has to be reinitialized. */
regmap_write(adau->regmap, ADAU1761_DEJITTER, 0);
if (!adau->master)
regmap_write(adau->regmap, ADAU1761_DEJITTER, 3);
return 0;
}
static const struct snd_soc_dapm_widget adau1x61_dapm_widgets[] = {
SND_SOC_DAPM_MIXER("Left Input Mixer", ADAU1761_REC_MIXER_LEFT0, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("Right Input Mixer", ADAU1761_REC_MIXER_RIGHT0, 0, 0,
NULL, 0),
SOC_MIXER_ARRAY("Left Playback Mixer", ADAU1761_PLAY_MIXER_LEFT0,
0, 0, adau1761_left_mixer_controls),
SOC_MIXER_ARRAY("Right Playback Mixer", ADAU1761_PLAY_MIXER_RIGHT0,
0, 0, adau1761_right_mixer_controls),
SOC_MIXER_ARRAY("Left LR Playback Mixer", ADAU1761_PLAY_LR_MIXER_LEFT,
0, 0, adau1761_left_lr_mixer_controls),
SOC_MIXER_ARRAY("Right LR Playback Mixer", ADAU1761_PLAY_LR_MIXER_RIGHT,
0, 0, adau1761_right_lr_mixer_controls),
SND_SOC_DAPM_SUPPLY("Headphone", ADAU1761_PLAY_HP_LEFT_VOL,
0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("SYSCLK", 2, SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_POST("Dejitter fixup", adau1761_dejitter_fixup),
SND_SOC_DAPM_INPUT("LAUX"),
SND_SOC_DAPM_INPUT("RAUX"),
SND_SOC_DAPM_INPUT("LINP"),
SND_SOC_DAPM_INPUT("LINN"),
SND_SOC_DAPM_INPUT("RINP"),
SND_SOC_DAPM_INPUT("RINN"),
SND_SOC_DAPM_OUTPUT("LOUT"),
SND_SOC_DAPM_OUTPUT("ROUT"),
SND_SOC_DAPM_OUTPUT("LHP"),
SND_SOC_DAPM_OUTPUT("RHP"),
};
static const struct snd_soc_dapm_widget adau1761_mono_dapm_widgets[] = {
SND_SOC_DAPM_MIXER("Mono Playback Mixer", ADAU1761_PLAY_MIXER_MONO,
0, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("MONOOUT"),
};
static const struct snd_soc_dapm_widget adau1761_capless_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY_S("Headphone VGND", 1, ADAU1761_PLAY_MIXER_MONO,
0, 0, NULL, 0),
};
static const struct snd_soc_dapm_route adau1x61_dapm_routes[] = {
{ "Left Input Mixer", NULL, "LINP" },
{ "Left Input Mixer", NULL, "LINN" },
{ "Left Input Mixer", NULL, "LAUX" },
{ "Right Input Mixer", NULL, "RINP" },
{ "Right Input Mixer", NULL, "RINN" },
{ "Right Input Mixer", NULL, "RAUX" },
{ "Left Playback Mixer", NULL, "Left Playback Enable"},
{ "Right Playback Mixer", NULL, "Right Playback Enable"},
{ "Left LR Playback Mixer", NULL, "Left Playback Enable"},
{ "Right LR Playback Mixer", NULL, "Right Playback Enable"},
{ "Left Playback Mixer", "Left DAC Switch", "Left DAC" },
{ "Left Playback Mixer", "Right DAC Switch", "Right DAC" },
{ "Right Playback Mixer", "Left DAC Switch", "Left DAC" },
{ "Right Playback Mixer", "Right DAC Switch", "Right DAC" },
{ "Left LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
{ "Left LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
{ "Right LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
{ "Right LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
{ "LHP", NULL, "Left Playback Mixer" },
{ "RHP", NULL, "Right Playback Mixer" },
{ "LHP", NULL, "Headphone" },
{ "RHP", NULL, "Headphone" },
{ "LOUT", NULL, "Left LR Playback Mixer" },
{ "ROUT", NULL, "Right LR Playback Mixer" },
{ "Left Playback Mixer", "Aux Bypass Volume", "LAUX" },
{ "Left Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
{ "Left Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
{ "Right Playback Mixer", "Aux Bypass Volume", "RAUX" },
{ "Right Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
{ "Right Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
};
static const struct snd_soc_dapm_route adau1761_mono_dapm_routes[] = {
{ "Mono Playback Mixer", NULL, "Left Playback Mixer" },
{ "Mono Playback Mixer", NULL, "Right Playback Mixer" },
{ "MONOOUT", NULL, "Mono Playback Mixer" },
};
static const struct snd_soc_dapm_route adau1761_capless_dapm_routes[] = {
{ "Headphone", NULL, "Headphone VGND" },
};
static const struct snd_soc_dapm_widget adau1761_dmic_widgets[] = {
SND_SOC_DAPM_MUX("Left Decimator Mux", SND_SOC_NOPM, 0, 0,
&adau1761_input_mux_control),
SND_SOC_DAPM_MUX("Right Decimator Mux", SND_SOC_NOPM, 0, 0,
&adau1761_input_mux_control),
SND_SOC_DAPM_INPUT("DMIC"),
};
static const struct snd_soc_dapm_route adau1761_dmic_routes[] = {
{ "Left Decimator Mux", "ADC", "Left Input Mixer" },
{ "Left Decimator Mux", "DMIC", "DMIC" },
{ "Right Decimator Mux", "ADC", "Right Input Mixer" },
{ "Right Decimator Mux", "DMIC", "DMIC" },
{ "Left Decimator", NULL, "Left Decimator Mux" },
{ "Right Decimator", NULL, "Right Decimator Mux" },
};
static const struct snd_soc_dapm_route adau1761_no_dmic_routes[] = {
{ "Left Decimator", NULL, "Left Input Mixer" },
{ "Right Decimator", NULL, "Right Input Mixer" },
};
static const struct snd_soc_dapm_widget adau1761_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("Serial Port Clock", ADAU1761_CLK_ENABLE0,
0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Serial Input Routing Clock", ADAU1761_CLK_ENABLE0,
1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Serial Output Routing Clock", ADAU1761_CLK_ENABLE0,
3, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Decimator Resync Clock", ADAU1761_CLK_ENABLE0,
4, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Interpolator Resync Clock", ADAU1761_CLK_ENABLE0,
2, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Slew Clock", ADAU1761_CLK_ENABLE0, 6, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ALC Clock", ADAU1761_CLK_ENABLE0, 5, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("Digital Clock 0", 1, ADAU1761_CLK_ENABLE1,
0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("Digital Clock 1", 1, ADAU1761_CLK_ENABLE1,
1, 0, NULL, 0),
};
static const struct snd_soc_dapm_route adau1761_dapm_routes[] = {
{ "Left Decimator", NULL, "Digital Clock 0", },
{ "Right Decimator", NULL, "Digital Clock 0", },
{ "Left DAC", NULL, "Digital Clock 0", },
{ "Right DAC", NULL, "Digital Clock 0", },
{ "AIFCLK", NULL, "Digital Clock 1" },
{ "Playback", NULL, "Serial Port Clock" },
{ "Capture", NULL, "Serial Port Clock" },
{ "Playback", NULL, "Serial Input Routing Clock" },
{ "Capture", NULL, "Serial Output Routing Clock" },
{ "Left Decimator", NULL, "Decimator Resync Clock" },
{ "Right Decimator", NULL, "Decimator Resync Clock" },
{ "Left DAC", NULL, "Interpolator Resync Clock" },
{ "Right DAC", NULL, "Interpolator Resync Clock" },
{ "DSP", NULL, "Digital Clock 0" },
{ "Slew Clock", NULL, "Digital Clock 0" },
{ "Right Playback Mixer", NULL, "Slew Clock" },
{ "Left Playback Mixer", NULL, "Slew Clock" },
{ "Left Input Mixer", NULL, "ALC Clock" },
{ "Right Input Mixer", NULL, "ALC Clock" },
{ "Digital Clock 0", NULL, "SYSCLK" },
{ "Digital Clock 1", NULL, "SYSCLK" },
};
static int adau1761_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
struct adau *adau = snd_soc_codec_get_drvdata(codec);
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN);
break;
case SND_SOC_BIAS_OFF:
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, 0);
break;
}
codec->dapm.bias_level = level;
return 0;
}
static enum adau1761_output_mode adau1761_get_lineout_mode(
struct snd_soc_codec *codec)
{
struct adau1761_platform_data *pdata = codec->dev->platform_data;
if (pdata)
return pdata->lineout_mode;
return ADAU1761_OUTPUT_MODE_LINE;
}
static int adau1761_setup_digmic_jackdetect(struct snd_soc_codec *codec)
{
struct adau1761_platform_data *pdata = codec->dev->platform_data;
struct adau *adau = snd_soc_codec_get_drvdata(codec);
enum adau1761_digmic_jackdet_pin_mode mode;
unsigned int val = 0;
int ret;
if (pdata)
mode = pdata->digmic_jackdetect_pin_mode;
else
mode = ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE;
switch (mode) {
case ADAU1761_DIGMIC_JACKDET_PIN_MODE_JACKDETECT:
switch (pdata->jackdetect_debounce_time) {
case ADAU1761_JACKDETECT_DEBOUNCE_5MS:
case ADAU1761_JACKDETECT_DEBOUNCE_10MS:
case ADAU1761_JACKDETECT_DEBOUNCE_20MS:
case ADAU1761_JACKDETECT_DEBOUNCE_40MS:
val |= pdata->jackdetect_debounce_time << 6;
break;
default:
return -EINVAL;
}
if (pdata->jackdetect_active_low)
val |= ADAU1761_DIGMIC_JACKDETECT_ACTIVE_LOW;
ret = snd_soc_add_codec_controls(codec,
adau1761_jack_detect_controls,
ARRAY_SIZE(adau1761_jack_detect_controls));
if (ret)
return ret;
case ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE: /* fallthrough */
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau1761_no_dmic_routes,
ARRAY_SIZE(adau1761_no_dmic_routes));
if (ret)
return ret;
break;
case ADAU1761_DIGMIC_JACKDET_PIN_MODE_DIGMIC:
ret = snd_soc_dapm_new_controls(&codec->dapm,
adau1761_dmic_widgets,
ARRAY_SIZE(adau1761_dmic_widgets));
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau1761_dmic_routes,
ARRAY_SIZE(adau1761_dmic_routes));
if (ret)
return ret;
val |= ADAU1761_DIGMIC_JACKDETECT_DIGMIC;
break;
default:
return -EINVAL;
}
regmap_write(adau->regmap, ADAU1761_DIGMIC_JACKDETECT, val);
return 0;
}
static int adau1761_setup_headphone_mode(struct snd_soc_codec *codec)
{
struct adau *adau = snd_soc_codec_get_drvdata(codec);
struct adau1761_platform_data *pdata = codec->dev->platform_data;
enum adau1761_output_mode mode;
int ret;
if (pdata)
mode = pdata->headphone_mode;
else
mode = ADAU1761_OUTPUT_MODE_HEADPHONE;
switch (mode) {
case ADAU1761_OUTPUT_MODE_LINE:
break;
case ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS:
regmap_update_bits(adau->regmap, ADAU1761_PLAY_MONO_OUTPUT_VOL,
ADAU1761_PLAY_MONO_OUTPUT_VOL_MODE_HP |
ADAU1761_PLAY_MONO_OUTPUT_VOL_UNMUTE,
ADAU1761_PLAY_MONO_OUTPUT_VOL_MODE_HP |
ADAU1761_PLAY_MONO_OUTPUT_VOL_UNMUTE);
/* fallthrough */
case ADAU1761_OUTPUT_MODE_HEADPHONE:
regmap_update_bits(adau->regmap, ADAU1761_PLAY_HP_RIGHT_VOL,
ADAU1761_PLAY_HP_RIGHT_VOL_MODE_HP,
ADAU1761_PLAY_HP_RIGHT_VOL_MODE_HP);
break;
default:
return -EINVAL;
}
if (mode == ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS) {
ret = snd_soc_dapm_new_controls(&codec->dapm,
adau1761_capless_dapm_widgets,
ARRAY_SIZE(adau1761_capless_dapm_widgets));
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau1761_capless_dapm_routes,
ARRAY_SIZE(adau1761_capless_dapm_routes));
} else {
ret = snd_soc_add_codec_controls(codec, adau1761_mono_controls,
ARRAY_SIZE(adau1761_mono_controls));
if (ret)
return ret;
ret = snd_soc_dapm_new_controls(&codec->dapm,
adau1761_mono_dapm_widgets,
ARRAY_SIZE(adau1761_mono_dapm_widgets));
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau1761_mono_dapm_routes,
ARRAY_SIZE(adau1761_mono_dapm_routes));
}
return ret;
}
static bool adau1761_readable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADAU1761_DIGMIC_JACKDETECT:
case ADAU1761_REC_MIXER_LEFT0:
case ADAU1761_REC_MIXER_LEFT1:
case ADAU1761_REC_MIXER_RIGHT0:
case ADAU1761_REC_MIXER_RIGHT1:
case ADAU1761_LEFT_DIFF_INPUT_VOL:
case ADAU1761_RIGHT_DIFF_INPUT_VOL:
case ADAU1761_PLAY_LR_MIXER_LEFT:
case ADAU1761_PLAY_MIXER_LEFT0:
case ADAU1761_PLAY_MIXER_LEFT1:
case ADAU1761_PLAY_MIXER_RIGHT0:
case ADAU1761_PLAY_MIXER_RIGHT1:
case ADAU1761_PLAY_LR_MIXER_RIGHT:
case ADAU1761_PLAY_MIXER_MONO:
case ADAU1761_PLAY_HP_LEFT_VOL:
case ADAU1761_PLAY_HP_RIGHT_VOL:
case ADAU1761_PLAY_LINE_LEFT_VOL:
case ADAU1761_PLAY_LINE_RIGHT_VOL:
case ADAU1761_PLAY_MONO_OUTPUT_VOL:
case ADAU1761_POP_CLICK_SUPPRESS:
case ADAU1761_JACK_DETECT_PIN:
case ADAU1761_DEJITTER:
case ADAU1761_CLK_ENABLE0:
case ADAU1761_CLK_ENABLE1:
return true;
default:
break;
}
return adau17x1_readable_register(dev, reg);
}
static int adau1761_codec_probe(struct snd_soc_codec *codec)
{
struct adau1761_platform_data *pdata = codec->dev->platform_data;
struct adau *adau = snd_soc_codec_get_drvdata(codec);
int ret;
ret = adau17x1_add_widgets(codec);
if (ret < 0)
return ret;
if (pdata && pdata->input_differential) {
regmap_update_bits(adau->regmap, ADAU1761_LEFT_DIFF_INPUT_VOL,
ADAU1761_DIFF_INPUT_VOL_LDEN,
ADAU1761_DIFF_INPUT_VOL_LDEN);
regmap_update_bits(adau->regmap, ADAU1761_RIGHT_DIFF_INPUT_VOL,
ADAU1761_DIFF_INPUT_VOL_LDEN,
ADAU1761_DIFF_INPUT_VOL_LDEN);
ret = snd_soc_add_codec_controls(codec,
adau1761_differential_mode_controls,
ARRAY_SIZE(adau1761_differential_mode_controls));
if (ret)
return ret;
} else {
ret = snd_soc_add_codec_controls(codec,
adau1761_single_mode_controls,
ARRAY_SIZE(adau1761_single_mode_controls));
if (ret)
return ret;
}
switch (adau1761_get_lineout_mode(codec)) {
case ADAU1761_OUTPUT_MODE_LINE:
break;
case ADAU1761_OUTPUT_MODE_HEADPHONE:
regmap_update_bits(adau->regmap, ADAU1761_PLAY_LINE_LEFT_VOL,
ADAU1761_PLAY_LINE_LEFT_VOL_MODE_HP,
ADAU1761_PLAY_LINE_LEFT_VOL_MODE_HP);
regmap_update_bits(adau->regmap, ADAU1761_PLAY_LINE_RIGHT_VOL,
ADAU1761_PLAY_LINE_RIGHT_VOL_MODE_HP,
ADAU1761_PLAY_LINE_RIGHT_VOL_MODE_HP);
break;
default:
return -EINVAL;
}
ret = adau1761_setup_headphone_mode(codec);
if (ret)
return ret;
ret = adau1761_setup_digmic_jackdetect(codec);
if (ret)
return ret;
if (adau->type == ADAU1761) {
ret = snd_soc_dapm_new_controls(&codec->dapm,
adau1761_dapm_widgets,
ARRAY_SIZE(adau1761_dapm_widgets));
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau1761_dapm_routes,
ARRAY_SIZE(adau1761_dapm_routes));
if (ret)
return ret;
ret = adau17x1_load_firmware(adau, codec->dev,
ADAU1761_FIRMWARE);
if (ret)
dev_warn(codec->dev, "Failed to firmware\n");
}
ret = adau17x1_add_routes(codec);
if (ret < 0)
return ret;
return 0;
}
static const struct snd_soc_codec_driver adau1761_codec_driver = {
.probe = adau1761_codec_probe,
.resume = adau17x1_resume,
.set_bias_level = adau1761_set_bias_level,
.suspend_bias_off = true,
.controls = adau1761_controls,
.num_controls = ARRAY_SIZE(adau1761_controls),
.dapm_widgets = adau1x61_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1x61_dapm_widgets),
.dapm_routes = adau1x61_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1x61_dapm_routes),
};
#define ADAU1761_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver adau1361_dai_driver = {
.name = "adau-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 4,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1761_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 4,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1761_FORMATS,
},
.ops = &adau17x1_dai_ops,
};
static struct snd_soc_dai_driver adau1761_dai_driver = {
.name = "adau-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1761_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1761_FORMATS,
},
.ops = &adau17x1_dai_ops,
};
int adau1761_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev))
{
struct snd_soc_dai_driver *dai_drv;
int ret;
ret = adau17x1_probe(dev, regmap, type, switch_mode);
if (ret)
return ret;
if (type == ADAU1361)
dai_drv = &adau1361_dai_driver;
else
dai_drv = &adau1761_dai_driver;
return snd_soc_register_codec(dev, &adau1761_codec_driver, dai_drv, 1);
}
EXPORT_SYMBOL_GPL(adau1761_probe);
const struct regmap_config adau1761_regmap_config = {
.val_bits = 8,
.reg_bits = 16,
.max_register = 0x40fa,
.reg_defaults = adau1761_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(adau1761_reg_defaults),
.readable_reg = adau1761_readable_register,
.volatile_reg = adau17x1_volatile_register,
.cache_type = REGCACHE_RBTREE,
};
EXPORT_SYMBOL_GPL(adau1761_regmap_config);
MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,23 @@
/*
* ADAU1361/ADAU1461/ADAU1761/ADAU1961 driver
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#ifndef __SOUND_SOC_CODECS_ADAU1761_H__
#define __SOUND_SOC_CODECS_ADAU1761_H__
#include <linux/regmap.h>
#include "adau17x1.h"
struct device;
int adau1761_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev));
extern const struct regmap_config adau1761_regmap_config;
#endif

View file

@ -0,0 +1,58 @@
/*
* Driver for ADAU1381/ADAU1781 CODEC
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "adau1781.h"
static int adau1781_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct regmap_config config;
config = adau1781_regmap_config;
config.val_bits = 8;
config.reg_bits = 16;
return adau1781_probe(&client->dev,
devm_regmap_init_i2c(client, &config),
id->driver_data, NULL);
}
static int adau1781_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
return 0;
}
static const struct i2c_device_id adau1781_i2c_ids[] = {
{ "adau1381", ADAU1381 },
{ "adau1781", ADAU1781 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1781_i2c_ids);
static struct i2c_driver adau1781_i2c_driver = {
.driver = {
.name = "adau1781",
.owner = THIS_MODULE,
},
.probe = adau1781_i2c_probe,
.remove = adau1781_i2c_remove,
.id_table = adau1781_i2c_ids,
};
module_i2c_driver(adau1781_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 CODEC I2C driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,75 @@
/*
* Driver for ADAU1381/ADAU1781 CODEC
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <sound/soc.h>
#include "adau1781.h"
static void adau1781_spi_switch_mode(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
/*
* To get the device into SPI mode CLATCH has to be pulled low three
* times. Do this by issuing three dummy reads.
*/
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
}
static int adau1781_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct regmap_config config;
if (!id)
return -EINVAL;
config = adau1781_regmap_config;
config.val_bits = 8;
config.reg_bits = 24;
config.read_flag_mask = 0x1;
return adau1781_probe(&spi->dev,
devm_regmap_init_spi(spi, &config),
id->driver_data, adau1781_spi_switch_mode);
}
static int adau1781_spi_remove(struct spi_device *spi)
{
snd_soc_unregister_codec(&spi->dev);
return 0;
}
static const struct spi_device_id adau1781_spi_id[] = {
{ "adau1381", ADAU1381 },
{ "adau1781", ADAU1781 },
{ }
};
MODULE_DEVICE_TABLE(spi, adau1781_spi_id);
static struct spi_driver adau1781_spi_driver = {
.driver = {
.name = "adau1781",
.owner = THIS_MODULE,
},
.probe = adau1781_spi_probe,
.remove = adau1781_spi_remove,
.id_table = adau1781_spi_id,
};
module_spi_driver(adau1781_spi_driver);
MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 CODEC SPI driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

511
sound/soc/codecs/adau1781.c Normal file
View file

@ -0,0 +1,511 @@
/*
* Driver for ADAU1781/ADAU1781 codec
*
* Copyright 2011-2013 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/platform_data/adau17x1.h>
#include "adau17x1.h"
#include "adau1781.h"
#define ADAU1781_DMIC_BEEP_CTRL 0x4008
#define ADAU1781_LEFT_PGA 0x400e
#define ADAU1781_RIGHT_PGA 0x400f
#define ADAU1781_LEFT_PLAYBACK_MIXER 0x401c
#define ADAU1781_RIGHT_PLAYBACK_MIXER 0x401e
#define ADAU1781_MONO_PLAYBACK_MIXER 0x401f
#define ADAU1781_LEFT_LINEOUT 0x4025
#define ADAU1781_RIGHT_LINEOUT 0x4026
#define ADAU1781_SPEAKER 0x4027
#define ADAU1781_BEEP_ZC 0x4028
#define ADAU1781_DEJITTER 0x4032
#define ADAU1781_DIG_PWDN0 0x4080
#define ADAU1781_DIG_PWDN1 0x4081
#define ADAU1781_INPUT_DIFFERNTIAL BIT(3)
#define ADAU1381_FIRMWARE "adau1381.bin"
#define ADAU1781_FIRMWARE "adau1781.bin"
static const struct reg_default adau1781_reg_defaults[] = {
{ ADAU1781_DMIC_BEEP_CTRL, 0x00 },
{ ADAU1781_LEFT_PGA, 0xc7 },
{ ADAU1781_RIGHT_PGA, 0xc7 },
{ ADAU1781_LEFT_PLAYBACK_MIXER, 0x00 },
{ ADAU1781_RIGHT_PLAYBACK_MIXER, 0x00 },
{ ADAU1781_MONO_PLAYBACK_MIXER, 0x00 },
{ ADAU1781_LEFT_LINEOUT, 0x00 },
{ ADAU1781_RIGHT_LINEOUT, 0x00 },
{ ADAU1781_SPEAKER, 0x00 },
{ ADAU1781_BEEP_ZC, 0x19 },
{ ADAU1781_DEJITTER, 0x60 },
{ ADAU1781_DIG_PWDN1, 0x0c },
{ ADAU1781_DIG_PWDN1, 0x00 },
{ ADAU17X1_CLOCK_CONTROL, 0x00 },
{ ADAU17X1_PLL_CONTROL, 0x00 },
{ ADAU17X1_REC_POWER_MGMT, 0x00 },
{ ADAU17X1_MICBIAS, 0x04 },
{ ADAU17X1_SERIAL_PORT0, 0x00 },
{ ADAU17X1_SERIAL_PORT1, 0x00 },
{ ADAU17X1_CONVERTER0, 0x00 },
{ ADAU17X1_CONVERTER1, 0x00 },
{ ADAU17X1_LEFT_INPUT_DIGITAL_VOL, 0x00 },
{ ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, 0x00 },
{ ADAU17X1_ADC_CONTROL, 0x00 },
{ ADAU17X1_PLAY_POWER_MGMT, 0x00 },
{ ADAU17X1_DAC_CONTROL0, 0x00 },
{ ADAU17X1_DAC_CONTROL1, 0x00 },
{ ADAU17X1_DAC_CONTROL2, 0x00 },
{ ADAU17X1_SERIAL_PORT_PAD, 0x00 },
{ ADAU17X1_CONTROL_PORT_PAD0, 0x00 },
{ ADAU17X1_CONTROL_PORT_PAD1, 0x00 },
{ ADAU17X1_DSP_SAMPLING_RATE, 0x01 },
{ ADAU17X1_SERIAL_INPUT_ROUTE, 0x00 },
{ ADAU17X1_SERIAL_OUTPUT_ROUTE, 0x00 },
{ ADAU17X1_DSP_ENABLE, 0x00 },
{ ADAU17X1_DSP_RUN, 0x00 },
{ ADAU17X1_SERIAL_SAMPLING_RATE, 0x00 },
};
static const DECLARE_TLV_DB_SCALE(adau1781_speaker_tlv, 0, 200, 0);
static const DECLARE_TLV_DB_RANGE(adau1781_pga_tlv,
0, 1, TLV_DB_SCALE_ITEM(0, 600, 0),
2, 3, TLV_DB_SCALE_ITEM(1000, 400, 0),
4, 4, TLV_DB_SCALE_ITEM(1700, 0, 0),
5, 7, TLV_DB_SCALE_ITEM(2000, 600, 0)
);
static const DECLARE_TLV_DB_RANGE(adau1781_beep_tlv,
0, 1, TLV_DB_SCALE_ITEM(0, 600, 0),
2, 3, TLV_DB_SCALE_ITEM(1000, 400, 0),
4, 4, TLV_DB_SCALE_ITEM(-2300, 0, 0),
5, 7, TLV_DB_SCALE_ITEM(2000, 600, 0)
);
static const DECLARE_TLV_DB_SCALE(adau1781_sidetone_tlv, -1800, 300, 1);
static const char * const adau1781_speaker_bias_select_text[] = {
"Normal operation", "Power saving", "Enhanced performance",
};
static const char * const adau1781_bias_select_text[] = {
"Normal operation", "Extreme power saving", "Power saving",
"Enhanced performance",
};
static SOC_ENUM_SINGLE_DECL(adau1781_adc_bias_enum,
ADAU17X1_REC_POWER_MGMT, 3, adau1781_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_speaker_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 6, adau1781_speaker_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_dac_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 4, adau1781_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_playback_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 2, adau1781_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_capture_bias_enum,
ADAU17X1_REC_POWER_MGMT, 1, adau1781_bias_select_text);
static const struct snd_kcontrol_new adau1781_controls[] = {
SOC_SINGLE_TLV("Beep Capture Volume", ADAU1781_DMIC_BEEP_CTRL, 0, 7, 0,
adau1781_beep_tlv),
SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAU1781_LEFT_PGA,
ADAU1781_RIGHT_PGA, 5, 7, 0, adau1781_pga_tlv),
SOC_DOUBLE_R("PGA Capture Switch", ADAU1781_LEFT_PGA,
ADAU1781_RIGHT_PGA, 1, 1, 0),
SOC_DOUBLE_R("Lineout Playback Switch", ADAU1781_LEFT_LINEOUT,
ADAU1781_RIGHT_LINEOUT, 1, 1, 0),
SOC_SINGLE("Beep ZC Switch", ADAU1781_BEEP_ZC, 0, 1, 0),
SOC_SINGLE("Mono Playback Switch", ADAU1781_MONO_PLAYBACK_MIXER,
0, 1, 0),
SOC_SINGLE_TLV("Mono Playback Volume", ADAU1781_SPEAKER, 6, 3, 0,
adau1781_speaker_tlv),
SOC_ENUM("ADC Bias", adau1781_adc_bias_enum),
SOC_ENUM("DAC Bias", adau1781_dac_bias_enum),
SOC_ENUM("Capture Bias", adau1781_capture_bias_enum),
SOC_ENUM("Playback Bias", adau1781_playback_bias_enum),
SOC_ENUM("Speaker Bias", adau1781_speaker_bias_enum),
};
static const struct snd_kcontrol_new adau1781_beep_mixer_controls[] = {
SOC_DAPM_SINGLE("Beep Capture Switch", ADAU1781_DMIC_BEEP_CTRL,
3, 1, 0),
};
static const struct snd_kcontrol_new adau1781_left_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Switch",
ADAU1781_LEFT_PLAYBACK_MIXER, 5, 1, 0),
SOC_DAPM_SINGLE_TLV("Beep Playback Volume",
ADAU1781_LEFT_PLAYBACK_MIXER, 1, 8, 0, adau1781_sidetone_tlv),
};
static const struct snd_kcontrol_new adau1781_right_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Switch",
ADAU1781_RIGHT_PLAYBACK_MIXER, 6, 1, 0),
SOC_DAPM_SINGLE_TLV("Beep Playback Volume",
ADAU1781_LEFT_PLAYBACK_MIXER, 1, 8, 0, adau1781_sidetone_tlv),
};
static const struct snd_kcontrol_new adau1781_mono_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Left Switch",
ADAU1781_MONO_PLAYBACK_MIXER, 7, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("Right Switch",
ADAU1781_MONO_PLAYBACK_MIXER, 6, 1, 0),
SOC_DAPM_SINGLE_TLV("Beep Playback Volume",
ADAU1781_MONO_PLAYBACK_MIXER, 2, 8, 0, adau1781_sidetone_tlv),
};
static int adau1781_dejitter_fixup(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
struct adau *adau = snd_soc_codec_get_drvdata(codec);
/* After any power changes have been made the dejitter circuit
* has to be reinitialized. */
regmap_write(adau->regmap, ADAU1781_DEJITTER, 0);
if (!adau->master)
regmap_write(adau->regmap, ADAU1781_DEJITTER, 5);
return 0;
}
static const struct snd_soc_dapm_widget adau1781_dapm_widgets[] = {
SND_SOC_DAPM_PGA("Left PGA", ADAU1781_LEFT_PGA, 0, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right PGA", ADAU1781_RIGHT_PGA, 0, 0, NULL, 0),
SND_SOC_DAPM_OUT_DRV("Speaker", ADAU1781_SPEAKER, 0, 0, NULL, 0),
SOC_MIXER_NAMED_CTL_ARRAY("Beep Mixer", ADAU17X1_MICBIAS, 4, 0,
adau1781_beep_mixer_controls),
SOC_MIXER_ARRAY("Left Lineout Mixer", SND_SOC_NOPM, 0, 0,
adau1781_left_mixer_controls),
SOC_MIXER_ARRAY("Right Lineout Mixer", SND_SOC_NOPM, 0, 0,
adau1781_right_mixer_controls),
SOC_MIXER_ARRAY("Mono Mixer", SND_SOC_NOPM, 0, 0,
adau1781_mono_mixer_controls),
SND_SOC_DAPM_SUPPLY("Serial Input Routing", ADAU1781_DIG_PWDN0,
2, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Serial Output Routing", ADAU1781_DIG_PWDN0,
3, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Clock Domain Transfer", ADAU1781_DIG_PWDN0,
5, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Serial Ports", ADAU1781_DIG_PWDN0, 4, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC Engine", ADAU1781_DIG_PWDN0, 7, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("DAC Engine", ADAU1781_DIG_PWDN1, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Digital Mic", ADAU1781_DIG_PWDN1, 1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Sound Engine", ADAU1781_DIG_PWDN0, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("SYSCLK", 1, ADAU1781_DIG_PWDN0, 1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Zero Crossing Detector", ADAU1781_DIG_PWDN1, 2, 0,
NULL, 0),
SND_SOC_DAPM_POST("Dejitter fixup", adau1781_dejitter_fixup),
SND_SOC_DAPM_INPUT("BEEP"),
SND_SOC_DAPM_OUTPUT("AOUTL"),
SND_SOC_DAPM_OUTPUT("AOUTR"),
SND_SOC_DAPM_OUTPUT("SP"),
SND_SOC_DAPM_INPUT("LMIC"),
SND_SOC_DAPM_INPUT("RMIC"),
};
static const struct snd_soc_dapm_route adau1781_dapm_routes[] = {
{ "Left Lineout Mixer", NULL, "Left Playback Enable" },
{ "Right Lineout Mixer", NULL, "Right Playback Enable" },
{ "Left Lineout Mixer", "Beep Playback Volume", "Beep Mixer" },
{ "Left Lineout Mixer", "Switch", "Left DAC" },
{ "Right Lineout Mixer", "Beep Playback Volume", "Beep Mixer" },
{ "Right Lineout Mixer", "Switch", "Right DAC" },
{ "Mono Mixer", "Beep Playback Volume", "Beep Mixer" },
{ "Mono Mixer", "Right Switch", "Right DAC" },
{ "Mono Mixer", "Left Switch", "Left DAC" },
{ "Speaker", NULL, "Mono Mixer" },
{ "Mono Mixer", NULL, "SYSCLK" },
{ "Left Lineout Mixer", NULL, "SYSCLK" },
{ "Left Lineout Mixer", NULL, "SYSCLK" },
{ "Beep Mixer", "Beep Capture Switch", "BEEP" },
{ "Beep Mixer", NULL, "Zero Crossing Detector" },
{ "Left DAC", NULL, "DAC Engine" },
{ "Right DAC", NULL, "DAC Engine" },
{ "Sound Engine", NULL, "SYSCLK" },
{ "DSP", NULL, "Sound Engine" },
{ "Left Decimator", NULL, "ADC Engine" },
{ "Right Decimator", NULL, "ADC Engine" },
{ "AIFCLK", NULL, "SYSCLK" },
{ "Playback", NULL, "Serial Input Routing" },
{ "Playback", NULL, "Serial Ports" },
{ "Playback", NULL, "Clock Domain Transfer" },
{ "Capture", NULL, "Serial Output Routing" },
{ "Capture", NULL, "Serial Ports" },
{ "Capture", NULL, "Clock Domain Transfer" },
{ "AOUTL", NULL, "Left Lineout Mixer" },
{ "AOUTR", NULL, "Right Lineout Mixer" },
{ "SP", NULL, "Speaker" },
};
static const struct snd_soc_dapm_route adau1781_adc_dapm_routes[] = {
{ "Left PGA", NULL, "LMIC" },
{ "Right PGA", NULL, "RMIC" },
{ "Left Decimator", NULL, "Left PGA" },
{ "Right Decimator", NULL, "Right PGA" },
};
static const char * const adau1781_dmic_select_text[] = {
"DMIC1", "DMIC2",
};
static SOC_ENUM_SINGLE_VIRT_DECL(adau1781_dmic_select_enum,
adau1781_dmic_select_text);
static const struct snd_kcontrol_new adau1781_dmic_mux =
SOC_DAPM_ENUM("DMIC Select", adau1781_dmic_select_enum);
static const struct snd_soc_dapm_widget adau1781_dmic_dapm_widgets[] = {
SND_SOC_DAPM_MUX("DMIC Select", SND_SOC_NOPM, 0, 0, &adau1781_dmic_mux),
SND_SOC_DAPM_ADC("DMIC1", NULL, ADAU1781_DMIC_BEEP_CTRL, 4, 0),
SND_SOC_DAPM_ADC("DMIC2", NULL, ADAU1781_DMIC_BEEP_CTRL, 5, 0),
};
static const struct snd_soc_dapm_route adau1781_dmic_dapm_routes[] = {
{ "DMIC1", NULL, "LMIC" },
{ "DMIC2", NULL, "RMIC" },
{ "DMIC1", NULL, "Digital Mic" },
{ "DMIC2", NULL, "Digital Mic" },
{ "DMIC Select", "DMIC1", "DMIC1" },
{ "DMIC Select", "DMIC2", "DMIC2" },
{ "Left Decimator", NULL, "DMIC Select" },
{ "Right Decimator", NULL, "DMIC Select" },
};
static int adau1781_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
struct adau *adau = snd_soc_codec_get_drvdata(codec);
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN);
/* Precharge */
regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0x8, 0x8);
break;
case SND_SOC_BIAS_OFF:
regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0xc, 0x0);
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, 0);
break;
}
codec->dapm.bias_level = level;
return 0;
}
static bool adau1781_readable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADAU1781_DMIC_BEEP_CTRL:
case ADAU1781_LEFT_PGA:
case ADAU1781_RIGHT_PGA:
case ADAU1781_LEFT_PLAYBACK_MIXER:
case ADAU1781_RIGHT_PLAYBACK_MIXER:
case ADAU1781_MONO_PLAYBACK_MIXER:
case ADAU1781_LEFT_LINEOUT:
case ADAU1781_RIGHT_LINEOUT:
case ADAU1781_SPEAKER:
case ADAU1781_BEEP_ZC:
case ADAU1781_DEJITTER:
case ADAU1781_DIG_PWDN0:
case ADAU1781_DIG_PWDN1:
return true;
default:
break;
}
return adau17x1_readable_register(dev, reg);
}
static int adau1781_set_input_mode(struct adau *adau, unsigned int reg,
bool differential)
{
unsigned int val;
if (differential)
val = ADAU1781_INPUT_DIFFERNTIAL;
else
val = 0;
return regmap_update_bits(adau->regmap, reg,
ADAU1781_INPUT_DIFFERNTIAL, val);
}
static int adau1781_codec_probe(struct snd_soc_codec *codec)
{
struct adau1781_platform_data *pdata = dev_get_platdata(codec->dev);
struct adau *adau = snd_soc_codec_get_drvdata(codec);
const char *firmware;
int ret;
ret = adau17x1_add_widgets(codec);
if (ret)
return ret;
if (pdata) {
ret = adau1781_set_input_mode(adau, ADAU1781_LEFT_PGA,
pdata->left_input_differential);
if (ret)
return ret;
ret = adau1781_set_input_mode(adau, ADAU1781_RIGHT_PGA,
pdata->right_input_differential);
if (ret)
return ret;
}
if (pdata && pdata->use_dmic) {
ret = snd_soc_dapm_new_controls(&codec->dapm,
adau1781_dmic_dapm_widgets,
ARRAY_SIZE(adau1781_dmic_dapm_widgets));
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau1781_dmic_dapm_routes,
ARRAY_SIZE(adau1781_dmic_dapm_routes));
if (ret)
return ret;
} else {
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau1781_adc_dapm_routes,
ARRAY_SIZE(adau1781_adc_dapm_routes));
if (ret)
return ret;
}
switch (adau->type) {
case ADAU1381:
firmware = ADAU1381_FIRMWARE;
break;
case ADAU1781:
firmware = ADAU1781_FIRMWARE;
break;
default:
return -EINVAL;
}
ret = adau17x1_add_routes(codec);
if (ret < 0)
return ret;
ret = adau17x1_load_firmware(adau, codec->dev, firmware);
if (ret)
dev_warn(codec->dev, "Failed to load firmware\n");
return 0;
}
static const struct snd_soc_codec_driver adau1781_codec_driver = {
.probe = adau1781_codec_probe,
.resume = adau17x1_resume,
.set_bias_level = adau1781_set_bias_level,
.suspend_bias_off = true,
.controls = adau1781_controls,
.num_controls = ARRAY_SIZE(adau1781_controls),
.dapm_widgets = adau1781_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1781_dapm_widgets),
.dapm_routes = adau1781_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1781_dapm_routes),
};
#define ADAU1781_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver adau1781_dai_driver = {
.name = "adau-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1781_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1781_FORMATS,
},
.ops = &adau17x1_dai_ops,
};
const struct regmap_config adau1781_regmap_config = {
.val_bits = 8,
.reg_bits = 16,
.max_register = 0x40f8,
.reg_defaults = adau1781_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(adau1781_reg_defaults),
.readable_reg = adau1781_readable_register,
.volatile_reg = adau17x1_volatile_register,
.cache_type = REGCACHE_RBTREE,
};
EXPORT_SYMBOL_GPL(adau1781_regmap_config);
int adau1781_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev))
{
int ret;
ret = adau17x1_probe(dev, regmap, type, switch_mode);
if (ret)
return ret;
return snd_soc_register_codec(dev, &adau1781_codec_driver,
&adau1781_dai_driver, 1);
}
EXPORT_SYMBOL_GPL(adau1781_probe);
MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,23 @@
/*
* ADAU1381/ADAU1781 driver
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#ifndef __SOUND_SOC_CODECS_ADAU1781_H__
#define __SOUND_SOC_CODECS_ADAU1781_H__
#include <linux/regmap.h>
#include "adau17x1.h"
struct device;
int adau1781_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev));
extern const struct regmap_config adau1781_regmap_config;
#endif

858
sound/soc/codecs/adau17x1.c Normal file
View file

@ -0,0 +1,858 @@
/*
* Common code for ADAU1X61 and ADAU1X81 codecs
*
* Copyright 2011-2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/gcd.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include "sigmadsp.h"
#include "adau17x1.h"
static const char * const adau17x1_capture_mixer_boost_text[] = {
"Normal operation", "Boost Level 1", "Boost Level 2", "Boost Level 3",
};
static SOC_ENUM_SINGLE_DECL(adau17x1_capture_boost_enum,
ADAU17X1_REC_POWER_MGMT, 5, adau17x1_capture_mixer_boost_text);
static const char * const adau17x1_mic_bias_mode_text[] = {
"Normal operation", "High performance",
};
static SOC_ENUM_SINGLE_DECL(adau17x1_mic_bias_mode_enum,
ADAU17X1_MICBIAS, 3, adau17x1_mic_bias_mode_text);
static const DECLARE_TLV_DB_MINMAX(adau17x1_digital_tlv, -9563, 0);
static const struct snd_kcontrol_new adau17x1_controls[] = {
SOC_DOUBLE_R_TLV("Digital Capture Volume",
ADAU17X1_LEFT_INPUT_DIGITAL_VOL,
ADAU17X1_RIGHT_INPUT_DIGITAL_VOL,
0, 0xff, 1, adau17x1_digital_tlv),
SOC_DOUBLE_R_TLV("Digital Playback Volume", ADAU17X1_DAC_CONTROL1,
ADAU17X1_DAC_CONTROL2, 0, 0xff, 1, adau17x1_digital_tlv),
SOC_SINGLE("ADC High Pass Filter Switch", ADAU17X1_ADC_CONTROL,
5, 1, 0),
SOC_SINGLE("Playback De-emphasis Switch", ADAU17X1_DAC_CONTROL0,
2, 1, 0),
SOC_ENUM("Capture Boost", adau17x1_capture_boost_enum),
SOC_ENUM("Mic Bias Mode", adau17x1_mic_bias_mode_enum),
};
static int adau17x1_pll_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct adau *adau = snd_soc_codec_get_drvdata(w->codec);
int ret;
if (SND_SOC_DAPM_EVENT_ON(event)) {
adau->pll_regs[5] = 1;
} else {
adau->pll_regs[5] = 0;
/* Bypass the PLL when disabled, otherwise registers will become
* inaccessible. */
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL, 0);
}
/* The PLL register is 6 bytes long and can only be written at once. */
ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL,
adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
if (SND_SOC_DAPM_EVENT_ON(event)) {
mdelay(5);
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL,
ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL);
}
return 0;
}
static const char * const adau17x1_mono_stereo_text[] = {
"Stereo",
"Mono Left Channel (L+R)",
"Mono Right Channel (L+R)",
"Mono (L+R)",
};
static SOC_ENUM_SINGLE_DECL(adau17x1_dac_mode_enum,
ADAU17X1_DAC_CONTROL0, 6, adau17x1_mono_stereo_text);
static const struct snd_kcontrol_new adau17x1_dac_mode_mux =
SOC_DAPM_ENUM("DAC Mono-Stereo-Mode", adau17x1_dac_mode_enum);
static const struct snd_soc_dapm_widget adau17x1_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY_S("PLL", 3, SND_SOC_NOPM, 0, 0, adau17x1_pll_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("AIFCLK", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU17X1_MICBIAS, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Left Playback Enable", ADAU17X1_PLAY_POWER_MGMT,
0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Right Playback Enable", ADAU17X1_PLAY_POWER_MGMT,
1, 0, NULL, 0),
SND_SOC_DAPM_MUX("Left DAC Mode Mux", SND_SOC_NOPM, 0, 0,
&adau17x1_dac_mode_mux),
SND_SOC_DAPM_MUX("Right DAC Mode Mux", SND_SOC_NOPM, 0, 0,
&adau17x1_dac_mode_mux),
SND_SOC_DAPM_ADC("Left Decimator", NULL, ADAU17X1_ADC_CONTROL, 0, 0),
SND_SOC_DAPM_ADC("Right Decimator", NULL, ADAU17X1_ADC_CONTROL, 1, 0),
SND_SOC_DAPM_DAC("Left DAC", NULL, ADAU17X1_DAC_CONTROL0, 0, 0),
SND_SOC_DAPM_DAC("Right DAC", NULL, ADAU17X1_DAC_CONTROL0, 1, 0),
};
static const struct snd_soc_dapm_route adau17x1_dapm_routes[] = {
{ "Left Decimator", NULL, "SYSCLK" },
{ "Right Decimator", NULL, "SYSCLK" },
{ "Left DAC", NULL, "SYSCLK" },
{ "Right DAC", NULL, "SYSCLK" },
{ "Capture", NULL, "SYSCLK" },
{ "Playback", NULL, "SYSCLK" },
{ "Left DAC", NULL, "Left DAC Mode Mux" },
{ "Right DAC", NULL, "Right DAC Mode Mux" },
{ "Capture", NULL, "AIFCLK" },
{ "Playback", NULL, "AIFCLK" },
};
static const struct snd_soc_dapm_route adau17x1_dapm_pll_route = {
"SYSCLK", NULL, "PLL",
};
/*
* The MUX register for the Capture and Playback MUXs selects either DSP as
* source/destination or one of the TDM slots. The TDM slot is selected via
* snd_soc_dai_set_tdm_slot(), so we only expose whether to go to the DSP or
* directly to the DAI interface with this control.
*/
static int adau17x1_dsp_mux_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol);
struct adau *adau = snd_soc_codec_get_drvdata(codec);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
struct snd_soc_dapm_update update;
unsigned int stream = e->shift_l;
unsigned int val, change;
int reg;
if (ucontrol->value.enumerated.item[0] >= e->items)
return -EINVAL;
switch (ucontrol->value.enumerated.item[0]) {
case 0:
val = 0;
adau->dsp_bypass[stream] = false;
break;
default:
val = (adau->tdm_slot[stream] * 2) + 1;
adau->dsp_bypass[stream] = true;
break;
}
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
reg = ADAU17X1_SERIAL_INPUT_ROUTE;
else
reg = ADAU17X1_SERIAL_OUTPUT_ROUTE;
change = snd_soc_test_bits(codec, reg, 0xff, val);
if (change) {
update.kcontrol = kcontrol;
update.mask = 0xff;
update.reg = reg;
update.val = val;
snd_soc_dapm_mux_update_power(&codec->dapm, kcontrol,
ucontrol->value.enumerated.item[0], e, &update);
}
return change;
}
static int adau17x1_dsp_mux_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol);
struct adau *adau = snd_soc_codec_get_drvdata(codec);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int stream = e->shift_l;
unsigned int reg, val;
int ret;
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
reg = ADAU17X1_SERIAL_INPUT_ROUTE;
else
reg = ADAU17X1_SERIAL_OUTPUT_ROUTE;
ret = regmap_read(adau->regmap, reg, &val);
if (ret)
return ret;
if (val != 0)
val = 1;
ucontrol->value.enumerated.item[0] = val;
return 0;
}
#define DECLARE_ADAU17X1_DSP_MUX_CTRL(_name, _label, _stream, _text) \
const struct snd_kcontrol_new _name = \
SOC_DAPM_ENUM_EXT(_label, (const struct soc_enum)\
SOC_ENUM_SINGLE(SND_SOC_NOPM, _stream, \
ARRAY_SIZE(_text), _text), \
adau17x1_dsp_mux_enum_get, adau17x1_dsp_mux_enum_put)
static const char * const adau17x1_dac_mux_text[] = {
"DSP",
"AIFIN",
};
static const char * const adau17x1_capture_mux_text[] = {
"DSP",
"Decimator",
};
static DECLARE_ADAU17X1_DSP_MUX_CTRL(adau17x1_dac_mux, "DAC Playback Mux",
SNDRV_PCM_STREAM_PLAYBACK, adau17x1_dac_mux_text);
static DECLARE_ADAU17X1_DSP_MUX_CTRL(adau17x1_capture_mux, "Capture Mux",
SNDRV_PCM_STREAM_CAPTURE, adau17x1_capture_mux_text);
static const struct snd_soc_dapm_widget adau17x1_dsp_dapm_widgets[] = {
SND_SOC_DAPM_PGA("DSP", ADAU17X1_DSP_RUN, 0, 0, NULL, 0),
SND_SOC_DAPM_SIGGEN("DSP Siggen"),
SND_SOC_DAPM_MUX("DAC Playback Mux", SND_SOC_NOPM, 0, 0,
&adau17x1_dac_mux),
SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0,
&adau17x1_capture_mux),
};
static const struct snd_soc_dapm_route adau17x1_dsp_dapm_routes[] = {
{ "DAC Playback Mux", "DSP", "DSP" },
{ "DAC Playback Mux", "AIFIN", "Playback" },
{ "Left DAC Mode Mux", "Stereo", "DAC Playback Mux" },
{ "Left DAC Mode Mux", "Mono (L+R)", "DAC Playback Mux" },
{ "Left DAC Mode Mux", "Mono Left Channel (L+R)", "DAC Playback Mux" },
{ "Right DAC Mode Mux", "Stereo", "DAC Playback Mux" },
{ "Right DAC Mode Mux", "Mono (L+R)", "DAC Playback Mux" },
{ "Right DAC Mode Mux", "Mono Right Channel (L+R)", "DAC Playback Mux" },
{ "Capture Mux", "DSP", "DSP" },
{ "Capture Mux", "Decimator", "Left Decimator" },
{ "Capture Mux", "Decimator", "Right Decimator" },
{ "Capture", NULL, "Capture Mux" },
{ "DSP", NULL, "DSP Siggen" },
{ "DSP", NULL, "Left Decimator" },
{ "DSP", NULL, "Right Decimator" },
};
static const struct snd_soc_dapm_route adau17x1_no_dsp_dapm_routes[] = {
{ "Left DAC Mode Mux", "Stereo", "Playback" },
{ "Left DAC Mode Mux", "Mono (L+R)", "Playback" },
{ "Left DAC Mode Mux", "Mono Left Channel (L+R)", "Playback" },
{ "Right DAC Mode Mux", "Stereo", "Playback" },
{ "Right DAC Mode Mux", "Mono (L+R)", "Playback" },
{ "Right DAC Mode Mux", "Mono Right Channel (L+R)", "Playback" },
{ "Capture", NULL, "Left Decimator" },
{ "Capture", NULL, "Right Decimator" },
};
bool adau17x1_has_dsp(struct adau *adau)
{
switch (adau->type) {
case ADAU1761:
case ADAU1381:
case ADAU1781:
return true;
default:
return false;
}
}
EXPORT_SYMBOL_GPL(adau17x1_has_dsp);
static int adau17x1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct snd_soc_codec *codec = dai->codec;
struct adau *adau = snd_soc_codec_get_drvdata(codec);
unsigned int val, div, dsp_div;
unsigned int freq;
if (adau->clk_src == ADAU17X1_CLK_SRC_PLL)
freq = adau->pll_freq;
else
freq = adau->sysclk;
if (freq % params_rate(params) != 0)
return -EINVAL;
switch (freq / params_rate(params)) {
case 1024: /* fs */
div = 0;
dsp_div = 1;
break;
case 6144: /* fs / 6 */
div = 1;
dsp_div = 6;
break;
case 4096: /* fs / 4 */
div = 2;
dsp_div = 5;
break;
case 3072: /* fs / 3 */
div = 3;
dsp_div = 4;
break;
case 2048: /* fs / 2 */
div = 4;
dsp_div = 3;
break;
case 1536: /* fs / 1.5 */
div = 5;
dsp_div = 2;
break;
case 512: /* fs / 0.5 */
div = 6;
dsp_div = 0;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0,
ADAU17X1_CONVERTER0_CONVSR_MASK, div);
if (adau17x1_has_dsp(adau)) {
regmap_write(adau->regmap, ADAU17X1_SERIAL_SAMPLING_RATE, div);
regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, dsp_div);
}
if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
return 0;
switch (params_width(params)) {
case 16:
val = ADAU17X1_SERIAL_PORT1_DELAY16;
break;
case 24:
val = ADAU17X1_SERIAL_PORT1_DELAY8;
break;
case 32:
val = ADAU17X1_SERIAL_PORT1_DELAY0;
break;
default:
return -EINVAL;
}
return regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT1,
ADAU17X1_SERIAL_PORT1_DELAY_MASK, val);
}
static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id,
int source, unsigned int freq_in, unsigned int freq_out)
{
struct snd_soc_codec *codec = dai->codec;
struct adau *adau = snd_soc_codec_get_drvdata(codec);
unsigned int r, n, m, i, j;
unsigned int div;
int ret;
if (freq_in < 8000000 || freq_in > 27000000)
return -EINVAL;
if (!freq_out) {
r = 0;
n = 0;
m = 0;
div = 0;
} else {
if (freq_out % freq_in != 0) {
div = DIV_ROUND_UP(freq_in, 13500000);
freq_in /= div;
r = freq_out / freq_in;
i = freq_out % freq_in;
j = gcd(i, freq_in);
n = i / j;
m = freq_in / j;
div--;
} else {
r = freq_out / freq_in;
n = 0;
m = 0;
div = 0;
}
if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2)
return -EINVAL;
}
adau->pll_regs[0] = m >> 8;
adau->pll_regs[1] = m & 0xff;
adau->pll_regs[2] = n >> 8;
adau->pll_regs[3] = n & 0xff;
adau->pll_regs[4] = (r << 3) | (div << 1);
if (m != 0)
adau->pll_regs[4] |= 1; /* Fractional mode */
/* The PLL register is 6 bytes long and can only be written at once. */
ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL,
adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
if (ret)
return ret;
adau->pll_freq = freq_out;
return 0;
}
static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
struct adau *adau = snd_soc_codec_get_drvdata(dai->codec);
struct snd_soc_dapm_context *dapm = &dai->codec->dapm;
switch (clk_id) {
case ADAU17X1_CLK_SRC_MCLK:
case ADAU17X1_CLK_SRC_PLL:
break;
default:
return -EINVAL;
}
adau->sysclk = freq;
if (adau->clk_src != clk_id) {
if (clk_id == ADAU17X1_CLK_SRC_PLL) {
snd_soc_dapm_add_routes(dapm,
&adau17x1_dapm_pll_route, 1);
} else {
snd_soc_dapm_del_routes(dapm,
&adau17x1_dapm_pll_route, 1);
}
}
adau->clk_src = clk_id;
return 0;
}
static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct adau *adau = snd_soc_codec_get_drvdata(dai->codec);
unsigned int ctrl0, ctrl1;
int lrclk_pol;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
ctrl0 = ADAU17X1_SERIAL_PORT0_MASTER;
adau->master = true;
break;
case SND_SOC_DAIFMT_CBS_CFS:
ctrl0 = 0;
adau->master = false;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
lrclk_pol = 0;
ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY1;
break;
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_RIGHT_J:
lrclk_pol = 1;
ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY0;
break;
case SND_SOC_DAIFMT_DSP_A:
lrclk_pol = 1;
ctrl0 |= ADAU17X1_SERIAL_PORT0_PULSE_MODE;
ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY1;
break;
case SND_SOC_DAIFMT_DSP_B:
lrclk_pol = 1;
ctrl0 |= ADAU17X1_SERIAL_PORT0_PULSE_MODE;
ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY0;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_NF:
ctrl0 |= ADAU17X1_SERIAL_PORT0_BCLK_POL;
break;
case SND_SOC_DAIFMT_NB_IF:
lrclk_pol = !lrclk_pol;
break;
case SND_SOC_DAIFMT_IB_IF:
ctrl0 |= ADAU17X1_SERIAL_PORT0_BCLK_POL;
lrclk_pol = !lrclk_pol;
break;
default:
return -EINVAL;
}
if (lrclk_pol)
ctrl0 |= ADAU17X1_SERIAL_PORT0_LRCLK_POL;
regmap_write(adau->regmap, ADAU17X1_SERIAL_PORT0, ctrl0);
regmap_write(adau->regmap, ADAU17X1_SERIAL_PORT1, ctrl1);
adau->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
return 0;
}
static int adau17x1_set_dai_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
{
struct adau *adau = snd_soc_codec_get_drvdata(dai->codec);
unsigned int ser_ctrl0, ser_ctrl1;
unsigned int conv_ctrl0, conv_ctrl1;
/* I2S mode */
if (slots == 0) {
slots = 2;
rx_mask = 3;
tx_mask = 3;
slot_width = 32;
}
switch (slots) {
case 2:
ser_ctrl0 = ADAU17X1_SERIAL_PORT0_STEREO;
break;
case 4:
ser_ctrl0 = ADAU17X1_SERIAL_PORT0_TDM4;
break;
case 8:
if (adau->type == ADAU1361)
return -EINVAL;
ser_ctrl0 = ADAU17X1_SERIAL_PORT0_TDM8;
break;
default:
return -EINVAL;
}
switch (slot_width * slots) {
case 32:
if (adau->type == ADAU1761)
return -EINVAL;
ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK32;
break;
case 64:
ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK64;
break;
case 48:
ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK48;
break;
case 128:
ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK128;
break;
case 256:
if (adau->type == ADAU1361)
return -EINVAL;
ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK256;
break;
default:
return -EINVAL;
}
switch (rx_mask) {
case 0x03:
conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(1);
adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 0;
break;
case 0x0c:
conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(2);
adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 1;
break;
case 0x30:
conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(3);
adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 2;
break;
case 0xc0:
conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(4);
adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 3;
break;
default:
return -EINVAL;
}
switch (tx_mask) {
case 0x03:
conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(1);
adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 0;
break;
case 0x0c:
conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(2);
adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 1;
break;
case 0x30:
conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(3);
adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 2;
break;
case 0xc0:
conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(4);
adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 3;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0,
ADAU17X1_CONVERTER0_DAC_PAIR_MASK, conv_ctrl0);
regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER1,
ADAU17X1_CONVERTER1_ADC_PAIR_MASK, conv_ctrl1);
regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT0,
ADAU17X1_SERIAL_PORT0_TDM_MASK, ser_ctrl0);
regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT1,
ADAU17X1_SERIAL_PORT1_BCLK_MASK, ser_ctrl1);
if (!adau17x1_has_dsp(adau))
return 0;
if (adau->dsp_bypass[SNDRV_PCM_STREAM_PLAYBACK]) {
regmap_write(adau->regmap, ADAU17X1_SERIAL_INPUT_ROUTE,
(adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] * 2) + 1);
}
if (adau->dsp_bypass[SNDRV_PCM_STREAM_CAPTURE]) {
regmap_write(adau->regmap, ADAU17X1_SERIAL_OUTPUT_ROUTE,
(adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] * 2) + 1);
}
return 0;
}
const struct snd_soc_dai_ops adau17x1_dai_ops = {
.hw_params = adau17x1_hw_params,
.set_sysclk = adau17x1_set_dai_sysclk,
.set_fmt = adau17x1_set_dai_fmt,
.set_pll = adau17x1_set_dai_pll,
.set_tdm_slot = adau17x1_set_dai_tdm_slot,
};
EXPORT_SYMBOL_GPL(adau17x1_dai_ops);
int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec,
enum adau17x1_micbias_voltage micbias)
{
struct adau *adau = snd_soc_codec_get_drvdata(codec);
switch (micbias) {
case ADAU17X1_MICBIAS_0_90_AVDD:
case ADAU17X1_MICBIAS_0_65_AVDD:
break;
default:
return -EINVAL;
}
return regmap_write(adau->regmap, ADAU17X1_MICBIAS, micbias << 2);
}
EXPORT_SYMBOL_GPL(adau17x1_set_micbias_voltage);
bool adau17x1_readable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADAU17X1_CLOCK_CONTROL:
case ADAU17X1_PLL_CONTROL:
case ADAU17X1_REC_POWER_MGMT:
case ADAU17X1_MICBIAS:
case ADAU17X1_SERIAL_PORT0:
case ADAU17X1_SERIAL_PORT1:
case ADAU17X1_CONVERTER0:
case ADAU17X1_CONVERTER1:
case ADAU17X1_LEFT_INPUT_DIGITAL_VOL:
case ADAU17X1_RIGHT_INPUT_DIGITAL_VOL:
case ADAU17X1_ADC_CONTROL:
case ADAU17X1_PLAY_POWER_MGMT:
case ADAU17X1_DAC_CONTROL0:
case ADAU17X1_DAC_CONTROL1:
case ADAU17X1_DAC_CONTROL2:
case ADAU17X1_SERIAL_PORT_PAD:
case ADAU17X1_CONTROL_PORT_PAD0:
case ADAU17X1_CONTROL_PORT_PAD1:
case ADAU17X1_DSP_SAMPLING_RATE:
case ADAU17X1_SERIAL_INPUT_ROUTE:
case ADAU17X1_SERIAL_OUTPUT_ROUTE:
case ADAU17X1_DSP_ENABLE:
case ADAU17X1_DSP_RUN:
case ADAU17X1_SERIAL_SAMPLING_RATE:
return true;
default:
break;
}
return false;
}
EXPORT_SYMBOL_GPL(adau17x1_readable_register);
bool adau17x1_volatile_register(struct device *dev, unsigned int reg)
{
/* SigmaDSP parameter and program memory */
if (reg < 0x4000)
return true;
switch (reg) {
/* The PLL register is 6 bytes long */
case ADAU17X1_PLL_CONTROL:
case ADAU17X1_PLL_CONTROL + 1:
case ADAU17X1_PLL_CONTROL + 2:
case ADAU17X1_PLL_CONTROL + 3:
case ADAU17X1_PLL_CONTROL + 4:
case ADAU17X1_PLL_CONTROL + 5:
return true;
default:
break;
}
return false;
}
EXPORT_SYMBOL_GPL(adau17x1_volatile_register);
int adau17x1_load_firmware(struct adau *adau, struct device *dev,
const char *firmware)
{
int ret;
int dspsr;
ret = regmap_read(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, &dspsr);
if (ret)
return ret;
regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 1);
regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, 0xf);
ret = process_sigma_firmware_regmap(dev, adau->regmap, firmware);
if (ret) {
regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 0);
return ret;
}
regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, dspsr);
return 0;
}
EXPORT_SYMBOL_GPL(adau17x1_load_firmware);
int adau17x1_add_widgets(struct snd_soc_codec *codec)
{
struct adau *adau = snd_soc_codec_get_drvdata(codec);
int ret;
ret = snd_soc_add_codec_controls(codec, adau17x1_controls,
ARRAY_SIZE(adau17x1_controls));
if (ret)
return ret;
ret = snd_soc_dapm_new_controls(&codec->dapm, adau17x1_dapm_widgets,
ARRAY_SIZE(adau17x1_dapm_widgets));
if (ret)
return ret;
if (adau17x1_has_dsp(adau)) {
ret = snd_soc_dapm_new_controls(&codec->dapm,
adau17x1_dsp_dapm_widgets,
ARRAY_SIZE(adau17x1_dsp_dapm_widgets));
}
return ret;
}
EXPORT_SYMBOL_GPL(adau17x1_add_widgets);
int adau17x1_add_routes(struct snd_soc_codec *codec)
{
struct adau *adau = snd_soc_codec_get_drvdata(codec);
int ret;
ret = snd_soc_dapm_add_routes(&codec->dapm, adau17x1_dapm_routes,
ARRAY_SIZE(adau17x1_dapm_routes));
if (ret)
return ret;
if (adau17x1_has_dsp(adau)) {
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau17x1_dsp_dapm_routes,
ARRAY_SIZE(adau17x1_dsp_dapm_routes));
} else {
ret = snd_soc_dapm_add_routes(&codec->dapm,
adau17x1_no_dsp_dapm_routes,
ARRAY_SIZE(adau17x1_no_dsp_dapm_routes));
}
return ret;
}
EXPORT_SYMBOL_GPL(adau17x1_add_routes);
int adau17x1_resume(struct snd_soc_codec *codec)
{
struct adau *adau = snd_soc_codec_get_drvdata(codec);
if (adau->switch_mode)
adau->switch_mode(codec->dev);
regcache_sync(adau->regmap);
return 0;
}
EXPORT_SYMBOL_GPL(adau17x1_resume);
int adau17x1_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev))
{
struct adau *adau;
if (IS_ERR(regmap))
return PTR_ERR(regmap);
adau = devm_kzalloc(dev, sizeof(*adau), GFP_KERNEL);
if (!adau)
return -ENOMEM;
adau->regmap = regmap;
adau->switch_mode = switch_mode;
adau->type = type;
dev_set_drvdata(dev, adau);
if (switch_mode)
switch_mode(dev);
return 0;
}
EXPORT_SYMBOL_GPL(adau17x1_probe);
MODULE_DESCRIPTION("ASoC ADAU1X61/ADAU1X81 common code");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

123
sound/soc/codecs/adau17x1.h Normal file
View file

@ -0,0 +1,123 @@
#ifndef __ADAU17X1_H__
#define __ADAU17X1_H__
#include <linux/regmap.h>
#include <linux/platform_data/adau17x1.h>
enum adau17x1_type {
ADAU1361,
ADAU1761,
ADAU1381,
ADAU1781,
};
enum adau17x1_pll {
ADAU17X1_PLL,
};
enum adau17x1_pll_src {
ADAU17X1_PLL_SRC_MCLK,
};
enum adau17x1_clk_src {
ADAU17X1_CLK_SRC_MCLK,
ADAU17X1_CLK_SRC_PLL,
};
struct adau {
unsigned int sysclk;
unsigned int pll_freq;
enum adau17x1_clk_src clk_src;
enum adau17x1_type type;
void (*switch_mode)(struct device *dev);
unsigned int dai_fmt;
uint8_t pll_regs[6];
bool master;
unsigned int tdm_slot[2];
bool dsp_bypass[2];
struct regmap *regmap;
};
int adau17x1_add_widgets(struct snd_soc_codec *codec);
int adau17x1_add_routes(struct snd_soc_codec *codec);
int adau17x1_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev));
int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec,
enum adau17x1_micbias_voltage micbias);
bool adau17x1_readable_register(struct device *dev, unsigned int reg);
bool adau17x1_volatile_register(struct device *dev, unsigned int reg);
int adau17x1_resume(struct snd_soc_codec *codec);
extern const struct snd_soc_dai_ops adau17x1_dai_ops;
int adau17x1_load_firmware(struct adau *adau, struct device *dev,
const char *firmware);
bool adau17x1_has_dsp(struct adau *adau);
#define ADAU17X1_CLOCK_CONTROL 0x4000
#define ADAU17X1_PLL_CONTROL 0x4002
#define ADAU17X1_REC_POWER_MGMT 0x4009
#define ADAU17X1_MICBIAS 0x4010
#define ADAU17X1_SERIAL_PORT0 0x4015
#define ADAU17X1_SERIAL_PORT1 0x4016
#define ADAU17X1_CONVERTER0 0x4017
#define ADAU17X1_CONVERTER1 0x4018
#define ADAU17X1_LEFT_INPUT_DIGITAL_VOL 0x401a
#define ADAU17X1_RIGHT_INPUT_DIGITAL_VOL 0x401b
#define ADAU17X1_ADC_CONTROL 0x4019
#define ADAU17X1_PLAY_POWER_MGMT 0x4029
#define ADAU17X1_DAC_CONTROL0 0x402a
#define ADAU17X1_DAC_CONTROL1 0x402b
#define ADAU17X1_DAC_CONTROL2 0x402c
#define ADAU17X1_SERIAL_PORT_PAD 0x402d
#define ADAU17X1_CONTROL_PORT_PAD0 0x402f
#define ADAU17X1_CONTROL_PORT_PAD1 0x4030
#define ADAU17X1_DSP_SAMPLING_RATE 0x40eb
#define ADAU17X1_SERIAL_INPUT_ROUTE 0x40f2
#define ADAU17X1_SERIAL_OUTPUT_ROUTE 0x40f3
#define ADAU17X1_DSP_ENABLE 0x40f5
#define ADAU17X1_DSP_RUN 0x40f6
#define ADAU17X1_SERIAL_SAMPLING_RATE 0x40f8
#define ADAU17X1_SERIAL_PORT0_BCLK_POL BIT(4)
#define ADAU17X1_SERIAL_PORT0_LRCLK_POL BIT(3)
#define ADAU17X1_SERIAL_PORT0_MASTER BIT(0)
#define ADAU17X1_SERIAL_PORT1_DELAY1 0x00
#define ADAU17X1_SERIAL_PORT1_DELAY0 0x01
#define ADAU17X1_SERIAL_PORT1_DELAY8 0x02
#define ADAU17X1_SERIAL_PORT1_DELAY16 0x03
#define ADAU17X1_SERIAL_PORT1_DELAY_MASK 0x03
#define ADAU17X1_CLOCK_CONTROL_INFREQ_MASK 0x6
#define ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL BIT(3)
#define ADAU17X1_CLOCK_CONTROL_SYSCLK_EN BIT(0)
#define ADAU17X1_SERIAL_PORT1_BCLK32 (0x0 << 5)
#define ADAU17X1_SERIAL_PORT1_BCLK48 (0x1 << 5)
#define ADAU17X1_SERIAL_PORT1_BCLK64 (0x2 << 5)
#define ADAU17X1_SERIAL_PORT1_BCLK128 (0x3 << 5)
#define ADAU17X1_SERIAL_PORT1_BCLK256 (0x4 << 5)
#define ADAU17X1_SERIAL_PORT1_BCLK_MASK (0x7 << 5)
#define ADAU17X1_SERIAL_PORT0_STEREO (0x0 << 1)
#define ADAU17X1_SERIAL_PORT0_TDM4 (0x1 << 1)
#define ADAU17X1_SERIAL_PORT0_TDM8 (0x2 << 1)
#define ADAU17X1_SERIAL_PORT0_TDM_MASK (0x3 << 1)
#define ADAU17X1_SERIAL_PORT0_PULSE_MODE BIT(5)
#define ADAU17X1_CONVERTER0_DAC_PAIR(x) (((x) - 1) << 5)
#define ADAU17X1_CONVERTER0_DAC_PAIR_MASK (0x3 << 5)
#define ADAU17X1_CONVERTER1_ADC_PAIR(x) ((x) - 1)
#define ADAU17X1_CONVERTER1_ADC_PAIR_MASK 0x3
#define ADAU17X1_CONVERTER0_CONVSR_MASK 0x7
#endif

View file

@ -0,0 +1,59 @@
/*
* ADAU1977/ADAU1978/ADAU1979 driver
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "adau1977.h"
static int adau1977_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct regmap_config config;
config = adau1977_regmap_config;
config.val_bits = 8;
config.reg_bits = 8;
return adau1977_probe(&client->dev,
devm_regmap_init_i2c(client, &config),
id->driver_data, NULL);
}
static int adau1977_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
return 0;
}
static const struct i2c_device_id adau1977_i2c_ids[] = {
{ "adau1977", ADAU1977 },
{ "adau1978", ADAU1978 },
{ "adau1979", ADAU1978 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids);
static struct i2c_driver adau1977_i2c_driver = {
.driver = {
.name = "adau1977",
.owner = THIS_MODULE,
},
.probe = adau1977_i2c_probe,
.remove = adau1977_i2c_remove,
.id_table = adau1977_i2c_ids,
};
module_i2c_driver(adau1977_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1977/ADAU1978/ADAU1979 driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,76 @@
/*
* ADAU1977/ADAU1978/ADAU1979 driver
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <sound/soc.h>
#include "adau1977.h"
static void adau1977_spi_switch_mode(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
/*
* To get the device into SPI mode CLATCH has to be pulled low three
* times. Do this by issuing three dummy reads.
*/
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
}
static int adau1977_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct regmap_config config;
if (!id)
return -EINVAL;
config = adau1977_regmap_config;
config.val_bits = 8;
config.reg_bits = 16;
config.read_flag_mask = 0x1;
return adau1977_probe(&spi->dev,
devm_regmap_init_spi(spi, &config),
id->driver_data, adau1977_spi_switch_mode);
}
static int adau1977_spi_remove(struct spi_device *spi)
{
snd_soc_unregister_codec(&spi->dev);
return 0;
}
static const struct spi_device_id adau1977_spi_ids[] = {
{ "adau1977", ADAU1977 },
{ "adau1978", ADAU1978 },
{ "adau1979", ADAU1978 },
{ }
};
MODULE_DEVICE_TABLE(spi, adau1977_spi_ids);
static struct spi_driver adau1977_spi_driver = {
.driver = {
.name = "adau1977",
.owner = THIS_MODULE,
},
.probe = adau1977_spi_probe,
.remove = adau1977_spi_remove,
.id_table = adau1977_spi_ids,
};
module_spi_driver(adau1977_spi_driver);
MODULE_DESCRIPTION("ASoC ADAU1977/ADAU1978/ADAU1979 driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");

1018
sound/soc/codecs/adau1977.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
/*
* ADAU1977/ADAU1978/ADAU1979 driver
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2.
*/
#ifndef __SOUND_SOC_CODECS_ADAU1977_H__
#define __SOUND_SOC_CODECS_ADAU1977_H__
#include <linux/regmap.h>
struct device;
enum adau1977_type {
ADAU1977,
ADAU1978,
ADAU1979,
};
int adau1977_probe(struct device *dev, struct regmap *regmap,
enum adau1977_type type, void (*switch_mode)(struct device *dev));
extern const struct regmap_config adau1977_regmap_config;
enum adau1977_clk_id {
ADAU1977_SYSCLK,
};
enum adau1977_sysclk_src {
ADAU1977_SYSCLK_SRC_MCLK,
ADAU1977_SYSCLK_SRC_LRCLK,
};
#endif

View file

@ -0,0 +1,53 @@
/*
* ADAV801 audio driver
*
* Copyright 2014 Analog Devices Inc.
*
* Licensed under the GPL-2.
*/
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "adav80x.h"
static const struct spi_device_id adav80x_spi_id[] = {
{ "adav801", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, adav80x_spi_id);
static int adav80x_spi_probe(struct spi_device *spi)
{
struct regmap_config config;
config = adav80x_regmap_config;
config.read_flag_mask = 0x01;
return adav80x_bus_probe(&spi->dev, devm_regmap_init_spi(spi, &config));
}
static int adav80x_spi_remove(struct spi_device *spi)
{
snd_soc_unregister_codec(&spi->dev);
return 0;
}
static struct spi_driver adav80x_spi_driver = {
.driver = {
.name = "adav801",
.owner = THIS_MODULE,
},
.probe = adav80x_spi_probe,
.remove = adav80x_spi_remove,
.id_table = adav80x_spi_id,
};
module_spi_driver(adav80x_spi_driver);
MODULE_DESCRIPTION("ASoC ADAV801 driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_AUTHOR("Yi Li <yi.li@analog.com>>");
MODULE_LICENSE("GPL");

Some files were not shown because too many files have changed in this diff Show more