Merge branch 'for-2.6.36' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6 into topic/asoc
This commit is contained in:
Коммит
1fab79b8a1
|
@ -25,6 +25,7 @@
|
|||
#include <asm/mach/time.h>
|
||||
#include <mach/kirkwood.h>
|
||||
#include <mach/bridge-regs.h>
|
||||
#include <plat/audio.h>
|
||||
#include <plat/cache-feroceon-l2.h>
|
||||
#include <plat/ehci-orion.h>
|
||||
#include <plat/mvsdio.h>
|
||||
|
@ -864,6 +865,42 @@ struct sys_timer kirkwood_timer = {
|
|||
.init = kirkwood_timer_init,
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* Audio
|
||||
****************************************************************************/
|
||||
static struct resource kirkwood_i2s_resources[] = {
|
||||
[0] = {
|
||||
.start = AUDIO_PHYS_BASE,
|
||||
.end = AUDIO_PHYS_BASE + SZ_16K - 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
[1] = {
|
||||
.start = IRQ_KIRKWOOD_I2S,
|
||||
.end = IRQ_KIRKWOOD_I2S,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct kirkwood_asoc_platform_data kirkwood_i2s_data = {
|
||||
.dram = &kirkwood_mbus_dram_info,
|
||||
.burst = 128,
|
||||
};
|
||||
|
||||
static struct platform_device kirkwood_i2s_device = {
|
||||
.name = "kirkwood-i2s",
|
||||
.id = -1,
|
||||
.num_resources = ARRAY_SIZE(kirkwood_i2s_resources),
|
||||
.resource = kirkwood_i2s_resources,
|
||||
.dev = {
|
||||
.platform_data = &kirkwood_i2s_data,
|
||||
},
|
||||
};
|
||||
|
||||
void __init kirkwood_audio_init(void)
|
||||
{
|
||||
kirkwood_clk_ctrl |= CGC_AUDIO;
|
||||
platform_device_register(&kirkwood_i2s_device);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* General
|
||||
|
@ -923,6 +960,7 @@ void __init kirkwood_init(void)
|
|||
kirkwood_spi_plat_data.tclk = kirkwood_tclk;
|
||||
kirkwood_uart0_data[0].uartclk = kirkwood_tclk;
|
||||
kirkwood_uart1_data[0].uartclk = kirkwood_tclk;
|
||||
kirkwood_i2s_data.tclk = kirkwood_tclk;
|
||||
|
||||
/*
|
||||
* Disable propagation of mbus errors to the CPU local bus,
|
||||
|
|
|
@ -17,6 +17,7 @@ struct mv_sata_platform_data;
|
|||
struct mvsdio_platform_data;
|
||||
struct mtd_partition;
|
||||
struct mtd_info;
|
||||
struct kirkwood_asoc_platform_data;
|
||||
|
||||
/*
|
||||
* Basic Kirkwood init functions used early by machine-setup.
|
||||
|
@ -43,6 +44,7 @@ void kirkwood_uart0_init(void);
|
|||
void kirkwood_uart1_init(void);
|
||||
void kirkwood_nand_init(struct mtd_partition *parts, int nr_parts, int delay);
|
||||
void kirkwood_nand_init_rnb(struct mtd_partition *parts, int nr_parts, int (*dev_ready)(struct mtd_info *));
|
||||
void kirkwood_audio_init(void);
|
||||
|
||||
extern int kirkwood_tclk;
|
||||
extern struct sys_timer kirkwood_timer;
|
||||
|
|
|
@ -96,6 +96,9 @@
|
|||
|
||||
#define SDIO_PHYS_BASE (KIRKWOOD_REGS_PHYS_BASE | 0x90000)
|
||||
|
||||
#define AUDIO_PHYS_BASE (KIRKWOOD_REGS_PHYS_BASE | 0xA0000)
|
||||
#define AUDIO_VIRT_BASE (KIRKWOOD_REGS_VIRT_BASE | 0xA0000)
|
||||
|
||||
/*
|
||||
* Supported devices and revisions.
|
||||
*/
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/ata_platform.h>
|
||||
#include <linux/mv643xx_eth.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include <asm/mach/arch.h>
|
||||
#include <mach/kirkwood.h>
|
||||
|
@ -60,6 +61,12 @@ static unsigned int openrd_mpp_config[] __initdata = {
|
|||
0
|
||||
};
|
||||
|
||||
static struct i2c_board_info i2c_board_info[] __initdata = {
|
||||
{
|
||||
I2C_BOARD_INFO("cs42l51", 0x4a),
|
||||
},
|
||||
};
|
||||
|
||||
static void __init openrd_init(void)
|
||||
{
|
||||
/*
|
||||
|
@ -80,6 +87,12 @@ static void __init openrd_init(void)
|
|||
kirkwood_sdio_init(&openrd_mvsdio_data);
|
||||
|
||||
kirkwood_i2c_init();
|
||||
|
||||
if (machine_is_openrd_client()) {
|
||||
i2c_register_board_info(0, i2c_board_info,
|
||||
ARRAY_SIZE(i2c_board_info));
|
||||
kirkwood_audio_init();
|
||||
}
|
||||
}
|
||||
|
||||
static int __init openrd_pci_init(void)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef __PLAT_AUDIO_H
|
||||
#define __PLAT_AUDIO_H
|
||||
|
||||
#include <linux/mbus.h>
|
||||
|
||||
struct kirkwood_asoc_platform_data {
|
||||
u32 tclk;
|
||||
struct mbus_dram_target_info *dram;
|
||||
int burst;
|
||||
};
|
||||
#endif
|
|
@ -273,6 +273,8 @@
|
|||
#define SND_SOC_DAPM_POST_PMD 0x8 /* after widget power down */
|
||||
#define SND_SOC_DAPM_PRE_REG 0x10 /* before audio path setup */
|
||||
#define SND_SOC_DAPM_POST_REG 0x20 /* after audio path setup */
|
||||
#define SND_SOC_DAPM_PRE_POST_PMD \
|
||||
(SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)
|
||||
|
||||
/* convenience event type detection */
|
||||
#define SND_SOC_DAPM_EVENT_ON(e) \
|
||||
|
|
|
@ -170,6 +170,21 @@
|
|||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = (unsigned long)&xenum }
|
||||
|
||||
#define SOC_DOUBLE_R_SX_TLV(xname, xreg_left, xreg_right, xshift,\
|
||||
xmin, xmax, tlv_array) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
|
||||
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
||||
.tlv.p = (tlv_array), \
|
||||
.info = snd_soc_info_volsw_2r_sx, \
|
||||
.get = snd_soc_get_volsw_2r_sx, \
|
||||
.put = snd_soc_put_volsw_2r_sx, \
|
||||
.private_value = (unsigned long)&(struct soc_mixer_control) \
|
||||
{.reg = xreg_left, \
|
||||
.rreg = xreg_right, .shift = xshift, \
|
||||
.min = xmin, .max = xmax} }
|
||||
|
||||
|
||||
/*
|
||||
* Simplified versions of above macros, declaring a struct and calculating
|
||||
* ARRAY_SIZE internally
|
||||
|
@ -329,6 +344,12 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol,
|
|||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_limit_volume(struct snd_soc_codec *codec,
|
||||
const char *name, int max);
|
||||
int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo);
|
||||
int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
|
||||
/**
|
||||
* struct snd_soc_jack_pin - Describes a pin to update based on jack detection
|
||||
|
|
|
@ -30,7 +30,9 @@ source "sound/soc/blackfin/Kconfig"
|
|||
source "sound/soc/davinci/Kconfig"
|
||||
source "sound/soc/fsl/Kconfig"
|
||||
source "sound/soc/imx/Kconfig"
|
||||
source "sound/soc/nuc900/Kconfig"
|
||||
source "sound/soc/omap/Kconfig"
|
||||
source "sound/soc/kirkwood/Kconfig"
|
||||
source "sound/soc/pxa/Kconfig"
|
||||
source "sound/soc/s3c24xx/Kconfig"
|
||||
source "sound/soc/s6000/Kconfig"
|
||||
|
|
|
@ -8,7 +8,9 @@ obj-$(CONFIG_SND_SOC) += blackfin/
|
|||
obj-$(CONFIG_SND_SOC) += davinci/
|
||||
obj-$(CONFIG_SND_SOC) += fsl/
|
||||
obj-$(CONFIG_SND_SOC) += imx/
|
||||
obj-$(CONFIG_SND_SOC) += nuc900/
|
||||
obj-$(CONFIG_SND_SOC) += omap/
|
||||
obj-$(CONFIG_SND_SOC) += kirkwood/
|
||||
obj-$(CONFIG_SND_SOC) += pxa/
|
||||
obj-$(CONFIG_SND_SOC) += s3c24xx/
|
||||
obj-$(CONFIG_SND_SOC) += s6000/
|
||||
|
|
|
@ -22,6 +22,7 @@ config SND_SOC_ALL_CODECS
|
|||
select SND_SOC_AK4642 if I2C
|
||||
select SND_SOC_AK4671 if I2C
|
||||
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
|
||||
select SND_SOC_CS42L51 if I2C
|
||||
select SND_SOC_CS4270 if I2C
|
||||
select SND_SOC_MAX9877 if I2C
|
||||
select SND_SOC_DA7210 if I2C
|
||||
|
@ -120,6 +121,9 @@ config SND_SOC_AK4671
|
|||
config SND_SOC_CQ0093VC
|
||||
tristate
|
||||
|
||||
config SND_SOC_CS42L51
|
||||
tristate
|
||||
|
||||
# Cirrus Logic CS4270 Codec
|
||||
config SND_SOC_CS4270
|
||||
tristate
|
||||
|
|
|
@ -9,6 +9,7 @@ snd-soc-ak4535-objs := ak4535.o
|
|||
snd-soc-ak4642-objs := ak4642.o
|
||||
snd-soc-ak4671-objs := ak4671.o
|
||||
snd-soc-cq93vc-objs := cq93vc.o
|
||||
snd-soc-cs42l51-objs := cs42l51.o
|
||||
snd-soc-cs4270-objs := cs4270.o
|
||||
snd-soc-cx20442-objs := cx20442.o
|
||||
snd-soc-da7210-objs := da7210.o
|
||||
|
@ -74,6 +75,7 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
|
|||
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
|
||||
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
|
||||
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
|
||||
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
|
||||
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
|
||||
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
|
||||
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
/* codec private data */
|
||||
struct ad193x_priv {
|
||||
unsigned int sysclk;
|
||||
struct snd_soc_codec codec;
|
||||
u8 reg_cache[AD193X_NUM_REGS];
|
||||
};
|
||||
|
@ -251,15 +252,32 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
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, reg = 0;
|
||||
int word_len = 0, reg = 0, master_rate = 0;
|
||||
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
|
@ -275,6 +293,25 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream,
|
|||
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;
|
||||
}
|
||||
|
||||
reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0);
|
||||
reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate;
|
||||
snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg);
|
||||
|
||||
reg = snd_soc_read(codec, AD193X_DAC_CTRL2);
|
||||
reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len;
|
||||
snd_soc_write(codec, AD193X_DAC_CTRL2, reg);
|
||||
|
@ -348,6 +385,7 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type)
|
|||
/* pll input: mclki/xi */
|
||||
snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */
|
||||
snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04);
|
||||
ad193x->sysclk = 12288000;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
|
@ -383,6 +421,7 @@ static 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,
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,11 @@
|
|||
|
||||
#define AD193X_PLL_CLK_CTRL0 0x800
|
||||
#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 0x801
|
||||
#define AD193X_DAC_CTRL0 0x802
|
||||
#define AD193X_DAC_POWERDOWN 0x01
|
||||
|
|
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
* cs42l51.c
|
||||
*
|
||||
* ASoC Driver for Cirrus Logic CS42L51 codecs
|
||||
*
|
||||
* Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com>
|
||||
*
|
||||
* Based on cs4270.c - Copyright (c) Freescale Semiconductor
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* For now:
|
||||
* - Only I2C is support. Not SPI
|
||||
* - master mode *NOT* supported
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include "cs42l51.h"
|
||||
|
||||
enum master_slave_mode {
|
||||
MODE_SLAVE,
|
||||
MODE_SLAVE_AUTO,
|
||||
MODE_MASTER,
|
||||
};
|
||||
|
||||
struct cs42l51_private {
|
||||
unsigned int mclk;
|
||||
unsigned int audio_mode; /* The mode (I2S or left-justified) */
|
||||
enum master_slave_mode func;
|
||||
struct snd_soc_codec codec;
|
||||
u8 reg_cache[CS42L51_NUMREGS];
|
||||
};
|
||||
|
||||
static struct snd_soc_codec *cs42l51_codec;
|
||||
|
||||
#define CS42L51_FORMATS ( \
|
||||
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
|
||||
SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE)
|
||||
|
||||
static int cs42l51_fill_cache(struct snd_soc_codec *codec)
|
||||
{
|
||||
u8 *cache = codec->reg_cache + 1;
|
||||
struct i2c_client *i2c_client = codec->control_data;
|
||||
s32 length;
|
||||
|
||||
length = i2c_smbus_read_i2c_block_data(i2c_client,
|
||||
CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache);
|
||||
if (length != CS42L51_NUMREGS) {
|
||||
dev_err(&i2c_client->dev,
|
||||
"I2C read failure, addr=0x%x (ret=%d vs %d)\n",
|
||||
i2c_client->addr, length, CS42L51_NUMREGS);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cs42l51_i2c_probe(struct i2c_client *i2c_client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
struct cs42l51_private *cs42l51;
|
||||
int ret = 0;
|
||||
int reg;
|
||||
|
||||
if (cs42l51_codec)
|
||||
return -EBUSY;
|
||||
|
||||
/* Verify that we have a CS42L51 */
|
||||
ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID);
|
||||
if (ret < 0) {
|
||||
dev_err(&i2c_client->dev, "failed to read I2C\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) &&
|
||||
(ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) {
|
||||
dev_err(&i2c_client->dev, "Invalid chip id\n");
|
||||
ret = -ENODEV;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n",
|
||||
ret & 7);
|
||||
|
||||
cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL);
|
||||
if (!cs42l51) {
|
||||
dev_err(&i2c_client->dev, "could not allocate codec\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
codec = &cs42l51->codec;
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->dev = &i2c_client->dev;
|
||||
codec->name = "CS42L51";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &cs42l51_dai;
|
||||
codec->num_dai = 1;
|
||||
snd_soc_codec_set_drvdata(codec, cs42l51);
|
||||
|
||||
codec->control_data = i2c_client;
|
||||
codec->reg_cache = cs42l51->reg_cache;
|
||||
codec->reg_cache_size = CS42L51_NUMREGS;
|
||||
i2c_set_clientdata(i2c_client, codec);
|
||||
|
||||
ret = cs42l51_fill_cache(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(&i2c_client->dev, "failed to fill register cache\n");
|
||||
goto error_alloc;
|
||||
}
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
|
||||
if (ret < 0) {
|
||||
dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto error_alloc;
|
||||
}
|
||||
|
||||
/*
|
||||
* DAC configuration
|
||||
* - Use signal processor
|
||||
* - auto mute
|
||||
* - vol changes immediate
|
||||
* - no de-emphasize
|
||||
*/
|
||||
reg = CS42L51_DAC_CTL_DATA_SEL(1)
|
||||
| CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0);
|
||||
ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg);
|
||||
if (ret < 0)
|
||||
goto error_alloc;
|
||||
|
||||
cs42l51_dai.dev = codec->dev;
|
||||
cs42l51_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto error_alloc;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&cs42l51_dai);
|
||||
if (ret < 0) {
|
||||
dev_err(&i2c_client->dev, "failed to register DAIe\n");
|
||||
goto error_reg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_reg:
|
||||
snd_soc_unregister_codec(codec);
|
||||
error_alloc:
|
||||
kfree(cs42l51);
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cs42l51_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct cs42l51_private *cs42l51 = i2c_get_clientdata(client);
|
||||
snd_soc_unregister_dai(&cs42l51_dai);
|
||||
snd_soc_unregister_codec(cs42l51_codec);
|
||||
cs42l51_codec = NULL;
|
||||
kfree(cs42l51);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct i2c_device_id cs42l51_id[] = {
|
||||
{"cs42l51", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, cs42l51_id);
|
||||
|
||||
static struct i2c_driver cs42l51_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "CS42L51 I2C",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.id_table = cs42l51_id,
|
||||
.probe = cs42l51_i2c_probe,
|
||||
.remove = cs42l51_i2c_remove,
|
||||
};
|
||||
|
||||
static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3;
|
||||
|
||||
switch (value) {
|
||||
default:
|
||||
case 0:
|
||||
ucontrol->value.integer.value[0] = 0;
|
||||
break;
|
||||
/* same value : (L+R)/2 and (R+L)/2 */
|
||||
case 1:
|
||||
case 2:
|
||||
ucontrol->value.integer.value[0] = 1;
|
||||
break;
|
||||
case 3:
|
||||
ucontrol->value.integer.value[0] = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CHAN_MIX_NORMAL 0x00
|
||||
#define CHAN_MIX_BOTH 0x55
|
||||
#define CHAN_MIX_SWAP 0xFF
|
||||
|
||||
static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
unsigned char val;
|
||||
|
||||
switch (ucontrol->value.integer.value[0]) {
|
||||
default:
|
||||
case 0:
|
||||
val = CHAN_MIX_NORMAL;
|
||||
break;
|
||||
case 1:
|
||||
val = CHAN_MIX_BOTH;
|
||||
break;
|
||||
case 2:
|
||||
val = CHAN_MIX_SWAP;
|
||||
break;
|
||||
}
|
||||
|
||||
snd_soc_write(codec, CS42L51_PCM_MIXER, val);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0);
|
||||
/* This is a lie. after -102 db, it stays at -102 */
|
||||
/* maybe a range would be better */
|
||||
static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0);
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0);
|
||||
static const char *chan_mix[] = {
|
||||
"L R",
|
||||
"L+R",
|
||||
"R L",
|
||||
};
|
||||
|
||||
static const struct soc_enum cs42l51_chan_mix =
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix);
|
||||
|
||||
static const struct snd_kcontrol_new cs42l51_snd_controls[] = {
|
||||
SOC_DOUBLE_R_SX_TLV("PCM Playback Volume",
|
||||
CS42L51_PCMA_VOL, CS42L51_PCMB_VOL,
|
||||
7, 0xffffff99, 0x18, adc_pcm_tlv),
|
||||
SOC_DOUBLE_R("PCM Playback Switch",
|
||||
CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1),
|
||||
SOC_DOUBLE_R_SX_TLV("Analog Playback Volume",
|
||||
CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL,
|
||||
8, 0xffffff19, 0x18, aout_tlv),
|
||||
SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume",
|
||||
CS42L51_ADCA_VOL, CS42L51_ADCB_VOL,
|
||||
7, 0xffffff99, 0x18, adc_pcm_tlv),
|
||||
SOC_DOUBLE_R("ADC Mixer Switch",
|
||||
CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1),
|
||||
SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0),
|
||||
SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0),
|
||||
SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0),
|
||||
SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0),
|
||||
SOC_DOUBLE_TLV("Mic Boost Volume",
|
||||
CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv),
|
||||
SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv),
|
||||
SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv),
|
||||
SOC_ENUM_EXT("PCM channel mixer",
|
||||
cs42l51_chan_mix,
|
||||
cs42l51_get_chan_mix, cs42l51_set_chan_mix),
|
||||
};
|
||||
|
||||
/*
|
||||
* to power down, one must:
|
||||
* 1.) Enable the PDN bit
|
||||
* 2.) enable power-down for the select channels
|
||||
* 3.) disable the PDN bit.
|
||||
*/
|
||||
static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
unsigned long value;
|
||||
|
||||
value = snd_soc_read(w->codec, CS42L51_POWER_CTL1);
|
||||
value &= ~CS42L51_POWER_CTL1_PDN;
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_PRE_PMD:
|
||||
value |= CS42L51_POWER_CTL1_PDN;
|
||||
break;
|
||||
default:
|
||||
case SND_SOC_DAPM_POST_PMD:
|
||||
break;
|
||||
}
|
||||
snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1,
|
||||
CS42L51_POWER_CTL1_PDN, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *cs42l51_dac_names[] = {"Direct PCM",
|
||||
"DSP PCM", "ADC"};
|
||||
static const struct soc_enum cs42l51_dac_mux_enum =
|
||||
SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names);
|
||||
static const struct snd_kcontrol_new cs42l51_dac_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum);
|
||||
|
||||
static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left",
|
||||
"MIC Left", "MIC+preamp Left"};
|
||||
static const struct soc_enum cs42l51_adcl_mux_enum =
|
||||
SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names);
|
||||
static const struct snd_kcontrol_new cs42l51_adcl_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum);
|
||||
|
||||
static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right",
|
||||
"MIC Right", "MIC+preamp Right"};
|
||||
static const struct soc_enum cs42l51_adcr_mux_enum =
|
||||
SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names);
|
||||
static const struct snd_kcontrol_new cs42l51_adcr_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum);
|
||||
|
||||
static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1),
|
||||
SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0,
|
||||
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
||||
SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0,
|
||||
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
||||
SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture",
|
||||
CS42L51_POWER_CTL1, 1, 1,
|
||||
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
||||
SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture",
|
||||
CS42L51_POWER_CTL1, 2, 1,
|
||||
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
||||
SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback",
|
||||
CS42L51_POWER_CTL1, 5, 1,
|
||||
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
||||
SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback",
|
||||
CS42L51_POWER_CTL1, 6, 1,
|
||||
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
||||
|
||||
/* analog/mic */
|
||||
SND_SOC_DAPM_INPUT("AIN1L"),
|
||||
SND_SOC_DAPM_INPUT("AIN1R"),
|
||||
SND_SOC_DAPM_INPUT("AIN2L"),
|
||||
SND_SOC_DAPM_INPUT("AIN2R"),
|
||||
SND_SOC_DAPM_INPUT("MICL"),
|
||||
SND_SOC_DAPM_INPUT("MICR"),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Mic Preamp Left",
|
||||
CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0),
|
||||
SND_SOC_DAPM_MIXER("Mic Preamp Right",
|
||||
CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0),
|
||||
|
||||
/* HP */
|
||||
SND_SOC_DAPM_OUTPUT("HPL"),
|
||||
SND_SOC_DAPM_OUTPUT("HPR"),
|
||||
|
||||
/* mux */
|
||||
SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0,
|
||||
&cs42l51_dac_mux_controls),
|
||||
SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0,
|
||||
&cs42l51_adcl_mux_controls),
|
||||
SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0,
|
||||
&cs42l51_adcr_mux_controls),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route cs42l51_routes[] = {
|
||||
{"HPL", NULL, "Left DAC"},
|
||||
{"HPR", NULL, "Right DAC"},
|
||||
|
||||
{"Left ADC", NULL, "Left PGA"},
|
||||
{"Right ADC", NULL, "Right PGA"},
|
||||
|
||||
{"Mic Preamp Left", NULL, "MICL"},
|
||||
{"Mic Preamp Right", NULL, "MICR"},
|
||||
|
||||
{"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" },
|
||||
{"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" },
|
||||
{"PGA-ADC Mux Left", "MIC Left", "MICL" },
|
||||
{"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" },
|
||||
{"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" },
|
||||
{"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" },
|
||||
{"PGA-ADC Mux Right", "MIC Right", "MICR" },
|
||||
{"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" },
|
||||
|
||||
{"Left PGA", NULL, "PGA-ADC Mux Left"},
|
||||
{"Right PGA", NULL, "PGA-ADC Mux Right"},
|
||||
};
|
||||
|
||||
static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int format)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
|
||||
int ret = 0;
|
||||
|
||||
switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "invalid DAI format\n");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
cs42l51->func = MODE_MASTER;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
cs42l51->func = MODE_SLAVE_AUTO;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct cs42l51_ratios {
|
||||
unsigned int ratio;
|
||||
unsigned char speed_mode;
|
||||
unsigned char mclk;
|
||||
};
|
||||
|
||||
static struct cs42l51_ratios slave_ratios[] = {
|
||||
{ 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 },
|
||||
{ 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
|
||||
{ 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 },
|
||||
{ 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 },
|
||||
{ 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 },
|
||||
{ 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 },
|
||||
{ 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 },
|
||||
{ 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 },
|
||||
{ 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 },
|
||||
{ 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 },
|
||||
{ 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 },
|
||||
};
|
||||
|
||||
static struct cs42l51_ratios slave_auto_ratios[] = {
|
||||
{ 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
|
||||
{ 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 },
|
||||
{ 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 },
|
||||
{ 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 },
|
||||
{ 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 },
|
||||
{ 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 },
|
||||
{ 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 },
|
||||
{ 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 },
|
||||
};
|
||||
|
||||
static int cs42l51_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 cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
|
||||
struct cs42l51_ratios *ratios = NULL;
|
||||
int nr_ratios = 0;
|
||||
unsigned int rates = 0;
|
||||
unsigned int rate_min = -1;
|
||||
unsigned int rate_max = 0;
|
||||
int i;
|
||||
|
||||
cs42l51->mclk = freq;
|
||||
|
||||
switch (cs42l51->func) {
|
||||
case MODE_MASTER:
|
||||
return -EINVAL;
|
||||
case MODE_SLAVE:
|
||||
ratios = slave_ratios;
|
||||
nr_ratios = ARRAY_SIZE(slave_ratios);
|
||||
break;
|
||||
case MODE_SLAVE_AUTO:
|
||||
ratios = slave_auto_ratios;
|
||||
nr_ratios = ARRAY_SIZE(slave_auto_ratios);
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_ratios; i++) {
|
||||
unsigned int rate = freq / ratios[i].ratio;
|
||||
rates |= snd_pcm_rate_to_rate_bit(rate);
|
||||
if (rate < rate_min)
|
||||
rate_min = rate;
|
||||
if (rate > rate_max)
|
||||
rate_max = rate;
|
||||
}
|
||||
rates &= ~SNDRV_PCM_RATE_KNOT;
|
||||
|
||||
if (!rates) {
|
||||
dev_err(codec->dev, "could not find a valid sample rate\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec_dai->playback.rates = rates;
|
||||
codec_dai->playback.rate_min = rate_min;
|
||||
codec_dai->playback.rate_max = rate_max;
|
||||
|
||||
codec_dai->capture.rates = rates;
|
||||
codec_dai->capture.rate_min = rate_min;
|
||||
codec_dai->capture.rate_max = rate_max;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cs42l51_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
|
||||
int ret;
|
||||
unsigned int i;
|
||||
unsigned int rate;
|
||||
unsigned int ratio;
|
||||
struct cs42l51_ratios *ratios = NULL;
|
||||
int nr_ratios = 0;
|
||||
int intf_ctl, power_ctl, fmt;
|
||||
|
||||
switch (cs42l51->func) {
|
||||
case MODE_MASTER:
|
||||
return -EINVAL;
|
||||
case MODE_SLAVE:
|
||||
ratios = slave_ratios;
|
||||
nr_ratios = ARRAY_SIZE(slave_ratios);
|
||||
break;
|
||||
case MODE_SLAVE_AUTO:
|
||||
ratios = slave_auto_ratios;
|
||||
nr_ratios = ARRAY_SIZE(slave_auto_ratios);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Figure out which MCLK/LRCK ratio to use */
|
||||
rate = params_rate(params); /* Sampling rate, in Hz */
|
||||
ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */
|
||||
for (i = 0; i < nr_ratios; i++) {
|
||||
if (ratios[i].ratio == ratio)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == nr_ratios) {
|
||||
/* We did not find a matching ratio */
|
||||
dev_err(codec->dev, "could not find matching ratio\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL);
|
||||
power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL);
|
||||
|
||||
intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S
|
||||
| CS42L51_INTF_CTL_DAC_FORMAT(7));
|
||||
power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3)
|
||||
| CS42L51_MIC_POWER_CTL_MCLK_DIV2);
|
||||
|
||||
switch (cs42l51->func) {
|
||||
case MODE_MASTER:
|
||||
intf_ctl |= CS42L51_INTF_CTL_MASTER;
|
||||
power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
|
||||
break;
|
||||
case MODE_SLAVE:
|
||||
power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
|
||||
break;
|
||||
case MODE_SLAVE_AUTO:
|
||||
power_ctl |= CS42L51_MIC_POWER_CTL_AUTO;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (cs42l51->audio_mode) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
intf_ctl |= CS42L51_INTF_CTL_ADC_I2S;
|
||||
intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
case SNDRV_PCM_FORMAT_S16_BE:
|
||||
fmt = CS42L51_DAC_DIF_RJ16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S18_3LE:
|
||||
case SNDRV_PCM_FORMAT_S18_3BE:
|
||||
fmt = CS42L51_DAC_DIF_RJ18;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
case SNDRV_PCM_FORMAT_S20_3BE:
|
||||
fmt = CS42L51_DAC_DIF_RJ20;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
case SNDRV_PCM_FORMAT_S24_BE:
|
||||
fmt = CS42L51_DAC_DIF_RJ24;
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "unknown format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt);
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "unknown format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ratios[i].mclk)
|
||||
power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2;
|
||||
|
||||
ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
int reg;
|
||||
int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE;
|
||||
|
||||
reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL);
|
||||
|
||||
if (mute)
|
||||
reg |= mask;
|
||||
else
|
||||
reg &= ~mask;
|
||||
|
||||
return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg);
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops cs42l51_dai_ops = {
|
||||
.hw_params = cs42l51_hw_params,
|
||||
.set_sysclk = cs42l51_set_dai_sysclk,
|
||||
.set_fmt = cs42l51_set_dai_fmt,
|
||||
.digital_mute = cs42l51_dai_mute,
|
||||
};
|
||||
|
||||
struct snd_soc_dai cs42l51_dai = {
|
||||
.name = "CS42L51 HiFi",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = CS42L51_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = CS42L51_FORMATS,
|
||||
},
|
||||
.ops = &cs42l51_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(cs42l51_dai);
|
||||
|
||||
|
||||
static int cs42l51_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
if (!cs42l51_codec) {
|
||||
dev_err(&pdev->dev, "CS42L51 codec not yet registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
socdev->card->codec = cs42l51_codec;
|
||||
codec = socdev->card->codec;
|
||||
|
||||
/* Register PCMs */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to create PCMs\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, cs42l51_snd_controls,
|
||||
ARRAY_SIZE(cs42l51_snd_controls));
|
||||
snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets,
|
||||
ARRAY_SIZE(cs42l51_dapm_widgets));
|
||||
snd_soc_dapm_add_routes(codec, cs42l51_routes,
|
||||
ARRAY_SIZE(cs42l51_routes));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int cs42l51_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_device_cs42l51 = {
|
||||
.probe = cs42l51_probe,
|
||||
.remove = cs42l51_remove
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_device_cs42l51);
|
||||
|
||||
static int __init cs42l51_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_add_driver(&cs42l51_i2c_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "%s: can't add i2c driver\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
module_init(cs42l51_init);
|
||||
|
||||
static void __exit cs42l51_exit(void)
|
||||
{
|
||||
i2c_del_driver(&cs42l51_i2c_driver);
|
||||
}
|
||||
module_exit(cs42l51_exit);
|
||||
|
||||
MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
|
||||
MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* cs42l51.h
|
||||
*
|
||||
* ASoC Driver for Cirrus Logic CS42L51 codecs
|
||||
*
|
||||
* Copyright (c) 2010 Arnaud Patard <apatard@mandriva.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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef _CS42L51_H
|
||||
#define _CS42L51_H
|
||||
|
||||
#define CS42L51_CHIP_ID 0x1B
|
||||
#define CS42L51_CHIP_REV_A 0x00
|
||||
#define CS42L51_CHIP_REV_B 0x01
|
||||
|
||||
#define CS42L51_CHIP_REV_ID 0x01
|
||||
#define CS42L51_MK_CHIP_REV(a, b) ((a)<<3|(b))
|
||||
|
||||
#define CS42L51_POWER_CTL1 0x02
|
||||
#define CS42L51_POWER_CTL1_PDN_DACB (1<<6)
|
||||
#define CS42L51_POWER_CTL1_PDN_DACA (1<<5)
|
||||
#define CS42L51_POWER_CTL1_PDN_PGAB (1<<4)
|
||||
#define CS42L51_POWER_CTL1_PDN_PGAA (1<<3)
|
||||
#define CS42L51_POWER_CTL1_PDN_ADCB (1<<2)
|
||||
#define CS42L51_POWER_CTL1_PDN_ADCA (1<<1)
|
||||
#define CS42L51_POWER_CTL1_PDN (1<<0)
|
||||
|
||||
#define CS42L51_MIC_POWER_CTL 0x03
|
||||
#define CS42L51_MIC_POWER_CTL_AUTO (1<<7)
|
||||
#define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5)
|
||||
#define CS42L51_QSM_MODE 3
|
||||
#define CS42L51_HSM_MODE 2
|
||||
#define CS42L51_SSM_MODE 1
|
||||
#define CS42L51_DSM_MODE 0
|
||||
#define CS42L51_MIC_POWER_CTL_3ST_SP (1<<4)
|
||||
#define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3)
|
||||
#define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2)
|
||||
#define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1)
|
||||
#define CS42L51_MIC_POWER_CTL_MCLK_DIV2 (1<<0)
|
||||
|
||||
#define CS42L51_INTF_CTL 0x04
|
||||
#define CS42L51_INTF_CTL_LOOPBACK (1<<7)
|
||||
#define CS42L51_INTF_CTL_MASTER (1<<6)
|
||||
#define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3)
|
||||
#define CS42L51_DAC_DIF_LJ24 0x00
|
||||
#define CS42L51_DAC_DIF_I2S 0x01
|
||||
#define CS42L51_DAC_DIF_RJ24 0x02
|
||||
#define CS42L51_DAC_DIF_RJ20 0x03
|
||||
#define CS42L51_DAC_DIF_RJ18 0x04
|
||||
#define CS42L51_DAC_DIF_RJ16 0x05
|
||||
#define CS42L51_INTF_CTL_ADC_I2S (1<<2)
|
||||
#define CS42L51_INTF_CTL_DIGMIX (1<<1)
|
||||
#define CS42L51_INTF_CTL_MICMIX (1<<0)
|
||||
|
||||
#define CS42L51_MIC_CTL 0x05
|
||||
#define CS42L51_MIC_CTL_ADC_SNGVOL (1<<7)
|
||||
#define CS42L51_MIC_CTL_ADCD_DBOOST (1<<6)
|
||||
#define CS42L51_MIC_CTL_ADCA_DBOOST (1<<5)
|
||||
#define CS42L51_MIC_CTL_MICBIAS_SEL (1<<4)
|
||||
#define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2)
|
||||
#define CS42L51_MIC_CTL_MICB_BOOST (1<<1)
|
||||
#define CS42L51_MIC_CTL_MICA_BOOST (1<<0)
|
||||
|
||||
#define CS42L51_ADC_CTL 0x06
|
||||
#define CS42L51_ADC_CTL_ADCB_HPFEN (1<<7)
|
||||
#define CS42L51_ADC_CTL_ADCB_HPFRZ (1<<6)
|
||||
#define CS42L51_ADC_CTL_ADCA_HPFEN (1<<5)
|
||||
#define CS42L51_ADC_CTL_ADCA_HPFRZ (1<<4)
|
||||
#define CS42L51_ADC_CTL_SOFTB (1<<3)
|
||||
#define CS42L51_ADC_CTL_ZCROSSB (1<<2)
|
||||
#define CS42L51_ADC_CTL_SOFTA (1<<1)
|
||||
#define CS42L51_ADC_CTL_ZCROSSA (1<<0)
|
||||
|
||||
#define CS42L51_ADC_INPUT 0x07
|
||||
#define CS42L51_ADC_INPUT_AINB_MUX(x) (((x)&3)<<6)
|
||||
#define CS42L51_ADC_INPUT_AINA_MUX(x) (((x)&3)<<4)
|
||||
#define CS42L51_ADC_INPUT_INV_ADCB (1<<3)
|
||||
#define CS42L51_ADC_INPUT_INV_ADCA (1<<2)
|
||||
#define CS42L51_ADC_INPUT_ADCB_MUTE (1<<1)
|
||||
#define CS42L51_ADC_INPUT_ADCA_MUTE (1<<0)
|
||||
|
||||
#define CS42L51_DAC_OUT_CTL 0x08
|
||||
#define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5)
|
||||
#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4)
|
||||
#define CS42L51_DAC_OUT_CTL_INV_PCMB (1<<3)
|
||||
#define CS42L51_DAC_OUT_CTL_INV_PCMA (1<<2)
|
||||
#define CS42L51_DAC_OUT_CTL_DACB_MUTE (1<<1)
|
||||
#define CS42L51_DAC_OUT_CTL_DACA_MUTE (1<<0)
|
||||
|
||||
#define CS42L51_DAC_CTL 0x09
|
||||
#define CS42L51_DAC_CTL_DATA_SEL(x) (((x)&3)<<6)
|
||||
#define CS42L51_DAC_CTL_FREEZE (1<<5)
|
||||
#define CS42L51_DAC_CTL_DEEMPH (1<<3)
|
||||
#define CS42L51_DAC_CTL_AMUTE (1<<2)
|
||||
#define CS42L51_DAC_CTL_DACSZ(x) (((x)&3)<<0)
|
||||
|
||||
#define CS42L51_ALC_PGA_CTL 0x0A
|
||||
#define CS42L51_ALC_PGB_CTL 0x0B
|
||||
#define CS42L51_ALC_PGX_ALCX_SRDIS (1<<7)
|
||||
#define CS42L51_ALC_PGX_ALCX_ZCDIS (1<<6)
|
||||
#define CS42L51_ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0)
|
||||
|
||||
#define CS42L51_ADCA_ATT 0x0C
|
||||
#define CS42L51_ADCB_ATT 0x0D
|
||||
|
||||
#define CS42L51_ADCA_VOL 0x0E
|
||||
#define CS42L51_ADCB_VOL 0x0F
|
||||
#define CS42L51_PCMA_VOL 0x10
|
||||
#define CS42L51_PCMB_VOL 0x11
|
||||
#define CS42L51_MIX_MUTE_ADCMIX (1<<7)
|
||||
#define CS42L51_MIX_VOLUME(x) (((x)&0x7f)<<0)
|
||||
|
||||
#define CS42L51_BEEP_FREQ 0x12
|
||||
#define CS42L51_BEEP_VOL 0x13
|
||||
#define CS42L51_BEEP_CONF 0x14
|
||||
|
||||
#define CS42L51_TONE_CTL 0x15
|
||||
#define CS42L51_TONE_CTL_TREB(x) (((x)&0xf)<<4)
|
||||
#define CS42L51_TONE_CTL_BASS(x) (((x)&0xf)<<0)
|
||||
|
||||
#define CS42L51_AOUTA_VOL 0x16
|
||||
#define CS42L51_AOUTB_VOL 0x17
|
||||
#define CS42L51_PCM_MIXER 0x18
|
||||
#define CS42L51_LIMIT_THRES_DIS 0x19
|
||||
#define CS42L51_LIMIT_REL 0x1A
|
||||
#define CS42L51_LIMIT_ATT 0x1B
|
||||
#define CS42L51_ALC_EN 0x1C
|
||||
#define CS42L51_ALC_REL 0x1D
|
||||
#define CS42L51_ALC_THRES 0x1E
|
||||
#define CS42L51_NOISE_CONF 0x1F
|
||||
|
||||
#define CS42L51_STATUS 0x20
|
||||
#define CS42L51_STATUS_SP_CLKERR (1<<6)
|
||||
#define CS42L51_STATUS_SPEA_OVFL (1<<5)
|
||||
#define CS42L51_STATUS_SPEB_OVFL (1<<4)
|
||||
#define CS42L51_STATUS_PCMA_OVFL (1<<3)
|
||||
#define CS42L51_STATUS_PCMB_OVFL (1<<2)
|
||||
#define CS42L51_STATUS_ADCA_OVFL (1<<1)
|
||||
#define CS42L51_STATUS_ADCB_OVFL (1<<0)
|
||||
|
||||
#define CS42L51_CHARGE_FREQ 0x21
|
||||
|
||||
#define CS42L51_FIRSTREG 0x01
|
||||
/*
|
||||
* Hack: with register 0x21, it makes 33 registers. Looks like someone in the
|
||||
* i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using
|
||||
* 32 regs
|
||||
*/
|
||||
#define CS42L51_LASTREG 0x20
|
||||
#define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1)
|
||||
|
||||
extern struct snd_soc_dai cs42l51_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_device_cs42l51;
|
||||
#endif
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
#include "spdif_transciever.h"
|
||||
|
||||
|
@ -26,6 +28,48 @@ MODULE_LICENSE("GPL");
|
|||
#define STUB_RATES SNDRV_PCM_RATE_8000_96000
|
||||
#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE
|
||||
|
||||
static struct snd_soc_codec *spdif_dit_codec;
|
||||
|
||||
static int spdif_dit_codec_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
if (spdif_dit_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = spdif_dit_codec;
|
||||
codec = spdif_dit_codec;
|
||||
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto err_create_pcms;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_create_pcms:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spdif_dit_codec_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_spdif_dit = {
|
||||
.probe = spdif_dit_codec_probe,
|
||||
.remove = spdif_dit_codec_remove,
|
||||
}; EXPORT_SYMBOL_GPL(soc_codec_dev_spdif_dit);
|
||||
|
||||
struct snd_soc_dai dit_stub_dai = {
|
||||
.name = "DIT",
|
||||
.playback = {
|
||||
|
@ -40,13 +84,61 @@ EXPORT_SYMBOL_GPL(dit_stub_dai);
|
|||
|
||||
static int spdif_dit_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
if (spdif_dit_codec) {
|
||||
dev_err(&pdev->dev, "Another Codec is registered\n");
|
||||
ret = -EINVAL;
|
||||
goto err_reg_codec;
|
||||
}
|
||||
|
||||
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
||||
if (codec == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec->dev = &pdev->dev;
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->name = "spdif-dit";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &dit_stub_dai;
|
||||
codec->num_dai = 1;
|
||||
|
||||
spdif_dit_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto err_reg_codec;
|
||||
}
|
||||
|
||||
dit_stub_dai.dev = &pdev->dev;
|
||||
return snd_soc_register_dai(&dit_stub_dai);
|
||||
ret = snd_soc_register_dai(&dit_stub_dai);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to register dai: %d\n", ret);
|
||||
goto err_reg_dai;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_reg_dai:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err_reg_codec:
|
||||
kfree(spdif_dit_codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spdif_dit_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_dai(&dit_stub_dai);
|
||||
snd_soc_unregister_codec(spdif_dit_codec);
|
||||
kfree(spdif_dit_codec);
|
||||
spdif_dit_codec = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#ifndef CODEC_STUBS_H
|
||||
#define CODEC_STUBS_H
|
||||
|
||||
extern struct snd_soc_codec_device soc_codec_dev_spdif_dit;
|
||||
extern struct snd_soc_dai dit_stub_dai;
|
||||
|
||||
#endif /* CODEC_STUBS_H */
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
|
||||
#include "wm8990.h"
|
||||
|
||||
#define WM8990_VERSION "0.2"
|
||||
|
||||
/* codec private data */
|
||||
struct wm8990_priv {
|
||||
unsigned int sysclk;
|
||||
|
@ -1511,8 +1509,6 @@ static int wm8990_probe(struct platform_device *pdev)
|
|||
struct wm8990_priv *wm8990;
|
||||
int ret;
|
||||
|
||||
pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION);
|
||||
|
||||
setup = socdev->codec_data;
|
||||
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
||||
if (codec == NULL)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
config SND_KIRKWOOD_SOC
|
||||
tristate "SoC Audio for the Marvell Kirkwood chip"
|
||||
depends on ARCH_KIRKWOOD
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the Kirkwood I2S interface. You will also need to select the
|
||||
audio interfaces to support below.
|
||||
|
||||
config SND_KIRKWOOD_SOC_I2S
|
||||
tristate
|
||||
|
||||
config SND_KIRKWOOD_SOC_OPENRD
|
||||
tristate "SoC Audio support for Kirkwood Openrd Client"
|
||||
depends on SND_KIRKWOOD_SOC && MACH_OPENRD_CLIENT
|
||||
select SND_KIRKWOOD_SOC_I2S
|
||||
select SND_SOC_CS42L51
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on
|
||||
Openrd Client.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
snd-soc-kirkwood-objs := kirkwood-dma.o
|
||||
snd-soc-kirkwood-i2s-objs := kirkwood-i2s.o
|
||||
|
||||
obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o
|
||||
obj-$(CONFIG_SND_KIRKWOOD_SOC_I2S) += snd-soc-kirkwood-i2s.o
|
||||
|
||||
snd-soc-openrd-objs := kirkwood-openrd.o
|
||||
|
||||
obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o
|
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* kirkwood-dma.c
|
||||
*
|
||||
* (c) 2010 Arnaud Patard <apatard@mandriva.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/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/mbus.h>
|
||||
#include <sound/soc.h>
|
||||
#include "kirkwood-dma.h"
|
||||
#include "kirkwood.h"
|
||||
|
||||
#define KIRKWOOD_RATES \
|
||||
(SNDRV_PCM_RATE_44100 | \
|
||||
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
|
||||
#define KIRKWOOD_FORMATS \
|
||||
(SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE | \
|
||||
SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
struct kirkwood_dma_priv {
|
||||
struct snd_pcm_substream *play_stream;
|
||||
struct snd_pcm_substream *rec_stream;
|
||||
struct kirkwood_dma_data *data;
|
||||
};
|
||||
|
||||
static struct snd_pcm_hardware kirkwood_dma_snd_hw = {
|
||||
.info = (SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_PAUSE),
|
||||
.formats = KIRKWOOD_FORMATS,
|
||||
.rates = KIRKWOOD_RATES,
|
||||
.rate_min = 44100,
|
||||
.rate_max = 96000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS,
|
||||
.period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES,
|
||||
.period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES,
|
||||
.periods_min = KIRKWOOD_SND_MIN_PERIODS,
|
||||
.periods_max = KIRKWOOD_SND_MAX_PERIODS,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL;
|
||||
|
||||
static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct kirkwood_dma_priv *prdata = dev_id;
|
||||
struct kirkwood_dma_data *priv = prdata->data;
|
||||
unsigned long mask, status, cause;
|
||||
|
||||
mask = readl(priv->io + KIRKWOOD_INT_MASK);
|
||||
status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask;
|
||||
|
||||
cause = readl(priv->io + KIRKWOOD_ERR_CAUSE);
|
||||
if (unlikely(cause)) {
|
||||
printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",
|
||||
__func__, cause);
|
||||
writel(cause, priv->io + KIRKWOOD_ERR_CAUSE);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* we've enabled only bytes interrupts ... */
|
||||
if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \
|
||||
KIRKWOOD_INT_CAUSE_REC_BYTES)) {
|
||||
printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
|
||||
__func__, status);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
/* ack int */
|
||||
writel(status, priv->io + KIRKWOOD_INT_CAUSE);
|
||||
|
||||
if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES)
|
||||
snd_pcm_period_elapsed(prdata->play_stream);
|
||||
|
||||
if (status & KIRKWOOD_INT_CAUSE_REC_BYTES)
|
||||
snd_pcm_period_elapsed(prdata->rec_stream);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win,
|
||||
unsigned long dma,
|
||||
struct mbus_dram_target_info *dram)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* First disable and clear windows */
|
||||
writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
|
||||
writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
|
||||
|
||||
/* try to find matching cs for current dma address */
|
||||
for (i = 0; i < dram->num_cs; i++) {
|
||||
struct mbus_dram_window *cs = dram->cs + i;
|
||||
if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {
|
||||
writel(cs->base & 0xffff0000,
|
||||
base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
|
||||
writel(((cs->size - 1) & 0xffff0000) |
|
||||
(cs->mbus_attr << 8) |
|
||||
(dram->mbus_dram_target_id << 4) | 1,
|
||||
base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int kirkwood_dma_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
int err;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
|
||||
struct kirkwood_dma_data *priv;
|
||||
struct kirkwood_dma_priv *prdata = cpu_dai->private_data;
|
||||
unsigned long addr;
|
||||
|
||||
priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
|
||||
snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw);
|
||||
|
||||
/* Ensure that all constraints linked to dma burst are fullfilled */
|
||||
err = snd_pcm_hw_constraint_minmax(runtime,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
||||
priv->burst * 2,
|
||||
KIRKWOOD_AUDIO_BUF_MAX-1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
||||
priv->burst);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_pcm_hw_constraint_step(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
||||
priv->burst);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (soc_runtime->dai->cpu_dai->private_data == NULL) {
|
||||
prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL);
|
||||
if (prdata == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
prdata->data = priv;
|
||||
|
||||
err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED,
|
||||
"kirkwood-i2s", prdata);
|
||||
if (err) {
|
||||
kfree(prdata);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
soc_runtime->dai->cpu_dai->private_data = prdata;
|
||||
|
||||
/*
|
||||
* Enable Error interrupts. We're only ack'ing them but
|
||||
* it's usefull for diagnostics
|
||||
*/
|
||||
writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK);
|
||||
}
|
||||
|
||||
addr = virt_to_phys(substream->dma_buffer.area);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
prdata->play_stream = substream;
|
||||
kirkwood_dma_conf_mbus_windows(priv->io,
|
||||
KIRKWOOD_PLAYBACK_WIN, addr, priv->dram);
|
||||
} else {
|
||||
prdata->rec_stream = substream;
|
||||
kirkwood_dma_conf_mbus_windows(priv->io,
|
||||
KIRKWOOD_RECORD_WIN, addr, priv->dram);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_dma_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
|
||||
struct kirkwood_dma_priv *prdata = cpu_dai->private_data;
|
||||
struct kirkwood_dma_data *priv;
|
||||
|
||||
priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
|
||||
|
||||
if (!prdata || !priv)
|
||||
return 0;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
prdata->play_stream = NULL;
|
||||
else
|
||||
prdata->rec_stream = NULL;
|
||||
|
||||
if (!prdata->play_stream && !prdata->rec_stream) {
|
||||
writel(0, priv->io + KIRKWOOD_ERR_MASK);
|
||||
free_irq(priv->irq, prdata);
|
||||
kfree(prdata);
|
||||
soc_runtime->dai->cpu_dai->private_data = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = params_buffer_bytes(params);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_dma_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
|
||||
struct kirkwood_dma_data *priv;
|
||||
unsigned long size, count;
|
||||
|
||||
priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
|
||||
|
||||
/* compute buffer size in term of "words" as requested in specs */
|
||||
size = frames_to_bytes(runtime, runtime->buffer_size);
|
||||
size = (size>>2)-1;
|
||||
count = snd_pcm_lib_period_bytes(substream);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT);
|
||||
writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR);
|
||||
writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE);
|
||||
} else {
|
||||
writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT);
|
||||
writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR);
|
||||
writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream
|
||||
*substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
|
||||
struct kirkwood_dma_data *priv;
|
||||
snd_pcm_uframes_t count;
|
||||
|
||||
priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
count = bytes_to_frames(substream->runtime,
|
||||
readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT));
|
||||
else
|
||||
count = bytes_to_frames(substream->runtime,
|
||||
readl(priv->io + KIRKWOOD_REC_BYTE_COUNT));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
struct snd_pcm_ops kirkwood_dma_ops = {
|
||||
.open = kirkwood_dma_open,
|
||||
.close = kirkwood_dma_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = kirkwood_dma_hw_params,
|
||||
.hw_free = kirkwood_dma_hw_free,
|
||||
.prepare = kirkwood_dma_prepare,
|
||||
.pointer = kirkwood_dma_pointer,
|
||||
};
|
||||
|
||||
static int kirkwood_dma_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 = kirkwood_dma_snd_hw.buffer_bytes_max;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->area = dma_alloc_coherent(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
buf->bytes = size;
|
||||
buf->private_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_dma_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &kirkwood_dma_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = 0xffffffff;
|
||||
|
||||
if (dai->playback.channels_min) {
|
||||
ret = kirkwood_dma_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (dai->capture.channels_min) {
|
||||
ret = kirkwood_dma_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kirkwood_dma_free_dma_buffers(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;
|
||||
}
|
||||
}
|
||||
|
||||
struct snd_soc_platform kirkwood_soc_platform = {
|
||||
.name = "kirkwood-dma",
|
||||
.pcm_ops = &kirkwood_dma_ops,
|
||||
.pcm_new = kirkwood_dma_new,
|
||||
.pcm_free = kirkwood_dma_free_dma_buffers,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(kirkwood_soc_platform);
|
||||
|
||||
static int __init kirkwood_soc_platform_init(void)
|
||||
{
|
||||
return snd_soc_register_platform(&kirkwood_soc_platform);
|
||||
}
|
||||
module_init(kirkwood_soc_platform_init);
|
||||
|
||||
static void __exit kirkwood_soc_platform_exit(void)
|
||||
{
|
||||
snd_soc_unregister_platform(&kirkwood_soc_platform);
|
||||
}
|
||||
module_exit(kirkwood_soc_platform_exit);
|
||||
|
||||
MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
|
||||
MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* kirkwood-dma.h
|
||||
*
|
||||
* (c) 2010 Arnaud Patard <apatard@mandriva.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.
|
||||
*/
|
||||
|
||||
#ifndef _KIRKWOOD_DMA_H
|
||||
#define _KIRKWOOD_DMA_H
|
||||
|
||||
extern struct snd_soc_platform kirkwood_soc_platform;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,485 @@
|
|||
/*
|
||||
* kirkwood-i2s.c
|
||||
*
|
||||
* (c) 2010 Arnaud Patard <apatard@mandriva.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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mbus.h>
|
||||
#include <linux/delay.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <plat/audio.h>
|
||||
#include "kirkwood-i2s.h"
|
||||
#include "kirkwood.h"
|
||||
|
||||
#define DRV_NAME "kirkwood-i2s"
|
||||
|
||||
#define KIRKWOOD_I2S_RATES \
|
||||
(SNDRV_PCM_RATE_44100 | \
|
||||
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
|
||||
#define KIRKWOOD_I2S_FORMATS \
|
||||
(SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE | \
|
||||
SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
|
||||
struct snd_soc_dai kirkwood_i2s_dai;
|
||||
static struct kirkwood_dma_data *priv;
|
||||
|
||||
static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
unsigned long mask;
|
||||
unsigned long value;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
mask = KIRKWOOD_I2S_CTL_RJ;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
mask = KIRKWOOD_I2S_CTL_LJ;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
mask = KIRKWOOD_I2S_CTL_I2S;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set same format for playback and record
|
||||
* This avoids some troubles.
|
||||
*/
|
||||
value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL);
|
||||
value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
|
||||
value |= mask;
|
||||
writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL);
|
||||
|
||||
value = readl(priv->io+KIRKWOOD_I2S_RECCTL);
|
||||
value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
|
||||
value |= mask;
|
||||
writel(value, priv->io+KIRKWOOD_I2S_RECCTL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate)
|
||||
{
|
||||
unsigned long value;
|
||||
|
||||
value = KIRKWOOD_DCO_CTL_OFFSET_0;
|
||||
switch (rate) {
|
||||
default:
|
||||
case 44100:
|
||||
value |= KIRKWOOD_DCO_CTL_FREQ_11;
|
||||
break;
|
||||
case 48000:
|
||||
value |= KIRKWOOD_DCO_CTL_FREQ_12;
|
||||
break;
|
||||
case 96000:
|
||||
value |= KIRKWOOD_DCO_CTL_FREQ_24;
|
||||
break;
|
||||
}
|
||||
writel(value, io + KIRKWOOD_DCO_CTL);
|
||||
|
||||
/* wait for dco locked */
|
||||
do {
|
||||
cpu_relax();
|
||||
value = readl(io + KIRKWOOD_DCO_SPCR_STATUS);
|
||||
value &= KIRKWOOD_DCO_SPCR_STATUS;
|
||||
} while (value == 0);
|
||||
}
|
||||
|
||||
static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
unsigned int i2s_reg, reg;
|
||||
unsigned long i2s_value, value;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
i2s_reg = KIRKWOOD_I2S_PLAYCTL;
|
||||
reg = KIRKWOOD_PLAYCTL;
|
||||
} else {
|
||||
i2s_reg = KIRKWOOD_I2S_RECCTL;
|
||||
reg = KIRKWOOD_RECCTL;
|
||||
}
|
||||
|
||||
/* set dco conf */
|
||||
kirkwood_set_dco(priv->io, params_rate(params));
|
||||
|
||||
i2s_value = readl(priv->io+i2s_reg);
|
||||
i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK;
|
||||
|
||||
value = readl(priv->io+reg);
|
||||
value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK;
|
||||
|
||||
/*
|
||||
* Size settings in play/rec i2s control regs and play/rec control
|
||||
* regs must be the same.
|
||||
*/
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16;
|
||||
value |= KIRKWOOD_PLAYCTL_SIZE_16_C;
|
||||
break;
|
||||
/*
|
||||
* doesn't work... S20_3LE != kirkwood 20bit format ?
|
||||
*
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20;
|
||||
value |= KIRKWOOD_PLAYCTL_SIZE_20;
|
||||
break;
|
||||
*/
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24;
|
||||
value |= KIRKWOOD_PLAYCTL_SIZE_24;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32;
|
||||
value |= KIRKWOOD_PLAYCTL_SIZE_32;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
writel(i2s_value, priv->io+i2s_reg);
|
||||
writel(value, priv->io+reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
unsigned long value;
|
||||
|
||||
/*
|
||||
* specs says KIRKWOOD_PLAYCTL must be read 2 times before
|
||||
* changing it. So read 1 time here and 1 later.
|
||||
*/
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
/* stop audio, enable interrupts */
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
value |= KIRKWOOD_PLAYCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_PLAYCTL);
|
||||
|
||||
value = readl(priv->io + KIRKWOOD_INT_MASK);
|
||||
value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES;
|
||||
writel(value, priv->io + KIRKWOOD_INT_MASK);
|
||||
|
||||
/* configure audio & enable i2s playback */
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
value &= ~KIRKWOOD_PLAYCTL_BURST_MASK;
|
||||
value &= ~(KIRKWOOD_PLAYCTL_PAUSE|KIRKWOOD_PLAYCTL_SPDIF_EN);
|
||||
|
||||
if (priv->burst == 32)
|
||||
value |= KIRKWOOD_PLAYCTL_BURST_32;
|
||||
else
|
||||
value |= KIRKWOOD_PLAYCTL_BURST_128;
|
||||
value |= KIRKWOOD_PLAYCTL_I2S_EN;
|
||||
writel(value, priv->io + KIRKWOOD_PLAYCTL);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
/* stop audio, disable interrupts */
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
value |= KIRKWOOD_PLAYCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_PLAYCTL);
|
||||
|
||||
value = readl(priv->io + KIRKWOOD_INT_MASK);
|
||||
value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES;
|
||||
writel(value, priv->io + KIRKWOOD_INT_MASK);
|
||||
|
||||
/* disable all playbacks */
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN);
|
||||
writel(value, priv->io + KIRKWOOD_PLAYCTL);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
value |= KIRKWOOD_PLAYCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_PLAYCTL);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
value &= ~KIRKWOOD_PLAYCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_PLAYCTL);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
unsigned long value;
|
||||
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
/* stop audio, enable interrupts */
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
value |= KIRKWOOD_RECCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_RECCTL);
|
||||
|
||||
value = readl(priv->io + KIRKWOOD_INT_MASK);
|
||||
value |= KIRKWOOD_INT_CAUSE_REC_BYTES;
|
||||
writel(value, priv->io + KIRKWOOD_INT_MASK);
|
||||
|
||||
/* configure audio & enable i2s record */
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
value &= ~KIRKWOOD_RECCTL_BURST_MASK;
|
||||
value &= ~KIRKWOOD_RECCTL_MONO;
|
||||
value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_SPDIF_EN);
|
||||
|
||||
if (priv->burst == 32)
|
||||
value |= KIRKWOOD_RECCTL_BURST_32;
|
||||
else
|
||||
value |= KIRKWOOD_RECCTL_BURST_128;
|
||||
value |= KIRKWOOD_RECCTL_I2S_EN;
|
||||
|
||||
writel(value, priv->io + KIRKWOOD_RECCTL);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
/* stop audio, disable interrupts */
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
value |= KIRKWOOD_RECCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_RECCTL);
|
||||
|
||||
value = readl(priv->io + KIRKWOOD_INT_MASK);
|
||||
value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES;
|
||||
writel(value, priv->io + KIRKWOOD_INT_MASK);
|
||||
|
||||
/* disable all records */
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
|
||||
writel(value, priv->io + KIRKWOOD_RECCTL);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
value |= KIRKWOOD_RECCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_RECCTL);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
value &= ~KIRKWOOD_RECCTL_PAUSE;
|
||||
writel(value, priv->io + KIRKWOOD_RECCTL);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
return kirkwood_i2s_play_trigger(substream, cmd, dai);
|
||||
else
|
||||
return kirkwood_i2s_rec_trigger(substream, cmd, dai);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_i2s_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
unsigned long value;
|
||||
unsigned int reg_data;
|
||||
|
||||
/* put system in a "safe" state : */
|
||||
/* disable audio interrupts */
|
||||
writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE);
|
||||
writel(0, priv->io + KIRKWOOD_INT_MASK);
|
||||
|
||||
reg_data = readl(priv->io + 0x1200);
|
||||
reg_data &= (~(0x333FF8));
|
||||
reg_data |= 0x111D18;
|
||||
writel(reg_data, priv->io + 0x1200);
|
||||
|
||||
msleep(500);
|
||||
|
||||
reg_data = readl(priv->io + 0x1200);
|
||||
reg_data &= (~(0x333FF8));
|
||||
reg_data |= 0x111D18;
|
||||
writel(reg_data, priv->io + 0x1200);
|
||||
|
||||
/* disable playback/record */
|
||||
value = readl(priv->io + KIRKWOOD_PLAYCTL);
|
||||
value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN);
|
||||
writel(value, priv->io + KIRKWOOD_PLAYCTL);
|
||||
|
||||
value = readl(priv->io + KIRKWOOD_RECCTL);
|
||||
value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
|
||||
writel(value, priv->io + KIRKWOOD_RECCTL);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void kirkwood_i2s_remove(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = {
|
||||
.trigger = kirkwood_i2s_trigger,
|
||||
.hw_params = kirkwood_i2s_hw_params,
|
||||
.set_fmt = kirkwood_i2s_set_fmt,
|
||||
};
|
||||
|
||||
|
||||
struct snd_soc_dai kirkwood_i2s_dai = {
|
||||
.name = DRV_NAME,
|
||||
.id = 0,
|
||||
.probe = kirkwood_i2s_probe,
|
||||
.remove = kirkwood_i2s_remove,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = KIRKWOOD_I2S_RATES,
|
||||
.formats = KIRKWOOD_I2S_FORMATS,},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = KIRKWOOD_I2S_RATES,
|
||||
.formats = KIRKWOOD_I2S_FORMATS,},
|
||||
.ops = &kirkwood_i2s_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(kirkwood_i2s_dai);
|
||||
|
||||
static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *mem;
|
||||
struct kirkwood_asoc_platform_data *data =
|
||||
pdev->dev.platform_data;
|
||||
int err;
|
||||
|
||||
priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
dev_err(&pdev->dev, "allocation failed\n");
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!mem) {
|
||||
dev_err(&pdev->dev, "platform_get_resource failed\n");
|
||||
err = -ENXIO;
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME);
|
||||
if (!priv->mem) {
|
||||
dev_err(&pdev->dev, "request_mem_region failed\n");
|
||||
err = -EBUSY;
|
||||
goto error;
|
||||
}
|
||||
|
||||
priv->io = ioremap(priv->mem->start, SZ_16K);
|
||||
if (!priv->io) {
|
||||
dev_err(&pdev->dev, "ioremap failed\n");
|
||||
err = -ENOMEM;
|
||||
goto err_iomem;
|
||||
}
|
||||
|
||||
priv->irq = platform_get_irq(pdev, 0);
|
||||
if (priv->irq <= 0) {
|
||||
dev_err(&pdev->dev, "platform_get_irq failed\n");
|
||||
err = -ENXIO;
|
||||
goto err_ioremap;
|
||||
}
|
||||
|
||||
if (!data || !data->dram) {
|
||||
dev_err(&pdev->dev, "no platform data ?!\n");
|
||||
err = -EINVAL;
|
||||
goto err_ioremap;
|
||||
}
|
||||
|
||||
priv->dram = data->dram;
|
||||
priv->burst = data->burst;
|
||||
|
||||
kirkwood_i2s_dai.capture.dma_data = priv;
|
||||
kirkwood_i2s_dai.playback.dma_data = priv;
|
||||
|
||||
return snd_soc_register_dai(&kirkwood_i2s_dai);
|
||||
|
||||
err_ioremap:
|
||||
iounmap(priv->io);
|
||||
err_iomem:
|
||||
release_mem_region(priv->mem->start, SZ_16K);
|
||||
err_alloc:
|
||||
kfree(priv);
|
||||
error:
|
||||
return err;
|
||||
}
|
||||
|
||||
static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev)
|
||||
{
|
||||
if (priv) {
|
||||
iounmap(priv->io);
|
||||
release_mem_region(priv->mem->start, SZ_16K);
|
||||
kfree(priv);
|
||||
}
|
||||
snd_soc_unregister_dai(&kirkwood_i2s_dai);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver kirkwood_i2s_driver = {
|
||||
.probe = kirkwood_i2s_dev_probe,
|
||||
.remove = kirkwood_i2s_dev_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init kirkwood_i2s_init(void)
|
||||
{
|
||||
return platform_driver_register(&kirkwood_i2s_driver);
|
||||
}
|
||||
module_init(kirkwood_i2s_init);
|
||||
|
||||
static void __exit kirkwood_i2s_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&kirkwood_i2s_driver);
|
||||
}
|
||||
module_exit(kirkwood_i2s_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Arnaud Patard, <apatard@mandriva.com>");
|
||||
MODULE_DESCRIPTION("Kirkwood I2S SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:kirkwood-i2s");
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* kirkwood-i2s.h
|
||||
*
|
||||
* (c) 2010 Arnaud Patard <apatard@mandriva.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.
|
||||
*/
|
||||
|
||||
#ifndef _KIRKWOOD_I2S_H
|
||||
#define _KIRKWOOD_I2S_H
|
||||
|
||||
extern struct snd_soc_dai kirkwood_i2s_dai;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* kirkwood-openrd.c
|
||||
*
|
||||
* (c) 2010 Arnaud Patard <apatard@mandriva.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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/soc.h>
|
||||
#include <mach/kirkwood.h>
|
||||
#include <plat/audio.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include "kirkwood-i2s.h"
|
||||
#include "kirkwood-dma.h"
|
||||
#include "../codecs/cs42l51.h"
|
||||
|
||||
static int openrd_client_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
int ret;
|
||||
unsigned int freq, fmt;
|
||||
|
||||
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
default:
|
||||
case 44100:
|
||||
freq = 11289600;
|
||||
break;
|
||||
case 48000:
|
||||
freq = 12288000;
|
||||
break;
|
||||
case 96000:
|
||||
freq = 24576000;
|
||||
break;
|
||||
}
|
||||
|
||||
return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN);
|
||||
|
||||
}
|
||||
|
||||
static struct snd_soc_ops openrd_client_ops = {
|
||||
.hw_params = openrd_client_hw_params,
|
||||
};
|
||||
|
||||
|
||||
static struct snd_soc_dai_link openrd_client_dai[] = {
|
||||
{
|
||||
.name = "CS42L51",
|
||||
.stream_name = "CS42L51 HiFi",
|
||||
.cpu_dai = &kirkwood_i2s_dai,
|
||||
.codec_dai = &cs42l51_dai,
|
||||
.ops = &openrd_client_ops,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
static struct snd_soc_card openrd_client = {
|
||||
.name = "OpenRD Client",
|
||||
.platform = &kirkwood_soc_platform,
|
||||
.dai_link = openrd_client_dai,
|
||||
.num_links = ARRAY_SIZE(openrd_client_dai),
|
||||
};
|
||||
|
||||
static struct snd_soc_device openrd_client_snd_devdata = {
|
||||
.card = &openrd_client,
|
||||
.codec_dev = &soc_codec_device_cs42l51,
|
||||
};
|
||||
|
||||
static struct platform_device *openrd_client_snd_device;
|
||||
|
||||
static int __init openrd_client_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_openrd_client())
|
||||
return 0;
|
||||
|
||||
openrd_client_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!openrd_client_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(openrd_client_snd_device,
|
||||
&openrd_client_snd_devdata);
|
||||
openrd_client_snd_devdata.dev = &openrd_client_snd_device->dev;
|
||||
|
||||
ret = platform_device_add(openrd_client_snd_device);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s: platform_device_add failed\n", __func__);
|
||||
platform_device_put(openrd_client_snd_device);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit openrd_client_exit(void)
|
||||
{
|
||||
platform_device_unregister(openrd_client_snd_device);
|
||||
}
|
||||
|
||||
module_init(openrd_client_init);
|
||||
module_exit(openrd_client_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC OpenRD Client");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:soc-audio");
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* kirkwood.h
|
||||
*
|
||||
* (c) 2010 Arnaud Patard <apatard@mandriva.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.
|
||||
*/
|
||||
|
||||
#ifndef _KIRKWOOD_AUDIO_H
|
||||
#define _KIRKWOOD_AUDIO_H
|
||||
|
||||
#define KIRKWOOD_RECORD_WIN 0
|
||||
#define KIRKWOOD_PLAYBACK_WIN 1
|
||||
#define KIRKWOOD_MAX_AUDIO_WIN 2
|
||||
|
||||
#define KIRKWOOD_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3))
|
||||
#define KIRKWOOD_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3))
|
||||
|
||||
|
||||
#define KIRKWOOD_RECCTL 0x1000
|
||||
#define KIRKWOOD_RECCTL_SPDIF_EN (1<<11)
|
||||
#define KIRKWOOD_RECCTL_I2S_EN (1<<10)
|
||||
#define KIRKWOOD_RECCTL_PAUSE (1<<9)
|
||||
#define KIRKWOOD_RECCTL_MUTE (1<<8)
|
||||
#define KIRKWOOD_RECCTL_BURST_MASK (3<<5)
|
||||
#define KIRKWOOD_RECCTL_BURST_128 (2<<5)
|
||||
#define KIRKWOOD_RECCTL_BURST_32 (1<<5)
|
||||
#define KIRKWOOD_RECCTL_MONO (1<<4)
|
||||
#define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT (1<<3)
|
||||
#define KIRKWOOD_RECCTL_MONO_CHAN_LEFT (0<<3)
|
||||
#define KIRKWOOD_RECCTL_SIZE_MASK (7<<0)
|
||||
#define KIRKWOOD_RECCTL_SIZE_16 (7<<0)
|
||||
#define KIRKWOOD_RECCTL_SIZE_16_C (3<<0)
|
||||
#define KIRKWOOD_RECCTL_SIZE_20 (2<<0)
|
||||
#define KIRKWOOD_RECCTL_SIZE_24 (1<<0)
|
||||
#define KIRKWOOD_RECCTL_SIZE_32 (0<<0)
|
||||
|
||||
#define KIRKWOOD_REC_BUF_ADDR 0x1004
|
||||
#define KIRKWOOD_REC_BUF_SIZE 0x1008
|
||||
#define KIRKWOOD_REC_BYTE_COUNT 0x100C
|
||||
|
||||
#define KIRKWOOD_PLAYCTL 0x1100
|
||||
#define KIRKWOOD_PLAYCTL_PLAY_BUSY (1<<16)
|
||||
#define KIRKWOOD_PLAYCTL_BURST_MASK (3<<11)
|
||||
#define KIRKWOOD_PLAYCTL_BURST_128 (2<<11)
|
||||
#define KIRKWOOD_PLAYCTL_BURST_32 (1<<11)
|
||||
#define KIRKWOOD_PLAYCTL_PAUSE (1<<9)
|
||||
#define KIRKWOOD_PLAYCTL_SPDIF_MUTE (1<<8)
|
||||
#define KIRKWOOD_PLAYCTL_I2S_MUTE (1<<7)
|
||||
#define KIRKWOOD_PLAYCTL_SPDIF_EN (1<<4)
|
||||
#define KIRKWOOD_PLAYCTL_I2S_EN (1<<3)
|
||||
#define KIRKWOOD_PLAYCTL_SIZE_MASK (7<<0)
|
||||
#define KIRKWOOD_PLAYCTL_SIZE_16 (7<<0)
|
||||
#define KIRKWOOD_PLAYCTL_SIZE_16_C (3<<0)
|
||||
#define KIRKWOOD_PLAYCTL_SIZE_20 (2<<0)
|
||||
#define KIRKWOOD_PLAYCTL_SIZE_24 (1<<0)
|
||||
#define KIRKWOOD_PLAYCTL_SIZE_32 (0<<0)
|
||||
|
||||
#define KIRKWOOD_PLAY_BUF_ADDR 0x1104
|
||||
#define KIRKWOOD_PLAY_BUF_SIZE 0x1108
|
||||
#define KIRKWOOD_PLAY_BYTE_COUNT 0x110C
|
||||
|
||||
#define KIRKWOOD_DCO_CTL 0x1204
|
||||
#define KIRKWOOD_DCO_CTL_OFFSET_MASK (0xFFF<<2)
|
||||
#define KIRKWOOD_DCO_CTL_OFFSET_0 (0x800<<2)
|
||||
#define KIRKWOOD_DCO_CTL_FREQ_MASK (3<<0)
|
||||
#define KIRKWOOD_DCO_CTL_FREQ_11 (0<<0)
|
||||
#define KIRKWOOD_DCO_CTL_FREQ_12 (1<<0)
|
||||
#define KIRKWOOD_DCO_CTL_FREQ_24 (2<<0)
|
||||
|
||||
#define KIRKWOOD_DCO_SPCR_STATUS 0x120c
|
||||
#define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK (1<<16)
|
||||
|
||||
#define KIRKWOOD_ERR_CAUSE 0x1300
|
||||
#define KIRKWOOD_ERR_MASK 0x1304
|
||||
|
||||
#define KIRKWOOD_INT_CAUSE 0x1308
|
||||
#define KIRKWOOD_INT_MASK 0x130C
|
||||
#define KIRKWOOD_INT_CAUSE_PLAY_BYTES (1<<14)
|
||||
#define KIRKWOOD_INT_CAUSE_REC_BYTES (1<<13)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_PLAY_END (1<<7)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q (1<<6)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF (1<<5)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q (1<<4)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_REC_END (1<<3)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_REC_3Q (1<<2)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_REC_HALF (1<<1)
|
||||
#define KIRKWOOD_INT_CAUSE_DMA_REC_1Q (1<<0)
|
||||
|
||||
#define KIRKWOOD_REC_BYTE_INT_COUNT 0x1310
|
||||
#define KIRKWOOD_PLAY_BYTE_INT_COUNT 0x1314
|
||||
#define KIRKWOOD_BYTE_INT_COUNT_MASK 0xffffff
|
||||
|
||||
#define KIRKWOOD_I2S_PLAYCTL 0x2508
|
||||
#define KIRKWOOD_I2S_RECCTL 0x2408
|
||||
#define KIRKWOOD_I2S_CTL_JUST_MASK (0xf<<26)
|
||||
#define KIRKWOOD_I2S_CTL_LJ (0<<26)
|
||||
#define KIRKWOOD_I2S_CTL_I2S (5<<26)
|
||||
#define KIRKWOOD_I2S_CTL_RJ (8<<26)
|
||||
#define KIRKWOOD_I2S_CTL_SIZE_MASK (3<<30)
|
||||
#define KIRKWOOD_I2S_CTL_SIZE_16 (3<<30)
|
||||
#define KIRKWOOD_I2S_CTL_SIZE_20 (2<<30)
|
||||
#define KIRKWOOD_I2S_CTL_SIZE_24 (1<<30)
|
||||
#define KIRKWOOD_I2S_CTL_SIZE_32 (0<<30)
|
||||
|
||||
#define KIRKWOOD_AUDIO_BUF_MAX (16*1024*1024)
|
||||
|
||||
/* Theses values come from the marvell alsa driver */
|
||||
/* need to find where they come from */
|
||||
#define KIRKWOOD_SND_MIN_PERIODS 8
|
||||
#define KIRKWOOD_SND_MAX_PERIODS 16
|
||||
#define KIRKWOOD_SND_MIN_PERIOD_BYTES 0x4000
|
||||
#define KIRKWOOD_SND_MAX_PERIOD_BYTES 0x4000
|
||||
|
||||
struct kirkwood_dma_data {
|
||||
struct resource *mem;
|
||||
void __iomem *io;
|
||||
int irq;
|
||||
int burst;
|
||||
struct mbus_dram_target_info *dram;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,27 @@
|
|||
##
|
||||
## NUC900 series AC97 API
|
||||
##
|
||||
config SND_SOC_NUC900
|
||||
tristate "SoC Audio for NUC900 series"
|
||||
depends on ARCH_W90X900
|
||||
help
|
||||
This option enables support for AC97 mode on the NUC900 SoC.
|
||||
|
||||
config SND_SOC_NUC900_AC97
|
||||
tristate
|
||||
select AC97_BUS
|
||||
select SND_AC97_CODEC
|
||||
select SND_SOC_AC97_BUS
|
||||
|
||||
|
||||
##
|
||||
## Boards
|
||||
##
|
||||
config SND_SOC_NUC900EVB
|
||||
tristate "NUC900 AC97 support for demo board"
|
||||
depends on SND_SOC_NUC900
|
||||
select SND_SOC_NUC900_AC97
|
||||
select SND_SOC_AC97_CODEC
|
||||
help
|
||||
Select this option to enable audio (AC97) on the
|
||||
NUC900 demoboard.
|
|
@ -0,0 +1,11 @@
|
|||
# NUC900 series audio
|
||||
snd-soc-nuc900-pcm-objs := nuc900-pcm.o
|
||||
snd-soc-nuc900-ac97-objs := nuc900-ac97.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o
|
||||
obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o
|
||||
|
||||
# Boards
|
||||
snd-soc-nuc900-audio-objs := nuc900-audio.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o
|
|
@ -0,0 +1,442 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2010 Nuvoton technology corporation.
|
||||
*
|
||||
* Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#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 <linux/device.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <mach/mfp.h>
|
||||
|
||||
#include "nuc900-auido.h"
|
||||
|
||||
static DEFINE_MUTEX(ac97_mutex);
|
||||
struct nuc900_audio *nuc900_ac97_data;
|
||||
|
||||
static int nuc900_checkready(void)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
|
||||
if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY))
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* AC97 controller reads codec register */
|
||||
static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97,
|
||||
unsigned short reg)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
unsigned long timeout = 0x10000, val;
|
||||
|
||||
mutex_lock(&ac97_mutex);
|
||||
|
||||
val = nuc900_checkready();
|
||||
if (!!val) {
|
||||
dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* set the R_WB bit and write register index */
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg);
|
||||
|
||||
/* set the valid frame bit and valid slots */
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
|
||||
val |= (VALID_FRAME | SLOT1_VALID);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
|
||||
|
||||
udelay(100);
|
||||
|
||||
/* polling the AC_R_FINISH */
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
|
||||
val &= AC_R_FINISH;
|
||||
while (!val && timeout--)
|
||||
mdelay(1);
|
||||
|
||||
if (!timeout) {
|
||||
dev_err(nuc900_audio->dev, "AC97 read register time out !\n");
|
||||
val = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ;
|
||||
val &= ~SLOT1_VALID;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
|
||||
|
||||
if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) {
|
||||
dev_err(nuc900_audio->dev,
|
||||
"R_INDEX of REG_ACTL_ACIS1 not match!\n");
|
||||
}
|
||||
|
||||
udelay(100);
|
||||
val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF);
|
||||
|
||||
out:
|
||||
mutex_unlock(&ac97_mutex);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* AC97 controller writes to codec register */
|
||||
static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
unsigned long tmp, timeout = 0x10000;
|
||||
|
||||
mutex_lock(&ac97_mutex);
|
||||
|
||||
tmp = nuc900_checkready();
|
||||
if (!!tmp)
|
||||
dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
|
||||
|
||||
/* clear the R_WB bit and write register index */
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg);
|
||||
|
||||
/* write register value */
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val);
|
||||
|
||||
/* set the valid frame bit and valid slots */
|
||||
tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
|
||||
tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
|
||||
|
||||
udelay(100);
|
||||
|
||||
/* polling the AC_W_FINISH */
|
||||
tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
|
||||
tmp &= AC_W_FINISH;
|
||||
while (tmp && timeout--)
|
||||
mdelay(1);
|
||||
|
||||
if (!timeout)
|
||||
dev_err(nuc900_audio->dev, "AC97 write register time out !\n");
|
||||
|
||||
tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
|
||||
tmp &= ~(SLOT1_VALID | SLOT2_VALID);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
|
||||
|
||||
mutex_unlock(&ac97_mutex);
|
||||
|
||||
}
|
||||
|
||||
static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
unsigned long val;
|
||||
|
||||
mutex_lock(&ac97_mutex);
|
||||
|
||||
/* warm reset AC 97 */
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
|
||||
val |= AC_W_RES;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
|
||||
|
||||
udelay(1000);
|
||||
|
||||
val = nuc900_checkready();
|
||||
if (!!val)
|
||||
dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
|
||||
|
||||
mutex_unlock(&ac97_mutex);
|
||||
}
|
||||
|
||||
static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
unsigned long val;
|
||||
|
||||
mutex_lock(&ac97_mutex);
|
||||
|
||||
/* reset Audio Controller */
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
|
||||
val |= ACTL_RESET_BIT;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
|
||||
udelay(1000);
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
|
||||
val &= (~ACTL_RESET_BIT);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
|
||||
udelay(1000);
|
||||
|
||||
/* reset AC-link interface */
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
|
||||
val |= AC_RESET;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
|
||||
udelay(1000);
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
|
||||
val &= ~AC_RESET;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
|
||||
udelay(1000);
|
||||
|
||||
/* cold reset AC 97 */
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
|
||||
val |= AC_C_RES;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
|
||||
|
||||
udelay(1000);
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
|
||||
val &= (~AC_C_RES);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
|
||||
|
||||
udelay(1000);
|
||||
|
||||
mutex_unlock(&ac97_mutex);
|
||||
|
||||
}
|
||||
|
||||
/* AC97 controller operations */
|
||||
struct snd_ac97_bus_ops soc_ac97_ops = {
|
||||
.read = nuc900_ac97_read,
|
||||
.write = nuc900_ac97_write,
|
||||
.reset = nuc900_ac97_cold_reset,
|
||||
.warm_reset = nuc900_ac97_warm_reset,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(soc_ac97_ops);
|
||||
|
||||
static int nuc900_ac97_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
int ret, stype = SUBSTREAM_TYPE(substream);
|
||||
unsigned long val, tmp;
|
||||
|
||||
ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
|
||||
if (PCM_TX == stype) {
|
||||
tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
|
||||
tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
|
||||
|
||||
tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
|
||||
tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp);
|
||||
val |= AC_PLAY;
|
||||
} else {
|
||||
tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
|
||||
tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ);
|
||||
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp);
|
||||
val |= AC_RECORD;
|
||||
}
|
||||
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
|
||||
if (PCM_TX == stype) {
|
||||
tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
|
||||
tmp &= ~(SLOT3_VALID | SLOT4_VALID);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
|
||||
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR);
|
||||
val &= ~AC_PLAY;
|
||||
} else {
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR);
|
||||
val &= ~AC_RECORD;
|
||||
}
|
||||
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nuc900_ac97_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
unsigned long val;
|
||||
|
||||
mutex_lock(&ac97_mutex);
|
||||
|
||||
/* enable unit clock */
|
||||
clk_enable(nuc900_audio->clk);
|
||||
|
||||
/* enable audio controller and AC-link interface */
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
|
||||
val |= (IIS_AC_PIN_SEL | ACLINK_EN);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
|
||||
|
||||
mutex_unlock(&ac97_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nuc900_ac97_remove(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
|
||||
|
||||
clk_disable(nuc900_audio->clk);
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops nuc900_ac97_dai_ops = {
|
||||
.trigger = nuc900_ac97_trigger,
|
||||
};
|
||||
|
||||
struct snd_soc_dai nuc900_ac97_dai = {
|
||||
.name = "nuc900-ac97",
|
||||
.probe = nuc900_ac97_probe,
|
||||
.remove = nuc900_ac97_remove,
|
||||
.ac97_control = 1,
|
||||
.playback = {
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
},
|
||||
.capture = {
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
},
|
||||
.ops = &nuc900_ac97_dai_ops,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nuc900_ac97_dai);
|
||||
|
||||
static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev)
|
||||
{
|
||||
struct nuc900_audio *nuc900_audio;
|
||||
int ret;
|
||||
|
||||
if (nuc900_ac97_data)
|
||||
return -EBUSY;
|
||||
|
||||
nuc900_audio = kzalloc(sizeof(struct nuc900_audio), GFP_KERNEL);
|
||||
if (!nuc900_audio)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&nuc900_audio->lock);
|
||||
|
||||
nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!nuc900_audio->res) {
|
||||
ret = -ENODEV;
|
||||
goto out0;
|
||||
}
|
||||
|
||||
if (!request_mem_region(nuc900_audio->res->start,
|
||||
resource_size(nuc900_audio->res), pdev->name)) {
|
||||
ret = -EBUSY;
|
||||
goto out0;
|
||||
}
|
||||
|
||||
nuc900_audio->mmio = ioremap(nuc900_audio->res->start,
|
||||
resource_size(nuc900_audio->res));
|
||||
if (!nuc900_audio->mmio) {
|
||||
ret = -ENOMEM;
|
||||
goto out1;
|
||||
}
|
||||
|
||||
nuc900_audio->clk = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(nuc900_audio->clk)) {
|
||||
ret = PTR_ERR(nuc900_audio->clk);
|
||||
goto out2;
|
||||
}
|
||||
|
||||
nuc900_audio->irq_num = platform_get_irq(pdev, 0);
|
||||
if (!nuc900_audio->irq_num) {
|
||||
ret = -EBUSY;
|
||||
goto out2;
|
||||
}
|
||||
|
||||
nuc900_ac97_data = nuc900_audio;
|
||||
|
||||
nuc900_audio->dev = nuc900_ac97_dai.dev = &pdev->dev;
|
||||
|
||||
ret = snd_soc_register_dai(&nuc900_ac97_dai);
|
||||
if (ret)
|
||||
goto out3;
|
||||
|
||||
mfp_set_groupg(nuc900_audio->dev); /* enbale ac97 multifunction pin*/
|
||||
|
||||
return 0;
|
||||
|
||||
out3:
|
||||
clk_put(nuc900_audio->clk);
|
||||
out2:
|
||||
iounmap(nuc900_audio->mmio);
|
||||
out1:
|
||||
release_mem_region(nuc900_audio->res->start,
|
||||
resource_size(nuc900_audio->res));
|
||||
out0:
|
||||
kfree(nuc900_audio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit nuc900_ac97_drvremove(struct platform_device *pdev)
|
||||
{
|
||||
|
||||
snd_soc_unregister_dai(&nuc900_ac97_dai);
|
||||
|
||||
clk_put(nuc900_ac97_data->clk);
|
||||
iounmap(nuc900_ac97_data->mmio);
|
||||
release_mem_region(nuc900_ac97_data->res->start,
|
||||
resource_size(nuc900_ac97_data->res));
|
||||
|
||||
nuc900_ac97_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver nuc900_ac97_driver = {
|
||||
.driver = {
|
||||
.name = "nuc900-audio",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = nuc900_ac97_drvprobe,
|
||||
.remove = __devexit_p(nuc900_ac97_drvremove),
|
||||
};
|
||||
|
||||
static int __init nuc900_ac97_init(void)
|
||||
{
|
||||
return platform_driver_register(&nuc900_ac97_driver);
|
||||
}
|
||||
|
||||
static void __exit nuc900_ac97_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&nuc900_ac97_driver);
|
||||
}
|
||||
|
||||
module_init(nuc900_ac97_init);
|
||||
module_exit(nuc900_ac97_exit);
|
||||
|
||||
MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
|
||||
MODULE_DESCRIPTION("NUC900 AC97 SoC driver!");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:nuc900-ac97");
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Nuvoton technology corporation.
|
||||
*
|
||||
* Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#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 <sound/soc-dapm.h>
|
||||
|
||||
#include "../codecs/ac97.h"
|
||||
#include "nuc900-auido.h"
|
||||
|
||||
static struct snd_soc_dai_link nuc900evb_ac97_dai = {
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 HiFi",
|
||||
.cpu_dai = &nuc900_ac97_dai,
|
||||
.codec_dai = &ac97_dai,
|
||||
};
|
||||
|
||||
static struct snd_soc_card nuc900evb_audio_machine = {
|
||||
.name = "NUC900EVB_AC97",
|
||||
.dai_link = &nuc900evb_ac97_dai,
|
||||
.num_links = 1,
|
||||
.platform = &nuc900_soc_platform,
|
||||
};
|
||||
|
||||
static struct snd_soc_device nuc900evb_ac97_devdata = {
|
||||
.card = &nuc900evb_audio_machine,
|
||||
.codec_dev = &soc_codec_dev_ac97,
|
||||
};
|
||||
|
||||
static struct platform_device *nuc900evb_asoc_dev;
|
||||
|
||||
static int __init nuc900evb_audio_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = -ENOMEM;
|
||||
nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1);
|
||||
if (!nuc900evb_asoc_dev)
|
||||
goto out;
|
||||
|
||||
/* nuc900 board audio device */
|
||||
platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_ac97_devdata);
|
||||
|
||||
nuc900evb_ac97_devdata.dev = &nuc900evb_asoc_dev->dev;
|
||||
ret = platform_device_add(nuc900evb_asoc_dev);
|
||||
|
||||
if (ret) {
|
||||
platform_device_put(nuc900evb_asoc_dev);
|
||||
nuc900evb_asoc_dev = NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit nuc900evb_audio_exit(void)
|
||||
{
|
||||
platform_device_unregister(nuc900evb_asoc_dev);
|
||||
}
|
||||
|
||||
module_init(nuc900evb_audio_init);
|
||||
module_exit(nuc900evb_audio_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("NUC900 Series ASoC audio support");
|
||||
MODULE_AUTHOR("Wan ZongShun");
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Nuvoton technology corporation.
|
||||
*
|
||||
* Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _NUC900_AUDIO_H
|
||||
#define _NUC900_AUDIO_H
|
||||
|
||||
#include <linux/io.h>
|
||||
|
||||
/* Audio Control Registers */
|
||||
#define ACTL_CON 0x00
|
||||
#define ACTL_RESET 0x04
|
||||
#define ACTL_RDSTB 0x08
|
||||
#define ACTL_RDST_LENGTH 0x0C
|
||||
#define ACTL_RDSTC 0x10
|
||||
#define ACTL_RSR 0x14
|
||||
#define ACTL_PDSTB 0x18
|
||||
#define ACTL_PDST_LENGTH 0x1C
|
||||
#define ACTL_PDSTC 0x20
|
||||
#define ACTL_PSR 0x24
|
||||
#define ACTL_IISCON 0x28
|
||||
#define ACTL_ACCON 0x2C
|
||||
#define ACTL_ACOS0 0x30
|
||||
#define ACTL_ACOS1 0x34
|
||||
#define ACTL_ACOS2 0x38
|
||||
#define ACTL_ACIS0 0x3C
|
||||
#define ACTL_ACIS1 0x40
|
||||
#define ACTL_ACIS2 0x44
|
||||
#define ACTL_COUNTER 0x48
|
||||
|
||||
/* bit definition of REG_ACTL_CON register */
|
||||
#define R_DMA_IRQ 0x1000
|
||||
#define T_DMA_IRQ 0x0800
|
||||
#define IIS_AC_PIN_SEL 0x0100
|
||||
#define FIFO_TH 0x0080
|
||||
#define ADC_EN 0x0010
|
||||
#define M80_EN 0x0008
|
||||
#define ACLINK_EN 0x0004
|
||||
#define IIS_EN 0x0002
|
||||
|
||||
/* bit definition of REG_ACTL_RESET register */
|
||||
#define W5691_PLAY 0x20000
|
||||
#define ACTL_RESET_BIT 0x10000
|
||||
#define RECORD_RIGHT_CHNNEL 0x08000
|
||||
#define RECORD_LEFT_CHNNEL 0x04000
|
||||
#define PLAY_RIGHT_CHNNEL 0x02000
|
||||
#define PLAY_LEFT_CHNNEL 0x01000
|
||||
#define DAC_PLAY 0x00800
|
||||
#define ADC_RECORD 0x00400
|
||||
#define M80_PLAY 0x00200
|
||||
#define AC_RECORD 0x00100
|
||||
#define AC_PLAY 0x00080
|
||||
#define IIS_RECORD 0x00040
|
||||
#define IIS_PLAY 0x00020
|
||||
#define DAC_RESET 0x00010
|
||||
#define ADC_RESET 0x00008
|
||||
#define M80_RESET 0x00004
|
||||
#define AC_RESET 0x00002
|
||||
#define IIS_RESET 0x00001
|
||||
|
||||
/* bit definition of REG_ACTL_ACCON register */
|
||||
#define AC_BCLK_PU_EN 0x20
|
||||
#define AC_R_FINISH 0x10
|
||||
#define AC_W_FINISH 0x08
|
||||
#define AC_W_RES 0x04
|
||||
#define AC_C_RES 0x02
|
||||
|
||||
/* bit definition of ACTL_RSR register */
|
||||
#define R_FIFO_EMPTY 0x04
|
||||
#define R_DMA_END_IRQ 0x02
|
||||
#define R_DMA_MIDDLE_IRQ 0x01
|
||||
|
||||
/* bit definition of ACTL_PSR register */
|
||||
#define P_FIFO_EMPTY 0x04
|
||||
#define P_DMA_END_IRQ 0x02
|
||||
#define P_DMA_MIDDLE_IRQ 0x01
|
||||
|
||||
/* bit definition of ACTL_ACOS0 register */
|
||||
#define SLOT1_VALID 0x01
|
||||
#define SLOT2_VALID 0x02
|
||||
#define SLOT3_VALID 0x04
|
||||
#define SLOT4_VALID 0x08
|
||||
#define VALID_FRAME 0x10
|
||||
|
||||
/* bit definition of ACTL_ACOS1 register */
|
||||
#define R_WB 0x80
|
||||
|
||||
#define CODEC_READY 0x10
|
||||
#define RESET_PRSR 0x00
|
||||
#define AUDIO_WRITE(addr, val) __raw_writel(val, addr)
|
||||
#define AUDIO_READ(addr) __raw_readl(addr)
|
||||
#define PCM_TX 0
|
||||
#define PCM_RX 1
|
||||
#define SUBSTREAM_TYPE(substream) \
|
||||
((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
|
||||
|
||||
struct nuc900_audio {
|
||||
void __iomem *mmio;
|
||||
spinlock_t lock;
|
||||
dma_addr_t dma_addr[2];
|
||||
unsigned long buffersize[2];
|
||||
unsigned long irq_num;
|
||||
struct snd_pcm_substream *substream;
|
||||
struct resource *res;
|
||||
struct clk *clk;
|
||||
struct device *dev;
|
||||
|
||||
};
|
||||
|
||||
extern struct nuc900_audio *nuc900_ac97_data;
|
||||
extern struct snd_soc_dai nuc900_ac97_dai;
|
||||
extern struct snd_soc_platform nuc900_soc_platform;
|
||||
|
||||
#endif /*end _NUC900_AUDIO_H */
|
|
@ -0,0 +1,352 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Nuvoton technology corporation.
|
||||
*
|
||||
* Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.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 <mach/hardware.h>
|
||||
|
||||
#include "nuc900-auido.h"
|
||||
|
||||
static const struct snd_pcm_hardware nuc900_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = 4*1024,
|
||||
.period_bytes_min = 1*1024,
|
||||
.period_bytes_max = 4*1024,
|
||||
.periods_min = 1,
|
||||
.periods_max = 1024,
|
||||
};
|
||||
|
||||
static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio = runtime->private_data;
|
||||
unsigned long flags, stype = SUBSTREAM_TYPE(substream);
|
||||
int ret = 0;
|
||||
|
||||
spin_lock_irqsave(&nuc900_audio->lock, flags);
|
||||
|
||||
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
nuc900_audio->substream = substream;
|
||||
nuc900_audio->dma_addr[stype] = runtime->dma_addr;
|
||||
nuc900_audio->buffersize[stype] = params_buffer_bytes(params);
|
||||
|
||||
spin_unlock_irqrestore(&nuc900_audio->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void nuc900_update_dma_register(struct snd_pcm_substream *substream,
|
||||
dma_addr_t dma_addr, size_t count)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio = runtime->private_data;
|
||||
void __iomem *mmio_addr, *mmio_len;
|
||||
|
||||
if (SUBSTREAM_TYPE(substream) == PCM_TX) {
|
||||
mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
|
||||
mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
|
||||
} else {
|
||||
mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
|
||||
mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
|
||||
}
|
||||
|
||||
AUDIO_WRITE(mmio_addr, dma_addr);
|
||||
AUDIO_WRITE(mmio_len, count);
|
||||
}
|
||||
|
||||
static void nuc900_dma_start(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio = runtime->private_data;
|
||||
unsigned long val;
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
|
||||
val |= (T_DMA_IRQ | R_DMA_IRQ);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
|
||||
}
|
||||
|
||||
static void nuc900_dma_stop(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio = runtime->private_data;
|
||||
unsigned long val;
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
|
||||
val &= ~(T_DMA_IRQ | R_DMA_IRQ);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
|
||||
}
|
||||
|
||||
static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct snd_pcm_substream *substream = dev_id;
|
||||
struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
|
||||
unsigned long val;
|
||||
|
||||
spin_lock(&nuc900_audio->lock);
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
|
||||
|
||||
if (val & R_DMA_IRQ) {
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
|
||||
|
||||
if (val & R_DMA_MIDDLE_IRQ) {
|
||||
val |= R_DMA_MIDDLE_IRQ;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
|
||||
}
|
||||
|
||||
if (val & R_DMA_END_IRQ) {
|
||||
val |= R_DMA_END_IRQ;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
|
||||
}
|
||||
} else if (val & T_DMA_IRQ) {
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
|
||||
|
||||
if (val & P_DMA_MIDDLE_IRQ) {
|
||||
val |= P_DMA_MIDDLE_IRQ;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
|
||||
}
|
||||
|
||||
if (val & P_DMA_END_IRQ) {
|
||||
val |= P_DMA_END_IRQ;
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
|
||||
}
|
||||
} else {
|
||||
dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
|
||||
spin_unlock(&nuc900_audio->lock);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
spin_unlock(&nuc900_audio->lock);
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio = runtime->private_data;
|
||||
unsigned long flags, val, stype = SUBSTREAM_TYPE(substream);;
|
||||
|
||||
spin_lock_irqsave(&nuc900_audio->lock, flags);
|
||||
|
||||
nuc900_update_dma_register(substream,
|
||||
nuc900_audio->dma_addr[stype], nuc900_audio->buffersize[stype]);
|
||||
|
||||
val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
|
||||
|
||||
switch (runtime->channels) {
|
||||
case 1:
|
||||
if (PCM_TX == stype) {
|
||||
val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
|
||||
val |= PLAY_RIGHT_CHNNEL;
|
||||
} else {
|
||||
val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
|
||||
val |= RECORD_RIGHT_CHNNEL;
|
||||
}
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
break;
|
||||
case 2:
|
||||
if (PCM_TX == stype)
|
||||
val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
|
||||
else
|
||||
val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
|
||||
AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
spin_unlock_irqrestore(&nuc900_audio->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
nuc900_dma_start(substream);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
nuc900_dma_stop(substream);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int nuc900_dma_getposition(struct snd_pcm_substream *substream,
|
||||
dma_addr_t *src, dma_addr_t *dst)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio = runtime->private_data;
|
||||
|
||||
if (src != NULL)
|
||||
*src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
|
||||
|
||||
if (dst != NULL)
|
||||
*dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
dma_addr_t src, dst;
|
||||
unsigned long res;
|
||||
|
||||
nuc900_dma_getposition(substream, &src, &dst);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
res = dst - runtime->dma_addr;
|
||||
else
|
||||
res = src - runtime->dma_addr;
|
||||
|
||||
return bytes_to_frames(substream->runtime, res);
|
||||
}
|
||||
|
||||
static int nuc900_dma_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
|
||||
|
||||
nuc900_audio = nuc900_ac97_data;
|
||||
|
||||
if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
|
||||
IRQF_DISABLED, "nuc900-dma", substream))
|
||||
return -EBUSY;
|
||||
|
||||
runtime->private_data = nuc900_audio;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuc900_dma_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct nuc900_audio *nuc900_audio = runtime->private_data;
|
||||
|
||||
free_irq(nuc900_audio->irq_num, substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuc900_dma_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
return dma_mmap_writecombine(substream->pcm->card->dev, vma,
|
||||
runtime->dma_area,
|
||||
runtime->dma_addr,
|
||||
runtime->dma_bytes);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops nuc900_dma_ops = {
|
||||
.open = nuc900_dma_open,
|
||||
.close = nuc900_dma_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = nuc900_dma_hw_params,
|
||||
.hw_free = nuc900_dma_hw_free,
|
||||
.prepare = nuc900_dma_prepare,
|
||||
.trigger = nuc900_dma_trigger,
|
||||
.pointer = nuc900_dma_pointer,
|
||||
.mmap = nuc900_dma_mmap,
|
||||
};
|
||||
|
||||
static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm)
|
||||
{
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
static int nuc900_dma_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
{
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &nuc900_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
card->dev, 4 * 1024, (4 * 1024) - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_platform nuc900_soc_platform = {
|
||||
.name = "nuc900-dma",
|
||||
.pcm_ops = &nuc900_dma_ops,
|
||||
.pcm_new = nuc900_dma_new,
|
||||
.pcm_free = nuc900_dma_free_dma_buffers,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nuc900_soc_platform);
|
||||
|
||||
static int __init nuc900_soc_platform_init(void)
|
||||
{
|
||||
return snd_soc_register_platform(&nuc900_soc_platform);
|
||||
}
|
||||
|
||||
static void __exit nuc900_soc_platform_exit(void)
|
||||
{
|
||||
snd_soc_unregister_platform(&nuc900_soc_platform);
|
||||
}
|
||||
|
||||
module_init(nuc900_soc_platform_init);
|
||||
module_exit(nuc900_soc_platform_exit);
|
||||
|
||||
MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
|
||||
MODULE_DESCRIPTION("nuc900 Audio DMA module");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,6 +1,6 @@
|
|||
config SND_S3C24XX_SOC
|
||||
tristate "SoC Audio for the Samsung S3CXXXX chips"
|
||||
depends on ARCH_S3C2410 || ARCH_S3C64XX
|
||||
depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210
|
||||
select S3C64XX_DMA if ARCH_S3C64XX
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
|
@ -120,7 +120,7 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES
|
|||
|
||||
config SND_SOC_SMDK_WM9713
|
||||
tristate "SoC AC97 Audio support for SMDK with WM9713"
|
||||
depends on SND_S3C24XX_SOC && MACH_SMDK6410
|
||||
depends on SND_S3C24XX_SOC && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110)
|
||||
select SND_SOC_WM9713
|
||||
select SND_S3C_SOC_AC97
|
||||
help
|
||||
|
|
|
@ -25,6 +25,9 @@ static struct snd_soc_card smdk;
|
|||
* Default CFG switch settings to use this driver:
|
||||
*
|
||||
* SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off
|
||||
* SMDKC100: Set CFG6 1-3 On, CFG7 1 On
|
||||
* SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On
|
||||
* SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On
|
||||
*/
|
||||
|
||||
/*
|
||||
|
|
|
@ -2352,6 +2352,101 @@ int snd_soc_limit_volume(struct snd_soc_codec *codec,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
|
||||
|
||||
/**
|
||||
* snd_soc_info_volsw_2r_sx - double with tlv and variable data size
|
||||
* mixer info callback
|
||||
* @kcontrol: mixer control
|
||||
* @uinfo: control element information
|
||||
*
|
||||
* Returns 0 for success.
|
||||
*/
|
||||
int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
int max = mc->max;
|
||||
int min = mc->min;
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 2;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = max-min;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx);
|
||||
|
||||
/**
|
||||
* snd_soc_get_volsw_2r_sx - double with tlv and variable data size
|
||||
* mixer get callback
|
||||
* @kcontrol: mixer control
|
||||
* @uinfo: control element information
|
||||
*
|
||||
* Returns 0 for success.
|
||||
*/
|
||||
int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int mask = (1<<mc->shift)-1;
|
||||
int min = mc->min;
|
||||
int val = snd_soc_read(codec, mc->reg) & mask;
|
||||
int valr = snd_soc_read(codec, mc->rreg) & mask;
|
||||
|
||||
ucontrol->value.integer.value[0] = ((val & 0xff)-min);
|
||||
ucontrol->value.integer.value[1] = ((valr & 0xff)-min);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
|
||||
|
||||
/**
|
||||
* snd_soc_put_volsw_2r_sx - double with tlv and variable data size
|
||||
* mixer put callback
|
||||
* @kcontrol: mixer control
|
||||
* @uinfo: control element information
|
||||
*
|
||||
* Returns 0 for success.
|
||||
*/
|
||||
int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int mask = (1<<mc->shift)-1;
|
||||
int min = mc->min;
|
||||
int ret;
|
||||
unsigned int val, valr, oval, ovalr;
|
||||
|
||||
val = ((ucontrol->value.integer.value[0]+min) & 0xff);
|
||||
val &= mask;
|
||||
valr = ((ucontrol->value.integer.value[1]+min) & 0xff);
|
||||
valr &= mask;
|
||||
|
||||
oval = snd_soc_read(codec, mc->reg) & mask;
|
||||
ovalr = snd_soc_read(codec, mc->rreg) & mask;
|
||||
|
||||
ret = 0;
|
||||
if (oval != val) {
|
||||
ret = snd_soc_write(codec, mc->reg, val);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
ret = 1;
|
||||
}
|
||||
if (ovalr != valr) {
|
||||
ret = snd_soc_write(codec, mc->rreg, valr);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx);
|
||||
|
||||
/**
|
||||
* snd_soc_dai_set_sysclk - configure DAI system or master clock.
|
||||
* @dai: DAI
|
||||
|
|
Загрузка…
Ссылка в новой задаче