mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 21:51:40 +00:00
Merge branch 'topic/asoc' into for-linus
This commit is contained in:
commit
13b137ef03
@ -533,6 +533,8 @@ L: device-drivers-devel@blackfin.uclinux.org
|
||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||
W: http://wiki.analog.com/
|
||||
S: Supported
|
||||
F: sound/soc/codecs/adau*
|
||||
F: sound/soc/codecs/adav*
|
||||
F: sound/soc/codecs/ad1*
|
||||
F: sound/soc/codecs/ssm*
|
||||
|
||||
|
@ -209,6 +209,10 @@ struct snd_soc_dai_driver {
|
||||
struct snd_soc_pcm_stream capture;
|
||||
struct snd_soc_pcm_stream playback;
|
||||
unsigned int symmetric_rates:1;
|
||||
|
||||
/* probe ordering - for components with runtime dependencies */
|
||||
int probe_order;
|
||||
int remove_order;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -348,6 +348,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm);
|
||||
void snd_soc_dapm_free(struct snd_soc_dapm_context *dapm);
|
||||
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
|
||||
const struct snd_soc_dapm_route *route, int num);
|
||||
int snd_soc_dapm_weak_routes(struct snd_soc_dapm_context *dapm,
|
||||
const struct snd_soc_dapm_route *route, int num);
|
||||
|
||||
/* dapm events */
|
||||
int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
|
||||
@ -429,6 +431,7 @@ struct snd_soc_dapm_path {
|
||||
/* status */
|
||||
u32 connect:1; /* source and sink widgets are connected */
|
||||
u32 walked:1; /* path has been walked */
|
||||
u32 weak:1; /* path ignored for power management */
|
||||
|
||||
int (*connected)(struct snd_soc_dapm_widget *source,
|
||||
struct snd_soc_dapm_widget *sink);
|
||||
@ -444,6 +447,7 @@ struct snd_soc_dapm_widget {
|
||||
char *name; /* widget name */
|
||||
char *sname; /* stream name */
|
||||
struct snd_soc_codec *codec;
|
||||
struct snd_soc_platform *platform;
|
||||
struct list_head list;
|
||||
struct snd_soc_dapm_context *dapm;
|
||||
|
||||
@ -507,10 +511,11 @@ struct snd_soc_dapm_context {
|
||||
|
||||
struct device *dev; /* from parent - for debug */
|
||||
struct snd_soc_codec *codec; /* parent codec */
|
||||
struct snd_soc_platform *platform; /* parent platform */
|
||||
struct snd_soc_card *card; /* parent card */
|
||||
|
||||
/* used during DAPM updates */
|
||||
int dev_power;
|
||||
enum snd_soc_bias_level target_bias_level;
|
||||
struct list_head list;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
@ -202,6 +202,16 @@
|
||||
#define SOC_VALUE_ENUM_SINGLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \
|
||||
SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues)
|
||||
|
||||
/*
|
||||
* Component probe and remove ordering levels for components with runtime
|
||||
* dependencies.
|
||||
*/
|
||||
#define SND_SOC_COMP_ORDER_FIRST -2
|
||||
#define SND_SOC_COMP_ORDER_EARLY -1
|
||||
#define SND_SOC_COMP_ORDER_NORMAL 0
|
||||
#define SND_SOC_COMP_ORDER_LATE 1
|
||||
#define SND_SOC_COMP_ORDER_LAST 2
|
||||
|
||||
/*
|
||||
* Bias levels
|
||||
*
|
||||
@ -214,10 +224,10 @@
|
||||
* @OFF: Power Off. No restrictions on transition times.
|
||||
*/
|
||||
enum snd_soc_bias_level {
|
||||
SND_SOC_BIAS_OFF,
|
||||
SND_SOC_BIAS_STANDBY,
|
||||
SND_SOC_BIAS_PREPARE,
|
||||
SND_SOC_BIAS_ON,
|
||||
SND_SOC_BIAS_OFF = 0,
|
||||
SND_SOC_BIAS_STANDBY = 1,
|
||||
SND_SOC_BIAS_PREPARE = 2,
|
||||
SND_SOC_BIAS_ON = 3,
|
||||
};
|
||||
|
||||
struct snd_jack;
|
||||
@ -258,6 +268,11 @@ enum snd_soc_compress_type {
|
||||
SND_SOC_RBTREE_COMPRESSION
|
||||
};
|
||||
|
||||
enum snd_soc_pcm_subclass {
|
||||
SND_SOC_PCM_CLASS_PCM = 0,
|
||||
SND_SOC_PCM_CLASS_BE = 1,
|
||||
};
|
||||
|
||||
int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id,
|
||||
unsigned int freq, int dir);
|
||||
int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
|
||||
@ -297,6 +312,10 @@ int snd_soc_default_readable_register(struct snd_soc_codec *codec,
|
||||
unsigned int reg);
|
||||
int snd_soc_default_writable_register(struct snd_soc_codec *codec,
|
||||
unsigned int reg);
|
||||
int snd_soc_platform_read(struct snd_soc_platform *platform,
|
||||
unsigned int reg);
|
||||
int snd_soc_platform_write(struct snd_soc_platform *platform,
|
||||
unsigned int reg, unsigned int val);
|
||||
|
||||
/* Utility functions to get clock rates from various things */
|
||||
int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots);
|
||||
@ -349,6 +368,8 @@ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
|
||||
const char *prefix);
|
||||
int snd_soc_add_controls(struct snd_soc_codec *codec,
|
||||
const struct snd_kcontrol_new *controls, int num_controls);
|
||||
int snd_soc_add_platform_controls(struct snd_soc_platform *platform,
|
||||
const struct snd_kcontrol_new *controls, int num_controls);
|
||||
int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo);
|
||||
int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol,
|
||||
@ -612,6 +633,10 @@ struct snd_soc_codec_driver {
|
||||
|
||||
void (*seq_notifier)(struct snd_soc_dapm_context *,
|
||||
enum snd_soc_dapm_type, int);
|
||||
|
||||
/* probe ordering - for components with runtime dependencies */
|
||||
int probe_order;
|
||||
int remove_order;
|
||||
};
|
||||
|
||||
/* SoC platform interface */
|
||||
@ -623,10 +648,17 @@ struct snd_soc_platform_driver {
|
||||
int (*resume)(struct snd_soc_dai *dai);
|
||||
|
||||
/* pcm creation and destruction */
|
||||
int (*pcm_new)(struct snd_card *, struct snd_soc_dai *,
|
||||
struct snd_pcm *);
|
||||
int (*pcm_new)(struct snd_soc_pcm_runtime *);
|
||||
void (*pcm_free)(struct snd_pcm *);
|
||||
|
||||
/* Default control and setup, added after probe() is run */
|
||||
const struct snd_kcontrol_new *controls;
|
||||
int num_controls;
|
||||
const struct snd_soc_dapm_widget *dapm_widgets;
|
||||
int num_dapm_widgets;
|
||||
const struct snd_soc_dapm_route *dapm_routes;
|
||||
int num_dapm_routes;
|
||||
|
||||
/*
|
||||
* For platform caused delay reporting.
|
||||
* Optional.
|
||||
@ -636,6 +668,14 @@ struct snd_soc_platform_driver {
|
||||
|
||||
/* platform stream ops */
|
||||
struct snd_pcm_ops *ops;
|
||||
|
||||
/* probe ordering - for components with runtime dependencies */
|
||||
int probe_order;
|
||||
int remove_order;
|
||||
|
||||
/* platform IO - used for platform DAPM */
|
||||
unsigned int (*read)(struct snd_soc_platform *, unsigned int);
|
||||
int (*write)(struct snd_soc_platform *, unsigned int, unsigned int);
|
||||
};
|
||||
|
||||
struct snd_soc_platform {
|
||||
@ -650,6 +690,8 @@ struct snd_soc_platform {
|
||||
struct snd_soc_card *card;
|
||||
struct list_head list;
|
||||
struct list_head card_list;
|
||||
|
||||
struct snd_soc_dapm_context dapm;
|
||||
};
|
||||
|
||||
struct snd_soc_dai_link {
|
||||
@ -725,8 +767,10 @@ struct snd_soc_card {
|
||||
|
||||
/* callbacks */
|
||||
int (*set_bias_level)(struct snd_soc_card *,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level);
|
||||
int (*set_bias_level_post)(struct snd_soc_card *,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level);
|
||||
|
||||
long pmdown_time;
|
||||
@ -789,6 +833,9 @@ struct snd_soc_pcm_runtime {
|
||||
struct device dev;
|
||||
struct snd_soc_card *card;
|
||||
struct snd_soc_dai_link *dai_link;
|
||||
struct mutex pcm_mutex;
|
||||
enum snd_soc_pcm_subclass pcm_subclass;
|
||||
struct snd_pcm_ops ops;
|
||||
|
||||
unsigned int complete:1;
|
||||
unsigned int dev_registered:1;
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
struct snd_soc_jack;
|
||||
struct snd_soc_codec;
|
||||
struct snd_soc_platform;
|
||||
struct snd_soc_card;
|
||||
struct snd_soc_dapm_widget;
|
||||
|
||||
@ -59,6 +60,50 @@ DEFINE_EVENT(snd_soc_reg, snd_soc_reg_read,
|
||||
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(snd_soc_preg,
|
||||
|
||||
TP_PROTO(struct snd_soc_platform *platform, unsigned int reg,
|
||||
unsigned int val),
|
||||
|
||||
TP_ARGS(platform, reg, val),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__string( name, platform->name )
|
||||
__field( int, id )
|
||||
__field( unsigned int, reg )
|
||||
__field( unsigned int, val )
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__assign_str(name, platform->name);
|
||||
__entry->id = platform->id;
|
||||
__entry->reg = reg;
|
||||
__entry->val = val;
|
||||
),
|
||||
|
||||
TP_printk("platform=%s.%d reg=%x val=%x", __get_str(name),
|
||||
(int)__entry->id, (unsigned int)__entry->reg,
|
||||
(unsigned int)__entry->val)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(snd_soc_preg, snd_soc_preg_write,
|
||||
|
||||
TP_PROTO(struct snd_soc_platform *platform, unsigned int reg,
|
||||
unsigned int val),
|
||||
|
||||
TP_ARGS(platform, reg, val)
|
||||
|
||||
);
|
||||
|
||||
DEFINE_EVENT(snd_soc_preg, snd_soc_preg_read,
|
||||
|
||||
TP_PROTO(struct snd_soc_platform *platform, unsigned int reg,
|
||||
unsigned int val),
|
||||
|
||||
TP_ARGS(platform, reg, val)
|
||||
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(snd_soc_card,
|
||||
|
||||
TP_PROTO(struct snd_soc_card *card, int val),
|
||||
|
@ -1,4 +1,5 @@
|
||||
snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o soc-utils.o
|
||||
snd-soc-core-objs += soc-pcm.o soc-io.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
|
||||
obj-$(CONFIG_SND_SOC) += codecs/
|
||||
|
@ -364,9 +364,11 @@ static struct snd_pcm_ops atmel_pcm_ops = {
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static u64 atmel_pcm_dmamask = 0xffffffff;
|
||||
|
||||
static int atmel_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
@ -382,7 +384,7 @@ static int atmel_pcm_new(struct snd_card *card,
|
||||
}
|
||||
|
||||
if (dai->driver->capture.channels_min) {
|
||||
pr_debug("at32-pcm:"
|
||||
pr_debug("atmel-pcm:"
|
||||
"Allocating PCM capture DMA buffer\n");
|
||||
ret = atmel_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
|
@ -60,7 +60,7 @@ struct atmel_ssc_mask {
|
||||
* This structure, shared between the PCM driver and the interface,
|
||||
* contains all information required by the PCM driver to perform the
|
||||
* PDC DMA operation. All fields except dma_intr_handler() are initialized
|
||||
* by the interface. The dms_intr_handler() pointer is set by the PCM
|
||||
* by the interface. The dma_intr_handler() pointer is set by the PCM
|
||||
* driver and called by the interface SSC interrupt handler if it is
|
||||
* non-NULL.
|
||||
*/
|
||||
|
@ -402,7 +402,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
|
||||
if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S
|
||||
&& bits > 16) {
|
||||
printk(KERN_WARNING
|
||||
"atmel_ssc_dai: sample size %d"
|
||||
"atmel_ssc_dai: sample size %d "
|
||||
"is too large for I2S\n", bits);
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -838,10 +838,8 @@ int atmel_ssc_set_audio(int ssc_id)
|
||||
}
|
||||
|
||||
ssc_pdev = platform_device_alloc("atmel-ssc-dai", ssc_id);
|
||||
if (!ssc_pdev) {
|
||||
ssc_free(ssc);
|
||||
if (!ssc_pdev)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* If we can grab the SSC briefly to parent the DAI device off it */
|
||||
ssc = ssc_request(ssc_id);
|
||||
|
@ -92,6 +92,7 @@ static struct snd_soc_ops at91sam9g20ek_ops = {
|
||||
};
|
||||
|
||||
static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
static int mclk_on;
|
||||
|
@ -319,10 +319,11 @@ static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
static int au1xpsc_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int au1xpsc_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1);
|
||||
|
||||
|
@ -10,13 +10,36 @@ config SND_BF5XX_I2S
|
||||
|
||||
config SND_BF5XX_SOC_SSM2602
|
||||
tristate "SoC SSM2602 Audio support for BF52x ezkit"
|
||||
depends on SND_BF5XX_I2S
|
||||
depends on SND_BF5XX_I2S && (SPI_MASTER || I2C)
|
||||
select SND_BF5XX_SOC_I2S
|
||||
select SND_SOC_SSM2602
|
||||
select I2C
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on BF527-EZKIT.
|
||||
|
||||
config SND_SOC_BFIN_EVAL_ADAU1701
|
||||
tristate "Support for the EVAL-ADAU1701MINIZ board on Blackfin eval boards"
|
||||
depends on SND_BF5XX_I2S
|
||||
select SND_BF5XX_SOC_I2S
|
||||
select SND_SOC_ADAU1701
|
||||
select I2C
|
||||
help
|
||||
Say Y if you want to add support for the Analog Devices EVAL-ADAU1701MINIZ
|
||||
board connected to one of the Blackfin evaluation boards like the
|
||||
BF5XX-STAMP or BF5XX-EZKIT.
|
||||
|
||||
config SND_SOC_BFIN_EVAL_ADAV80X
|
||||
tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards"
|
||||
depends on SND_BF5XX_I2S && (SPI_MASTER || I2C)
|
||||
select SND_BF5XX_SOC_I2S
|
||||
select SND_SOC_ADAV80X
|
||||
help
|
||||
Say Y if you want to add support for the Analog Devices EVAL-ADAV801 or
|
||||
EVAL-ADAV803 board connected to one of the Blackfin evaluation boards
|
||||
like the BF5XX-STAMP or BF5XX-EZKIT.
|
||||
|
||||
Note: This driver assumes that the ADAV80X digital record and playback
|
||||
interfaces are connected to the first SPORT port on the BF5XX board.
|
||||
|
||||
config SND_BF5XX_SOC_AD73311
|
||||
tristate "SoC AD73311 Audio support for Blackfin"
|
||||
depends on SND_BF5XX_I2S
|
||||
|
@ -21,9 +21,13 @@ snd-ad1980-objs := bf5xx-ad1980.o
|
||||
snd-ssm2602-objs := bf5xx-ssm2602.o
|
||||
snd-ad73311-objs := bf5xx-ad73311.o
|
||||
snd-ad193x-objs := bf5xx-ad193x.o
|
||||
snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o
|
||||
snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o
|
||||
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o
|
||||
obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) += snd-soc-bfin-eval-adau1701.o
|
||||
obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAV80X) += snd-soc-bfin-eval-adav80x.o
|
||||
|
@ -418,9 +418,11 @@ static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
|
||||
static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
int bf5xx_pcm_ac97_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("%s enter\n", __func__);
|
||||
|
@ -168,7 +168,7 @@ static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime, \
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
@ -257,9 +257,11 @@ static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
|
||||
static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
int bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("%s enter\n", __func__);
|
||||
@ -304,8 +306,8 @@ static int __devexit bfin_i2s_soc_platform_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver bfin_i2s_pcm_driver = {
|
||||
.driver = {
|
||||
.name = "bfin-i2s-pcm-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.name = "bfin-i2s-pcm-audio",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
|
||||
.probe = bfin_i2s_soc_platform_probe,
|
||||
|
@ -283,9 +283,11 @@ static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
|
||||
static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int bf5xx_pcm_tdm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
139
sound/soc/blackfin/bfin-eval-adau1701.c
Normal file
139
sound/soc/blackfin/bfin-eval-adau1701.c
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Machine driver for EVAL-ADAU1701MINIZ on Analog Devices bfin
|
||||
* evaluation boards.
|
||||
*
|
||||
* Copyright 2011 Analog Devices Inc.
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "../codecs/adau1701.h"
|
||||
|
||||
static const struct snd_soc_dapm_widget bfin_eval_adau1701_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_SPK("Speaker", NULL),
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route bfin_eval_adau1701_dapm_routes[] = {
|
||||
{ "Speaker", NULL, "OUT0" },
|
||||
{ "Speaker", NULL, "OUT1" },
|
||||
{ "Line Out", NULL, "OUT2" },
|
||||
{ "Line Out", NULL, "OUT3" },
|
||||
|
||||
{ "IN0", NULL, "Line In" },
|
||||
{ "IN1", NULL, "Line In" },
|
||||
};
|
||||
|
||||
static int bfin_eval_adau1701_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1701_CLK_SRC_OSC, 12288000,
|
||||
SND_SOC_CLOCK_IN);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops bfin_eval_adau1701_ops = {
|
||||
.hw_params = bfin_eval_adau1701_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link bfin_eval_adau1701_dai[] = {
|
||||
{
|
||||
.name = "adau1701",
|
||||
.stream_name = "adau1701",
|
||||
.cpu_dai_name = "bfin-i2s.0",
|
||||
.codec_dai_name = "adau1701",
|
||||
.platform_name = "bfin-i2s-pcm-audio",
|
||||
.codec_name = "adau1701.0-0034",
|
||||
.ops = &bfin_eval_adau1701_ops,
|
||||
},
|
||||
{
|
||||
.name = "adau1701",
|
||||
.stream_name = "adau1701",
|
||||
.cpu_dai_name = "bfin-i2s.1",
|
||||
.codec_dai_name = "adau1701",
|
||||
.platform_name = "bfin-i2s-pcm-audio",
|
||||
.codec_name = "adau1701.0-0034",
|
||||
.ops = &bfin_eval_adau1701_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card bfin_eval_adau1701 = {
|
||||
.name = "bfin-eval-adau1701",
|
||||
.dai_link = &bfin_eval_adau1701_dai[CONFIG_SND_BF5XX_SPORT_NUM],
|
||||
.num_links = 1,
|
||||
|
||||
.dapm_widgets = bfin_eval_adau1701_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(bfin_eval_adau1701_dapm_widgets),
|
||||
.dapm_routes = bfin_eval_adau1701_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(bfin_eval_adau1701_dapm_routes),
|
||||
};
|
||||
|
||||
static int bfin_eval_adau1701_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &bfin_eval_adau1701;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
return snd_soc_register_card(&bfin_eval_adau1701);
|
||||
}
|
||||
|
||||
static int __devexit bfin_eval_adau1701_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver bfin_eval_adau1701_driver = {
|
||||
.driver = {
|
||||
.name = "bfin-eval-adau1701",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
},
|
||||
.probe = bfin_eval_adau1701_probe,
|
||||
.remove = __devexit_p(bfin_eval_adau1701_remove),
|
||||
};
|
||||
|
||||
static int __init bfin_eval_adau1701_init(void)
|
||||
{
|
||||
return platform_driver_register(&bfin_eval_adau1701_driver);
|
||||
}
|
||||
module_init(bfin_eval_adau1701_init);
|
||||
|
||||
static void __exit bfin_eval_adau1701_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&bfin_eval_adau1701_driver);
|
||||
}
|
||||
module_exit(bfin_eval_adau1701_exit);
|
||||
|
||||
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
||||
MODULE_DESCRIPTION("ALSA SoC bfin ADAU1701 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:bfin-eval-adau1701");
|
173
sound/soc/blackfin/bfin-eval-adav80x.c
Normal file
173
sound/soc/blackfin/bfin-eval-adav80x.c
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Machine driver for EVAL-ADAV801 and EVAL-ADAV803 on Analog Devices bfin
|
||||
* evaluation boards.
|
||||
*
|
||||
* Copyright 2011 Analog Devices Inc.
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "../codecs/adav80x.h"
|
||||
|
||||
static const struct snd_soc_dapm_widget bfin_eval_adav80x_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route bfin_eval_adav80x_dapm_routes[] = {
|
||||
{ "Line Out", NULL, "VOUTL" },
|
||||
{ "Line Out", NULL, "VOUTR" },
|
||||
|
||||
{ "VINL", NULL, "Line In" },
|
||||
{ "VINR", NULL, "Line In" },
|
||||
};
|
||||
|
||||
static int bfin_eval_adav80x_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, ADAV80X_PLL1, ADAV80X_PLL_SRC_XTAL,
|
||||
27000000, params_rate(params) * 256);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_PLL1,
|
||||
params_rate(params) * 256, SND_SOC_CLOCK_IN);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bfin_eval_adav80x_codec_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
|
||||
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK1, 0,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK2, 0,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK3, 0,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
|
||||
snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_XTAL, 2700000, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops bfin_eval_adav80x_ops = {
|
||||
.hw_params = bfin_eval_adav80x_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link bfin_eval_adav80x_dais[] = {
|
||||
{
|
||||
.name = "adav80x",
|
||||
.stream_name = "ADAV80x HiFi",
|
||||
.cpu_dai_name = "bfin-i2s.0",
|
||||
.codec_dai_name = "adav80x-hifi",
|
||||
.platform_name = "bfin-i2s-pcm-audio",
|
||||
.init = bfin_eval_adav80x_codec_init,
|
||||
.ops = &bfin_eval_adav80x_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card bfin_eval_adav80x = {
|
||||
.name = "bfin-eval-adav80x",
|
||||
.dai_link = bfin_eval_adav80x_dais,
|
||||
.num_links = ARRAY_SIZE(bfin_eval_adav80x_dais),
|
||||
|
||||
.dapm_widgets = bfin_eval_adav80x_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(bfin_eval_adav80x_dapm_widgets),
|
||||
.dapm_routes = bfin_eval_adav80x_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(bfin_eval_adav80x_dapm_routes),
|
||||
};
|
||||
|
||||
enum bfin_eval_adav80x_type {
|
||||
BFIN_EVAL_ADAV801,
|
||||
BFIN_EVAL_ADAV803,
|
||||
};
|
||||
|
||||
static int bfin_eval_adav80x_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &bfin_eval_adav80x;
|
||||
const char *codec_name;
|
||||
|
||||
switch (platform_get_device_id(pdev)->driver_data) {
|
||||
case BFIN_EVAL_ADAV801:
|
||||
codec_name = "spi0.1";
|
||||
break;
|
||||
case BFIN_EVAL_ADAV803:
|
||||
codec_name = "adav803.0-0034";
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bfin_eval_adav80x_dais[0].codec_name = codec_name;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
return snd_soc_register_card(&bfin_eval_adav80x);
|
||||
}
|
||||
|
||||
static int __devexit bfin_eval_adav80x_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id bfin_eval_adav80x_ids[] = {
|
||||
{ "bfin-eval-adav801", BFIN_EVAL_ADAV801 },
|
||||
{ "bfin-eval-adav803", BFIN_EVAL_ADAV803 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, bfin_eval_adav80x_ids);
|
||||
|
||||
static struct platform_driver bfin_eval_adav80x_driver = {
|
||||
.driver = {
|
||||
.name = "bfin-eval-adav80x",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
},
|
||||
.probe = bfin_eval_adav80x_probe,
|
||||
.remove = __devexit_p(bfin_eval_adav80x_remove),
|
||||
.id_table = bfin_eval_adav80x_ids,
|
||||
};
|
||||
|
||||
static int __init bfin_eval_adav80x_init(void)
|
||||
{
|
||||
return platform_driver_register(&bfin_eval_adav80x_driver);
|
||||
}
|
||||
module_init(bfin_eval_adav80x_init);
|
||||
|
||||
static void __exit bfin_eval_adav80x_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&bfin_eval_adav80x_driver);
|
||||
}
|
||||
module_exit(bfin_eval_adav80x_exit);
|
||||
|
||||
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
||||
MODULE_DESCRIPTION("ALSA SoC bfin adav80x driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS
|
||||
select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_AD1980 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_AD73311
|
||||
select SND_SOC_ADAV80X
|
||||
select SND_SOC_ADS117X
|
||||
select SND_SOC_AK4104 if SPI_MASTER
|
||||
select SND_SOC_AK4535 if I2C
|
||||
@ -42,6 +43,7 @@ config SND_SOC_ALL_CODECS
|
||||
select SND_SOC_SN95031 if INTEL_SCU_IPC
|
||||
select SND_SOC_SPDIF
|
||||
select SND_SOC_SSM2602 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_STA32X if I2C
|
||||
select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_TLV320AIC23 if I2C
|
||||
select SND_SOC_TLV320AIC26 if SPI_MASTER
|
||||
@ -71,6 +73,7 @@ config SND_SOC_ALL_CODECS
|
||||
select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8770 if SPI_MASTER
|
||||
select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8782
|
||||
select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8900 if I2C
|
||||
select SND_SOC_WM8903 if I2C
|
||||
@ -84,6 +87,7 @@ config SND_SOC_ALL_CODECS
|
||||
select SND_SOC_WM8971 if I2C
|
||||
select SND_SOC_WM8974 if I2C
|
||||
select SND_SOC_WM8978 if I2C
|
||||
select SND_SOC_WM8983 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8990 if I2C
|
||||
@ -130,7 +134,14 @@ config SND_SOC_AD1980
|
||||
|
||||
config SND_SOC_AD73311
|
||||
tristate
|
||||
|
||||
|
||||
config SND_SOC_ADAU1701
|
||||
select SIGMA
|
||||
tristate
|
||||
|
||||
config SND_SOC_ADAV80X
|
||||
tristate
|
||||
|
||||
config SND_SOC_ADS117X
|
||||
tristate
|
||||
|
||||
@ -216,6 +227,9 @@ config SND_SOC_SPDIF
|
||||
config SND_SOC_SSM2602
|
||||
tristate
|
||||
|
||||
config SND_SOC_STA32X
|
||||
tristate
|
||||
|
||||
config SND_SOC_STAC9766
|
||||
tristate
|
||||
|
||||
@ -299,6 +313,9 @@ config SND_SOC_WM8770
|
||||
config SND_SOC_WM8776
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8782
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8804
|
||||
tristate
|
||||
|
||||
@ -338,6 +355,9 @@ config SND_SOC_WM8974
|
||||
config SND_SOC_WM8978
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8983
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8985
|
||||
tristate
|
||||
|
||||
|
@ -4,6 +4,8 @@ snd-soc-ad1836-objs := ad1836.o
|
||||
snd-soc-ad193x-objs := ad193x.o
|
||||
snd-soc-ad1980-objs := ad1980.o
|
||||
snd-soc-ad73311-objs := ad73311.o
|
||||
snd-soc-adau1701-objs := adau1701.o
|
||||
snd-soc-adav80x-objs := adav80x.o
|
||||
snd-soc-ads117x-objs := ads117x.o
|
||||
snd-soc-ak4104-objs := ak4104.o
|
||||
snd-soc-ak4535-objs := ak4535.o
|
||||
@ -28,6 +30,7 @@ snd-soc-alc5623-objs := alc5623.o
|
||||
snd-soc-sn95031-objs := sn95031.o
|
||||
snd-soc-spdif-objs := spdif_transciever.o
|
||||
snd-soc-ssm2602-objs := ssm2602.o
|
||||
snd-soc-sta32x-objs := sta32x.o
|
||||
snd-soc-stac9766-objs := stac9766.o
|
||||
snd-soc-tlv320aic23-objs := tlv320aic23.o
|
||||
snd-soc-tlv320aic26-objs := tlv320aic26.o
|
||||
@ -55,6 +58,7 @@ snd-soc-wm8750-objs := wm8750.o
|
||||
snd-soc-wm8753-objs := wm8753.o
|
||||
snd-soc-wm8770-objs := wm8770.o
|
||||
snd-soc-wm8776-objs := wm8776.o
|
||||
snd-soc-wm8782-objs := wm8782.o
|
||||
snd-soc-wm8804-objs := wm8804.o
|
||||
snd-soc-wm8900-objs := wm8900.o
|
||||
snd-soc-wm8903-objs := wm8903.o
|
||||
@ -68,6 +72,7 @@ snd-soc-wm8962-objs := wm8962.o
|
||||
snd-soc-wm8971-objs := wm8971.o
|
||||
snd-soc-wm8974-objs := wm8974.o
|
||||
snd-soc-wm8978-objs := wm8978.o
|
||||
snd-soc-wm8983-objs := wm8983.o
|
||||
snd-soc-wm8985-objs := wm8985.o
|
||||
snd-soc-wm8988-objs := wm8988.o
|
||||
snd-soc-wm8990-objs := wm8990.o
|
||||
@ -95,6 +100,8 @@ obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
|
||||
obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o
|
||||
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
|
||||
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
|
||||
obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o
|
||||
obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o
|
||||
obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o
|
||||
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
|
||||
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
|
||||
@ -120,6 +127,7 @@ obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
|
||||
obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o
|
||||
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
|
||||
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
|
||||
obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o
|
||||
obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
|
||||
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
|
||||
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
|
||||
@ -147,6 +155,7 @@ obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
|
||||
obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
|
||||
obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o
|
||||
obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o
|
||||
obj-$(CONFIG_SND_SOC_WM8782) += snd-soc-wm8782.o
|
||||
obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o
|
||||
obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
|
||||
obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
|
||||
@ -160,6 +169,7 @@ obj-$(CONFIG_SND_SOC_WM8962) += snd-soc-wm8962.o
|
||||
obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
|
||||
obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o
|
||||
obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o
|
||||
obj-$(CONFIG_SND_SOC_WM8983) += snd-soc-wm8983.o
|
||||
obj-$(CONFIG_SND_SOC_WM8985) += snd-soc-wm8985.o
|
||||
obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
|
||||
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
|
||||
|
@ -1,19 +1,10 @@
|
||||
/*
|
||||
* File: sound/soc/codecs/ad1836.c
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
/*
|
||||
* Audio Codec driver supporting:
|
||||
* AD1835A, AD1836, AD1837A, AD1838A, AD1839A
|
||||
*
|
||||
* Created: Aug 04 2009
|
||||
* Description: Driver for AD1836 sound chip
|
||||
* Copyright 2009-2011 Analog Devices Inc.
|
||||
*
|
||||
* Modified:
|
||||
* Copyright 2009 Analog Devices Inc.
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
@ -30,10 +21,15 @@
|
||||
#include <linux/spi/spi.h>
|
||||
#include "ad1836.h"
|
||||
|
||||
enum ad1836_type {
|
||||
AD1835,
|
||||
AD1836,
|
||||
AD1838,
|
||||
};
|
||||
|
||||
/* codec private data */
|
||||
struct ad1836_priv {
|
||||
enum snd_soc_control_type control_type;
|
||||
void *control_data;
|
||||
enum ad1836_type type;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -44,29 +40,60 @@ static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"};
|
||||
static const struct soc_enum ad1836_deemp_enum =
|
||||
SOC_ENUM_SINGLE(AD1836_DAC_CTRL1, 8, 4, ad1836_deemp);
|
||||
|
||||
static const struct snd_kcontrol_new ad1836_snd_controls[] = {
|
||||
/* DAC volume control */
|
||||
SOC_DOUBLE_R("DAC1 Volume", AD1836_DAC_L1_VOL,
|
||||
AD1836_DAC_R1_VOL, 0, 0x3FF, 0),
|
||||
SOC_DOUBLE_R("DAC2 Volume", AD1836_DAC_L2_VOL,
|
||||
AD1836_DAC_R2_VOL, 0, 0x3FF, 0),
|
||||
SOC_DOUBLE_R("DAC3 Volume", AD1836_DAC_L3_VOL,
|
||||
AD1836_DAC_R3_VOL, 0, 0x3FF, 0),
|
||||
#define AD1836_DAC_VOLUME(x) \
|
||||
SOC_DOUBLE_R("DAC" #x " Playback Volume", AD1836_DAC_L_VOL(x), \
|
||||
AD1836_DAC_R_VOL(x), 0, 0x3FF, 0)
|
||||
|
||||
/* ADC switch control */
|
||||
SOC_DOUBLE("ADC1 Switch", AD1836_ADC_CTRL2, AD1836_ADCL1_MUTE,
|
||||
AD1836_ADCR1_MUTE, 1, 1),
|
||||
SOC_DOUBLE("ADC2 Switch", AD1836_ADC_CTRL2, AD1836_ADCL2_MUTE,
|
||||
AD1836_ADCR2_MUTE, 1, 1),
|
||||
#define AD1836_DAC_SWITCH(x) \
|
||||
SOC_DOUBLE("DAC" #x " Playback Switch", AD1836_DAC_CTRL2, \
|
||||
AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
|
||||
|
||||
/* DAC switch control */
|
||||
SOC_DOUBLE("DAC1 Switch", AD1836_DAC_CTRL2, AD1836_DACL1_MUTE,
|
||||
AD1836_DACR1_MUTE, 1, 1),
|
||||
SOC_DOUBLE("DAC2 Switch", AD1836_DAC_CTRL2, AD1836_DACL2_MUTE,
|
||||
AD1836_DACR2_MUTE, 1, 1),
|
||||
SOC_DOUBLE("DAC3 Switch", AD1836_DAC_CTRL2, AD1836_DACL3_MUTE,
|
||||
AD1836_DACR3_MUTE, 1, 1),
|
||||
#define AD1836_ADC_SWITCH(x) \
|
||||
SOC_DOUBLE("ADC" #x " Capture Switch", AD1836_ADC_CTRL2, \
|
||||
AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
|
||||
|
||||
static const struct snd_kcontrol_new ad183x_dac_controls[] = {
|
||||
AD1836_DAC_VOLUME(1),
|
||||
AD1836_DAC_SWITCH(1),
|
||||
AD1836_DAC_VOLUME(2),
|
||||
AD1836_DAC_SWITCH(2),
|
||||
AD1836_DAC_VOLUME(3),
|
||||
AD1836_DAC_SWITCH(3),
|
||||
AD1836_DAC_VOLUME(4),
|
||||
AD1836_DAC_SWITCH(4),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget ad183x_dac_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC4OUT"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route ad183x_dac_routes[] = {
|
||||
{ "DAC1OUT", NULL, "DAC" },
|
||||
{ "DAC2OUT", NULL, "DAC" },
|
||||
{ "DAC3OUT", NULL, "DAC" },
|
||||
{ "DAC4OUT", NULL, "DAC" },
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new ad183x_adc_controls[] = {
|
||||
AD1836_ADC_SWITCH(1),
|
||||
AD1836_ADC_SWITCH(2),
|
||||
AD1836_ADC_SWITCH(3),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget ad183x_adc_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_INPUT("ADC1IN"),
|
||||
SND_SOC_DAPM_INPUT("ADC2IN"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route ad183x_adc_routes[] = {
|
||||
{ "ADC", NULL, "ADC1IN" },
|
||||
{ "ADC", NULL, "ADC2IN" },
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new ad183x_controls[] = {
|
||||
/* ADC high-pass filter */
|
||||
SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1,
|
||||
AD1836_ADC_HIGHPASS_FILTER, 1, 0),
|
||||
@ -75,27 +102,24 @@ static const struct snd_kcontrol_new ad1836_snd_controls[] = {
|
||||
SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget ad1836_dapm_widgets[] = {
|
||||
static const struct snd_soc_dapm_widget ad183x_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1,
|
||||
AD1836_DAC_POWERDOWN, 1),
|
||||
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1,
|
||||
AD1836_ADC_POWERDOWN, 1, NULL, 0),
|
||||
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
|
||||
SND_SOC_DAPM_INPUT("ADC1IN"),
|
||||
SND_SOC_DAPM_INPUT("ADC2IN"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_paths[] = {
|
||||
static const struct snd_soc_dapm_route ad183x_dapm_routes[] = {
|
||||
{ "DAC", NULL, "ADC_PWR" },
|
||||
{ "ADC", NULL, "ADC_PWR" },
|
||||
{ "DAC1OUT", "DAC1 Switch", "DAC" },
|
||||
{ "DAC2OUT", "DAC2 Switch", "DAC" },
|
||||
{ "DAC3OUT", "DAC3 Switch", "DAC" },
|
||||
{ "ADC", "ADC1 Switch", "ADC1IN" },
|
||||
{ "ADC", "ADC2 Switch", "ADC2IN" },
|
||||
};
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(ad1836_in_tlv, 0, 300, 0);
|
||||
|
||||
static const struct snd_kcontrol_new ad1836_controls[] = {
|
||||
SOC_DOUBLE_TLV("ADC2 Capture Volume", AD1836_ADC_CTRL1, 3, 0, 4, 0,
|
||||
ad1836_in_tlv),
|
||||
};
|
||||
|
||||
/*
|
||||
@ -165,64 +189,69 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ad1836_soc_suspend(struct snd_soc_codec *codec,
|
||||
pm_message_t state)
|
||||
{
|
||||
/* reset clock control mode */
|
||||
u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
|
||||
adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
|
||||
|
||||
return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
|
||||
}
|
||||
|
||||
static int ad1836_soc_resume(struct snd_soc_codec *codec)
|
||||
{
|
||||
/* restore clock control mode */
|
||||
u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
|
||||
adc_ctrl2 |= AD1836_ADC_AUX;
|
||||
|
||||
return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
|
||||
}
|
||||
#else
|
||||
#define ad1836_soc_suspend NULL
|
||||
#define ad1836_soc_resume NULL
|
||||
#endif
|
||||
|
||||
static struct snd_soc_dai_ops ad1836_dai_ops = {
|
||||
.hw_params = ad1836_hw_params,
|
||||
.set_fmt = ad1836_set_dai_fmt,
|
||||
};
|
||||
|
||||
/* codec DAI instance */
|
||||
static struct snd_soc_dai_driver ad1836_dai = {
|
||||
.name = "ad1836-hifi",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 6,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 4,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
|
||||
},
|
||||
.ops = &ad1836_dai_ops,
|
||||
#define AD183X_DAI(_name, num_dacs, num_adcs) \
|
||||
{ \
|
||||
.name = _name "-hifi", \
|
||||
.playback = { \
|
||||
.stream_name = "Playback", \
|
||||
.channels_min = 2, \
|
||||
.channels_max = (num_dacs) * 2, \
|
||||
.rates = SNDRV_PCM_RATE_48000, \
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
|
||||
}, \
|
||||
.capture = { \
|
||||
.stream_name = "Capture", \
|
||||
.channels_min = 2, \
|
||||
.channels_max = (num_adcs) * 2, \
|
||||
.rates = SNDRV_PCM_RATE_48000, \
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
|
||||
}, \
|
||||
.ops = &ad1836_dai_ops, \
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_driver ad183x_dais[] = {
|
||||
[AD1835] = AD183X_DAI("ad1835", 4, 1),
|
||||
[AD1836] = AD183X_DAI("ad1836", 3, 2),
|
||||
[AD1838] = AD183X_DAI("ad1838", 3, 1),
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ad1836_suspend(struct snd_soc_codec *codec, pm_message_t state)
|
||||
{
|
||||
/* reset clock control mode */
|
||||
return snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
|
||||
AD1836_ADC_SERFMT_MASK, 0);
|
||||
}
|
||||
|
||||
static int ad1836_resume(struct snd_soc_codec *codec)
|
||||
{
|
||||
/* restore clock control mode */
|
||||
return snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
|
||||
AD1836_ADC_SERFMT_MASK, AD1836_ADC_AUX);
|
||||
}
|
||||
#else
|
||||
#define ad1836_suspend NULL
|
||||
#define ad1836_resume NULL
|
||||
#endif
|
||||
|
||||
static int ad1836_probe(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
int num_dacs, num_adcs;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
num_dacs = ad183x_dais[ad1836->type].playback.channels_max / 2;
|
||||
num_adcs = ad183x_dais[ad1836->type].capture.channels_max / 2;
|
||||
|
||||
codec->control_data = ad1836->control_data;
|
||||
ret = snd_soc_codec_set_cache_io(codec, 4, 12, SND_SOC_SPI);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to set cache I/O: %d\n",
|
||||
@ -239,21 +268,46 @@ static int ad1836_probe(struct snd_soc_codec *codec)
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL1, 0x100);
|
||||
/* unmute adc channles, adc aux mode */
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL2, 0x180);
|
||||
/* left/right diff:PGA/MUX */
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
|
||||
/* volume */
|
||||
snd_soc_write(codec, AD1836_DAC_L1_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_R1_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_L2_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_R2_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_L3_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_R3_VOL, 0x3FF);
|
||||
for (i = 1; i <= num_dacs; ++i) {
|
||||
snd_soc_write(codec, AD1836_DAC_L_VOL(i), 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_R_VOL(i), 0x3FF);
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, ad1836_snd_controls,
|
||||
ARRAY_SIZE(ad1836_snd_controls));
|
||||
snd_soc_dapm_new_controls(dapm, ad1836_dapm_widgets,
|
||||
ARRAY_SIZE(ad1836_dapm_widgets));
|
||||
snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
|
||||
if (ad1836->type == AD1836) {
|
||||
/* left/right diff:PGA/MUX */
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
|
||||
ret = snd_soc_add_controls(codec, ad1836_controls,
|
||||
ARRAY_SIZE(ad1836_controls));
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL3, 0x00);
|
||||
}
|
||||
|
||||
ret = snd_soc_add_controls(codec, ad183x_dac_controls, num_dacs * 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_add_controls(codec, ad183x_adc_controls, num_adcs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dapm_new_controls(dapm, ad183x_dac_dapm_widgets, num_dacs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dapm_new_controls(dapm, ad183x_adc_dapm_widgets, num_adcs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dapm_add_routes(dapm, ad183x_dac_routes, num_dacs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dapm_add_routes(dapm, ad183x_adc_routes, num_adcs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -262,19 +316,24 @@ static int ad1836_probe(struct snd_soc_codec *codec)
|
||||
static int ad1836_remove(struct snd_soc_codec *codec)
|
||||
{
|
||||
/* reset clock control mode */
|
||||
u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
|
||||
adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
|
||||
|
||||
return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
|
||||
return snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
|
||||
AD1836_ADC_SERFMT_MASK, 0);
|
||||
}
|
||||
|
||||
static struct snd_soc_codec_driver soc_codec_dev_ad1836 = {
|
||||
.probe = ad1836_probe,
|
||||
.remove = ad1836_remove,
|
||||
.suspend = ad1836_soc_suspend,
|
||||
.resume = ad1836_soc_resume,
|
||||
.probe = ad1836_probe,
|
||||
.remove = ad1836_remove,
|
||||
.suspend = ad1836_suspend,
|
||||
.resume = ad1836_resume,
|
||||
.reg_cache_size = AD1836_NUM_REGS,
|
||||
.reg_word_size = sizeof(u16),
|
||||
|
||||
.controls = ad183x_controls,
|
||||
.num_controls = ARRAY_SIZE(ad183x_controls),
|
||||
.dapm_widgets = ad183x_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(ad183x_dapm_widgets),
|
||||
.dapm_routes = ad183x_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(ad183x_dapm_routes),
|
||||
};
|
||||
|
||||
static int __devinit ad1836_spi_probe(struct spi_device *spi)
|
||||
@ -286,12 +345,12 @@ static int __devinit ad1836_spi_probe(struct spi_device *spi)
|
||||
if (ad1836 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ad1836->type = spi_get_device_id(spi)->driver_data;
|
||||
|
||||
spi_set_drvdata(spi, ad1836);
|
||||
ad1836->control_data = spi;
|
||||
ad1836->control_type = SND_SOC_SPI;
|
||||
|
||||
ret = snd_soc_register_codec(&spi->dev,
|
||||
&soc_codec_dev_ad1836, &ad1836_dai, 1);
|
||||
&soc_codec_dev_ad1836, &ad183x_dais[ad1836->type], 1);
|
||||
if (ret < 0)
|
||||
kfree(ad1836);
|
||||
return ret;
|
||||
@ -303,27 +362,29 @@ static int __devexit ad1836_spi_remove(struct spi_device *spi)
|
||||
kfree(spi_get_drvdata(spi));
|
||||
return 0;
|
||||
}
|
||||
static const struct spi_device_id ad1836_ids[] = {
|
||||
{ "ad1835", AD1835 },
|
||||
{ "ad1836", AD1836 },
|
||||
{ "ad1837", AD1835 },
|
||||
{ "ad1838", AD1838 },
|
||||
{ "ad1839", AD1838 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad1836_ids);
|
||||
|
||||
static struct spi_driver ad1836_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ad1836-codec",
|
||||
.name = "ad1836",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ad1836_spi_probe,
|
||||
.remove = __devexit_p(ad1836_spi_remove),
|
||||
.id_table = ad1836_ids,
|
||||
};
|
||||
|
||||
static int __init ad1836_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_register_driver(&ad1836_spi_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "Failed to register ad1836 SPI driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return spi_register_driver(&ad1836_spi_driver);
|
||||
}
|
||||
module_init(ad1836_init);
|
||||
|
||||
|
@ -1,19 +1,10 @@
|
||||
/*
|
||||
* File: sound/soc/codecs/ad1836.h
|
||||
* Based on:
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
* Audio Codec driver supporting:
|
||||
* AD1835A, AD1836, AD1837A, AD1838A, AD1839A
|
||||
*
|
||||
* Created: Aug 04, 2009
|
||||
* Description: definitions for AD1836 registers
|
||||
* Copyright 2009-2011 Analog Devices Inc.
|
||||
*
|
||||
* Modified:
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#ifndef __AD1836_H__
|
||||
@ -21,39 +12,30 @@
|
||||
|
||||
#define AD1836_DAC_CTRL1 0
|
||||
#define AD1836_DAC_POWERDOWN 2
|
||||
#define AD1836_DAC_SERFMT_MASK 0xE0
|
||||
#define AD1836_DAC_SERFMT_MASK 0xE0
|
||||
#define AD1836_DAC_SERFMT_PCK256 (0x4 << 5)
|
||||
#define AD1836_DAC_SERFMT_PCK128 (0x5 << 5)
|
||||
#define AD1836_DAC_WORD_LEN_MASK 0x18
|
||||
#define AD1836_DAC_WORD_LEN_OFFSET 3
|
||||
|
||||
#define AD1836_DAC_CTRL2 1
|
||||
#define AD1836_DACL1_MUTE 0
|
||||
#define AD1836_DACR1_MUTE 1
|
||||
#define AD1836_DACL2_MUTE 2
|
||||
#define AD1836_DACR2_MUTE 3
|
||||
#define AD1836_DACL3_MUTE 4
|
||||
#define AD1836_DACR3_MUTE 5
|
||||
|
||||
#define AD1836_DAC_L1_VOL 2
|
||||
#define AD1836_DAC_R1_VOL 3
|
||||
#define AD1836_DAC_L2_VOL 4
|
||||
#define AD1836_DAC_R2_VOL 5
|
||||
#define AD1836_DAC_L3_VOL 6
|
||||
#define AD1836_DAC_R3_VOL 7
|
||||
/* These macros are one-based. So AD183X_MUTE_LEFT(1) will return the mute bit
|
||||
* for the first ADC/DAC */
|
||||
#define AD1836_MUTE_LEFT(x) (((x) * 2) - 2)
|
||||
#define AD1836_MUTE_RIGHT(x) (((x) * 2) - 1)
|
||||
|
||||
#define AD1836_DAC_L_VOL(x) ((x) * 2)
|
||||
#define AD1836_DAC_R_VOL(x) (1 + ((x) * 2))
|
||||
|
||||
#define AD1836_ADC_CTRL1 12
|
||||
#define AD1836_ADC_POWERDOWN 7
|
||||
#define AD1836_ADC_HIGHPASS_FILTER 8
|
||||
|
||||
#define AD1836_ADC_CTRL2 13
|
||||
#define AD1836_ADCL1_MUTE 0
|
||||
#define AD1836_ADCR1_MUTE 1
|
||||
#define AD1836_ADCL2_MUTE 2
|
||||
#define AD1836_ADCR2_MUTE 3
|
||||
#define AD1836_ADC_WORD_LEN_MASK 0x30
|
||||
#define AD1836_ADC_WORD_OFFSET 5
|
||||
#define AD1836_ADC_SERFMT_MASK (7 << 6)
|
||||
#define AD1836_ADC_SERFMT_MASK (7 << 6)
|
||||
#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6)
|
||||
#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6)
|
||||
#define AD1836_ADC_AUX (0x6 << 6)
|
||||
|
549
sound/soc/codecs/adau1701.c
Normal file
549
sound/soc/codecs/adau1701.c
Normal file
@ -0,0 +1,549 @@
|
||||
/*
|
||||
* Driver for ADAU1701 SigmaDSP processor
|
||||
*
|
||||
* Copyright 2011 Analog Devices Inc.
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
* based on an inital version by Cliff Cai <cliff.cai@analog.com>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/sigma.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "adau1701.h"
|
||||
|
||||
#define ADAU1701_DSPCTRL 0x1c
|
||||
#define ADAU1701_SEROCTL 0x1e
|
||||
#define ADAU1701_SERICTL 0x1f
|
||||
|
||||
#define ADAU1701_AUXNPOW 0x22
|
||||
|
||||
#define ADAU1701_OSCIPOW 0x26
|
||||
#define ADAU1701_DACSET 0x27
|
||||
|
||||
#define ADAU1701_NUM_REGS 0x28
|
||||
|
||||
#define ADAU1701_DSPCTRL_CR (1 << 2)
|
||||
#define ADAU1701_DSPCTRL_DAM (1 << 3)
|
||||
#define ADAU1701_DSPCTRL_ADM (1 << 4)
|
||||
#define ADAU1701_DSPCTRL_SR_48 0x00
|
||||
#define ADAU1701_DSPCTRL_SR_96 0x01
|
||||
#define ADAU1701_DSPCTRL_SR_192 0x02
|
||||
#define ADAU1701_DSPCTRL_SR_MASK 0x03
|
||||
|
||||
#define ADAU1701_SEROCTL_INV_LRCLK 0x2000
|
||||
#define ADAU1701_SEROCTL_INV_BCLK 0x1000
|
||||
#define ADAU1701_SEROCTL_MASTER 0x0800
|
||||
|
||||
#define ADAU1701_SEROCTL_OBF16 0x0000
|
||||
#define ADAU1701_SEROCTL_OBF8 0x0200
|
||||
#define ADAU1701_SEROCTL_OBF4 0x0400
|
||||
#define ADAU1701_SEROCTL_OBF2 0x0600
|
||||
#define ADAU1701_SEROCTL_OBF_MASK 0x0600
|
||||
|
||||
#define ADAU1701_SEROCTL_OLF1024 0x0000
|
||||
#define ADAU1701_SEROCTL_OLF512 0x0080
|
||||
#define ADAU1701_SEROCTL_OLF256 0x0100
|
||||
#define ADAU1701_SEROCTL_OLF_MASK 0x0180
|
||||
|
||||
#define ADAU1701_SEROCTL_MSB_DEALY1 0x0000
|
||||
#define ADAU1701_SEROCTL_MSB_DEALY0 0x0004
|
||||
#define ADAU1701_SEROCTL_MSB_DEALY8 0x0008
|
||||
#define ADAU1701_SEROCTL_MSB_DEALY12 0x000c
|
||||
#define ADAU1701_SEROCTL_MSB_DEALY16 0x0010
|
||||
#define ADAU1701_SEROCTL_MSB_DEALY_MASK 0x001c
|
||||
|
||||
#define ADAU1701_SEROCTL_WORD_LEN_24 0x0000
|
||||
#define ADAU1701_SEROCTL_WORD_LEN_20 0x0001
|
||||
#define ADAU1701_SEROCTL_WORD_LEN_16 0x0010
|
||||
#define ADAU1701_SEROCTL_WORD_LEN_MASK 0x0003
|
||||
|
||||
#define ADAU1701_AUXNPOW_VBPD 0x40
|
||||
#define ADAU1701_AUXNPOW_VRPD 0x20
|
||||
|
||||
#define ADAU1701_SERICTL_I2S 0
|
||||
#define ADAU1701_SERICTL_LEFTJ 1
|
||||
#define ADAU1701_SERICTL_TDM 2
|
||||
#define ADAU1701_SERICTL_RIGHTJ_24 3
|
||||
#define ADAU1701_SERICTL_RIGHTJ_20 4
|
||||
#define ADAU1701_SERICTL_RIGHTJ_18 5
|
||||
#define ADAU1701_SERICTL_RIGHTJ_16 6
|
||||
#define ADAU1701_SERICTL_MODE_MASK 7
|
||||
#define ADAU1701_SERICTL_INV_BCLK BIT(3)
|
||||
#define ADAU1701_SERICTL_INV_LRCLK BIT(4)
|
||||
|
||||
#define ADAU1701_OSCIPOW_OPD 0x04
|
||||
#define ADAU1701_DACSET_DACINIT 1
|
||||
|
||||
#define ADAU1701_FIRMWARE "adau1701.bin"
|
||||
|
||||
struct adau1701 {
|
||||
unsigned int dai_fmt;
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new adau1701_controls[] = {
|
||||
SOC_SINGLE("Master Capture Switch", ADAU1701_DSPCTRL, 4, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget adau1701_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC0", "Playback", ADAU1701_AUXNPOW, 3, 1),
|
||||
SND_SOC_DAPM_DAC("DAC1", "Playback", ADAU1701_AUXNPOW, 2, 1),
|
||||
SND_SOC_DAPM_DAC("DAC2", "Playback", ADAU1701_AUXNPOW, 1, 1),
|
||||
SND_SOC_DAPM_DAC("DAC3", "Playback", ADAU1701_AUXNPOW, 0, 1),
|
||||
SND_SOC_DAPM_ADC("ADC", "Capture", ADAU1701_AUXNPOW, 7, 1),
|
||||
|
||||
SND_SOC_DAPM_OUTPUT("OUT0"),
|
||||
SND_SOC_DAPM_OUTPUT("OUT1"),
|
||||
SND_SOC_DAPM_OUTPUT("OUT2"),
|
||||
SND_SOC_DAPM_OUTPUT("OUT3"),
|
||||
SND_SOC_DAPM_INPUT("IN0"),
|
||||
SND_SOC_DAPM_INPUT("IN1"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route adau1701_dapm_routes[] = {
|
||||
{ "OUT0", NULL, "DAC0" },
|
||||
{ "OUT1", NULL, "DAC1" },
|
||||
{ "OUT2", NULL, "DAC2" },
|
||||
{ "OUT3", NULL, "DAC3" },
|
||||
|
||||
{ "ADC", NULL, "IN0" },
|
||||
{ "ADC", NULL, "IN1" },
|
||||
};
|
||||
|
||||
static unsigned int adau1701_register_size(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case ADAU1701_DSPCTRL:
|
||||
case ADAU1701_SEROCTL:
|
||||
case ADAU1701_AUXNPOW:
|
||||
case ADAU1701_OSCIPOW:
|
||||
case ADAU1701_DACSET:
|
||||
return 2;
|
||||
case ADAU1701_SERICTL:
|
||||
return 1;
|
||||
}
|
||||
|
||||
dev_err(codec->dev, "Unsupported register address: %d\n", reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adau1701_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int size;
|
||||
uint8_t buf[4];
|
||||
int ret;
|
||||
|
||||
size = adau1701_register_size(codec, reg);
|
||||
if (size == 0)
|
||||
return -EINVAL;
|
||||
|
||||
snd_soc_cache_write(codec, reg, value);
|
||||
|
||||
buf[0] = 0x08;
|
||||
buf[1] = reg;
|
||||
|
||||
for (i = size + 1; i >= 2; --i) {
|
||||
buf[i] = value;
|
||||
value >>= 8;
|
||||
}
|
||||
|
||||
ret = i2c_master_send(to_i2c_client(codec->dev), buf, size + 2);
|
||||
if (ret == size + 2)
|
||||
return 0;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static unsigned int adau1701_read(struct snd_soc_codec *codec, unsigned int reg)
|
||||
{
|
||||
unsigned int value;
|
||||
unsigned int ret;
|
||||
|
||||
ret = snd_soc_cache_read(codec, reg, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int adau1701_load_firmware(struct snd_soc_codec *codec)
|
||||
{
|
||||
return process_sigma_firmware(codec->control_data, ADAU1701_FIRMWARE);
|
||||
}
|
||||
|
||||
static int adau1701_set_capture_pcm_format(struct snd_soc_codec *codec,
|
||||
snd_pcm_format_t format)
|
||||
{
|
||||
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int mask = ADAU1701_SEROCTL_WORD_LEN_MASK;
|
||||
unsigned int val;
|
||||
|
||||
switch (format) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
val = ADAU1701_SEROCTL_WORD_LEN_16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
val = ADAU1701_SEROCTL_WORD_LEN_20;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
val = ADAU1701_SEROCTL_WORD_LEN_24;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (adau1701->dai_fmt == SND_SOC_DAIFMT_RIGHT_J) {
|
||||
switch (format) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
val |= ADAU1701_SEROCTL_MSB_DEALY16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
val |= ADAU1701_SEROCTL_MSB_DEALY12;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
val |= ADAU1701_SEROCTL_MSB_DEALY8;
|
||||
break;
|
||||
}
|
||||
mask |= ADAU1701_SEROCTL_MSB_DEALY_MASK;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, ADAU1701_SEROCTL, mask, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adau1701_set_playback_pcm_format(struct snd_soc_codec *codec,
|
||||
snd_pcm_format_t format)
|
||||
{
|
||||
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int val;
|
||||
|
||||
if (adau1701->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
|
||||
return 0;
|
||||
|
||||
switch (format) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
val = ADAU1701_SERICTL_RIGHTJ_16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
val = ADAU1701_SERICTL_RIGHTJ_20;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
val = ADAU1701_SERICTL_RIGHTJ_24;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, ADAU1701_SERICTL,
|
||||
ADAU1701_SERICTL_MODE_MASK, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adau1701_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
snd_pcm_format_t format;
|
||||
unsigned int val;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 192000:
|
||||
val = ADAU1701_DSPCTRL_SR_192;
|
||||
break;
|
||||
case 96000:
|
||||
val = ADAU1701_DSPCTRL_SR_96;
|
||||
break;
|
||||
case 48000:
|
||||
val = ADAU1701_DSPCTRL_SR_48;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, ADAU1701_DSPCTRL,
|
||||
ADAU1701_DSPCTRL_SR_MASK, val);
|
||||
|
||||
format = params_format(params);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
return adau1701_set_playback_pcm_format(codec, format);
|
||||
else
|
||||
return adau1701_set_capture_pcm_format(codec, format);
|
||||
}
|
||||
|
||||
static int adau1701_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int serictl = 0x00, seroctl = 0x00;
|
||||
bool invert_lrclk;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
/* master, 64-bits per sample, 1 frame per sample */
|
||||
seroctl |= ADAU1701_SEROCTL_MASTER | ADAU1701_SEROCTL_OBF16
|
||||
| ADAU1701_SEROCTL_OLF1024;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
invert_lrclk = false;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
invert_lrclk = true;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
invert_lrclk = false;
|
||||
serictl |= ADAU1701_SERICTL_INV_BCLK;
|
||||
seroctl |= ADAU1701_SEROCTL_INV_BCLK;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
invert_lrclk = true;
|
||||
serictl |= ADAU1701_SERICTL_INV_BCLK;
|
||||
seroctl |= ADAU1701_SEROCTL_INV_BCLK;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
serictl |= ADAU1701_SERICTL_LEFTJ;
|
||||
seroctl |= ADAU1701_SEROCTL_MSB_DEALY0;
|
||||
invert_lrclk = !invert_lrclk;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
serictl |= ADAU1701_SERICTL_RIGHTJ_24;
|
||||
seroctl |= ADAU1701_SEROCTL_MSB_DEALY8;
|
||||
invert_lrclk = !invert_lrclk;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (invert_lrclk) {
|
||||
seroctl |= ADAU1701_SEROCTL_INV_LRCLK;
|
||||
serictl |= ADAU1701_SERICTL_INV_LRCLK;
|
||||
}
|
||||
|
||||
adau1701->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
||||
|
||||
snd_soc_write(codec, ADAU1701_SERICTL, serictl);
|
||||
snd_soc_update_bits(codec, ADAU1701_SEROCTL,
|
||||
~ADAU1701_SEROCTL_WORD_LEN_MASK, seroctl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adau1701_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
unsigned int mask = ADAU1701_AUXNPOW_VBPD | ADAU1701_AUXNPOW_VRPD;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
/* Enable VREF and VREF buffer */
|
||||
snd_soc_update_bits(codec, ADAU1701_AUXNPOW, mask, 0x00);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* Disable VREF and VREF buffer */
|
||||
snd_soc_update_bits(codec, ADAU1701_AUXNPOW, mask, mask);
|
||||
break;
|
||||
}
|
||||
|
||||
codec->dapm.bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adau1701_digital_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
unsigned int mask = ADAU1701_DSPCTRL_DAM;
|
||||
unsigned int val;
|
||||
|
||||
if (mute)
|
||||
val = 0;
|
||||
else
|
||||
val = mask;
|
||||
|
||||
snd_soc_update_bits(codec, ADAU1701_DSPCTRL, mask, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adau1701_set_sysclk(struct snd_soc_codec *codec, int clk_id,
|
||||
unsigned int freq, int dir)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
switch (clk_id) {
|
||||
case ADAU1701_CLK_SRC_OSC:
|
||||
val = 0x0;
|
||||
break;
|
||||
case ADAU1701_CLK_SRC_MCLK:
|
||||
val = ADAU1701_OSCIPOW_OPD;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, ADAU1701_OSCIPOW, ADAU1701_OSCIPOW_OPD, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define ADAU1701_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \
|
||||
SNDRV_PCM_RATE_192000)
|
||||
|
||||
#define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
static const struct snd_soc_dai_ops adau1701_dai_ops = {
|
||||
.set_fmt = adau1701_set_dai_fmt,
|
||||
.hw_params = adau1701_hw_params,
|
||||
.digital_mute = adau1701_digital_mute,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver adau1701_dai = {
|
||||
.name = "adau1701",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.rates = ADAU1701_RATES,
|
||||
.formats = ADAU1701_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.rates = ADAU1701_RATES,
|
||||
.formats = ADAU1701_FORMATS,
|
||||
},
|
||||
.ops = &adau1701_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
|
||||
static int adau1701_probe(struct snd_soc_codec *codec)
|
||||
{
|
||||
int ret;
|
||||
|
||||
codec->dapm.idle_bias_off = 1;
|
||||
|
||||
ret = adau1701_load_firmware(codec);
|
||||
if (ret)
|
||||
dev_warn(codec->dev, "Failed to load firmware\n");
|
||||
|
||||
snd_soc_write(codec, ADAU1701_DACSET, ADAU1701_DACSET_DACINIT);
|
||||
snd_soc_write(codec, ADAU1701_DSPCTRL, ADAU1701_DSPCTRL_CR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_codec_driver adau1701_codec_drv = {
|
||||
.probe = adau1701_probe,
|
||||
.set_bias_level = adau1701_set_bias_level,
|
||||
|
||||
.reg_cache_size = ADAU1701_NUM_REGS,
|
||||
.reg_word_size = sizeof(u16),
|
||||
|
||||
.controls = adau1701_controls,
|
||||
.num_controls = ARRAY_SIZE(adau1701_controls),
|
||||
.dapm_widgets = adau1701_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(adau1701_dapm_widgets),
|
||||
.dapm_routes = adau1701_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(adau1701_dapm_routes),
|
||||
|
||||
.write = adau1701_write,
|
||||
.read = adau1701_read,
|
||||
|
||||
.set_sysclk = adau1701_set_sysclk,
|
||||
};
|
||||
|
||||
static __devinit int adau1701_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct adau1701 *adau1701;
|
||||
int ret;
|
||||
|
||||
adau1701 = kzalloc(sizeof(*adau1701), GFP_KERNEL);
|
||||
if (!adau1701)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, adau1701);
|
||||
ret = snd_soc_register_codec(&client->dev, &adau1701_codec_drv,
|
||||
&adau1701_dai, 1);
|
||||
if (ret < 0)
|
||||
kfree(adau1701);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __devexit int adau1701_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
snd_soc_unregister_codec(&client->dev);
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adau1701_i2c_id[] = {
|
||||
{ "adau1701", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adau1701_i2c_id);
|
||||
|
||||
static struct i2c_driver adau1701_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "adau1701",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = adau1701_i2c_probe,
|
||||
.remove = __devexit_p(adau1701_i2c_remove),
|
||||
.id_table = adau1701_i2c_id,
|
||||
};
|
||||
|
||||
static int __init adau1701_init(void)
|
||||
{
|
||||
return i2c_add_driver(&adau1701_i2c_driver);
|
||||
}
|
||||
module_init(adau1701_init);
|
||||
|
||||
static void __exit adau1701_exit(void)
|
||||
{
|
||||
i2c_del_driver(&adau1701_i2c_driver);
|
||||
}
|
||||
module_exit(adau1701_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC ADAU1701 SigmaDSP driver");
|
||||
MODULE_AUTHOR("Cliff Cai <cliff.cai@analog.com>");
|
||||
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
||||
MODULE_LICENSE("GPL");
|
17
sound/soc/codecs/adau1701.h
Normal file
17
sound/soc/codecs/adau1701.h
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* header file for ADAU1701 SigmaDSP processor
|
||||
*
|
||||
* Copyright 2011 Analog Devices Inc.
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#ifndef _ADAU1701_H
|
||||
#define _ADAU1701_H
|
||||
|
||||
enum adau1701_clk_src {
|
||||
ADAU1701_CLK_SRC_OSC,
|
||||
ADAU1701_CLK_SRC_MCLK,
|
||||
};
|
||||
|
||||
#endif
|
951
sound/soc/codecs/adav80x.c
Normal file
951
sound/soc/codecs/adav80x.c
Normal file
@ -0,0 +1,951 @@
|
||||
/*
|
||||
* ADAV80X Audio Codec driver supporting ADAV801, ADAV803
|
||||
*
|
||||
* Copyright 2011 Analog Devices Inc.
|
||||
* Author: Yi Li <yi.li@analog.com>
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "adav80x.h"
|
||||
|
||||
#define ADAV80X_PLAYBACK_CTRL 0x04
|
||||
#define ADAV80X_AUX_IN_CTRL 0x05
|
||||
#define ADAV80X_REC_CTRL 0x06
|
||||
#define ADAV80X_AUX_OUT_CTRL 0x07
|
||||
#define ADAV80X_DPATH_CTRL1 0x62
|
||||
#define ADAV80X_DPATH_CTRL2 0x63
|
||||
#define ADAV80X_DAC_CTRL1 0x64
|
||||
#define ADAV80X_DAC_CTRL2 0x65
|
||||
#define ADAV80X_DAC_CTRL3 0x66
|
||||
#define ADAV80X_DAC_L_VOL 0x68
|
||||
#define ADAV80X_DAC_R_VOL 0x69
|
||||
#define ADAV80X_PGA_L_VOL 0x6c
|
||||
#define ADAV80X_PGA_R_VOL 0x6d
|
||||
#define ADAV80X_ADC_CTRL1 0x6e
|
||||
#define ADAV80X_ADC_CTRL2 0x6f
|
||||
#define ADAV80X_ADC_L_VOL 0x70
|
||||
#define ADAV80X_ADC_R_VOL 0x71
|
||||
#define ADAV80X_PLL_CTRL1 0x74
|
||||
#define ADAV80X_PLL_CTRL2 0x75
|
||||
#define ADAV80X_ICLK_CTRL1 0x76
|
||||
#define ADAV80X_ICLK_CTRL2 0x77
|
||||
#define ADAV80X_PLL_CLK_SRC 0x78
|
||||
#define ADAV80X_PLL_OUTE 0x7a
|
||||
|
||||
#define ADAV80X_PLL_CLK_SRC_PLL_XIN(pll) 0x00
|
||||
#define ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll) (0x40 << (pll))
|
||||
#define ADAV80X_PLL_CLK_SRC_PLL_MASK(pll) (0x40 << (pll))
|
||||
|
||||
#define ADAV80X_ICLK_CTRL1_DAC_SRC(src) ((src) << 5)
|
||||
#define ADAV80X_ICLK_CTRL1_ADC_SRC(src) ((src) << 2)
|
||||
#define ADAV80X_ICLK_CTRL1_ICLK2_SRC(src) (src)
|
||||
#define ADAV80X_ICLK_CTRL2_ICLK1_SRC(src) ((src) << 3)
|
||||
|
||||
#define ADAV80X_PLL_CTRL1_PLLDIV 0x10
|
||||
#define ADAV80X_PLL_CTRL1_PLLPD(pll) (0x04 << (pll))
|
||||
#define ADAV80X_PLL_CTRL1_XTLPD 0x02
|
||||
|
||||
#define ADAV80X_PLL_CTRL2_FIELD(pll, x) ((x) << ((pll) * 4))
|
||||
|
||||
#define ADAV80X_PLL_CTRL2_FS_48(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x00)
|
||||
#define ADAV80X_PLL_CTRL2_FS_32(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x08)
|
||||
#define ADAV80X_PLL_CTRL2_FS_44(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0c)
|
||||
|
||||
#define ADAV80X_PLL_CTRL2_SEL(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x02)
|
||||
#define ADAV80X_PLL_CTRL2_DOUB(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x01)
|
||||
#define ADAV80X_PLL_CTRL2_PLL_MASK(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0f)
|
||||
|
||||
#define ADAV80X_ADC_CTRL1_MODULATOR_MASK 0x80
|
||||
#define ADAV80X_ADC_CTRL1_MODULATOR_128FS 0x00
|
||||
#define ADAV80X_ADC_CTRL1_MODULATOR_64FS 0x80
|
||||
|
||||
#define ADAV80X_DAC_CTRL1_PD 0x80
|
||||
|
||||
#define ADAV80X_DAC_CTRL2_DIV1 0x00
|
||||
#define ADAV80X_DAC_CTRL2_DIV1_5 0x10
|
||||
#define ADAV80X_DAC_CTRL2_DIV2 0x20
|
||||
#define ADAV80X_DAC_CTRL2_DIV3 0x30
|
||||
#define ADAV80X_DAC_CTRL2_DIV_MASK 0x30
|
||||
|
||||
#define ADAV80X_DAC_CTRL2_INTERPOL_256FS 0x00
|
||||
#define ADAV80X_DAC_CTRL2_INTERPOL_128FS 0x40
|
||||
#define ADAV80X_DAC_CTRL2_INTERPOL_64FS 0x80
|
||||
#define ADAV80X_DAC_CTRL2_INTERPOL_MASK 0xc0
|
||||
|
||||
#define ADAV80X_DAC_CTRL2_DEEMPH_NONE 0x00
|
||||
#define ADAV80X_DAC_CTRL2_DEEMPH_44 0x01
|
||||
#define ADAV80X_DAC_CTRL2_DEEMPH_32 0x02
|
||||
#define ADAV80X_DAC_CTRL2_DEEMPH_48 0x03
|
||||
#define ADAV80X_DAC_CTRL2_DEEMPH_MASK 0x01
|
||||
|
||||
#define ADAV80X_CAPTURE_MODE_MASTER 0x20
|
||||
#define ADAV80X_CAPTURE_WORD_LEN24 0x00
|
||||
#define ADAV80X_CAPTURE_WORD_LEN20 0x04
|
||||
#define ADAV80X_CAPTRUE_WORD_LEN18 0x08
|
||||
#define ADAV80X_CAPTURE_WORD_LEN16 0x0c
|
||||
#define ADAV80X_CAPTURE_WORD_LEN_MASK 0x0c
|
||||
|
||||
#define ADAV80X_CAPTURE_MODE_LEFT_J 0x00
|
||||
#define ADAV80X_CAPTURE_MODE_I2S 0x01
|
||||
#define ADAV80X_CAPTURE_MODE_RIGHT_J 0x03
|
||||
#define ADAV80X_CAPTURE_MODE_MASK 0x03
|
||||
|
||||
#define ADAV80X_PLAYBACK_MODE_MASTER 0x10
|
||||
#define ADAV80X_PLAYBACK_MODE_LEFT_J 0x00
|
||||
#define ADAV80X_PLAYBACK_MODE_I2S 0x01
|
||||
#define ADAV80X_PLAYBACK_MODE_RIGHT_J_24 0x04
|
||||
#define ADAV80X_PLAYBACK_MODE_RIGHT_J_20 0x05
|
||||
#define ADAV80X_PLAYBACK_MODE_RIGHT_J_18 0x06
|
||||
#define ADAV80X_PLAYBACK_MODE_RIGHT_J_16 0x07
|
||||
#define ADAV80X_PLAYBACK_MODE_MASK 0x07
|
||||
|
||||
#define ADAV80X_PLL_OUTE_SYSCLKPD(x) BIT(2 - (x))
|
||||
|
||||
static u8 adav80x_default_regs[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x01, 0x80, 0x26, 0x00, 0x00,
|
||||
0x02, 0x40, 0x20, 0x00, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x92, 0xb1, 0x37,
|
||||
0x48, 0xd2, 0xfb, 0xca, 0xd2, 0x15, 0xe8, 0x29, 0xb9, 0x6a, 0xda, 0x2b,
|
||||
0xb7, 0xc0, 0x11, 0x65, 0x5c, 0xf6, 0xff, 0x8d, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00,
|
||||
0x00, 0xe8, 0x46, 0xe1, 0x5b, 0xd3, 0x43, 0x77, 0x93, 0xa7, 0x44, 0xee,
|
||||
0x32, 0x12, 0xc0, 0x11, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x3f,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x52, 0x00,
|
||||
};
|
||||
|
||||
struct adav80x {
|
||||
enum snd_soc_control_type control_type;
|
||||
|
||||
enum adav80x_clk_src clk_src;
|
||||
unsigned int sysclk;
|
||||
enum adav80x_pll_src pll_src;
|
||||
|
||||
unsigned int dai_fmt[2];
|
||||
unsigned int rate;
|
||||
bool deemph;
|
||||
bool sysclk_pd[3];
|
||||
};
|
||||
|
||||
static const char *adav80x_mux_text[] = {
|
||||
"ADC",
|
||||
"Playback",
|
||||
"Aux Playback",
|
||||
};
|
||||
|
||||
static const unsigned int adav80x_mux_values[] = {
|
||||
0, 2, 3,
|
||||
};
|
||||
|
||||
#define ADAV80X_MUX_ENUM_DECL(name, reg, shift) \
|
||||
SOC_VALUE_ENUM_DOUBLE_DECL(name, reg, shift, 7, \
|
||||
ARRAY_SIZE(adav80x_mux_text), adav80x_mux_text, \
|
||||
adav80x_mux_values)
|
||||
|
||||
static ADAV80X_MUX_ENUM_DECL(adav80x_aux_capture_enum, ADAV80X_DPATH_CTRL1, 0);
|
||||
static ADAV80X_MUX_ENUM_DECL(adav80x_capture_enum, ADAV80X_DPATH_CTRL1, 3);
|
||||
static ADAV80X_MUX_ENUM_DECL(adav80x_dac_enum, ADAV80X_DPATH_CTRL2, 3);
|
||||
|
||||
static const struct snd_kcontrol_new adav80x_aux_capture_mux_ctrl =
|
||||
SOC_DAPM_VALUE_ENUM("Route", adav80x_aux_capture_enum);
|
||||
static const struct snd_kcontrol_new adav80x_capture_mux_ctrl =
|
||||
SOC_DAPM_VALUE_ENUM("Route", adav80x_capture_enum);
|
||||
static const struct snd_kcontrol_new adav80x_dac_mux_ctrl =
|
||||
SOC_DAPM_VALUE_ENUM("Route", adav80x_dac_enum);
|
||||
|
||||
#define ADAV80X_MUX(name, ctrl) \
|
||||
SND_SOC_DAPM_VALUE_MUX(name, SND_SOC_NOPM, 0, 0, ctrl)
|
||||
|
||||
static const struct snd_soc_dapm_widget adav80x_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC", NULL, ADAV80X_DAC_CTRL1, 7, 1),
|
||||
SND_SOC_DAPM_ADC("ADC", NULL, ADAV80X_ADC_CTRL1, 5, 1),
|
||||
|
||||
SND_SOC_DAPM_PGA("Right PGA", ADAV80X_ADC_CTRL1, 0, 1, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Left PGA", ADAV80X_ADC_CTRL1, 1, 1, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_AIF_OUT("AIFOUT", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_AIF_IN("AIFIN", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0),
|
||||
|
||||
SND_SOC_DAPM_AIF_OUT("AIFAUXOUT", "Aux Capture", 0, SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_AIF_IN("AIFAUXIN", "Aux Playback", 0, SND_SOC_NOPM, 0, 0),
|
||||
|
||||
ADAV80X_MUX("Aux Capture Select", &adav80x_aux_capture_mux_ctrl),
|
||||
ADAV80X_MUX("Capture Select", &adav80x_capture_mux_ctrl),
|
||||
ADAV80X_MUX("DAC Select", &adav80x_dac_mux_ctrl),
|
||||
|
||||
SND_SOC_DAPM_INPUT("VINR"),
|
||||
SND_SOC_DAPM_INPUT("VINL"),
|
||||
SND_SOC_DAPM_OUTPUT("VOUTR"),
|
||||
SND_SOC_DAPM_OUTPUT("VOUTL"),
|
||||
|
||||
SND_SOC_DAPM_SUPPLY("SYSCLK", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_SUPPLY("PLL1", ADAV80X_PLL_CTRL1, 2, 1, NULL, 0),
|
||||
SND_SOC_DAPM_SUPPLY("PLL2", ADAV80X_PLL_CTRL1, 3, 1, NULL, 0),
|
||||
SND_SOC_DAPM_SUPPLY("OSC", ADAV80X_PLL_CTRL1, 1, 1, NULL, 0),
|
||||
};
|
||||
|
||||
static int adav80x_dapm_sysclk_check(struct snd_soc_dapm_widget *source,
|
||||
struct snd_soc_dapm_widget *sink)
|
||||
{
|
||||
struct snd_soc_codec *codec = source->codec;
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
const char *clk;
|
||||
|
||||
switch (adav80x->clk_src) {
|
||||
case ADAV80X_CLK_PLL1:
|
||||
clk = "PLL1";
|
||||
break;
|
||||
case ADAV80X_CLK_PLL2:
|
||||
clk = "PLL2";
|
||||
break;
|
||||
case ADAV80X_CLK_XTAL:
|
||||
clk = "OSC";
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return strcmp(source->name, clk) == 0;
|
||||
}
|
||||
|
||||
static int adav80x_dapm_pll_check(struct snd_soc_dapm_widget *source,
|
||||
struct snd_soc_dapm_widget *sink)
|
||||
{
|
||||
struct snd_soc_codec *codec = source->codec;
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
return adav80x->pll_src == ADAV80X_PLL_SRC_XTAL;
|
||||
}
|
||||
|
||||
|
||||
static const struct snd_soc_dapm_route adav80x_dapm_routes[] = {
|
||||
{ "DAC Select", "ADC", "ADC" },
|
||||
{ "DAC Select", "Playback", "AIFIN" },
|
||||
{ "DAC Select", "Aux Playback", "AIFAUXIN" },
|
||||
{ "DAC", NULL, "DAC Select" },
|
||||
|
||||
{ "Capture Select", "ADC", "ADC" },
|
||||
{ "Capture Select", "Playback", "AIFIN" },
|
||||
{ "Capture Select", "Aux Playback", "AIFAUXIN" },
|
||||
{ "AIFOUT", NULL, "Capture Select" },
|
||||
|
||||
{ "Aux Capture Select", "ADC", "ADC" },
|
||||
{ "Aux Capture Select", "Playback", "AIFIN" },
|
||||
{ "Aux Capture Select", "Aux Playback", "AIFAUXIN" },
|
||||
{ "AIFAUXOUT", NULL, "Aux Capture Select" },
|
||||
|
||||
{ "VOUTR", NULL, "DAC" },
|
||||
{ "VOUTL", NULL, "DAC" },
|
||||
|
||||
{ "Left PGA", NULL, "VINL" },
|
||||
{ "Right PGA", NULL, "VINR" },
|
||||
{ "ADC", NULL, "Left PGA" },
|
||||
{ "ADC", NULL, "Right PGA" },
|
||||
|
||||
{ "SYSCLK", NULL, "PLL1", adav80x_dapm_sysclk_check },
|
||||
{ "SYSCLK", NULL, "PLL2", adav80x_dapm_sysclk_check },
|
||||
{ "SYSCLK", NULL, "OSC", adav80x_dapm_sysclk_check },
|
||||
{ "PLL1", NULL, "OSC", adav80x_dapm_pll_check },
|
||||
{ "PLL2", NULL, "OSC", adav80x_dapm_pll_check },
|
||||
|
||||
{ "ADC", NULL, "SYSCLK" },
|
||||
{ "DAC", NULL, "SYSCLK" },
|
||||
{ "AIFOUT", NULL, "SYSCLK" },
|
||||
{ "AIFAUXOUT", NULL, "SYSCLK" },
|
||||
{ "AIFIN", NULL, "SYSCLK" },
|
||||
{ "AIFAUXIN", NULL, "SYSCLK" },
|
||||
};
|
||||
|
||||
static int adav80x_set_deemph(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int val;
|
||||
|
||||
if (adav80x->deemph) {
|
||||
switch (adav80x->rate) {
|
||||
case 32000:
|
||||
val = ADAV80X_DAC_CTRL2_DEEMPH_32;
|
||||
break;
|
||||
case 44100:
|
||||
val = ADAV80X_DAC_CTRL2_DEEMPH_44;
|
||||
break;
|
||||
case 48000:
|
||||
case 64000:
|
||||
case 88200:
|
||||
case 96000:
|
||||
val = ADAV80X_DAC_CTRL2_DEEMPH_48;
|
||||
break;
|
||||
default:
|
||||
val = ADAV80X_DAC_CTRL2_DEEMPH_NONE;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
val = ADAV80X_DAC_CTRL2_DEEMPH_NONE;
|
||||
}
|
||||
|
||||
return snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2,
|
||||
ADAV80X_DAC_CTRL2_DEEMPH_MASK, val);
|
||||
}
|
||||
|
||||
static int adav80x_put_deemph(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int deemph = ucontrol->value.enumerated.item[0];
|
||||
|
||||
if (deemph > 1)
|
||||
return -EINVAL;
|
||||
|
||||
adav80x->deemph = deemph;
|
||||
|
||||
return adav80x_set_deemph(codec);
|
||||
}
|
||||
|
||||
static int adav80x_get_deemph(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
ucontrol->value.enumerated.item[0] = adav80x->deemph;
|
||||
return 0;
|
||||
};
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(adav80x_inpga_tlv, 0, 50, 0);
|
||||
static const DECLARE_TLV_DB_MINMAX(adav80x_digital_tlv, -9563, 0);
|
||||
|
||||
static const struct snd_kcontrol_new adav80x_controls[] = {
|
||||
SOC_DOUBLE_R_TLV("Master Playback Volume", ADAV80X_DAC_L_VOL,
|
||||
ADAV80X_DAC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv),
|
||||
SOC_DOUBLE_R_TLV("Master Capture Volume", ADAV80X_ADC_L_VOL,
|
||||
ADAV80X_ADC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv),
|
||||
|
||||
SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAV80X_PGA_L_VOL,
|
||||
ADAV80X_PGA_R_VOL, 0, 0x30, 0, adav80x_inpga_tlv),
|
||||
|
||||
SOC_DOUBLE("Master Playback Switch", ADAV80X_DAC_CTRL1, 0, 1, 1, 0),
|
||||
SOC_DOUBLE("Master Capture Switch", ADAV80X_ADC_CTRL1, 2, 3, 1, 1),
|
||||
|
||||
SOC_SINGLE("ADC High Pass Filter Switch", ADAV80X_ADC_CTRL1, 6, 1, 0),
|
||||
|
||||
SOC_SINGLE_BOOL_EXT("Playback De-emphasis Switch", 0,
|
||||
adav80x_get_deemph, adav80x_put_deemph),
|
||||
};
|
||||
|
||||
static unsigned int adav80x_port_ctrl_regs[2][2] = {
|
||||
{ ADAV80X_REC_CTRL, ADAV80X_PLAYBACK_CTRL, },
|
||||
{ ADAV80X_AUX_OUT_CTRL, ADAV80X_AUX_IN_CTRL },
|
||||
};
|
||||
|
||||
static int adav80x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int capture = 0x00;
|
||||
unsigned int playback = 0x00;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
capture |= ADAV80X_CAPTURE_MODE_MASTER;
|
||||
playback |= ADAV80X_PLAYBACK_MODE_MASTER;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
capture |= ADAV80X_CAPTURE_MODE_I2S;
|
||||
playback |= ADAV80X_PLAYBACK_MODE_I2S;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
capture |= ADAV80X_CAPTURE_MODE_LEFT_J;
|
||||
playback |= ADAV80X_PLAYBACK_MODE_LEFT_J;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
capture |= ADAV80X_CAPTURE_MODE_RIGHT_J;
|
||||
playback |= ADAV80X_PLAYBACK_MODE_RIGHT_J_24;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0],
|
||||
ADAV80X_CAPTURE_MODE_MASK | ADAV80X_CAPTURE_MODE_MASTER,
|
||||
capture);
|
||||
snd_soc_write(codec, adav80x_port_ctrl_regs[dai->id][1], playback);
|
||||
|
||||
adav80x->dai_fmt[dai->id] = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_set_adc_clock(struct snd_soc_codec *codec,
|
||||
unsigned int sample_rate)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (sample_rate <= 48000)
|
||||
val = ADAV80X_ADC_CTRL1_MODULATOR_128FS;
|
||||
else
|
||||
val = ADAV80X_ADC_CTRL1_MODULATOR_64FS;
|
||||
|
||||
snd_soc_update_bits(codec, ADAV80X_ADC_CTRL1,
|
||||
ADAV80X_ADC_CTRL1_MODULATOR_MASK, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_set_dac_clock(struct snd_soc_codec *codec,
|
||||
unsigned int sample_rate)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (sample_rate <= 48000)
|
||||
val = ADAV80X_DAC_CTRL2_DIV1 | ADAV80X_DAC_CTRL2_INTERPOL_256FS;
|
||||
else
|
||||
val = ADAV80X_DAC_CTRL2_DIV2 | ADAV80X_DAC_CTRL2_INTERPOL_128FS;
|
||||
|
||||
snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2,
|
||||
ADAV80X_DAC_CTRL2_DIV_MASK | ADAV80X_DAC_CTRL2_INTERPOL_MASK,
|
||||
val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_set_capture_pcm_format(struct snd_soc_codec *codec,
|
||||
struct snd_soc_dai *dai, snd_pcm_format_t format)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
switch (format) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
val = ADAV80X_CAPTURE_WORD_LEN16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S18_3LE:
|
||||
val = ADAV80X_CAPTRUE_WORD_LEN18;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
val = ADAV80X_CAPTURE_WORD_LEN20;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
val = ADAV80X_CAPTURE_WORD_LEN24;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0],
|
||||
ADAV80X_CAPTURE_WORD_LEN_MASK, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_set_playback_pcm_format(struct snd_soc_codec *codec,
|
||||
struct snd_soc_dai *dai, snd_pcm_format_t format)
|
||||
{
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int val;
|
||||
|
||||
if (adav80x->dai_fmt[dai->id] != SND_SOC_DAIFMT_RIGHT_J)
|
||||
return 0;
|
||||
|
||||
switch (format) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
val = ADAV80X_PLAYBACK_MODE_RIGHT_J_16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S18_3LE:
|
||||
val = ADAV80X_PLAYBACK_MODE_RIGHT_J_18;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
val = ADAV80X_PLAYBACK_MODE_RIGHT_J_20;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
val = ADAV80X_PLAYBACK_MODE_RIGHT_J_24;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][1],
|
||||
ADAV80X_PLAYBACK_MODE_MASK, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int rate = params_rate(params);
|
||||
|
||||
if (rate * 256 != adav80x->sysclk)
|
||||
return -EINVAL;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
adav80x_set_playback_pcm_format(codec, dai,
|
||||
params_format(params));
|
||||
adav80x_set_dac_clock(codec, rate);
|
||||
} else {
|
||||
adav80x_set_capture_pcm_format(codec, dai,
|
||||
params_format(params));
|
||||
adav80x_set_adc_clock(codec, rate);
|
||||
}
|
||||
adav80x->rate = rate;
|
||||
adav80x_set_deemph(codec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_set_sysclk(struct snd_soc_codec *codec,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
if (dir == SND_SOC_CLOCK_IN) {
|
||||
switch (clk_id) {
|
||||
case ADAV80X_CLK_XIN:
|
||||
case ADAV80X_CLK_XTAL:
|
||||
case ADAV80X_CLK_MCLKI:
|
||||
case ADAV80X_CLK_PLL1:
|
||||
case ADAV80X_CLK_PLL2:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
adav80x->sysclk = freq;
|
||||
|
||||
if (adav80x->clk_src != clk_id) {
|
||||
unsigned int iclk_ctrl1, iclk_ctrl2;
|
||||
|
||||
adav80x->clk_src = clk_id;
|
||||
if (clk_id == ADAV80X_CLK_XTAL)
|
||||
clk_id = ADAV80X_CLK_XIN;
|
||||
|
||||
iclk_ctrl1 = ADAV80X_ICLK_CTRL1_DAC_SRC(clk_id) |
|
||||
ADAV80X_ICLK_CTRL1_ADC_SRC(clk_id) |
|
||||
ADAV80X_ICLK_CTRL1_ICLK2_SRC(clk_id);
|
||||
iclk_ctrl2 = ADAV80X_ICLK_CTRL2_ICLK1_SRC(clk_id);
|
||||
|
||||
snd_soc_write(codec, ADAV80X_ICLK_CTRL1, iclk_ctrl1);
|
||||
snd_soc_write(codec, ADAV80X_ICLK_CTRL2, iclk_ctrl2);
|
||||
|
||||
snd_soc_dapm_sync(&codec->dapm);
|
||||
}
|
||||
} else {
|
||||
unsigned int mask;
|
||||
|
||||
switch (clk_id) {
|
||||
case ADAV80X_CLK_SYSCLK1:
|
||||
case ADAV80X_CLK_SYSCLK2:
|
||||
case ADAV80X_CLK_SYSCLK3:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk_id -= ADAV80X_CLK_SYSCLK1;
|
||||
mask = ADAV80X_PLL_OUTE_SYSCLKPD(clk_id);
|
||||
|
||||
if (freq == 0) {
|
||||
snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, mask);
|
||||
adav80x->sysclk_pd[clk_id] = true;
|
||||
} else {
|
||||
snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, 0);
|
||||
adav80x->sysclk_pd[clk_id] = false;
|
||||
}
|
||||
|
||||
if (adav80x->sysclk_pd[0])
|
||||
snd_soc_dapm_disable_pin(&codec->dapm, "PLL1");
|
||||
else
|
||||
snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1");
|
||||
|
||||
if (adav80x->sysclk_pd[1] || adav80x->sysclk_pd[2])
|
||||
snd_soc_dapm_disable_pin(&codec->dapm, "PLL2");
|
||||
else
|
||||
snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2");
|
||||
|
||||
snd_soc_dapm_sync(&codec->dapm);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_set_pll(struct snd_soc_codec *codec, int pll_id,
|
||||
int source, unsigned int freq_in, unsigned int freq_out)
|
||||
{
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int pll_ctrl1 = 0;
|
||||
unsigned int pll_ctrl2 = 0;
|
||||
unsigned int pll_src;
|
||||
|
||||
switch (source) {
|
||||
case ADAV80X_PLL_SRC_XTAL:
|
||||
case ADAV80X_PLL_SRC_XIN:
|
||||
case ADAV80X_PLL_SRC_MCLKI:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!freq_out)
|
||||
return 0;
|
||||
|
||||
switch (freq_in) {
|
||||
case 27000000:
|
||||
break;
|
||||
case 54000000:
|
||||
if (source == ADAV80X_PLL_SRC_XIN) {
|
||||
pll_ctrl1 |= ADAV80X_PLL_CTRL1_PLLDIV;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (freq_out > 12288000) {
|
||||
pll_ctrl2 |= ADAV80X_PLL_CTRL2_DOUB(pll_id);
|
||||
freq_out /= 2;
|
||||
}
|
||||
|
||||
/* freq_out = sample_rate * 256 */
|
||||
switch (freq_out) {
|
||||
case 8192000:
|
||||
pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_32(pll_id);
|
||||
break;
|
||||
case 11289600:
|
||||
pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_44(pll_id);
|
||||
break;
|
||||
case 12288000:
|
||||
pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_48(pll_id);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, ADAV80X_PLL_CTRL1, ADAV80X_PLL_CTRL1_PLLDIV,
|
||||
pll_ctrl1);
|
||||
snd_soc_update_bits(codec, ADAV80X_PLL_CTRL2,
|
||||
ADAV80X_PLL_CTRL2_PLL_MASK(pll_id), pll_ctrl2);
|
||||
|
||||
if (source != adav80x->pll_src) {
|
||||
if (source == ADAV80X_PLL_SRC_MCLKI)
|
||||
pll_src = ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll_id);
|
||||
else
|
||||
pll_src = ADAV80X_PLL_CLK_SRC_PLL_XIN(pll_id);
|
||||
|
||||
snd_soc_update_bits(codec, ADAV80X_PLL_CLK_SRC,
|
||||
ADAV80X_PLL_CLK_SRC_PLL_MASK(pll_id), pll_src);
|
||||
|
||||
adav80x->pll_src = source;
|
||||
|
||||
snd_soc_dapm_sync(&codec->dapm);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
unsigned int mask = ADAV80X_DAC_CTRL1_PD;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, 0x00);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, mask);
|
||||
break;
|
||||
}
|
||||
|
||||
codec->dapm.bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enforce the same sample rate on all audio interfaces */
|
||||
static int adav80x_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
if (!codec->active || !adav80x->rate)
|
||||
return 0;
|
||||
|
||||
return snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE, adav80x->rate, adav80x->rate);
|
||||
}
|
||||
|
||||
static void adav80x_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
if (!codec->active)
|
||||
adav80x->rate = 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops adav80x_dai_ops = {
|
||||
.set_fmt = adav80x_set_dai_fmt,
|
||||
.hw_params = adav80x_hw_params,
|
||||
.startup = adav80x_dai_startup,
|
||||
.shutdown = adav80x_dai_shutdown,
|
||||
};
|
||||
|
||||
#define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
|
||||
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | \
|
||||
SNDRV_PCM_RATE_96000)
|
||||
|
||||
#define ADAV80X_CAPTURE_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
|
||||
|
||||
#define ADAV80X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
static struct snd_soc_dai_driver adav80x_dais[] = {
|
||||
{
|
||||
.name = "adav80x-hifi",
|
||||
.id = 0,
|
||||
.playback = {
|
||||
.stream_name = "HiFi Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = ADAV80X_PLAYBACK_RATES,
|
||||
.formats = ADAV80X_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "HiFi Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = ADAV80X_CAPTURE_RATES,
|
||||
.formats = ADAV80X_FORMATS,
|
||||
},
|
||||
.ops = &adav80x_dai_ops,
|
||||
},
|
||||
{
|
||||
.name = "adav80x-aux",
|
||||
.id = 1,
|
||||
.playback = {
|
||||
.stream_name = "Aux Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = ADAV80X_PLAYBACK_RATES,
|
||||
.formats = ADAV80X_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Aux Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = ADAV80X_CAPTURE_RATES,
|
||||
.formats = ADAV80X_FORMATS,
|
||||
},
|
||||
.ops = &adav80x_dai_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static int adav80x_probe(struct snd_soc_codec *codec)
|
||||
{
|
||||
int ret;
|
||||
struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, adav80x->control_type);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "failed to set cache I/O: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Force PLLs on for SYSCLK output */
|
||||
snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1");
|
||||
snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2");
|
||||
|
||||
/* Power down S/PDIF receiver, since it is currently not supported */
|
||||
snd_soc_write(codec, ADAV80X_PLL_OUTE, 0x20);
|
||||
/* Disable DAC zero flag */
|
||||
snd_soc_write(codec, ADAV80X_DAC_CTRL3, 0x6);
|
||||
|
||||
return adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
}
|
||||
|
||||
static int adav80x_suspend(struct snd_soc_codec *codec, pm_message_t state)
|
||||
{
|
||||
return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
}
|
||||
|
||||
static int adav80x_resume(struct snd_soc_codec *codec)
|
||||
{
|
||||
adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
codec->cache_sync = 1;
|
||||
snd_soc_cache_sync(codec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adav80x_remove(struct snd_soc_codec *codec)
|
||||
{
|
||||
return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
}
|
||||
|
||||
static struct snd_soc_codec_driver adav80x_codec_driver = {
|
||||
.probe = adav80x_probe,
|
||||
.remove = adav80x_remove,
|
||||
.suspend = adav80x_suspend,
|
||||
.resume = adav80x_resume,
|
||||
.set_bias_level = adav80x_set_bias_level,
|
||||
|
||||
.set_pll = adav80x_set_pll,
|
||||
.set_sysclk = adav80x_set_sysclk,
|
||||
|
||||
.reg_word_size = sizeof(u8),
|
||||
.reg_cache_size = ARRAY_SIZE(adav80x_default_regs),
|
||||
.reg_cache_default = adav80x_default_regs,
|
||||
|
||||
.controls = adav80x_controls,
|
||||
.num_controls = ARRAY_SIZE(adav80x_controls),
|
||||
.dapm_widgets = adav80x_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(adav80x_dapm_widgets),
|
||||
.dapm_routes = adav80x_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(adav80x_dapm_routes),
|
||||
};
|
||||
|
||||
static int __devinit adav80x_bus_probe(struct device *dev,
|
||||
enum snd_soc_control_type control_type)
|
||||
{
|
||||
struct adav80x *adav80x;
|
||||
int ret;
|
||||
|
||||
adav80x = kzalloc(sizeof(*adav80x), GFP_KERNEL);
|
||||
if (!adav80x)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, adav80x);
|
||||
adav80x->control_type = control_type;
|
||||
|
||||
ret = snd_soc_register_codec(dev, &adav80x_codec_driver,
|
||||
adav80x_dais, ARRAY_SIZE(adav80x_dais));
|
||||
if (ret)
|
||||
kfree(adav80x);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit adav80x_bus_remove(struct device *dev)
|
||||
{
|
||||
snd_soc_unregister_codec(dev);
|
||||
kfree(dev_get_drvdata(dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
static int __devinit adav80x_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
return adav80x_bus_probe(&spi->dev, SND_SOC_SPI);
|
||||
}
|
||||
|
||||
static int __devexit adav80x_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return adav80x_bus_remove(&spi->dev);
|
||||
}
|
||||
|
||||
static struct spi_driver adav80x_spi_driver = {
|
||||
.driver = {
|
||||
.name = "adav801",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = adav80x_spi_probe,
|
||||
.remove = __devexit_p(adav80x_spi_remove),
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
static const struct i2c_device_id adav80x_id[] = {
|
||||
{ "adav803", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adav80x_id);
|
||||
|
||||
static int __devinit adav80x_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
return adav80x_bus_probe(&client->dev, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static int __devexit adav80x_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
return adav80x_bus_remove(&client->dev);
|
||||
}
|
||||
|
||||
static struct i2c_driver adav80x_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "adav803",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = adav80x_i2c_probe,
|
||||
.remove = __devexit_p(adav80x_i2c_remove),
|
||||
.id_table = adav80x_id,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int __init adav80x_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
ret = i2c_add_driver(&adav80x_i2c_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
ret = spi_register_driver(&adav80x_spi_driver);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(adav80x_init);
|
||||
|
||||
static void __exit adav80x_exit(void)
|
||||
{
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_del_driver(&adav80x_i2c_driver);
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
spi_unregister_driver(&adav80x_spi_driver);
|
||||
#endif
|
||||
}
|
||||
module_exit(adav80x_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC ADAV80x driver");
|
||||
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
||||
MODULE_AUTHOR("Yi Li <yi.li@analog.com>>");
|
||||
MODULE_LICENSE("GPL");
|
35
sound/soc/codecs/adav80x.h
Normal file
35
sound/soc/codecs/adav80x.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* header file for ADAV80X parts
|
||||
*
|
||||
* Copyright 2011 Analog Devices Inc.
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#ifndef _ADAV80X_H
|
||||
#define _ADAV80X_H
|
||||
|
||||
enum adav80x_pll_src {
|
||||
ADAV80X_PLL_SRC_XIN,
|
||||
ADAV80X_PLL_SRC_XTAL,
|
||||
ADAV80X_PLL_SRC_MCLKI,
|
||||
};
|
||||
|
||||
enum adav80x_pll {
|
||||
ADAV80X_PLL1 = 0,
|
||||
ADAV80X_PLL2 = 1,
|
||||
};
|
||||
|
||||
enum adav80x_clk_src {
|
||||
ADAV80X_CLK_XIN = 0,
|
||||
ADAV80X_CLK_MCLKI = 1,
|
||||
ADAV80X_CLK_PLL1 = 2,
|
||||
ADAV80X_CLK_PLL2 = 3,
|
||||
ADAV80X_CLK_XTAL = 6,
|
||||
|
||||
ADAV80X_CLK_SYSCLK1 = 6,
|
||||
ADAV80X_CLK_SYSCLK2 = 7,
|
||||
ADAV80X_CLK_SYSCLK3 = 8,
|
||||
};
|
||||
|
||||
#endif
|
@ -457,7 +457,7 @@ static struct snd_soc_dai_ops ak4641_pcm_dai_ops = {
|
||||
.set_sysclk = ak4641_set_dai_sysclk,
|
||||
};
|
||||
|
||||
struct snd_soc_dai_driver ak4641_dai[] = {
|
||||
static struct snd_soc_dai_driver ak4641_dai[] = {
|
||||
{
|
||||
.name = "ak4641-hifi",
|
||||
.id = 1,
|
||||
|
@ -636,10 +636,7 @@ static int cs4270_soc_resume(struct snd_soc_codec *codec)
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
/*
|
||||
* ASoC codec device structure
|
||||
*
|
||||
* Assign this variable to the codec_dev field of the machine driver's
|
||||
* snd_soc_device structure.
|
||||
* ASoC codec driver structure
|
||||
*/
|
||||
static const struct snd_soc_codec_driver soc_codec_device_cs4270 = {
|
||||
.probe = cs4270_probe,
|
||||
|
@ -1397,8 +1397,6 @@ static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
|
||||
if (freq == max98088->sysclk)
|
||||
return 0;
|
||||
|
||||
max98088->sysclk = freq; /* remember current sysclk */
|
||||
|
||||
/* Setup clocks for slave mode, and using the PLL
|
||||
* PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
|
||||
* 0x02 (when master clk is 20MHz to 30MHz)..
|
||||
|
@ -1517,8 +1517,6 @@ static int max98095_dai_set_sysclk(struct snd_soc_dai *dai,
|
||||
if (freq == max98095->sysclk)
|
||||
return 0;
|
||||
|
||||
max98095->sysclk = freq; /* remember current sysclk */
|
||||
|
||||
/* Setup clocks for slave mode, and using the PLL
|
||||
* PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
|
||||
* 0x02 (when master clk is 20MHz to 40MHz)..
|
||||
@ -2261,11 +2259,11 @@ static int max98095_probe(struct snd_soc_codec *codec)
|
||||
|
||||
ret = snd_soc_read(codec, M98095_0FF_REV_ID);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to read device revision: %d\n",
|
||||
dev_err(codec->dev, "Failure reading hardware revision: %d\n",
|
||||
ret);
|
||||
goto err_access;
|
||||
}
|
||||
dev_info(codec->dev, "revision %c\n", ret + 'A');
|
||||
dev_info(codec->dev, "Hardware revision: %c\n", ret - 0x40 + 'A');
|
||||
|
||||
snd_soc_write(codec, M98095_097_PWR_SYS, M98095_PWRSV);
|
||||
|
||||
@ -2342,8 +2340,8 @@ static int max98095_i2c_probe(struct i2c_client *i2c,
|
||||
max98095->control_data = i2c;
|
||||
max98095->pdata = i2c->dev.platform_data;
|
||||
|
||||
ret = snd_soc_register_codec(&i2c->dev,
|
||||
&soc_codec_dev_max98095, &max98095_dai[0], 3);
|
||||
ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_max98095,
|
||||
max98095_dai, ARRAY_SIZE(max98095_dai));
|
||||
if (ret < 0)
|
||||
kfree(max98095);
|
||||
return ret;
|
||||
|
917
sound/soc/codecs/sta32x.c
Normal file
917
sound/soc/codecs/sta32x.c
Normal file
@ -0,0 +1,917 @@
|
||||
/*
|
||||
* Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system
|
||||
*
|
||||
* Copyright: 2011 Raumfeld GmbH
|
||||
* Author: Johannes Stezenbach <js@sig21.net>
|
||||
*
|
||||
* based on code from:
|
||||
* Wolfson Microelectronics PLC.
|
||||
* Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
* Freescale Semiconductor, Inc.
|
||||
* Timur Tabi <timur@freescale.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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "sta32x.h"
|
||||
|
||||
#define STA32X_RATES (SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | \
|
||||
SNDRV_PCM_RATE_48000 | \
|
||||
SNDRV_PCM_RATE_88200 | \
|
||||
SNDRV_PCM_RATE_96000 | \
|
||||
SNDRV_PCM_RATE_176400 | \
|
||||
SNDRV_PCM_RATE_192000)
|
||||
|
||||
#define STA32X_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_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \
|
||||
SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE)
|
||||
|
||||
/* Power-up register defaults */
|
||||
static const u8 sta32x_regs[STA32X_REGISTER_COUNT] = {
|
||||
0x63, 0x80, 0xc2, 0x40, 0xc2, 0x5c, 0x10, 0xff, 0x60, 0x60,
|
||||
0x60, 0x80, 0x00, 0x00, 0x00, 0x40, 0x80, 0x77, 0x6a, 0x69,
|
||||
0x6a, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,
|
||||
0xc0, 0xf3, 0x33, 0x00, 0x0c,
|
||||
};
|
||||
|
||||
/* regulator power supply names */
|
||||
static const char *sta32x_supply_names[] = {
|
||||
"Vdda", /* analog supply, 3.3VV */
|
||||
"Vdd3", /* digital supply, 3.3V */
|
||||
"Vcc" /* power amp spply, 10V - 36V */
|
||||
};
|
||||
|
||||
/* codec private data */
|
||||
struct sta32x_priv {
|
||||
struct regulator_bulk_data supplies[ARRAY_SIZE(sta32x_supply_names)];
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
unsigned int mclk;
|
||||
unsigned int format;
|
||||
};
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(mvol_tlv, -12700, 50, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(chvol_tlv, -7950, 50, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(tone_tlv, -120, 200, 0);
|
||||
|
||||
static const char *sta32x_drc_ac[] = {
|
||||
"Anti-Clipping", "Dynamic Range Compression" };
|
||||
static const char *sta32x_auto_eq_mode[] = {
|
||||
"User", "Preset", "Loudness" };
|
||||
static const char *sta32x_auto_gc_mode[] = {
|
||||
"User", "AC no clipping", "AC limited clipping (10%)",
|
||||
"DRC nighttime listening mode" };
|
||||
static const char *sta32x_auto_xo_mode[] = {
|
||||
"User", "80Hz", "100Hz", "120Hz", "140Hz", "160Hz", "180Hz", "200Hz",
|
||||
"220Hz", "240Hz", "260Hz", "280Hz", "300Hz", "320Hz", "340Hz", "360Hz" };
|
||||
static const char *sta32x_preset_eq_mode[] = {
|
||||
"Flat", "Rock", "Soft Rock", "Jazz", "Classical", "Dance", "Pop", "Soft",
|
||||
"Hard", "Party", "Vocal", "Hip-Hop", "Dialog", "Bass-boost #1",
|
||||
"Bass-boost #2", "Bass-boost #3", "Loudness 1", "Loudness 2",
|
||||
"Loudness 3", "Loudness 4", "Loudness 5", "Loudness 6", "Loudness 7",
|
||||
"Loudness 8", "Loudness 9", "Loudness 10", "Loudness 11", "Loudness 12",
|
||||
"Loudness 13", "Loudness 14", "Loudness 15", "Loudness 16" };
|
||||
static const char *sta32x_limiter_select[] = {
|
||||
"Limiter Disabled", "Limiter #1", "Limiter #2" };
|
||||
static const char *sta32x_limiter_attack_rate[] = {
|
||||
"3.1584", "2.7072", "2.2560", "1.8048", "1.3536", "0.9024",
|
||||
"0.4512", "0.2256", "0.1504", "0.1123", "0.0902", "0.0752",
|
||||
"0.0645", "0.0564", "0.0501", "0.0451" };
|
||||
static const char *sta32x_limiter_release_rate[] = {
|
||||
"0.5116", "0.1370", "0.0744", "0.0499", "0.0360", "0.0299",
|
||||
"0.0264", "0.0208", "0.0198", "0.0172", "0.0147", "0.0137",
|
||||
"0.0134", "0.0117", "0.0110", "0.0104" };
|
||||
|
||||
static const unsigned int sta32x_limiter_ac_attack_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(2),
|
||||
0, 7, TLV_DB_SCALE_ITEM(-1200, 200, 0),
|
||||
8, 16, TLV_DB_SCALE_ITEM(300, 100, 0),
|
||||
};
|
||||
|
||||
static const unsigned int sta32x_limiter_ac_release_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(5),
|
||||
0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0),
|
||||
1, 1, TLV_DB_SCALE_ITEM(-2900, 0, 0),
|
||||
2, 2, TLV_DB_SCALE_ITEM(-2000, 0, 0),
|
||||
3, 8, TLV_DB_SCALE_ITEM(-1400, 200, 0),
|
||||
8, 16, TLV_DB_SCALE_ITEM(-700, 100, 0),
|
||||
};
|
||||
|
||||
static const unsigned int sta32x_limiter_drc_attack_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(3),
|
||||
0, 7, TLV_DB_SCALE_ITEM(-3100, 200, 0),
|
||||
8, 13, TLV_DB_SCALE_ITEM(-1600, 100, 0),
|
||||
14, 16, TLV_DB_SCALE_ITEM(-1000, 300, 0),
|
||||
};
|
||||
|
||||
static const unsigned int sta32x_limiter_drc_release_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(5),
|
||||
0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0),
|
||||
1, 2, TLV_DB_SCALE_ITEM(-3800, 200, 0),
|
||||
3, 4, TLV_DB_SCALE_ITEM(-3300, 200, 0),
|
||||
5, 12, TLV_DB_SCALE_ITEM(-3000, 200, 0),
|
||||
13, 16, TLV_DB_SCALE_ITEM(-1500, 300, 0),
|
||||
};
|
||||
|
||||
static const struct soc_enum sta32x_drc_ac_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_CONFD, STA32X_CONFD_DRC_SHIFT,
|
||||
2, sta32x_drc_ac);
|
||||
static const struct soc_enum sta32x_auto_eq_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_AUTO1, STA32X_AUTO1_AMEQ_SHIFT,
|
||||
3, sta32x_auto_eq_mode);
|
||||
static const struct soc_enum sta32x_auto_gc_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_AUTO1, STA32X_AUTO1_AMGC_SHIFT,
|
||||
4, sta32x_auto_gc_mode);
|
||||
static const struct soc_enum sta32x_auto_xo_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_AUTO2, STA32X_AUTO2_XO_SHIFT,
|
||||
16, sta32x_auto_xo_mode);
|
||||
static const struct soc_enum sta32x_preset_eq_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_AUTO3, STA32X_AUTO3_PEQ_SHIFT,
|
||||
32, sta32x_preset_eq_mode);
|
||||
static const struct soc_enum sta32x_limiter_ch1_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_C1CFG, STA32X_CxCFG_LS_SHIFT,
|
||||
3, sta32x_limiter_select);
|
||||
static const struct soc_enum sta32x_limiter_ch2_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_C2CFG, STA32X_CxCFG_LS_SHIFT,
|
||||
3, sta32x_limiter_select);
|
||||
static const struct soc_enum sta32x_limiter_ch3_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_C3CFG, STA32X_CxCFG_LS_SHIFT,
|
||||
3, sta32x_limiter_select);
|
||||
static const struct soc_enum sta32x_limiter1_attack_rate_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_L1AR, STA32X_LxA_SHIFT,
|
||||
16, sta32x_limiter_attack_rate);
|
||||
static const struct soc_enum sta32x_limiter2_attack_rate_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_L2AR, STA32X_LxA_SHIFT,
|
||||
16, sta32x_limiter_attack_rate);
|
||||
static const struct soc_enum sta32x_limiter1_release_rate_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_L1AR, STA32X_LxR_SHIFT,
|
||||
16, sta32x_limiter_release_rate);
|
||||
static const struct soc_enum sta32x_limiter2_release_rate_enum =
|
||||
SOC_ENUM_SINGLE(STA32X_L2AR, STA32X_LxR_SHIFT,
|
||||
16, sta32x_limiter_release_rate);
|
||||
|
||||
/* byte array controls for setting biquad, mixer, scaling coefficients;
|
||||
* for biquads all five coefficients need to be set in one go,
|
||||
* mixer and pre/postscale coefs can be set individually;
|
||||
* each coef is 24bit, the bytes are ordered in the same way
|
||||
* as given in the STA32x data sheet (big endian; b1, b2, a1, a2, b0)
|
||||
*/
|
||||
|
||||
static int sta32x_coefficient_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
int numcoef = kcontrol->private_value >> 16;
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
|
||||
uinfo->count = 3 * numcoef;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
int numcoef = kcontrol->private_value >> 16;
|
||||
int index = kcontrol->private_value & 0xffff;
|
||||
unsigned int cfud;
|
||||
int i;
|
||||
|
||||
/* preserve reserved bits in STA32X_CFUD */
|
||||
cfud = snd_soc_read(codec, STA32X_CFUD) & 0xf0;
|
||||
/* chip documentation does not say if the bits are self clearing,
|
||||
* so do it explicitly */
|
||||
snd_soc_write(codec, STA32X_CFUD, cfud);
|
||||
|
||||
snd_soc_write(codec, STA32X_CFADDR2, index);
|
||||
if (numcoef == 1)
|
||||
snd_soc_write(codec, STA32X_CFUD, cfud | 0x04);
|
||||
else if (numcoef == 5)
|
||||
snd_soc_write(codec, STA32X_CFUD, cfud | 0x08);
|
||||
else
|
||||
return -EINVAL;
|
||||
for (i = 0; i < 3 * numcoef; i++)
|
||||
ucontrol->value.bytes.data[i] =
|
||||
snd_soc_read(codec, STA32X_B1CF1 + i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
int numcoef = kcontrol->private_value >> 16;
|
||||
int index = kcontrol->private_value & 0xffff;
|
||||
unsigned int cfud;
|
||||
int i;
|
||||
|
||||
/* preserve reserved bits in STA32X_CFUD */
|
||||
cfud = snd_soc_read(codec, STA32X_CFUD) & 0xf0;
|
||||
/* chip documentation does not say if the bits are self clearing,
|
||||
* so do it explicitly */
|
||||
snd_soc_write(codec, STA32X_CFUD, cfud);
|
||||
|
||||
snd_soc_write(codec, STA32X_CFADDR2, index);
|
||||
for (i = 0; i < 3 * numcoef; i++)
|
||||
snd_soc_write(codec, STA32X_B1CF1 + i,
|
||||
ucontrol->value.bytes.data[i]);
|
||||
if (numcoef == 1)
|
||||
snd_soc_write(codec, STA32X_CFUD, cfud | 0x01);
|
||||
else if (numcoef == 5)
|
||||
snd_soc_write(codec, STA32X_CFUD, cfud | 0x02);
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SINGLE_COEF(xname, index) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
.info = sta32x_coefficient_info, \
|
||||
.get = sta32x_coefficient_get,\
|
||||
.put = sta32x_coefficient_put, \
|
||||
.private_value = index | (1 << 16) }
|
||||
|
||||
#define BIQUAD_COEFS(xname, index) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
.info = sta32x_coefficient_info, \
|
||||
.get = sta32x_coefficient_get,\
|
||||
.put = sta32x_coefficient_put, \
|
||||
.private_value = index | (5 << 16) }
|
||||
|
||||
static const struct snd_kcontrol_new sta32x_snd_controls[] = {
|
||||
SOC_SINGLE_TLV("Master Volume", STA32X_MVOL, 0, 0xff, 1, mvol_tlv),
|
||||
SOC_SINGLE("Master Switch", STA32X_MMUTE, 0, 1, 1),
|
||||
SOC_SINGLE("Ch1 Switch", STA32X_MMUTE, 1, 1, 1),
|
||||
SOC_SINGLE("Ch2 Switch", STA32X_MMUTE, 2, 1, 1),
|
||||
SOC_SINGLE("Ch3 Switch", STA32X_MMUTE, 3, 1, 1),
|
||||
SOC_SINGLE_TLV("Ch1 Volume", STA32X_C1VOL, 0, 0xff, 1, chvol_tlv),
|
||||
SOC_SINGLE_TLV("Ch2 Volume", STA32X_C2VOL, 0, 0xff, 1, chvol_tlv),
|
||||
SOC_SINGLE_TLV("Ch3 Volume", STA32X_C3VOL, 0, 0xff, 1, chvol_tlv),
|
||||
SOC_SINGLE("De-emphasis Filter Switch", STA32X_CONFD, STA32X_CONFD_DEMP_SHIFT, 1, 0),
|
||||
SOC_ENUM("Compressor/Limiter Switch", sta32x_drc_ac_enum),
|
||||
SOC_SINGLE("Miami Mode Switch", STA32X_CONFD, STA32X_CONFD_MME_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Zero Cross Switch", STA32X_CONFE, STA32X_CONFE_ZCE_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Soft Ramp Switch", STA32X_CONFE, STA32X_CONFE_SVE_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Auto-Mute Switch", STA32X_CONFF, STA32X_CONFF_IDE_SHIFT, 1, 0),
|
||||
SOC_ENUM("Automode EQ", sta32x_auto_eq_enum),
|
||||
SOC_ENUM("Automode GC", sta32x_auto_gc_enum),
|
||||
SOC_ENUM("Automode XO", sta32x_auto_xo_enum),
|
||||
SOC_ENUM("Preset EQ", sta32x_preset_eq_enum),
|
||||
SOC_SINGLE("Ch1 Tone Control Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Ch2 Tone Control Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Ch1 EQ Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Ch2 EQ Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0),
|
||||
SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0),
|
||||
SOC_ENUM("Ch1 Limiter Select", sta32x_limiter_ch1_enum),
|
||||
SOC_ENUM("Ch2 Limiter Select", sta32x_limiter_ch2_enum),
|
||||
SOC_ENUM("Ch3 Limiter Select", sta32x_limiter_ch3_enum),
|
||||
SOC_SINGLE_TLV("Bass Tone Control", STA32X_TONE, STA32X_TONE_BTC_SHIFT, 15, 0, tone_tlv),
|
||||
SOC_SINGLE_TLV("Treble Tone Control", STA32X_TONE, STA32X_TONE_TTC_SHIFT, 15, 0, tone_tlv),
|
||||
SOC_ENUM("Limiter1 Attack Rate (dB/ms)", sta32x_limiter1_attack_rate_enum),
|
||||
SOC_ENUM("Limiter2 Attack Rate (dB/ms)", sta32x_limiter2_attack_rate_enum),
|
||||
SOC_ENUM("Limiter1 Release Rate (dB/ms)", sta32x_limiter1_release_rate_enum),
|
||||
SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta32x_limiter1_release_rate_enum),
|
||||
|
||||
/* depending on mode, the attack/release thresholds have
|
||||
* two different enum definitions; provide both
|
||||
*/
|
||||
SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT,
|
||||
16, 0, sta32x_limiter_ac_attack_tlv),
|
||||
SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT,
|
||||
16, 0, sta32x_limiter_ac_attack_tlv),
|
||||
SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT,
|
||||
16, 0, sta32x_limiter_ac_release_tlv),
|
||||
SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT,
|
||||
16, 0, sta32x_limiter_ac_release_tlv),
|
||||
SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT,
|
||||
16, 0, sta32x_limiter_drc_attack_tlv),
|
||||
SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT,
|
||||
16, 0, sta32x_limiter_drc_attack_tlv),
|
||||
SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT,
|
||||
16, 0, sta32x_limiter_drc_release_tlv),
|
||||
SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT,
|
||||
16, 0, sta32x_limiter_drc_release_tlv),
|
||||
|
||||
BIQUAD_COEFS("Ch1 - Biquad 1", 0),
|
||||
BIQUAD_COEFS("Ch1 - Biquad 2", 5),
|
||||
BIQUAD_COEFS("Ch1 - Biquad 3", 10),
|
||||
BIQUAD_COEFS("Ch1 - Biquad 4", 15),
|
||||
BIQUAD_COEFS("Ch2 - Biquad 1", 20),
|
||||
BIQUAD_COEFS("Ch2 - Biquad 2", 25),
|
||||
BIQUAD_COEFS("Ch2 - Biquad 3", 30),
|
||||
BIQUAD_COEFS("Ch2 - Biquad 4", 35),
|
||||
BIQUAD_COEFS("High-pass", 40),
|
||||
BIQUAD_COEFS("Low-pass", 45),
|
||||
SINGLE_COEF("Ch1 - Prescale", 50),
|
||||
SINGLE_COEF("Ch2 - Prescale", 51),
|
||||
SINGLE_COEF("Ch1 - Postscale", 52),
|
||||
SINGLE_COEF("Ch2 - Postscale", 53),
|
||||
SINGLE_COEF("Ch3 - Postscale", 54),
|
||||
SINGLE_COEF("Thermal warning - Postscale", 55),
|
||||
SINGLE_COEF("Ch1 - Mix 1", 56),
|
||||
SINGLE_COEF("Ch1 - Mix 2", 57),
|
||||
SINGLE_COEF("Ch2 - Mix 1", 58),
|
||||
SINGLE_COEF("Ch2 - Mix 2", 59),
|
||||
SINGLE_COEF("Ch3 - Mix 1", 60),
|
||||
SINGLE_COEF("Ch3 - Mix 2", 61),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget sta32x_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_OUTPUT("LEFT"),
|
||||
SND_SOC_DAPM_OUTPUT("RIGHT"),
|
||||
SND_SOC_DAPM_OUTPUT("SUB"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route sta32x_dapm_routes[] = {
|
||||
{ "LEFT", NULL, "DAC" },
|
||||
{ "RIGHT", NULL, "DAC" },
|
||||
{ "SUB", NULL, "DAC" },
|
||||
};
|
||||
|
||||
/* MCLK interpolation ratio per fs */
|
||||
static struct {
|
||||
int fs;
|
||||
int ir;
|
||||
} interpolation_ratios[] = {
|
||||
{ 32000, 0 },
|
||||
{ 44100, 0 },
|
||||
{ 48000, 0 },
|
||||
{ 88200, 1 },
|
||||
{ 96000, 1 },
|
||||
{ 176400, 2 },
|
||||
{ 192000, 2 },
|
||||
};
|
||||
|
||||
/* MCLK to fs clock ratios */
|
||||
static struct {
|
||||
int ratio;
|
||||
int mcs;
|
||||
} mclk_ratios[3][7] = {
|
||||
{ { 768, 0 }, { 512, 1 }, { 384, 2 }, { 256, 3 },
|
||||
{ 128, 4 }, { 576, 5 }, { 0, 0 } },
|
||||
{ { 384, 2 }, { 256, 3 }, { 192, 4 }, { 128, 5 }, {64, 0 }, { 0, 0 } },
|
||||
{ { 384, 2 }, { 256, 3 }, { 192, 4 }, { 128, 5 }, {64, 0 }, { 0, 0 } },
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* sta32x_set_dai_sysclk - configure MCLK
|
||||
* @codec_dai: the codec DAI
|
||||
* @clk_id: the clock ID (ignored)
|
||||
* @freq: the MCLK input frequency
|
||||
* @dir: the clock direction (ignored)
|
||||
*
|
||||
* The value of MCLK is used to determine which sample rates are supported
|
||||
* by the STA32X, based on the mclk_ratios table.
|
||||
*
|
||||
* This function must be called by the machine driver's 'startup' function,
|
||||
* otherwise the list of supported sample rates will not be available in
|
||||
* time for ALSA.
|
||||
*
|
||||
* For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause
|
||||
* theoretically possible sample rates to be enabled. Call it again with a
|
||||
* proper value set one the external clock is set (most probably you would do
|
||||
* that from a machine's driver 'hw_param' hook.
|
||||
*/
|
||||
static int sta32x_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 sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
|
||||
int i, j, ir, fs;
|
||||
unsigned int rates = 0;
|
||||
unsigned int rate_min = -1;
|
||||
unsigned int rate_max = 0;
|
||||
|
||||
pr_debug("mclk=%u\n", freq);
|
||||
sta32x->mclk = freq;
|
||||
|
||||
if (sta32x->mclk) {
|
||||
for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++) {
|
||||
ir = interpolation_ratios[i].ir;
|
||||
fs = interpolation_ratios[i].fs;
|
||||
for (j = 0; mclk_ratios[ir][j].ratio; j++) {
|
||||
if (mclk_ratios[ir][j].ratio * fs == freq) {
|
||||
rates |= snd_pcm_rate_to_rate_bit(fs);
|
||||
if (fs < rate_min)
|
||||
rate_min = fs;
|
||||
if (fs > rate_max)
|
||||
rate_max = fs;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* FIXME: soc should support a rate list */
|
||||
rates &= ~SNDRV_PCM_RATE_KNOT;
|
||||
|
||||
if (!rates) {
|
||||
dev_err(codec->dev, "could not find a valid sample rate\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
/* enable all possible rates */
|
||||
rates = STA32X_RATES;
|
||||
rate_min = 32000;
|
||||
rate_max = 192000;
|
||||
}
|
||||
|
||||
codec_dai->driver->playback.rates = rates;
|
||||
codec_dai->driver->playback.rate_min = rate_min;
|
||||
codec_dai->driver->playback.rate_max = rate_max;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sta32x_set_dai_fmt - configure the codec for the selected audio format
|
||||
* @codec_dai: the codec DAI
|
||||
* @fmt: a SND_SOC_DAIFMT_x value indicating the data format
|
||||
*
|
||||
* This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
|
||||
* codec accordingly.
|
||||
*/
|
||||
static int sta32x_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
|
||||
u8 confb = snd_soc_read(codec, STA32X_CONFB);
|
||||
|
||||
pr_debug("\n");
|
||||
confb &= ~(STA32X_CONFB_C1IM | STA32X_CONFB_C2IM);
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
sta32x->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
confb |= STA32X_CONFB_C2IM;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
confb |= STA32X_CONFB_C1IM;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_write(codec, STA32X_CONFB, confb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sta32x_hw_params - program the STA32X with the given hardware parameters.
|
||||
* @substream: the audio stream
|
||||
* @params: the hardware parameters to set
|
||||
* @dai: the SOC DAI (ignored)
|
||||
*
|
||||
* This function programs the hardware with the values provided.
|
||||
* Specifically, the sample rate and the data format.
|
||||
*/
|
||||
static int sta32x_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_codec *codec = rtd->codec;
|
||||
struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int rate;
|
||||
int i, mcs = -1, ir = -1;
|
||||
u8 confa, confb;
|
||||
|
||||
rate = params_rate(params);
|
||||
pr_debug("rate: %u\n", rate);
|
||||
for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++)
|
||||
if (interpolation_ratios[i].fs == rate)
|
||||
ir = interpolation_ratios[i].ir;
|
||||
if (ir < 0)
|
||||
return -EINVAL;
|
||||
for (i = 0; mclk_ratios[ir][i].ratio; i++)
|
||||
if (mclk_ratios[ir][i].ratio * rate == sta32x->mclk)
|
||||
mcs = mclk_ratios[ir][i].mcs;
|
||||
if (mcs < 0)
|
||||
return -EINVAL;
|
||||
|
||||
confa = snd_soc_read(codec, STA32X_CONFA);
|
||||
confa &= ~(STA32X_CONFA_MCS_MASK | STA32X_CONFA_IR_MASK);
|
||||
confa |= (ir << STA32X_CONFA_IR_SHIFT) | (mcs << STA32X_CONFA_MCS_SHIFT);
|
||||
|
||||
confb = snd_soc_read(codec, STA32X_CONFB);
|
||||
confb &= ~(STA32X_CONFB_SAI_MASK | STA32X_CONFB_SAIFB);
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
case SNDRV_PCM_FORMAT_S24_BE:
|
||||
case SNDRV_PCM_FORMAT_S24_3LE:
|
||||
case SNDRV_PCM_FORMAT_S24_3BE:
|
||||
pr_debug("24bit\n");
|
||||
/* fall through */
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
case SNDRV_PCM_FORMAT_S32_BE:
|
||||
pr_debug("24bit or 32bit\n");
|
||||
switch (sta32x->format) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
confb |= 0x0;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
confb |= 0x1;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
confb |= 0x2;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
case SNDRV_PCM_FORMAT_S20_3BE:
|
||||
pr_debug("20bit\n");
|
||||
switch (sta32x->format) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
confb |= 0x4;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
confb |= 0x5;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
confb |= 0x6;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S18_3LE:
|
||||
case SNDRV_PCM_FORMAT_S18_3BE:
|
||||
pr_debug("18bit\n");
|
||||
switch (sta32x->format) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
confb |= 0x8;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
confb |= 0x9;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
confb |= 0xa;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
case SNDRV_PCM_FORMAT_S16_BE:
|
||||
pr_debug("16bit\n");
|
||||
switch (sta32x->format) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
confb |= 0x0;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
confb |= 0xd;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
confb |= 0xe;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_write(codec, STA32X_CONFA, confa);
|
||||
snd_soc_write(codec, STA32X_CONFB, confb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sta32x_set_bias_level - DAPM callback
|
||||
* @codec: the codec device
|
||||
* @level: DAPM power level
|
||||
*
|
||||
* This is called by ALSA to put the codec into low power mode
|
||||
* or to wake it up. If the codec is powered off completely
|
||||
* all registers must be restored after power on.
|
||||
*/
|
||||
static int sta32x_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
int ret;
|
||||
struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
pr_debug("level = %d\n", level);
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* Full power on */
|
||||
snd_soc_update_bits(codec, STA32X_CONFF,
|
||||
STA32X_CONFF_PWDN | STA32X_CONFF_EAPD,
|
||||
STA32X_CONFF_PWDN | STA32X_CONFF_EAPD);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies),
|
||||
sta32x->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev,
|
||||
"Failed to enable supplies: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
snd_soc_cache_sync(codec);
|
||||
}
|
||||
|
||||
/* Power up to mute */
|
||||
/* FIXME */
|
||||
snd_soc_update_bits(codec, STA32X_CONFF,
|
||||
STA32X_CONFF_PWDN | STA32X_CONFF_EAPD,
|
||||
STA32X_CONFF_PWDN | STA32X_CONFF_EAPD);
|
||||
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* The chip runs through the power down sequence for us. */
|
||||
snd_soc_update_bits(codec, STA32X_CONFF,
|
||||
STA32X_CONFF_PWDN | STA32X_CONFF_EAPD,
|
||||
STA32X_CONFF_PWDN);
|
||||
msleep(300);
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies),
|
||||
sta32x->supplies);
|
||||
break;
|
||||
}
|
||||
codec->dapm.bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops sta32x_dai_ops = {
|
||||
.hw_params = sta32x_hw_params,
|
||||
.set_sysclk = sta32x_set_dai_sysclk,
|
||||
.set_fmt = sta32x_set_dai_fmt,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver sta32x_dai = {
|
||||
.name = "STA32X",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = STA32X_RATES,
|
||||
.formats = STA32X_FORMATS,
|
||||
},
|
||||
.ops = &sta32x_dai_ops,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int sta32x_suspend(struct snd_soc_codec *codec, pm_message_t state)
|
||||
{
|
||||
sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sta32x_resume(struct snd_soc_codec *codec)
|
||||
{
|
||||
sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define sta32x_suspend NULL
|
||||
#define sta32x_resume NULL
|
||||
#endif
|
||||
|
||||
static int sta32x_probe(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
|
||||
int i, ret = 0;
|
||||
|
||||
sta32x->codec = codec;
|
||||
|
||||
/* regulators */
|
||||
for (i = 0; i < ARRAY_SIZE(sta32x->supplies); i++)
|
||||
sta32x->supplies[i].supply = sta32x_supply_names[i];
|
||||
|
||||
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sta32x->supplies),
|
||||
sta32x->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies),
|
||||
sta32x->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
|
||||
goto err_get;
|
||||
}
|
||||
|
||||
/* Tell ASoC what kind of I/O to use to read the registers. ASoC will
|
||||
* then do the I2C transactions itself.
|
||||
*/
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to set cache I/O (ret=%i)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* read reg reset values into cache */
|
||||
for (i = 0; i < STA32X_REGISTER_COUNT; i++)
|
||||
snd_soc_cache_write(codec, i, sta32x_regs[i]);
|
||||
|
||||
/* preserve reset values of reserved register bits */
|
||||
snd_soc_cache_write(codec, STA32X_CONFC,
|
||||
codec->hw_read(codec, STA32X_CONFC));
|
||||
snd_soc_cache_write(codec, STA32X_CONFE,
|
||||
codec->hw_read(codec, STA32X_CONFE));
|
||||
snd_soc_cache_write(codec, STA32X_CONFF,
|
||||
codec->hw_read(codec, STA32X_CONFF));
|
||||
snd_soc_cache_write(codec, STA32X_MMUTE,
|
||||
codec->hw_read(codec, STA32X_MMUTE));
|
||||
snd_soc_cache_write(codec, STA32X_AUTO1,
|
||||
codec->hw_read(codec, STA32X_AUTO1));
|
||||
snd_soc_cache_write(codec, STA32X_AUTO3,
|
||||
codec->hw_read(codec, STA32X_AUTO3));
|
||||
snd_soc_cache_write(codec, STA32X_C3CFG,
|
||||
codec->hw_read(codec, STA32X_C3CFG));
|
||||
|
||||
/* FIXME enable thermal warning adjustment and recovery */
|
||||
snd_soc_update_bits(codec, STA32X_CONFA,
|
||||
STA32X_CONFA_TWAB | STA32X_CONFA_TWRB, 0);
|
||||
|
||||
/* FIXME select 2.1 mode */
|
||||
snd_soc_update_bits(codec, STA32X_CONFF,
|
||||
STA32X_CONFF_OCFG_MASK,
|
||||
1 << STA32X_CONFF_OCFG_SHIFT);
|
||||
|
||||
/* FIXME channel to output mapping */
|
||||
snd_soc_update_bits(codec, STA32X_C1CFG,
|
||||
STA32X_CxCFG_OM_MASK,
|
||||
0 << STA32X_CxCFG_OM_SHIFT);
|
||||
snd_soc_update_bits(codec, STA32X_C2CFG,
|
||||
STA32X_CxCFG_OM_MASK,
|
||||
1 << STA32X_CxCFG_OM_SHIFT);
|
||||
snd_soc_update_bits(codec, STA32X_C3CFG,
|
||||
STA32X_CxCFG_OM_MASK,
|
||||
2 << STA32X_CxCFG_OM_SHIFT);
|
||||
|
||||
sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
/* Bias level configuration will have done an extra enable */
|
||||
regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
|
||||
|
||||
return 0;
|
||||
|
||||
err_get:
|
||||
regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sta32x_remove(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
|
||||
regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sta32x_reg_is_volatile(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case STA32X_CONFA ... STA32X_L2ATRT:
|
||||
case STA32X_MPCC1 ... STA32X_FDRC2:
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct snd_soc_codec_driver sta32x_codec = {
|
||||
.probe = sta32x_probe,
|
||||
.remove = sta32x_remove,
|
||||
.suspend = sta32x_suspend,
|
||||
.resume = sta32x_resume,
|
||||
.reg_cache_size = STA32X_REGISTER_COUNT,
|
||||
.reg_word_size = sizeof(u8),
|
||||
.volatile_register = sta32x_reg_is_volatile,
|
||||
.set_bias_level = sta32x_set_bias_level,
|
||||
.controls = sta32x_snd_controls,
|
||||
.num_controls = ARRAY_SIZE(sta32x_snd_controls),
|
||||
.dapm_widgets = sta32x_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(sta32x_dapm_widgets),
|
||||
.dapm_routes = sta32x_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(sta32x_dapm_routes),
|
||||
};
|
||||
|
||||
static __devinit int sta32x_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct sta32x_priv *sta32x;
|
||||
int ret;
|
||||
|
||||
sta32x = kzalloc(sizeof(struct sta32x_priv), GFP_KERNEL);
|
||||
if (!sta32x)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(i2c, sta32x);
|
||||
|
||||
ret = snd_soc_register_codec(&i2c->dev, &sta32x_codec, &sta32x_dai, 1);
|
||||
if (ret != 0) {
|
||||
dev_err(&i2c->dev, "Failed to register codec (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __devexit int sta32x_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct sta32x_priv *sta32x = i2c_get_clientdata(client);
|
||||
struct snd_soc_codec *codec = sta32x->codec;
|
||||
|
||||
if (codec)
|
||||
sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
|
||||
regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
|
||||
|
||||
if (codec) {
|
||||
snd_soc_unregister_codec(&client->dev);
|
||||
snd_soc_codec_set_drvdata(codec, NULL);
|
||||
}
|
||||
|
||||
kfree(sta32x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id sta32x_i2c_id[] = {
|
||||
{ "sta326", 0 },
|
||||
{ "sta328", 0 },
|
||||
{ "sta329", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, sta32x_i2c_id);
|
||||
|
||||
static struct i2c_driver sta32x_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "sta32x",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sta32x_i2c_probe,
|
||||
.remove = __devexit_p(sta32x_i2c_remove),
|
||||
.id_table = sta32x_i2c_id,
|
||||
};
|
||||
|
||||
static int __init sta32x_init(void)
|
||||
{
|
||||
return i2c_add_driver(&sta32x_i2c_driver);
|
||||
}
|
||||
module_init(sta32x_init);
|
||||
|
||||
static void __exit sta32x_exit(void)
|
||||
{
|
||||
i2c_del_driver(&sta32x_i2c_driver);
|
||||
}
|
||||
module_exit(sta32x_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC STA32X driver");
|
||||
MODULE_AUTHOR("Johannes Stezenbach <js@sig21.net>");
|
||||
MODULE_LICENSE("GPL");
|
210
sound/soc/codecs/sta32x.h
Normal file
210
sound/soc/codecs/sta32x.h
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system
|
||||
*
|
||||
* Copyright: 2011 Raumfeld GmbH
|
||||
* Author: Johannes Stezenbach <js@sig21.net>
|
||||
*
|
||||
* based on code from:
|
||||
* Wolfson Microelectronics PLC.
|
||||
* Mark Brown <broonie@opensource.wolfsonmicro.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 _ASOC_STA_32X_H
|
||||
#define _ASOC_STA_32X_H
|
||||
|
||||
/* STA326 register addresses */
|
||||
|
||||
#define STA32X_REGISTER_COUNT 0x2d
|
||||
|
||||
#define STA32X_CONFA 0x00
|
||||
#define STA32X_CONFB 0x01
|
||||
#define STA32X_CONFC 0x02
|
||||
#define STA32X_CONFD 0x03
|
||||
#define STA32X_CONFE 0x04
|
||||
#define STA32X_CONFF 0x05
|
||||
#define STA32X_MMUTE 0x06
|
||||
#define STA32X_MVOL 0x07
|
||||
#define STA32X_C1VOL 0x08
|
||||
#define STA32X_C2VOL 0x09
|
||||
#define STA32X_C3VOL 0x0a
|
||||
#define STA32X_AUTO1 0x0b
|
||||
#define STA32X_AUTO2 0x0c
|
||||
#define STA32X_AUTO3 0x0d
|
||||
#define STA32X_C1CFG 0x0e
|
||||
#define STA32X_C2CFG 0x0f
|
||||
#define STA32X_C3CFG 0x10
|
||||
#define STA32X_TONE 0x11
|
||||
#define STA32X_L1AR 0x12
|
||||
#define STA32X_L1ATRT 0x13
|
||||
#define STA32X_L2AR 0x14
|
||||
#define STA32X_L2ATRT 0x15
|
||||
#define STA32X_CFADDR2 0x16
|
||||
#define STA32X_B1CF1 0x17
|
||||
#define STA32X_B1CF2 0x18
|
||||
#define STA32X_B1CF3 0x19
|
||||
#define STA32X_B2CF1 0x1a
|
||||
#define STA32X_B2CF2 0x1b
|
||||
#define STA32X_B2CF3 0x1c
|
||||
#define STA32X_A1CF1 0x1d
|
||||
#define STA32X_A1CF2 0x1e
|
||||
#define STA32X_A1CF3 0x1f
|
||||
#define STA32X_A2CF1 0x20
|
||||
#define STA32X_A2CF2 0x21
|
||||
#define STA32X_A2CF3 0x22
|
||||
#define STA32X_B0CF1 0x23
|
||||
#define STA32X_B0CF2 0x24
|
||||
#define STA32X_B0CF3 0x25
|
||||
#define STA32X_CFUD 0x26
|
||||
#define STA32X_MPCC1 0x27
|
||||
#define STA32X_MPCC2 0x28
|
||||
/* Reserved 0x29 */
|
||||
/* Reserved 0x2a */
|
||||
#define STA32X_Reserved 0x2a
|
||||
#define STA32X_FDRC1 0x2b
|
||||
#define STA32X_FDRC2 0x2c
|
||||
/* Reserved 0x2d */
|
||||
|
||||
|
||||
/* STA326 register field definitions */
|
||||
|
||||
/* 0x00 CONFA */
|
||||
#define STA32X_CONFA_MCS_MASK 0x03
|
||||
#define STA32X_CONFA_MCS_SHIFT 0
|
||||
#define STA32X_CONFA_IR_MASK 0x18
|
||||
#define STA32X_CONFA_IR_SHIFT 3
|
||||
#define STA32X_CONFA_TWRB 0x20
|
||||
#define STA32X_CONFA_TWAB 0x40
|
||||
#define STA32X_CONFA_FDRB 0x80
|
||||
|
||||
/* 0x01 CONFB */
|
||||
#define STA32X_CONFB_SAI_MASK 0x0f
|
||||
#define STA32X_CONFB_SAI_SHIFT 0
|
||||
#define STA32X_CONFB_SAIFB 0x10
|
||||
#define STA32X_CONFB_DSCKE 0x20
|
||||
#define STA32X_CONFB_C1IM 0x40
|
||||
#define STA32X_CONFB_C2IM 0x80
|
||||
|
||||
/* 0x02 CONFC */
|
||||
#define STA32X_CONFC_OM_MASK 0x03
|
||||
#define STA32X_CONFC_OM_SHIFT 0
|
||||
#define STA32X_CONFC_CSZ_MASK 0x7c
|
||||
#define STA32X_CONFC_CSZ_SHIFT 2
|
||||
|
||||
/* 0x03 CONFD */
|
||||
#define STA32X_CONFD_HPB 0x01
|
||||
#define STA32X_CONFD_HPB_SHIFT 0
|
||||
#define STA32X_CONFD_DEMP 0x02
|
||||
#define STA32X_CONFD_DEMP_SHIFT 1
|
||||
#define STA32X_CONFD_DSPB 0x04
|
||||
#define STA32X_CONFD_DSPB_SHIFT 2
|
||||
#define STA32X_CONFD_PSL 0x08
|
||||
#define STA32X_CONFD_PSL_SHIFT 3
|
||||
#define STA32X_CONFD_BQL 0x10
|
||||
#define STA32X_CONFD_BQL_SHIFT 4
|
||||
#define STA32X_CONFD_DRC 0x20
|
||||
#define STA32X_CONFD_DRC_SHIFT 5
|
||||
#define STA32X_CONFD_ZDE 0x40
|
||||
#define STA32X_CONFD_ZDE_SHIFT 6
|
||||
#define STA32X_CONFD_MME 0x80
|
||||
#define STA32X_CONFD_MME_SHIFT 7
|
||||
|
||||
/* 0x04 CONFE */
|
||||
#define STA32X_CONFE_MPCV 0x01
|
||||
#define STA32X_CONFE_MPCV_SHIFT 0
|
||||
#define STA32X_CONFE_MPC 0x02
|
||||
#define STA32X_CONFE_MPC_SHIFT 1
|
||||
#define STA32X_CONFE_AME 0x08
|
||||
#define STA32X_CONFE_AME_SHIFT 3
|
||||
#define STA32X_CONFE_PWMS 0x10
|
||||
#define STA32X_CONFE_PWMS_SHIFT 4
|
||||
#define STA32X_CONFE_ZCE 0x40
|
||||
#define STA32X_CONFE_ZCE_SHIFT 6
|
||||
#define STA32X_CONFE_SVE 0x80
|
||||
#define STA32X_CONFE_SVE_SHIFT 7
|
||||
|
||||
/* 0x05 CONFF */
|
||||
#define STA32X_CONFF_OCFG_MASK 0x03
|
||||
#define STA32X_CONFF_OCFG_SHIFT 0
|
||||
#define STA32X_CONFF_IDE 0x04
|
||||
#define STA32X_CONFF_IDE_SHIFT 3
|
||||
#define STA32X_CONFF_BCLE 0x08
|
||||
#define STA32X_CONFF_ECLE 0x20
|
||||
#define STA32X_CONFF_PWDN 0x40
|
||||
#define STA32X_CONFF_EAPD 0x80
|
||||
|
||||
/* 0x06 MMUTE */
|
||||
#define STA32X_MMUTE_MMUTE 0x01
|
||||
|
||||
/* 0x0b AUTO1 */
|
||||
#define STA32X_AUTO1_AMEQ_MASK 0x03
|
||||
#define STA32X_AUTO1_AMEQ_SHIFT 0
|
||||
#define STA32X_AUTO1_AMV_MASK 0xc0
|
||||
#define STA32X_AUTO1_AMV_SHIFT 2
|
||||
#define STA32X_AUTO1_AMGC_MASK 0x30
|
||||
#define STA32X_AUTO1_AMGC_SHIFT 4
|
||||
#define STA32X_AUTO1_AMPS 0x80
|
||||
|
||||
/* 0x0c AUTO2 */
|
||||
#define STA32X_AUTO2_AMAME 0x01
|
||||
#define STA32X_AUTO2_AMAM_MASK 0x0e
|
||||
#define STA32X_AUTO2_AMAM_SHIFT 1
|
||||
#define STA32X_AUTO2_XO_MASK 0xf0
|
||||
#define STA32X_AUTO2_XO_SHIFT 4
|
||||
|
||||
/* 0x0d AUTO3 */
|
||||
#define STA32X_AUTO3_PEQ_MASK 0x1f
|
||||
#define STA32X_AUTO3_PEQ_SHIFT 0
|
||||
|
||||
/* 0x0e 0x0f 0x10 CxCFG */
|
||||
#define STA32X_CxCFG_TCB 0x01 /* only C1 and C2 */
|
||||
#define STA32X_CxCFG_TCB_SHIFT 0
|
||||
#define STA32X_CxCFG_EQBP 0x02 /* only C1 and C2 */
|
||||
#define STA32X_CxCFG_EQBP_SHIFT 1
|
||||
#define STA32X_CxCFG_VBP 0x03
|
||||
#define STA32X_CxCFG_VBP_SHIFT 2
|
||||
#define STA32X_CxCFG_BO 0x04
|
||||
#define STA32X_CxCFG_LS_MASK 0x30
|
||||
#define STA32X_CxCFG_LS_SHIFT 4
|
||||
#define STA32X_CxCFG_OM_MASK 0xc0
|
||||
#define STA32X_CxCFG_OM_SHIFT 6
|
||||
|
||||
/* 0x11 TONE */
|
||||
#define STA32X_TONE_BTC_SHIFT 0
|
||||
#define STA32X_TONE_TTC_SHIFT 4
|
||||
|
||||
/* 0x12 0x13 0x14 0x15 limiter attack/release */
|
||||
#define STA32X_LxA_SHIFT 0
|
||||
#define STA32X_LxR_SHIFT 4
|
||||
|
||||
/* 0x26 CFUD */
|
||||
#define STA32X_CFUD_W1 0x01
|
||||
#define STA32X_CFUD_WA 0x02
|
||||
#define STA32X_CFUD_R1 0x04
|
||||
#define STA32X_CFUD_RA 0x08
|
||||
|
||||
|
||||
/* biquad filter coefficient table offsets */
|
||||
#define STA32X_C1_BQ_BASE 0
|
||||
#define STA32X_C2_BQ_BASE 20
|
||||
#define STA32X_CH_BQ_NUM 4
|
||||
#define STA32X_BQ_NUM_COEF 5
|
||||
#define STA32X_XO_HP_BQ_BASE 40
|
||||
#define STA32X_XO_LP_BQ_BASE 45
|
||||
#define STA32X_C1_PRESCALE 50
|
||||
#define STA32X_C2_PRESCALE 51
|
||||
#define STA32X_C1_POSTSCALE 52
|
||||
#define STA32X_C2_POSTSCALE 53
|
||||
#define STA32X_C3_POSTSCALE 54
|
||||
#define STA32X_TW_POSTSCALE 55
|
||||
#define STA32X_C1_MIX1 56
|
||||
#define STA32X_C1_MIX2 57
|
||||
#define STA32X_C2_MIX1 58
|
||||
#define STA32X_C2_MIX2 59
|
||||
#define STA32X_C3_MIX1 60
|
||||
#define STA32X_C3_MIX2 61
|
||||
|
||||
#endif /* _ASOC_STA_32X_H */
|
@ -226,11 +226,13 @@ static const char *aic3x_adc_hpf[] =
|
||||
#define RDAC_ENUM 1
|
||||
#define LHPCOM_ENUM 2
|
||||
#define RHPCOM_ENUM 3
|
||||
#define LINE1L_ENUM 4
|
||||
#define LINE1R_ENUM 5
|
||||
#define LINE2L_ENUM 6
|
||||
#define LINE2R_ENUM 7
|
||||
#define ADC_HPF_ENUM 8
|
||||
#define LINE1L_2_L_ENUM 4
|
||||
#define LINE1L_2_R_ENUM 5
|
||||
#define LINE1R_2_L_ENUM 6
|
||||
#define LINE1R_2_R_ENUM 7
|
||||
#define LINE2L_ENUM 8
|
||||
#define LINE2R_ENUM 9
|
||||
#define ADC_HPF_ENUM 10
|
||||
|
||||
static const struct soc_enum aic3x_enum[] = {
|
||||
SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux),
|
||||
@ -238,6 +240,8 @@ static const struct soc_enum aic3x_enum[] = {
|
||||
SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux),
|
||||
SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux),
|
||||
SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
|
||||
SOC_ENUM_SINGLE(LINE1L_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
|
||||
SOC_ENUM_SINGLE(LINE1R_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
|
||||
SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
|
||||
SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
|
||||
SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
|
||||
@ -490,12 +494,16 @@ static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = {
|
||||
};
|
||||
|
||||
/* Left Line1 Mux */
|
||||
static const struct snd_kcontrol_new aic3x_left_line1_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]);
|
||||
static const struct snd_kcontrol_new aic3x_left_line1l_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_L_ENUM]);
|
||||
static const struct snd_kcontrol_new aic3x_right_line1l_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_R_ENUM]);
|
||||
|
||||
/* Right Line1 Mux */
|
||||
static const struct snd_kcontrol_new aic3x_right_line1_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]);
|
||||
static const struct snd_kcontrol_new aic3x_right_line1r_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_R_ENUM]);
|
||||
static const struct snd_kcontrol_new aic3x_left_line1r_mux_controls =
|
||||
SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_L_ENUM]);
|
||||
|
||||
/* Left Line2 Mux */
|
||||
static const struct snd_kcontrol_new aic3x_left_line2_mux_controls =
|
||||
@ -535,9 +543,9 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
|
||||
&aic3x_left_pga_mixer_controls[0],
|
||||
ARRAY_SIZE(aic3x_left_pga_mixer_controls)),
|
||||
SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0,
|
||||
&aic3x_left_line1_mux_controls),
|
||||
&aic3x_left_line1l_mux_controls),
|
||||
SND_SOC_DAPM_MUX("Left Line1R Mux", SND_SOC_NOPM, 0, 0,
|
||||
&aic3x_left_line1_mux_controls),
|
||||
&aic3x_left_line1r_mux_controls),
|
||||
SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0,
|
||||
&aic3x_left_line2_mux_controls),
|
||||
|
||||
@ -548,9 +556,9 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
|
||||
&aic3x_right_pga_mixer_controls[0],
|
||||
ARRAY_SIZE(aic3x_right_pga_mixer_controls)),
|
||||
SND_SOC_DAPM_MUX("Right Line1L Mux", SND_SOC_NOPM, 0, 0,
|
||||
&aic3x_right_line1_mux_controls),
|
||||
&aic3x_right_line1l_mux_controls),
|
||||
SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0,
|
||||
&aic3x_right_line1_mux_controls),
|
||||
&aic3x_right_line1r_mux_controls),
|
||||
SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0,
|
||||
&aic3x_right_line2_mux_controls),
|
||||
|
||||
|
@ -954,9 +954,9 @@ static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0);
|
||||
|
||||
/*
|
||||
* MICGAIN volume control:
|
||||
* from -6 to 30 dB in 6 dB steps
|
||||
* from 6 to 30 dB in 6 dB steps
|
||||
*/
|
||||
static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -600, 600, 0);
|
||||
static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0);
|
||||
|
||||
/*
|
||||
* AFMGAIN volume control:
|
||||
|
80
sound/soc/codecs/wm8782.c
Normal file
80
sound/soc/codecs/wm8782.c
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* sound/soc/codecs/wm8782.c
|
||||
* simple, strap-pin configured 24bit 2ch ADC
|
||||
*
|
||||
* Copyright: 2011 Raumfeld GmbH
|
||||
* Author: Johannes Stezenbach <js@sig21.net>
|
||||
*
|
||||
* based on ad73311.c
|
||||
* Copyright: Analog Device Inc.
|
||||
* Author: Cliff Cai <cliff.cai@analog.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/ac97_codec.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
static struct snd_soc_dai_driver wm8782_dai = {
|
||||
.name = "wm8782",
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
/* For configurations with FSAMPEN=0 */
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_codec_driver soc_codec_dev_wm8782;
|
||||
|
||||
static __devinit int wm8782_probe(struct platform_device *pdev)
|
||||
{
|
||||
return snd_soc_register_codec(&pdev->dev,
|
||||
&soc_codec_dev_wm8782, &wm8782_dai, 1);
|
||||
}
|
||||
|
||||
static int __devexit wm8782_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_codec(&pdev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver wm8782_codec_driver = {
|
||||
.driver = {
|
||||
.name = "wm8782",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8782_probe,
|
||||
.remove = wm8782_remove,
|
||||
};
|
||||
|
||||
static int __init wm8782_init(void)
|
||||
{
|
||||
return platform_driver_register(&wm8782_codec_driver);
|
||||
}
|
||||
module_init(wm8782_init);
|
||||
|
||||
static void __exit wm8782_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&wm8782_codec_driver);
|
||||
}
|
||||
module_exit(wm8782_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC WM8782 driver");
|
||||
MODULE_AUTHOR("Johannes Stezenbach <js@sig21.net>");
|
||||
MODULE_LICENSE("GPL");
|
@ -1167,6 +1167,7 @@ static int wm8900_resume(struct snd_soc_codec *codec)
|
||||
ret = wm8900_set_fll(codec, 0, fll_in, fll_out);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to restart FLL\n");
|
||||
kfree(cache);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -2560,6 +2560,7 @@ static __devexit int wm8904_i2c_remove(struct i2c_client *client)
|
||||
static const struct i2c_device_id wm8904_i2c_id[] = {
|
||||
{ "wm8904", WM8904 },
|
||||
{ "wm8912", WM8912 },
|
||||
{ "wm8918", WM8904 }, /* Actually a subset, updates to follow */
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id);
|
||||
|
@ -41,14 +41,12 @@
|
||||
#define HPOUT2L 4
|
||||
#define HPOUT2R 8
|
||||
|
||||
#define WM8915_NUM_SUPPLIES 6
|
||||
#define WM8915_NUM_SUPPLIES 4
|
||||
static const char *wm8915_supply_names[WM8915_NUM_SUPPLIES] = {
|
||||
"DCVDD",
|
||||
"DBVDD",
|
||||
"AVDD1",
|
||||
"AVDD2",
|
||||
"CPVDD",
|
||||
"MICVDD",
|
||||
};
|
||||
|
||||
struct wm8915_priv {
|
||||
@ -57,6 +55,7 @@ struct wm8915_priv {
|
||||
int ldo1ena;
|
||||
|
||||
int sysclk;
|
||||
int sysclk_src;
|
||||
|
||||
int fll_src;
|
||||
int fll_fref;
|
||||
@ -76,6 +75,7 @@ struct wm8915_priv {
|
||||
struct wm8915_pdata pdata;
|
||||
|
||||
int rx_rate[WM8915_AIFS];
|
||||
int bclk_rate[WM8915_AIFS];
|
||||
|
||||
/* Platform dependant ReTune mobile configuration */
|
||||
int num_retune_mobile_texts;
|
||||
@ -113,8 +113,6 @@ WM8915_REGULATOR_EVENT(0)
|
||||
WM8915_REGULATOR_EVENT(1)
|
||||
WM8915_REGULATOR_EVENT(2)
|
||||
WM8915_REGULATOR_EVENT(3)
|
||||
WM8915_REGULATOR_EVENT(4)
|
||||
WM8915_REGULATOR_EVENT(5)
|
||||
|
||||
static const u16 wm8915_reg[WM8915_MAX_REGISTER] = {
|
||||
[WM8915_SOFTWARE_RESET] = 0x8915,
|
||||
@ -1565,6 +1563,50 @@ static int wm8915_reset(struct snd_soc_codec *codec)
|
||||
return snd_soc_write(codec, WM8915_SOFTWARE_RESET, 0x8915);
|
||||
}
|
||||
|
||||
static const int bclk_divs[] = {
|
||||
1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96
|
||||
};
|
||||
|
||||
static void wm8915_update_bclk(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
|
||||
int aif, best, cur_val, bclk_rate, bclk_reg, i;
|
||||
|
||||
/* Don't bother if we're in a low frequency idle mode that
|
||||
* can't support audio.
|
||||
*/
|
||||
if (wm8915->sysclk < 64000)
|
||||
return;
|
||||
|
||||
for (aif = 0; aif < WM8915_AIFS; aif++) {
|
||||
switch (aif) {
|
||||
case 0:
|
||||
bclk_reg = WM8915_AIF1_BCLK;
|
||||
break;
|
||||
case 1:
|
||||
bclk_reg = WM8915_AIF2_BCLK;
|
||||
break;
|
||||
}
|
||||
|
||||
bclk_rate = wm8915->bclk_rate[aif];
|
||||
|
||||
/* Pick a divisor for BCLK as close as we can get to ideal */
|
||||
best = 0;
|
||||
for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
|
||||
cur_val = (wm8915->sysclk / bclk_divs[i]) - bclk_rate;
|
||||
if (cur_val < 0) /* BCLK table is sorted */
|
||||
break;
|
||||
best = i;
|
||||
}
|
||||
bclk_rate = wm8915->sysclk / bclk_divs[best];
|
||||
dev_dbg(codec->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
|
||||
bclk_divs[best], bclk_rate);
|
||||
|
||||
snd_soc_update_bits(codec, bclk_reg,
|
||||
WM8915_AIF1_BCLK_DIV_MASK, best);
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8915_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
@ -1717,10 +1759,6 @@ static int wm8915_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const int bclk_divs[] = {
|
||||
1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96
|
||||
};
|
||||
|
||||
static const int dsp_divs[] = {
|
||||
48000, 32000, 16000, 8000
|
||||
};
|
||||
@ -1731,17 +1769,11 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
|
||||
int bits, i, bclk_rate, best, cur_val;
|
||||
int bits, i, bclk_rate;
|
||||
int aifdata = 0;
|
||||
int bclk = 0;
|
||||
int lrclk = 0;
|
||||
int dsp = 0;
|
||||
int aifdata_reg, bclk_reg, lrclk_reg, dsp_shift;
|
||||
|
||||
if (!wm8915->sysclk) {
|
||||
dev_err(codec->dev, "SYSCLK not configured\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
int aifdata_reg, lrclk_reg, dsp_shift;
|
||||
|
||||
switch (dai->id) {
|
||||
case 0:
|
||||
@ -1753,7 +1785,6 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
|
||||
aifdata_reg = WM8915_AIF1TX_DATA_CONFIGURATION_1;
|
||||
lrclk_reg = WM8915_AIF1_TX_LRCLK_1;
|
||||
}
|
||||
bclk_reg = WM8915_AIF1_BCLK;
|
||||
dsp_shift = 0;
|
||||
break;
|
||||
case 1:
|
||||
@ -1765,7 +1796,6 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
|
||||
aifdata_reg = WM8915_AIF2TX_DATA_CONFIGURATION_1;
|
||||
lrclk_reg = WM8915_AIF2_TX_LRCLK_1;
|
||||
}
|
||||
bclk_reg = WM8915_AIF2_BCLK;
|
||||
dsp_shift = WM8915_DSP2_DIV_SHIFT;
|
||||
break;
|
||||
default:
|
||||
@ -1779,6 +1809,9 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
|
||||
return bclk_rate;
|
||||
}
|
||||
|
||||
wm8915->bclk_rate[dai->id] = bclk_rate;
|
||||
wm8915->rx_rate[dai->id] = params_rate(params);
|
||||
|
||||
/* Needs looking at for TDM */
|
||||
bits = snd_pcm_format_width(params_format(params));
|
||||
if (bits < 0)
|
||||
@ -1796,18 +1829,7 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
|
||||
}
|
||||
dsp |= i << dsp_shift;
|
||||
|
||||
/* Pick a divisor for BCLK as close as we can get to ideal */
|
||||
best = 0;
|
||||
for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
|
||||
cur_val = (wm8915->sysclk / bclk_divs[i]) - bclk_rate;
|
||||
if (cur_val < 0) /* BCLK table is sorted */
|
||||
break;
|
||||
best = i;
|
||||
}
|
||||
bclk_rate = wm8915->sysclk / bclk_divs[best];
|
||||
dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
|
||||
bclk_divs[best], bclk_rate);
|
||||
bclk |= best;
|
||||
wm8915_update_bclk(codec);
|
||||
|
||||
lrclk = bclk_rate / params_rate(params);
|
||||
dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n",
|
||||
@ -1817,14 +1839,11 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
|
||||
WM8915_AIF1TX_WL_MASK |
|
||||
WM8915_AIF1TX_SLOT_LEN_MASK,
|
||||
aifdata);
|
||||
snd_soc_update_bits(codec, bclk_reg, WM8915_AIF1_BCLK_DIV_MASK, bclk);
|
||||
snd_soc_update_bits(codec, lrclk_reg, WM8915_AIF1RX_RATE_MASK,
|
||||
lrclk);
|
||||
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_2,
|
||||
WM8915_DSP1_DIV_SHIFT << dsp_shift, dsp);
|
||||
|
||||
wm8915->rx_rate[dai->id] = params_rate(params);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1838,6 +1857,9 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai,
|
||||
int src;
|
||||
int old;
|
||||
|
||||
if (freq == wm8915->sysclk && clk_id == wm8915->sysclk_src)
|
||||
return 0;
|
||||
|
||||
/* Disable SYSCLK while we reconfigure */
|
||||
old = snd_soc_read(codec, WM8915_AIF_CLOCKING_1) & WM8915_SYSCLK_ENA;
|
||||
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
|
||||
@ -1882,6 +1904,8 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8915_update_bclk(codec);
|
||||
|
||||
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
|
||||
WM8915_SYSCLK_SRC_MASK | WM8915_SYSCLK_DIV_MASK,
|
||||
src << WM8915_SYSCLK_SRC_SHIFT | ratediv);
|
||||
@ -1889,6 +1913,8 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai,
|
||||
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
|
||||
WM8915_SYSCLK_ENA, old);
|
||||
|
||||
wm8915->sysclk_src = clk_id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2007,6 +2033,7 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
|
||||
unsigned int Fref, unsigned int Fout)
|
||||
{
|
||||
struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
|
||||
struct i2c_client *i2c = to_i2c_client(codec->dev);
|
||||
struct _fll_div fll_div;
|
||||
unsigned long timeout;
|
||||
int ret, reg;
|
||||
@ -2093,7 +2120,18 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
|
||||
else
|
||||
timeout = msecs_to_jiffies(2);
|
||||
|
||||
wait_for_completion_timeout(&wm8915->fll_lock, timeout);
|
||||
/* Allow substantially longer if we've actually got the IRQ */
|
||||
if (i2c->irq)
|
||||
timeout *= 1000;
|
||||
|
||||
ret = wait_for_completion_timeout(&wm8915->fll_lock, timeout);
|
||||
|
||||
if (ret == 0 && i2c->irq) {
|
||||
dev_err(codec->dev, "Timed out waiting for FLL\n");
|
||||
ret = -ETIMEDOUT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
|
||||
|
||||
@ -2101,7 +2139,7 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
|
||||
wm8915->fll_fout = Fout;
|
||||
wm8915->fll_src = source;
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GPIOLIB
|
||||
@ -2293,6 +2331,12 @@ static void wm8915_micd(struct snd_soc_codec *codec)
|
||||
SND_JACK_HEADSET | SND_JACK_BTN_0);
|
||||
wm8915->jack_mic = true;
|
||||
wm8915->detecting = false;
|
||||
|
||||
/* Increase poll rate to give better responsiveness
|
||||
* for buttons */
|
||||
snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
|
||||
WM8915_MICD_RATE_MASK,
|
||||
5 << WM8915_MICD_RATE_SHIFT);
|
||||
}
|
||||
|
||||
/* If we detected a lower impedence during initial startup
|
||||
@ -2333,15 +2377,17 @@ static void wm8915_micd(struct snd_soc_codec *codec)
|
||||
SND_JACK_HEADPHONE,
|
||||
SND_JACK_HEADSET |
|
||||
SND_JACK_BTN_0);
|
||||
|
||||
/* Increase the detection rate a bit for
|
||||
* responsiveness.
|
||||
*/
|
||||
snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
|
||||
WM8915_MICD_RATE_MASK,
|
||||
7 << WM8915_MICD_RATE_SHIFT);
|
||||
|
||||
wm8915->detecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Increase poll rate to give better responsiveness for buttons */
|
||||
if (!wm8915->detecting)
|
||||
snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
|
||||
WM8915_MICD_RATE_MASK,
|
||||
5 << WM8915_MICD_RATE_SHIFT);
|
||||
}
|
||||
|
||||
static irqreturn_t wm8915_irq(int irq, void *data)
|
||||
@ -2383,6 +2429,20 @@ static irqreturn_t wm8915_irq(int irq, void *data)
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t wm8915_edge_irq(int irq, void *data)
|
||||
{
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
irqreturn_t val;
|
||||
|
||||
do {
|
||||
val = wm8915_irq(irq, data);
|
||||
if (val != IRQ_NONE)
|
||||
ret = val;
|
||||
} while (val != IRQ_NONE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wm8915_retune_mobile_pdata(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
|
||||
@ -2482,8 +2542,6 @@ static int wm8915_probe(struct snd_soc_codec *codec)
|
||||
wm8915->disable_nb[1].notifier_call = wm8915_regulator_event_1;
|
||||
wm8915->disable_nb[2].notifier_call = wm8915_regulator_event_2;
|
||||
wm8915->disable_nb[3].notifier_call = wm8915_regulator_event_3;
|
||||
wm8915->disable_nb[4].notifier_call = wm8915_regulator_event_4;
|
||||
wm8915->disable_nb[5].notifier_call = wm8915_regulator_event_5;
|
||||
|
||||
/* This should really be moved into the regulator core */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8915->supplies); i++) {
|
||||
@ -2709,8 +2767,14 @@ static int wm8915_probe(struct snd_soc_codec *codec)
|
||||
|
||||
irq_flags |= IRQF_ONESHOT;
|
||||
|
||||
ret = request_threaded_irq(i2c->irq, NULL, wm8915_irq,
|
||||
irq_flags, "wm8915", codec);
|
||||
if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING))
|
||||
ret = request_threaded_irq(i2c->irq, NULL,
|
||||
wm8915_edge_irq,
|
||||
irq_flags, "wm8915", codec);
|
||||
else
|
||||
ret = request_threaded_irq(i2c->irq, NULL, wm8915_irq,
|
||||
irq_flags, "wm8915", codec);
|
||||
|
||||
if (ret == 0) {
|
||||
/* Unmask the interrupt */
|
||||
snd_soc_update_bits(codec, WM8915_INTERRUPT_CONTROL,
|
||||
|
@ -297,8 +297,6 @@ static int wm8940_add_widgets(struct snd_soc_codec *codec)
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
|
||||
error_ret:
|
||||
return ret;
|
||||
@ -683,8 +681,6 @@ static int wm8940_resume(struct snd_soc_codec *codec)
|
||||
}
|
||||
}
|
||||
ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
|
||||
error_ret:
|
||||
return ret;
|
||||
@ -730,9 +726,6 @@ static int wm8940_probe(struct snd_soc_codec *codec)
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = wm8940_add_widgets(codec);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,8 @@ struct wm8962_priv {
|
||||
#ifdef CONFIG_GPIOLIB
|
||||
struct gpio_chip gpio_chip;
|
||||
#endif
|
||||
|
||||
int irq;
|
||||
};
|
||||
|
||||
/* We can't use the same notifier block for more than one supply and
|
||||
@ -1982,6 +1984,7 @@ static const unsigned int classd_tlv[] = {
|
||||
0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
|
||||
7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
|
||||
};
|
||||
static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
|
||||
|
||||
/* The VU bits for the headphones are in a different register to the mute
|
||||
* bits and only take effect on the PGA if it is actually powered.
|
||||
@ -2119,6 +2122,18 @@ SOC_SINGLE_TLV("HPMIXR MIXINR Volume", WM8962_HEADPHONE_MIXER_4,
|
||||
|
||||
SOC_SINGLE_TLV("Speaker Boost Volume", WM8962_CLASS_D_CONTROL_2, 0, 7, 0,
|
||||
classd_tlv),
|
||||
|
||||
SOC_SINGLE("EQ Switch", WM8962_EQ1, WM8962_EQ_ENA_SHIFT, 1, 0),
|
||||
SOC_DOUBLE_R_TLV("EQ1 Volume", WM8962_EQ2, WM8962_EQ22,
|
||||
WM8962_EQL_B1_GAIN_SHIFT, 31, 0, eq_tlv),
|
||||
SOC_DOUBLE_R_TLV("EQ2 Volume", WM8962_EQ2, WM8962_EQ22,
|
||||
WM8962_EQL_B2_GAIN_SHIFT, 31, 0, eq_tlv),
|
||||
SOC_DOUBLE_R_TLV("EQ3 Volume", WM8962_EQ2, WM8962_EQ22,
|
||||
WM8962_EQL_B3_GAIN_SHIFT, 31, 0, eq_tlv),
|
||||
SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23,
|
||||
WM8962_EQL_B4_GAIN_SHIFT, 31, 0, eq_tlv),
|
||||
SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23,
|
||||
WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = {
|
||||
@ -2184,6 +2199,8 @@ static int sysclk_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned long timeout;
|
||||
int src;
|
||||
int fll;
|
||||
|
||||
@ -2203,9 +2220,19 @@ static int sysclk_event(struct snd_soc_dapm_widget *w,
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_PRE_PMU:
|
||||
if (fll)
|
||||
if (fll) {
|
||||
snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
|
||||
WM8962_FLL_ENA, WM8962_FLL_ENA);
|
||||
if (wm8962->irq) {
|
||||
timeout = msecs_to_jiffies(5);
|
||||
timeout = wait_for_completion_timeout(&wm8962->fll_lock,
|
||||
timeout);
|
||||
|
||||
if (timeout == 0)
|
||||
dev_err(codec->dev,
|
||||
"Timed out starting FLL\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SND_SOC_DAPM_POST_PMD:
|
||||
@ -2763,18 +2790,44 @@ static const int bclk_divs[] = {
|
||||
1, -1, 2, 3, 4, -1, 6, 8, -1, 12, 16, 24, -1, 32, 32, 32
|
||||
};
|
||||
|
||||
static const int sysclk_rates[] = {
|
||||
64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536,
|
||||
};
|
||||
|
||||
static void wm8962_configure_bclk(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
|
||||
int dspclk, i;
|
||||
int clocking2 = 0;
|
||||
int clocking4 = 0;
|
||||
int aif2 = 0;
|
||||
|
||||
if (!wm8962->bclk) {
|
||||
dev_dbg(codec->dev, "No BCLK rate configured\n");
|
||||
if (!wm8962->sysclk_rate) {
|
||||
dev_dbg(codec->dev, "No SYSCLK configured\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wm8962->bclk || !wm8962->lrclk) {
|
||||
dev_dbg(codec->dev, "No audio clocks configured\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) {
|
||||
if (sysclk_rates[i] == wm8962->sysclk_rate / wm8962->lrclk) {
|
||||
clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(sysclk_rates)) {
|
||||
dev_err(codec->dev, "Unsupported sysclk ratio %d\n",
|
||||
wm8962->sysclk_rate / wm8962->lrclk);
|
||||
return;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, WM8962_CLOCKING_4,
|
||||
WM8962_SYSCLK_RATE_MASK, clocking4);
|
||||
|
||||
dspclk = snd_soc_read(codec, WM8962_CLOCKING1);
|
||||
if (dspclk < 0) {
|
||||
dev_err(codec->dev, "Failed to read DSPCLK: %d\n", dspclk);
|
||||
@ -2844,6 +2897,8 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec,
|
||||
/* VMID 2*50k */
|
||||
snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
|
||||
WM8962_VMID_SEL_MASK, 0x80);
|
||||
|
||||
wm8962_configure_bclk(codec);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
@ -2876,8 +2931,6 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec,
|
||||
snd_soc_update_bits(codec, WM8962_CLOCKING2,
|
||||
WM8962_CLKREG_OVD,
|
||||
WM8962_CLKREG_OVD);
|
||||
|
||||
wm8962_configure_bclk(codec);
|
||||
}
|
||||
|
||||
/* VMID 2*250k */
|
||||
@ -2918,10 +2971,6 @@ static const struct {
|
||||
{ 96000, 6 },
|
||||
};
|
||||
|
||||
static const int sysclk_rates[] = {
|
||||
64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536,
|
||||
};
|
||||
|
||||
static int wm8962_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
@ -2929,41 +2978,27 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
|
||||
int rate = params_rate(params);
|
||||
int i;
|
||||
int aif0 = 0;
|
||||
int adctl3 = 0;
|
||||
int clocking4 = 0;
|
||||
|
||||
wm8962->bclk = snd_soc_params_to_bclk(params);
|
||||
wm8962->lrclk = params_rate(params);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sr_vals); i++) {
|
||||
if (sr_vals[i].rate == rate) {
|
||||
if (sr_vals[i].rate == wm8962->lrclk) {
|
||||
adctl3 |= sr_vals[i].reg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(sr_vals)) {
|
||||
dev_err(codec->dev, "Unsupported rate %dHz\n", rate);
|
||||
dev_err(codec->dev, "Unsupported rate %dHz\n", wm8962->lrclk);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rate % 8000 == 0)
|
||||
if (wm8962->lrclk % 8000 == 0)
|
||||
adctl3 |= WM8962_SAMPLE_RATE_INT_MODE;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) {
|
||||
if (sysclk_rates[i] == wm8962->sysclk_rate / rate) {
|
||||
clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(sysclk_rates)) {
|
||||
dev_err(codec->dev, "Unsupported sysclk ratio %d\n",
|
||||
wm8962->sysclk_rate / rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
break;
|
||||
@ -2985,8 +3020,6 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream,
|
||||
snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_3,
|
||||
WM8962_SAMPLE_RATE_INT_MODE |
|
||||
WM8962_SAMPLE_RATE_MASK, adctl3);
|
||||
snd_soc_update_bits(codec, WM8962_CLOCKING_4,
|
||||
WM8962_SYSCLK_RATE_MASK, clocking4);
|
||||
|
||||
wm8962_configure_bclk(codec);
|
||||
|
||||
@ -3261,16 +3294,31 @@ static int wm8962_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
|
||||
|
||||
dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
|
||||
|
||||
/* This should be a massive overestimate */
|
||||
timeout = msecs_to_jiffies(1);
|
||||
ret = 0;
|
||||
|
||||
wait_for_completion_timeout(&wm8962->fll_lock, timeout);
|
||||
if (fll1 & WM8962_FLL_ENA) {
|
||||
/* This should be a massive overestimate but go even
|
||||
* higher if we'll error out
|
||||
*/
|
||||
if (wm8962->irq)
|
||||
timeout = msecs_to_jiffies(5);
|
||||
else
|
||||
timeout = msecs_to_jiffies(1);
|
||||
|
||||
timeout = wait_for_completion_timeout(&wm8962->fll_lock,
|
||||
timeout);
|
||||
|
||||
if (timeout == 0 && wm8962->irq) {
|
||||
dev_err(codec->dev, "FLL lock timed out");
|
||||
ret = -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
wm8962->fll_fref = Fref;
|
||||
wm8962->fll_fout = Fout;
|
||||
wm8962->fll_src = source;
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm8962_mute(struct snd_soc_dai *dai, int mute)
|
||||
@ -3731,8 +3779,6 @@ static int wm8962_probe(struct snd_soc_codec *codec)
|
||||
int ret;
|
||||
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
|
||||
struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
|
||||
struct i2c_client *i2c = container_of(codec->dev, struct i2c_client,
|
||||
dev);
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
int i, trigger, irq_pol;
|
||||
bool dmicclk, dmicdat;
|
||||
@ -3871,6 +3917,9 @@ static int wm8962_probe(struct snd_soc_codec *codec)
|
||||
snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME,
|
||||
WM8962_HPOUT_VU, WM8962_HPOUT_VU);
|
||||
|
||||
/* Stereo control for EQ */
|
||||
snd_soc_update_bits(codec, WM8962_EQ1, WM8962_EQ_SHARED_COEFF, 0);
|
||||
|
||||
wm8962_add_widgets(codec);
|
||||
|
||||
/* Save boards having to disable DMIC when not in use */
|
||||
@ -3899,7 +3948,7 @@ static int wm8962_probe(struct snd_soc_codec *codec)
|
||||
wm8962_init_beep(codec);
|
||||
wm8962_init_gpio(codec);
|
||||
|
||||
if (i2c->irq) {
|
||||
if (wm8962->irq) {
|
||||
if (pdata && pdata->irq_active_low) {
|
||||
trigger = IRQF_TRIGGER_LOW;
|
||||
irq_pol = WM8962_IRQ_POL;
|
||||
@ -3911,12 +3960,13 @@ static int wm8962_probe(struct snd_soc_codec *codec)
|
||||
snd_soc_update_bits(codec, WM8962_INTERRUPT_CONTROL,
|
||||
WM8962_IRQ_POL, irq_pol);
|
||||
|
||||
ret = request_threaded_irq(i2c->irq, NULL, wm8962_irq,
|
||||
ret = request_threaded_irq(wm8962->irq, NULL, wm8962_irq,
|
||||
trigger | IRQF_ONESHOT,
|
||||
"wm8962", codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to request IRQ %d: %d\n",
|
||||
i2c->irq, ret);
|
||||
wm8962->irq, ret);
|
||||
wm8962->irq = 0;
|
||||
/* Non-fatal */
|
||||
} else {
|
||||
/* Enable some IRQs by default */
|
||||
@ -3941,12 +3991,10 @@ err:
|
||||
static int wm8962_remove(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
|
||||
struct i2c_client *i2c = container_of(codec->dev, struct i2c_client,
|
||||
dev);
|
||||
int i;
|
||||
|
||||
if (i2c->irq)
|
||||
free_irq(i2c->irq, codec);
|
||||
if (wm8962->irq)
|
||||
free_irq(wm8962->irq, codec);
|
||||
|
||||
cancel_delayed_work_sync(&wm8962->mic_work);
|
||||
|
||||
@ -3986,6 +4034,8 @@ static __devinit int wm8962_i2c_probe(struct i2c_client *i2c,
|
||||
|
||||
i2c_set_clientdata(i2c, wm8962);
|
||||
|
||||
wm8962->irq = i2c->irq;
|
||||
|
||||
ret = snd_soc_register_codec(&i2c->dev,
|
||||
&soc_codec_dev_wm8962, &wm8962_dai, 1);
|
||||
if (ret < 0)
|
||||
|
1203
sound/soc/codecs/wm8983.c
Normal file
1203
sound/soc/codecs/wm8983.c
Normal file
File diff suppressed because it is too large
Load Diff
1029
sound/soc/codecs/wm8983.h
Normal file
1029
sound/soc/codecs/wm8983.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -876,7 +876,7 @@ SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,
|
||||
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
|
||||
SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0,
|
||||
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
|
||||
|
||||
SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route routes[] = {
|
||||
@ -1434,6 +1434,7 @@ static int wm8993_probe(struct snd_soc_codec *codec)
|
||||
|
||||
wm8993->hubs_data.hp_startup_mode = 1;
|
||||
wm8993->hubs_data.dcs_codes = -2;
|
||||
wm8993->hubs_data.series_startup = 1;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
|
||||
if (ret != 0) {
|
||||
|
@ -195,10 +195,6 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
|
||||
aif + 1, rate);
|
||||
}
|
||||
|
||||
if (rate && rate < 3000000)
|
||||
dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n",
|
||||
aif + 1, rate);
|
||||
|
||||
wm8994->aifclk[aif] = rate;
|
||||
|
||||
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset,
|
||||
@ -1146,13 +1142,33 @@ SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
|
||||
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
||||
SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
|
||||
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
||||
SND_SOC_DAPM_PGA_E("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0,
|
||||
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
||||
|
||||
SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
|
||||
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer),
|
||||
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
||||
SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
|
||||
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer),
|
||||
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
||||
SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux,
|
||||
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
||||
SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux,
|
||||
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
||||
|
||||
SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev)
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = {
|
||||
SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0)
|
||||
SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
|
||||
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
|
||||
SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
|
||||
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
|
||||
SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
|
||||
SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = {
|
||||
@ -1190,7 +1206,6 @@ SND_SOC_DAPM_INPUT("DMIC1DAT"),
|
||||
SND_SOC_DAPM_INPUT("DMIC2DAT"),
|
||||
SND_SOC_DAPM_INPUT("Clock"),
|
||||
|
||||
SND_SOC_DAPM_MICBIAS("MICBIAS", WM8994_MICBIAS, 2, 0),
|
||||
SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev,
|
||||
SND_SOC_DAPM_PRE_PMU),
|
||||
|
||||
@ -1283,14 +1298,6 @@ SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0),
|
||||
SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0),
|
||||
SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0),
|
||||
|
||||
SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
|
||||
SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
|
||||
|
||||
SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
|
||||
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
|
||||
SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
|
||||
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
|
||||
|
||||
SND_SOC_DAPM_POST("Debug log", post_ev),
|
||||
};
|
||||
|
||||
@ -1509,8 +1516,10 @@ static const struct snd_soc_dapm_route wm8994_revd_intercon[] = {
|
||||
{ "AIF2DACDAT", NULL, "AIF1DACDAT" },
|
||||
{ "AIF1ADCDAT", NULL, "AIF2ADCDAT" },
|
||||
{ "AIF2ADCDAT", NULL, "AIF1ADCDAT" },
|
||||
{ "MICBIAS", NULL, "CLK_SYS" },
|
||||
{ "MICBIAS", NULL, "MICBIAS Supply" },
|
||||
{ "MICBIAS1", NULL, "CLK_SYS" },
|
||||
{ "MICBIAS1", NULL, "MICBIAS Supply" },
|
||||
{ "MICBIAS2", NULL, "CLK_SYS" },
|
||||
{ "MICBIAS2", NULL, "MICBIAS Supply" },
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route wm8994_intercon[] = {
|
||||
@ -1623,6 +1632,7 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
|
||||
int reg_offset, ret;
|
||||
struct fll_div fll;
|
||||
u16 reg, aif1, aif2;
|
||||
unsigned long timeout;
|
||||
|
||||
aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)
|
||||
& WM8994_AIF1CLK_ENA;
|
||||
@ -1704,6 +1714,9 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
|
||||
(fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) |
|
||||
(src - 1));
|
||||
|
||||
/* Clear any pending completion from a previous failure */
|
||||
try_wait_for_completion(&wm8994->fll_locked[id]);
|
||||
|
||||
/* Enable (with fractional mode if required) */
|
||||
if (freq_out) {
|
||||
if (fll.k)
|
||||
@ -1714,7 +1727,15 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
|
||||
WM8994_FLL1_ENA | WM8994_FLL1_FRAC,
|
||||
reg);
|
||||
|
||||
msleep(5);
|
||||
if (wm8994->fll_locked_irq) {
|
||||
timeout = wait_for_completion_timeout(&wm8994->fll_locked[id],
|
||||
msecs_to_jiffies(10));
|
||||
if (timeout == 0)
|
||||
dev_warn(codec->dev,
|
||||
"Timed out waiting for FLL lock\n");
|
||||
} else {
|
||||
msleep(5);
|
||||
}
|
||||
}
|
||||
|
||||
wm8994->fll[id].in = freq_in;
|
||||
@ -1732,6 +1753,14 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t wm8994_fll_locked_irq(int irq, void *data)
|
||||
{
|
||||
struct completion *completion = data;
|
||||
|
||||
complete(completion);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
|
||||
|
||||
@ -2271,6 +2300,33 @@ static int wm8994_aif3_hw_params(struct snd_pcm_substream *substream,
|
||||
return snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
|
||||
}
|
||||
|
||||
static void wm8994_aif_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
int rate_reg = 0;
|
||||
|
||||
switch (dai->id) {
|
||||
case 1:
|
||||
rate_reg = WM8994_AIF1_RATE;
|
||||
break;
|
||||
case 2:
|
||||
rate_reg = WM8994_AIF1_RATE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* If the DAI is idle then configure the divider tree for the
|
||||
* lowest output rate to save a little power if the clock is
|
||||
* still active (eg, because it is system clock).
|
||||
*/
|
||||
if (rate_reg && !dai->playback_active && !dai->capture_active)
|
||||
snd_soc_update_bits(codec, rate_reg,
|
||||
WM8994_AIF1_SR_MASK |
|
||||
WM8994_AIF1CLK_RATE_MASK, 0x9);
|
||||
}
|
||||
|
||||
static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
@ -2337,6 +2393,7 @@ static struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
|
||||
.set_sysclk = wm8994_set_dai_sysclk,
|
||||
.set_fmt = wm8994_set_dai_fmt,
|
||||
.hw_params = wm8994_hw_params,
|
||||
.shutdown = wm8994_aif_shutdown,
|
||||
.digital_mute = wm8994_aif_mute,
|
||||
.set_pll = wm8994_set_fll,
|
||||
.set_tristate = wm8994_set_tristate,
|
||||
@ -2346,6 +2403,7 @@ static struct snd_soc_dai_ops wm8994_aif2_dai_ops = {
|
||||
.set_sysclk = wm8994_set_dai_sysclk,
|
||||
.set_fmt = wm8994_set_dai_fmt,
|
||||
.hw_params = wm8994_hw_params,
|
||||
.shutdown = wm8994_aif_shutdown,
|
||||
.digital_mute = wm8994_aif_mute,
|
||||
.set_pll = wm8994_set_fll,
|
||||
.set_tristate = wm8994_set_tristate,
|
||||
@ -2763,7 +2821,7 @@ static void wm8958_default_micdet(u16 status, void *data)
|
||||
report = SND_JACK_MICROPHONE;
|
||||
|
||||
/* Everything else is buttons; just assign slots */
|
||||
if (status & 0x1c0)
|
||||
if (status & 0x1c)
|
||||
report |= SND_JACK_BTN_0;
|
||||
|
||||
done:
|
||||
@ -2849,6 +2907,15 @@ out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t wm8994_fifo_error(int irq, void *data)
|
||||
{
|
||||
struct snd_soc_codec *codec = data;
|
||||
|
||||
dev_err(codec->dev, "FIFO error\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int wm8994_codec_probe(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct wm8994 *control;
|
||||
@ -2867,6 +2934,9 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
|
||||
wm8994->pdata = dev_get_platdata(codec->dev->parent);
|
||||
wm8994->codec = codec;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
|
||||
init_completion(&wm8994->fll_locked[i]);
|
||||
|
||||
if (wm8994->pdata && wm8994->pdata->micdet_irq)
|
||||
wm8994->micdet_irq = wm8994->pdata->micdet_irq;
|
||||
else if (wm8994->pdata && wm8994->pdata->irq_base)
|
||||
@ -2905,6 +2975,7 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
|
||||
wm8994->hubs.dcs_codes = -5;
|
||||
wm8994->hubs.hp_startup_mode = 1;
|
||||
wm8994->hubs.dcs_readback_mode = 1;
|
||||
wm8994->hubs.series_startup = 1;
|
||||
break;
|
||||
default:
|
||||
wm8994->hubs.dcs_readback_mode = 1;
|
||||
@ -2919,6 +2990,15 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
|
||||
break;
|
||||
}
|
||||
|
||||
wm8994_request_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR,
|
||||
wm8994_fifo_error, "FIFO error", codec);
|
||||
|
||||
ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
|
||||
wm_hubs_dcs_done, "DC servo done",
|
||||
&wm8994->hubs);
|
||||
if (ret == 0)
|
||||
wm8994->hubs.dcs_done_irq = true;
|
||||
|
||||
switch (control->type) {
|
||||
case WM8994:
|
||||
if (wm8994->micdet_irq) {
|
||||
@ -2975,6 +3055,16 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
|
||||
}
|
||||
}
|
||||
|
||||
wm8994->fll_locked_irq = true;
|
||||
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) {
|
||||
ret = wm8994_request_irq(codec->control_data,
|
||||
WM8994_IRQ_FLL1_LOCK + i,
|
||||
wm8994_fll_locked_irq, "FLL lock",
|
||||
&wm8994->fll_locked[i]);
|
||||
if (ret != 0)
|
||||
wm8994->fll_locked_irq = false;
|
||||
}
|
||||
|
||||
/* Remember if AIFnLRCLK is configured as a GPIO. This should be
|
||||
* configured on init - if a system wants to do this dynamically
|
||||
* at runtime we can deal with that then.
|
||||
@ -3050,10 +3140,18 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
|
||||
1 << WM8994_AIF2DAC_3D_GAIN_SHIFT,
|
||||
1 << WM8994_AIF2DAC_3D_GAIN_SHIFT);
|
||||
|
||||
/* Unconditionally enable AIF1 ADC TDM mode; it only affects
|
||||
* behaviour on idle TDM clock cycles. */
|
||||
snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
|
||||
WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
|
||||
/* Unconditionally enable AIF1 ADC TDM mode on chips which can
|
||||
* use this; it only affects behaviour on idle TDM clock
|
||||
* cycles. */
|
||||
switch (control->type) {
|
||||
case WM8994:
|
||||
case WM8958:
|
||||
snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
|
||||
WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
wm8994_update_class_w(codec);
|
||||
|
||||
@ -3152,6 +3250,12 @@ err_irq:
|
||||
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
|
||||
if (wm8994->micdet_irq)
|
||||
free_irq(wm8994->micdet_irq, wm8994);
|
||||
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
|
||||
wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i,
|
||||
&wm8994->fll_locked[i]);
|
||||
wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
|
||||
&wm8994->hubs);
|
||||
wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec);
|
||||
err:
|
||||
kfree(wm8994);
|
||||
return ret;
|
||||
@ -3161,11 +3265,20 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
||||
struct wm8994 *control = codec->control_data;
|
||||
int i;
|
||||
|
||||
wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
|
||||
pm_runtime_disable(codec->dev);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
|
||||
wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i,
|
||||
&wm8994->fll_locked[i]);
|
||||
|
||||
wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
|
||||
&wm8994->hubs);
|
||||
wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec);
|
||||
|
||||
switch (control->type) {
|
||||
case WM8994:
|
||||
if (wm8994->micdet_irq)
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <sound/soc.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
#include "wm_hubs.h"
|
||||
|
||||
@ -79,6 +80,8 @@ struct wm8994_priv {
|
||||
int mclk[2];
|
||||
int aifclk[2];
|
||||
struct wm8994_fll_config fll[2], fll_suspend[2];
|
||||
struct completion fll_locked[2];
|
||||
bool fll_locked_irq;
|
||||
|
||||
int dac_rates[2];
|
||||
int lrclk_shared[2];
|
||||
|
@ -727,7 +727,7 @@ SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0,
|
||||
SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0),
|
||||
SND_SOC_DAPM_OUT_DRV("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_OUTPUT("LINEOUT"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKN"),
|
||||
|
@ -63,8 +63,10 @@ static const struct soc_enum speaker_mode =
|
||||
|
||||
static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
|
||||
{
|
||||
struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
|
||||
unsigned int reg;
|
||||
int count = 0;
|
||||
int timeout;
|
||||
unsigned int val;
|
||||
|
||||
val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1;
|
||||
@ -74,18 +76,39 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
|
||||
|
||||
dev_dbg(codec->dev, "Waiting for DC servo...\n");
|
||||
|
||||
if (hubs->dcs_done_irq)
|
||||
timeout = 4;
|
||||
else
|
||||
timeout = 400;
|
||||
|
||||
do {
|
||||
count++;
|
||||
msleep(1);
|
||||
|
||||
if (hubs->dcs_done_irq)
|
||||
wait_for_completion_timeout(&hubs->dcs_done,
|
||||
msecs_to_jiffies(250));
|
||||
else
|
||||
msleep(1);
|
||||
|
||||
reg = snd_soc_read(codec, WM8993_DC_SERVO_0);
|
||||
dev_dbg(codec->dev, "DC servo: %x\n", reg);
|
||||
} while (reg & op && count < 400);
|
||||
} while (reg & op && count < timeout);
|
||||
|
||||
if (reg & op)
|
||||
dev_err(codec->dev, "Timed out waiting for DC Servo %x\n",
|
||||
op);
|
||||
}
|
||||
|
||||
irqreturn_t wm_hubs_dcs_done(int irq, void *data)
|
||||
{
|
||||
struct wm_hubs_data *hubs = data;
|
||||
|
||||
complete(&hubs->dcs_done);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm_hubs_dcs_done);
|
||||
|
||||
/*
|
||||
* Startup calibration of the DC servo
|
||||
*/
|
||||
@ -107,8 +130,7 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Devices not using a DCS code correction have startup mode */
|
||||
if (hubs->dcs_codes) {
|
||||
if (hubs->series_startup) {
|
||||
/* Set for 32 series updates */
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
|
||||
WM8993_DCS_SERIES_NO_01_MASK,
|
||||
@ -134,9 +156,9 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
|
||||
break;
|
||||
case 1:
|
||||
reg = snd_soc_read(codec, WM8993_DC_SERVO_3);
|
||||
reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
|
||||
reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
|
||||
>> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
|
||||
reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
|
||||
reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
|
||||
break;
|
||||
default:
|
||||
WARN(1, "Unknown DCS readback method\n");
|
||||
@ -150,13 +172,13 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
|
||||
dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
|
||||
hubs->dcs_codes);
|
||||
|
||||
/* HPOUT1L */
|
||||
offset = reg_l;
|
||||
/* HPOUT1R */
|
||||
offset = reg_r;
|
||||
offset += hubs->dcs_codes;
|
||||
dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
|
||||
|
||||
/* HPOUT1R */
|
||||
offset = reg_r;
|
||||
/* HPOUT1L */
|
||||
offset = reg_l;
|
||||
offset += hubs->dcs_codes;
|
||||
dcs_cfg |= (u8)offset;
|
||||
|
||||
@ -168,8 +190,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
|
||||
WM8993_DCS_TRIG_DAC_WR_0 |
|
||||
WM8993_DCS_TRIG_DAC_WR_1);
|
||||
} else {
|
||||
dcs_cfg = reg_l << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
|
||||
dcs_cfg |= reg_r;
|
||||
dcs_cfg = reg_r << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
|
||||
dcs_cfg |= reg_l;
|
||||
}
|
||||
|
||||
/* Save the callibrated offset if we're in class W mode and
|
||||
@ -195,7 +217,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
|
||||
|
||||
/* If we're applying an offset correction then updating the
|
||||
* callibration would be likely to introduce further offsets. */
|
||||
if (hubs->dcs_codes)
|
||||
if (hubs->dcs_codes || hubs->no_series_update)
|
||||
return ret;
|
||||
|
||||
/* Only need to do this if the outputs are active */
|
||||
@ -599,9 +621,6 @@ SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0,
|
||||
SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0,
|
||||
in2r_pga, ARRAY_SIZE(in2r_pga)),
|
||||
|
||||
/* Dummy widgets to represent differential paths */
|
||||
SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0,
|
||||
mixinl, ARRAY_SIZE(mixinl)),
|
||||
SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0,
|
||||
@ -867,8 +886,11 @@ EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls);
|
||||
int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,
|
||||
int lineout1_diff, int lineout2_diff)
|
||||
{
|
||||
struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
|
||||
init_completion(&hubs->dcs_done);
|
||||
|
||||
snd_soc_dapm_add_routes(dapm, analogue_routes,
|
||||
ARRAY_SIZE(analogue_routes));
|
||||
|
||||
|
@ -14,6 +14,9 @@
|
||||
#ifndef _WM_HUBS_H
|
||||
#define _WM_HUBS_H
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
struct snd_soc_codec;
|
||||
|
||||
extern const unsigned int wm_hubs_spkmix_tlv[];
|
||||
@ -23,9 +26,14 @@ struct wm_hubs_data {
|
||||
int dcs_codes;
|
||||
int dcs_readback_mode;
|
||||
int hp_startup_mode;
|
||||
int series_startup;
|
||||
int no_series_update;
|
||||
|
||||
bool class_w;
|
||||
u16 class_w_dcs;
|
||||
|
||||
bool dcs_done_irq;
|
||||
struct completion dcs_done;
|
||||
};
|
||||
|
||||
extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
|
||||
@ -36,4 +44,6 @@ extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *,
|
||||
int jd_scthr, int jd_thr,
|
||||
int micbias1_lvl, int micbias2_lvl);
|
||||
|
||||
extern irqreturn_t wm_hubs_dcs_done(int irq, void *data);
|
||||
|
||||
#endif
|
||||
|
@ -46,11 +46,28 @@ static void print_buf_info(int slot, char *name)
|
||||
}
|
||||
#endif
|
||||
|
||||
#define DAVINCI_PCM_FMTBITS (\
|
||||
SNDRV_PCM_FMTBIT_S8 |\
|
||||
SNDRV_PCM_FMTBIT_U8 |\
|
||||
SNDRV_PCM_FMTBIT_S16_LE |\
|
||||
SNDRV_PCM_FMTBIT_S16_BE |\
|
||||
SNDRV_PCM_FMTBIT_U16_LE |\
|
||||
SNDRV_PCM_FMTBIT_U16_BE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_BE |\
|
||||
SNDRV_PCM_FMTBIT_U24_LE |\
|
||||
SNDRV_PCM_FMTBIT_U24_BE |\
|
||||
SNDRV_PCM_FMTBIT_S32_LE |\
|
||||
SNDRV_PCM_FMTBIT_S32_BE |\
|
||||
SNDRV_PCM_FMTBIT_U32_LE |\
|
||||
SNDRV_PCM_FMTBIT_U32_BE)
|
||||
|
||||
static struct snd_pcm_hardware pcm_hardware_playback = {
|
||||
.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),
|
||||
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME|
|
||||
SNDRV_PCM_INFO_BATCH),
|
||||
.formats = DAVINCI_PCM_FMTBITS,
|
||||
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
|
||||
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
|
||||
@ -59,7 +76,7 @@ static struct snd_pcm_hardware pcm_hardware_playback = {
|
||||
.rate_min = 8000,
|
||||
.rate_max = 96000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.channels_max = 384,
|
||||
.buffer_bytes_max = 128 * 1024,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 8 * 1024,
|
||||
@ -71,8 +88,9 @@ static struct snd_pcm_hardware pcm_hardware_playback = {
|
||||
static struct snd_pcm_hardware pcm_hardware_capture = {
|
||||
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE),
|
||||
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_BATCH),
|
||||
.formats = DAVINCI_PCM_FMTBITS,
|
||||
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
|
||||
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
|
||||
@ -81,7 +99,7 @@ static struct snd_pcm_hardware pcm_hardware_capture = {
|
||||
.rate_min = 8000,
|
||||
.rate_max = 96000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.channels_max = 384,
|
||||
.buffer_bytes_max = 128 * 1024,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 8 * 1024,
|
||||
@ -139,6 +157,22 @@ struct davinci_runtime_data {
|
||||
struct edmacc_param ram_params;
|
||||
};
|
||||
|
||||
static void davinci_pcm_period_elapsed(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct davinci_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
prtd->period++;
|
||||
if (unlikely(prtd->period >= runtime->periods))
|
||||
prtd->period = 0;
|
||||
}
|
||||
|
||||
static void davinci_pcm_period_reset(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct davinci_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
prtd->period = 0;
|
||||
}
|
||||
/*
|
||||
* Not used with ping/pong
|
||||
*/
|
||||
@ -199,10 +233,6 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
|
||||
else
|
||||
edma_set_transfer_params(link, acnt, fifo_level, count,
|
||||
fifo_level, ABSYNC);
|
||||
|
||||
prtd->period++;
|
||||
if (unlikely(prtd->period >= runtime->periods))
|
||||
prtd->period = 0;
|
||||
}
|
||||
|
||||
static void davinci_pcm_dma_irq(unsigned link, u16 ch_status, void *data)
|
||||
@ -217,12 +247,13 @@ static void davinci_pcm_dma_irq(unsigned link, u16 ch_status, void *data)
|
||||
return;
|
||||
|
||||
if (snd_pcm_running(substream)) {
|
||||
spin_lock(&prtd->lock);
|
||||
if (prtd->ram_channel < 0) {
|
||||
/* No ping/pong must fix up link dma data*/
|
||||
spin_lock(&prtd->lock);
|
||||
davinci_pcm_enqueue_dma(substream);
|
||||
spin_unlock(&prtd->lock);
|
||||
}
|
||||
davinci_pcm_period_elapsed(substream);
|
||||
spin_unlock(&prtd->lock);
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
}
|
||||
@ -425,7 +456,8 @@ static int request_ping_pong(struct snd_pcm_substream *substream,
|
||||
|
||||
edma_read_slot(link, &prtd->asp_params);
|
||||
prtd->asp_params.opt &= ~(TCCMODE | EDMA_TCC(0x3f) | TCINTEN);
|
||||
prtd->asp_params.opt |= TCCHEN | EDMA_TCC(prtd->ram_channel & 0x3f);
|
||||
prtd->asp_params.opt |= TCCHEN |
|
||||
EDMA_TCC(prtd->ram_channel & 0x3f);
|
||||
edma_write_slot(link, &prtd->asp_params);
|
||||
|
||||
/* pong */
|
||||
@ -439,7 +471,7 @@ static int request_ping_pong(struct snd_pcm_substream *substream,
|
||||
prtd->asp_params.opt &= ~(TCCMODE | EDMA_TCC(0x3f));
|
||||
/* interrupt after every pong completion */
|
||||
prtd->asp_params.opt |= TCINTEN | TCCHEN |
|
||||
EDMA_TCC(EDMA_CHAN_SLOT(prtd->ram_channel));
|
||||
EDMA_TCC(prtd->ram_channel & 0x3f);
|
||||
edma_write_slot(link, &prtd->asp_params);
|
||||
|
||||
/* ram */
|
||||
@ -527,6 +559,13 @@ static int davinci_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
edma_start(prtd->asp_channel);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
prtd->ram_channel >= 0) {
|
||||
/* copy 1st iram buffer */
|
||||
edma_start(prtd->ram_channel);
|
||||
}
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
edma_resume(prtd->asp_channel);
|
||||
@ -550,6 +589,7 @@ static int davinci_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct davinci_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
davinci_pcm_period_reset(substream);
|
||||
if (prtd->ram_channel >= 0) {
|
||||
int ret = ping_pong_dma_setup(substream);
|
||||
if (ret < 0)
|
||||
@ -565,21 +605,31 @@ static int davinci_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
print_buf_info(prtd->asp_link[0], "asp_link[0]");
|
||||
print_buf_info(prtd->asp_link[1], "asp_link[1]");
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/* copy 1st iram buffer */
|
||||
edma_start(prtd->ram_channel);
|
||||
}
|
||||
edma_start(prtd->asp_channel);
|
||||
/*
|
||||
* There is a phase offset of 2 periods between the position
|
||||
* used by dma setup and the position reported in the pointer
|
||||
* function.
|
||||
*
|
||||
* The phase offset, when not using ping-pong buffers, is due to
|
||||
* the two consecutive calls to davinci_pcm_enqueue_dma() below.
|
||||
*
|
||||
* Whereas here, with ping-pong buffers, the phase is due to
|
||||
* there being an entire buffer transfer complete before the
|
||||
* first dma completion event triggers davinci_pcm_dma_irq().
|
||||
*/
|
||||
davinci_pcm_period_elapsed(substream);
|
||||
davinci_pcm_period_elapsed(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
prtd->period = 0;
|
||||
davinci_pcm_enqueue_dma(substream);
|
||||
davinci_pcm_period_elapsed(substream);
|
||||
|
||||
/* Copy self-linked parameter RAM entry into master channel */
|
||||
edma_read_slot(prtd->asp_link[0], &prtd->asp_params);
|
||||
edma_write_slot(prtd->asp_channel, &prtd->asp_params);
|
||||
davinci_pcm_enqueue_dma(substream);
|
||||
edma_start(prtd->asp_channel);
|
||||
davinci_pcm_period_elapsed(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -591,51 +641,23 @@ davinci_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
struct davinci_runtime_data *prtd = runtime->private_data;
|
||||
unsigned int offset;
|
||||
int asp_count;
|
||||
dma_addr_t asp_src, asp_dst;
|
||||
unsigned int period_size = snd_pcm_lib_period_bytes(substream);
|
||||
|
||||
/*
|
||||
* There is a phase offset of 2 periods between the position used by dma
|
||||
* setup and the position reported in the pointer function. Either +2 in
|
||||
* the dma setup or -2 here in the pointer function (with wrapping,
|
||||
* both) accounts for this offset -- choose the latter since it makes
|
||||
* the first-time setup clearer.
|
||||
*/
|
||||
spin_lock(&prtd->lock);
|
||||
if (prtd->ram_channel >= 0) {
|
||||
int ram_count;
|
||||
int mod_ram;
|
||||
dma_addr_t ram_src, ram_dst;
|
||||
unsigned int period_size = snd_pcm_lib_period_bytes(substream);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/* reading ram before asp should be safe
|
||||
* as long as the asp transfers less than a ping size
|
||||
* of bytes between the 2 reads
|
||||
*/
|
||||
edma_get_position(prtd->ram_channel,
|
||||
&ram_src, &ram_dst);
|
||||
edma_get_position(prtd->asp_channel,
|
||||
&asp_src, &asp_dst);
|
||||
asp_count = asp_src - prtd->asp_params.src;
|
||||
ram_count = ram_src - prtd->ram_params.src;
|
||||
mod_ram = ram_count % period_size;
|
||||
mod_ram -= asp_count;
|
||||
if (mod_ram < 0)
|
||||
mod_ram += period_size;
|
||||
else if (mod_ram == 0) {
|
||||
if (snd_pcm_running(substream))
|
||||
mod_ram += period_size;
|
||||
}
|
||||
ram_count -= mod_ram;
|
||||
if (ram_count < 0)
|
||||
ram_count += period_size * runtime->periods;
|
||||
} else {
|
||||
edma_get_position(prtd->ram_channel,
|
||||
&ram_src, &ram_dst);
|
||||
ram_count = ram_dst - prtd->ram_params.dst;
|
||||
}
|
||||
asp_count = ram_count;
|
||||
} else {
|
||||
edma_get_position(prtd->asp_channel, &asp_src, &asp_dst);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
asp_count = asp_src - runtime->dma_addr;
|
||||
else
|
||||
asp_count = asp_dst - runtime->dma_addr;
|
||||
}
|
||||
asp_count = prtd->period - 2;
|
||||
spin_unlock(&prtd->lock);
|
||||
|
||||
if (asp_count < 0)
|
||||
asp_count += runtime->periods;
|
||||
asp_count *= period_size;
|
||||
|
||||
offset = bytes_to_frames(runtime, asp_count);
|
||||
if (offset >= runtime->buffer_size)
|
||||
offset = 0;
|
||||
@ -811,9 +833,11 @@ static void davinci_pcm_free(struct snd_pcm *pcm)
|
||||
|
||||
static u64 davinci_pcm_dmamask = 0xffffffff;
|
||||
|
||||
static int davinci_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
static int davinci_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
@ -266,9 +266,11 @@ static void ep93xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
|
||||
static u64 ep93xx_pcm_dmamask = 0xffffffff;
|
||||
|
||||
static int ep93xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int ep93xx_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
@ -294,9 +294,11 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
|
||||
* Regardless of where the memory is actually allocated, since the device can
|
||||
* technically DMA to any 36-bit address, we do need to set the DMA mask to 36.
|
||||
*/
|
||||
static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int fsl_dma_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
static u64 fsl_dma_dmamask = DMA_BIT_MASK(36);
|
||||
int ret;
|
||||
|
||||
@ -939,7 +941,7 @@ static int __devinit fsl_soc_dma_probe(struct platform_device *pdev)
|
||||
|
||||
iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);
|
||||
if (iprop)
|
||||
dma->ssi_fifo_depth = *iprop;
|
||||
dma->ssi_fifo_depth = be32_to_cpup(iprop);
|
||||
else
|
||||
/* Older 8610 DTs didn't have the fifo-depth property */
|
||||
dma->ssi_fifo_depth = 8;
|
||||
|
@ -678,7 +678,12 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)
|
||||
kfree(ssi_private);
|
||||
return ret;
|
||||
}
|
||||
ssi_private->ssi = ioremap(res.start, 1 + res.end - res.start);
|
||||
ssi_private->ssi = of_iomap(np, 0);
|
||||
if (!ssi_private->ssi) {
|
||||
dev_err(&pdev->dev, "could not map device resources\n");
|
||||
kfree(ssi_private);
|
||||
return -ENOMEM;
|
||||
}
|
||||
ssi_private->ssi_phys = res.start;
|
||||
ssi_private->irq = irq_of_parse_and_map(np, 0);
|
||||
|
||||
@ -691,7 +696,7 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)
|
||||
/* Determine the FIFO depth. */
|
||||
iprop = of_get_property(np, "fsl,fifo-depth", NULL);
|
||||
if (iprop)
|
||||
ssi_private->fifo_depth = *iprop;
|
||||
ssi_private->fifo_depth = be32_to_cpup(iprop);
|
||||
else
|
||||
/* Older 8610 DTs didn't have the fifo-depth property */
|
||||
ssi_private->fifo_depth = 8;
|
||||
|
@ -299,10 +299,11 @@ static struct snd_pcm_ops psc_dma_ops = {
|
||||
};
|
||||
|
||||
static u64 psc_dma_dmamask = 0xffffffff;
|
||||
static int psc_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int psc_dma_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
size_t size = psc_dma_hardware.buffer_bytes_max;
|
||||
int rc = 0;
|
||||
|
@ -233,7 +233,7 @@ static int get_parent_cell_index(struct device_node *np)
|
||||
if (!iprop)
|
||||
return -1;
|
||||
|
||||
return *iprop;
|
||||
return be32_to_cpup(iprop);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,7 +258,7 @@ static int codec_node_dev_name(struct device_node *np, char *buf, size_t len)
|
||||
if (!iprop)
|
||||
return -EINVAL;
|
||||
|
||||
addr = *iprop;
|
||||
addr = be32_to_cpup(iprop);
|
||||
|
||||
bus = get_parent_cell_index(np);
|
||||
if (bus < 0)
|
||||
@ -305,7 +305,7 @@ static int get_dma_channel(struct device_node *ssi_np,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*dma_channel_id = *iprop;
|
||||
*dma_channel_id = be32_to_cpup(iprop);
|
||||
*dma_id = get_parent_cell_index(dma_channel_np);
|
||||
of_node_put(dma_channel_np);
|
||||
|
||||
@ -379,7 +379,7 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev)
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
machine_data->ssi_id = *iprop;
|
||||
machine_data->ssi_id = be32_to_cpup(iprop);
|
||||
|
||||
/* Get the serial format and clock direction. */
|
||||
sprop = of_get_property(np, "fsl,mode", NULL);
|
||||
@ -405,7 +405,7 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev)
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
machine_data->clk_frequency = *iprop;
|
||||
machine_data->clk_frequency = be32_to_cpup(iprop);
|
||||
} else if (strcasecmp(sprop, "i2s-master") == 0) {
|
||||
machine_data->dai_format = SND_SOC_DAIFMT_I2S;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
|
@ -232,7 +232,7 @@ static int get_parent_cell_index(struct device_node *np)
|
||||
|
||||
iprop = of_get_property(parent, "cell-index", NULL);
|
||||
if (iprop)
|
||||
ret = *iprop;
|
||||
ret = be32_to_cpup(iprop);
|
||||
|
||||
of_node_put(parent);
|
||||
|
||||
@ -261,7 +261,7 @@ static int codec_node_dev_name(struct device_node *np, char *buf, size_t len)
|
||||
if (!iprop)
|
||||
return -EINVAL;
|
||||
|
||||
addr = *iprop;
|
||||
addr = be32_to_cpup(iprop);
|
||||
|
||||
bus = get_parent_cell_index(np);
|
||||
if (bus < 0)
|
||||
@ -308,7 +308,7 @@ static int get_dma_channel(struct device_node *ssi_np,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*dma_channel_id = *iprop;
|
||||
*dma_channel_id = be32_to_cpup(iprop);
|
||||
*dma_id = get_parent_cell_index(dma_channel_np);
|
||||
of_node_put(dma_channel_np);
|
||||
|
||||
@ -379,7 +379,7 @@ static int p1022_ds_probe(struct platform_device *pdev)
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
mdata->ssi_id = *iprop;
|
||||
mdata->ssi_id = be32_to_cpup(iprop);
|
||||
|
||||
/* Get the serial format and clock direction. */
|
||||
sprop = of_get_property(np, "fsl,mode", NULL);
|
||||
@ -405,7 +405,7 @@ static int p1022_ds_probe(struct platform_device *pdev)
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
mdata->clk_frequency = *iprop;
|
||||
mdata->clk_frequency = be32_to_cpup(iprop);
|
||||
} else if (strcasecmp(sprop, "i2s-master") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_I2S;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
|
@ -238,12 +238,14 @@ static struct snd_pcm_ops imx_pcm_ops = {
|
||||
|
||||
static int ssi_irq = 0;
|
||||
|
||||
static int imx_pcm_fiq_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret;
|
||||
|
||||
ret = imx_pcm_new(card, dai, pcm);
|
||||
ret = imx_pcm_new(rtd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -388,10 +388,11 @@ static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
|
||||
static u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
int imx_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
@ -225,8 +225,7 @@ struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
|
||||
struct imx_ssi *ssi);
|
||||
|
||||
int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
|
||||
int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm);
|
||||
int imx_pcm_new(struct snd_soc_pcm_runtime *rtd);
|
||||
void imx_pcm_free(struct snd_pcm *pcm);
|
||||
|
||||
/*
|
||||
|
@ -299,9 +299,11 @@ static void jz4740_pcm_free(struct snd_pcm *pcm)
|
||||
|
||||
static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
int jz4740_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
@ -312,9 +312,11 @@ static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kirkwood_dma_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
static int kirkwood_dma_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
@ -402,9 +402,10 @@ static void sst_pcm_free(struct snd_pcm *pcm)
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
int sst_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
int sst_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int retval = 0;
|
||||
|
||||
pr_debug("sst_pcm_new called\n");
|
||||
|
@ -356,7 +356,7 @@ static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev)
|
||||
nuc900_audio->irq_num = platform_get_irq(pdev, 0);
|
||||
if (!nuc900_audio->irq_num) {
|
||||
ret = -EBUSY;
|
||||
goto out2;
|
||||
goto out3;
|
||||
}
|
||||
|
||||
nuc900_ac97_data = nuc900_audio;
|
||||
|
@ -315,9 +315,12 @@ static void nuc900_dma_free_dma_buffers(struct snd_pcm *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)
|
||||
static int nuc900_dma_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &nuc900_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
|
@ -9,6 +9,9 @@ config SND_OMAP_SOC_MCBSP
|
||||
config SND_OMAP_SOC_MCPDM
|
||||
tristate
|
||||
|
||||
config SND_OMAP_SOC_HDMI
|
||||
tristate
|
||||
|
||||
config SND_OMAP_SOC_N810
|
||||
tristate "SoC Audio support for Nokia N810"
|
||||
depends on SND_OMAP_SOC && MACH_NOKIA_N810 && I2C
|
||||
@ -100,6 +103,14 @@ config SND_OMAP_SOC_SDP4430
|
||||
Say Y if you want to add support for SoC audio on Texas Instruments
|
||||
SDP4430.
|
||||
|
||||
config SND_OMAP_SOC_OMAP4_HDMI
|
||||
tristate "SoC Audio support for Texas Instruments OMAP4 HDMI"
|
||||
depends on SND_OMAP_SOC && OMAP4_DSS_HDMI && OMAP2_DSS && ARCH_OMAP4
|
||||
select SND_OMAP_SOC_HDMI
|
||||
help
|
||||
Say Y if you want to add support for SoC HDMI audio on Texas Instruments
|
||||
OMAP4 chips
|
||||
|
||||
config SND_OMAP_SOC_OMAP3_PANDORA
|
||||
tristate "SoC Audio support for OMAP3 Pandora"
|
||||
depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3_PANDORA
|
||||
|
@ -2,10 +2,12 @@
|
||||
snd-soc-omap-objs := omap-pcm.o
|
||||
snd-soc-omap-mcbsp-objs := omap-mcbsp.o
|
||||
snd-soc-omap-mcpdm-objs := omap-mcpdm.o mcpdm.o
|
||||
snd-soc-omap-hdmi-objs := omap-hdmi.o
|
||||
|
||||
obj-$(CONFIG_SND_OMAP_SOC) += snd-soc-omap.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_HDMI) += snd-soc-omap-hdmi.o
|
||||
|
||||
# OMAP Machine Support
|
||||
snd-soc-n810-objs := n810.o
|
||||
@ -21,6 +23,7 @@ snd-soc-omap3pandora-objs := omap3pandora.o
|
||||
snd-soc-omap3beagle-objs := omap3beagle.o
|
||||
snd-soc-zoom2-objs := zoom2.o
|
||||
snd-soc-igep0020-objs := igep0020.o
|
||||
snd-soc-omap4-hdmi-objs := omap4-hdmi-card.o
|
||||
|
||||
obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o
|
||||
@ -36,3 +39,4 @@ obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_IGEP0020) += snd-soc-igep0020.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP4_HDMI) += snd-soc-omap4-hdmi.o
|
||||
|
@ -427,7 +427,8 @@ static struct snd_soc_ops ams_delta_ops = {
|
||||
|
||||
/* Board specific codec bias level control */
|
||||
static int ams_delta_set_bias_level(struct snd_soc_card *card,
|
||||
enum snd_soc_bias_level level)
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_codec *codec = card->rtd->codec;
|
||||
|
||||
|
158
sound/soc/omap/omap-hdmi.c
Normal file
158
sound/soc/omap/omap-hdmi.c
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* omap-hdmi.c
|
||||
*
|
||||
* OMAP ALSA SoC DAI driver for HDMI audio on OMAP4 processors.
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/
|
||||
* Authors: Jorge Candelaria <jorge.candelaria@ti.com>
|
||||
* Ricardo Neri <ricardo.neri@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <plat/dma.h>
|
||||
#include "omap-pcm.h"
|
||||
#include "omap-hdmi.h"
|
||||
|
||||
#define DRV_NAME "hdmi-audio-dai"
|
||||
|
||||
static struct omap_pcm_dma_data omap_hdmi_dai_dma_params = {
|
||||
.name = "HDMI playback",
|
||||
.sync_mode = OMAP_DMA_SYNC_PACKET,
|
||||
};
|
||||
|
||||
static int omap_hdmi_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int err;
|
||||
/*
|
||||
* Make sure that the period bytes are multiple of the DMA packet size.
|
||||
* Largest packet size we use is 32 32-bit words = 128 bytes
|
||||
*/
|
||||
err = snd_pcm_hw_constraint_step(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_hdmi_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
omap_hdmi_dai_dma_params.packet_size = 16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
omap_hdmi_dai_dma_params.packet_size = 32;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
omap_hdmi_dai_dma_params.data_type = OMAP_DMA_DATA_TYPE_S32;
|
||||
|
||||
snd_soc_dai_set_dma_data(dai, substream,
|
||||
&omap_hdmi_dai_dma_params);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops omap_hdmi_dai_ops = {
|
||||
.startup = omap_hdmi_dai_startup,
|
||||
.hw_params = omap_hdmi_dai_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver omap_hdmi_dai = {
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = OMAP_HDMI_RATES,
|
||||
.formats = OMAP_HDMI_FORMATS,
|
||||
},
|
||||
.ops = &omap_hdmi_dai_ops,
|
||||
};
|
||||
|
||||
static __devinit int omap_hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct resource *hdmi_rsrc;
|
||||
|
||||
hdmi_rsrc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!hdmi_rsrc) {
|
||||
dev_err(&pdev->dev, "Cannot obtain IORESOURCE_MEM HDMI\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
omap_hdmi_dai_dma_params.port_addr = hdmi_rsrc->start
|
||||
+ OMAP_HDMI_AUDIO_DMA_PORT;
|
||||
|
||||
hdmi_rsrc = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!hdmi_rsrc) {
|
||||
dev_err(&pdev->dev, "Cannot obtain IORESOURCE_DMA HDMI\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
omap_hdmi_dai_dma_params.dma_req = hdmi_rsrc->start;
|
||||
|
||||
ret = snd_soc_register_dai(&pdev->dev, &omap_hdmi_dai);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit omap_hdmi_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_dai(&pdev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver hdmi_dai_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = omap_hdmi_probe,
|
||||
.remove = __devexit_p(omap_hdmi_remove),
|
||||
};
|
||||
|
||||
static int __init hdmi_dai_init(void)
|
||||
{
|
||||
return platform_driver_register(&hdmi_dai_driver);
|
||||
}
|
||||
module_init(hdmi_dai_init);
|
||||
|
||||
static void __exit hdmi_dai_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&hdmi_dai_driver);
|
||||
}
|
||||
module_exit(hdmi_dai_exit);
|
||||
|
||||
MODULE_AUTHOR("Jorge Candelaria <jorge.candelaria@ti.com>");
|
||||
MODULE_AUTHOR("Ricardo Neri <ricardo.neri@ti.com>");
|
||||
MODULE_DESCRIPTION("OMAP HDMI SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
36
sound/soc/omap/omap-hdmi.h
Normal file
36
sound/soc/omap/omap-hdmi.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* omap-hdmi.h
|
||||
*
|
||||
* Definitions for OMAP ALSA SoC DAI driver for HDMI audio on OMAP4 processors.
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/
|
||||
* Authors: Jorge Candelaria <jorge.candelaria@ti.com>
|
||||
* Ricardo Neri <ricardo.neri@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __OMAP_HDMI_H__
|
||||
#define __OMAP_HDMI_H__
|
||||
|
||||
#define OMAP_HDMI_AUDIO_DMA_PORT 0x8c
|
||||
|
||||
#define OMAP_HDMI_RATES (SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
|
||||
|
||||
#define OMAP_HDMI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
#endif
|
@ -366,9 +366,11 @@ static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
}
|
||||
}
|
||||
|
||||
static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int omap_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
129
sound/soc/omap/omap4-hdmi-card.c
Normal file
129
sound/soc/omap/omap4-hdmi-card.c
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* omap4-hdmi-card.c
|
||||
*
|
||||
* OMAP ALSA SoC machine driver for TI OMAP4 HDMI
|
||||
* Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
|
||||
* Author: Ricardo Neri <ricardo.neri@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include <video/omapdss.h>
|
||||
|
||||
#define DRV_NAME "omap4-hdmi-audio"
|
||||
|
||||
static int omap4_hdmi_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int i;
|
||||
struct omap_overlay_manager *mgr = NULL;
|
||||
struct device *dev = substream->pcm->card->dev;
|
||||
|
||||
/* Find DSS HDMI device */
|
||||
for (i = 0; i < omap_dss_get_num_overlay_managers(); i++) {
|
||||
mgr = omap_dss_get_overlay_manager(i);
|
||||
if (mgr && mgr->device
|
||||
&& mgr->device->type == OMAP_DISPLAY_TYPE_HDMI)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == omap_dss_get_num_overlay_managers()) {
|
||||
dev_err(dev, "HDMI display device not found!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Make sure HDMI is power-on to avoid L3 interconnect errors */
|
||||
if (mgr->device->state != OMAP_DSS_DISPLAY_ACTIVE) {
|
||||
dev_err(dev, "HDMI display is not active!\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops omap4_hdmi_dai_ops = {
|
||||
.hw_params = omap4_hdmi_dai_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link omap4_hdmi_dai = {
|
||||
.name = "HDMI",
|
||||
.stream_name = "HDMI",
|
||||
.cpu_dai_name = "hdmi-audio-dai",
|
||||
.platform_name = "omap-pcm-audio",
|
||||
.codec_name = "omapdss_hdmi",
|
||||
.codec_dai_name = "hdmi-audio-codec",
|
||||
.ops = &omap4_hdmi_dai_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_omap4_hdmi = {
|
||||
.name = "OMAP4HDMI",
|
||||
.dai_link = &omap4_hdmi_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static __devinit int omap4_hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &snd_soc_omap4_hdmi;
|
||||
int ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
card->dev = NULL;
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit omap4_hdmi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
card->dev = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap4_hdmi_driver = {
|
||||
.driver = {
|
||||
.name = "omap4-hdmi-audio",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = omap4_hdmi_probe,
|
||||
.remove = __devexit_p(omap4_hdmi_remove),
|
||||
};
|
||||
|
||||
static int __init omap4_hdmi_init(void)
|
||||
{
|
||||
return platform_driver_register(&omap4_hdmi_driver);
|
||||
}
|
||||
module_init(omap4_hdmi_init);
|
||||
|
||||
static void __exit omap4_hdmi_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&omap4_hdmi_driver);
|
||||
}
|
||||
module_exit(omap4_hdmi_exit);
|
||||
|
||||
MODULE_AUTHOR("Ricardo Neri <ricardo.neri@ti.com>");
|
||||
MODULE_DESCRIPTION("OMAP4 HDMI machine ASoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
@ -85,9 +85,10 @@ static struct snd_pcm_ops pxa2xx_pcm_ops = {
|
||||
|
||||
static u64 pxa2xx_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int pxa2xx_soc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
@ -443,10 +443,11 @@ static void s6000_pcm_free(struct snd_pcm *pcm)
|
||||
|
||||
static u64 s6000_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int s6000_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
static int s6000_pcm_new(struct snd_soc_pcm_runtime *runtime)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *runtime = pcm->private_data;
|
||||
struct snd_card *card = runtime->card->snd_card;
|
||||
struct snd_soc_dai *dai = runtime->cpu_dai;
|
||||
struct snd_pcm *pcm = runtime->pcm;
|
||||
struct s6000_pcm_dma_params *params;
|
||||
int res;
|
||||
|
||||
|
@ -158,7 +158,7 @@ config SND_SOC_GONI_AQUILA_WM8994
|
||||
|
||||
config SND_SOC_SAMSUNG_SMDK_SPDIF
|
||||
tristate "SoC S/PDIF Audio support for SMDK"
|
||||
depends on SND_SOC_SAMSUNG && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210)
|
||||
depends on SND_SOC_SAMSUNG && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210 || MACH_SMDKV310)
|
||||
select SND_SAMSUNG_SPDIF
|
||||
help
|
||||
Say Y if you want to add support for SoC S/PDIF audio on the SMDK.
|
||||
@ -171,9 +171,23 @@ config SND_SOC_SMDK_WM8580_PCM
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the SMDK.
|
||||
|
||||
config SND_SOC_SMDK_WM8994_PCM
|
||||
tristate "SoC PCM Audio support for WM8994 on SMDK"
|
||||
depends on SND_SOC_SAMSUNG && (MACH_SMDKC210 || MACH_SMDKV310)
|
||||
select SND_SOC_WM8994
|
||||
select SND_SAMSUNG_PCM
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the SMDK
|
||||
|
||||
config SND_SOC_SPEYSIDE
|
||||
tristate "Audio support for Wolfson Speyside"
|
||||
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
|
||||
select SND_SAMSUNG_I2S
|
||||
select SND_SOC_WM8915
|
||||
select SND_SOC_WM9081
|
||||
|
||||
config SND_SOC_SPEYSIDE_WM8962
|
||||
tristate "Audio support for Wolfson Speyside with WM8962"
|
||||
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
|
||||
select SND_SAMSUNG_I2S
|
||||
select SND_SOC_WM8962
|
||||
|
@ -35,7 +35,9 @@ snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
|
||||
snd-soc-goni-wm8994-objs := goni_wm8994.o
|
||||
snd-soc-smdk-spdif-objs := smdk_spdif.o
|
||||
snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o
|
||||
snd-soc-smdk-wm8994pcm-objs := smdk_wm8994pcm.o
|
||||
snd-soc-speyside-objs := speyside.o
|
||||
snd-soc-speyside-wm8962-objs := speyside_wm8962.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
|
||||
obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
|
||||
@ -54,4 +56,6 @@ obj-$(CONFIG_SND_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o
|
||||
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o
|
||||
obj-$(CONFIG_SND_SOC_GONI_AQUILA_WM8994) += snd-soc-goni-wm8994.o
|
||||
obj-$(CONFIG_SND_SOC_SMDK_WM8580_PCM) += snd-soc-smdk-wm8580pcm.o
|
||||
obj-$(CONFIG_SND_SOC_SMDK_WM8994_PCM) += snd-soc-smdk-wm8994pcm.o
|
||||
obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o
|
||||
obj-$(CONFIG_SND_SOC_SPEYSIDE_WM8962) += snd-soc-speyside-wm8962.o
|
||||
|
@ -425,9 +425,11 @@ static void dma_free_dma_buffers(struct snd_pcm *pcm)
|
||||
|
||||
static u64 dma_mask = DMA_BIT_MASK(32);
|
||||
|
||||
static int dma_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
static int dma_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("Entered %s\n", __func__);
|
||||
|
143
sound/soc/samsung/i2s-regs.h
Normal file
143
sound/soc/samsung/i2s-regs.h
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* linux/sound/soc/samsung/i2s-regs.h
|
||||
*
|
||||
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Samsung I2S driver's register header
|
||||
*
|
||||
* 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 __SND_SOC_SAMSUNG_I2S_REGS_H
|
||||
#define __SND_SOC_SAMSUNG_I2S_REGS_H
|
||||
|
||||
#define I2SCON 0x0
|
||||
#define I2SMOD 0x4
|
||||
#define I2SFIC 0x8
|
||||
#define I2SPSR 0xc
|
||||
#define I2STXD 0x10
|
||||
#define I2SRXD 0x14
|
||||
#define I2SFICS 0x18
|
||||
#define I2STXDS 0x1c
|
||||
#define I2SAHB 0x20
|
||||
#define I2SSTR0 0x24
|
||||
#define I2SSIZE 0x28
|
||||
#define I2STRNCNT 0x2c
|
||||
#define I2SLVL0ADDR 0x30
|
||||
#define I2SLVL1ADDR 0x34
|
||||
#define I2SLVL2ADDR 0x38
|
||||
#define I2SLVL3ADDR 0x3c
|
||||
|
||||
#define CON_RSTCLR (1 << 31)
|
||||
#define CON_FRXOFSTATUS (1 << 26)
|
||||
#define CON_FRXORINTEN (1 << 25)
|
||||
#define CON_FTXSURSTAT (1 << 24)
|
||||
#define CON_FTXSURINTEN (1 << 23)
|
||||
#define CON_TXSDMA_PAUSE (1 << 20)
|
||||
#define CON_TXSDMA_ACTIVE (1 << 18)
|
||||
|
||||
#define CON_FTXURSTATUS (1 << 17)
|
||||
#define CON_FTXURINTEN (1 << 16)
|
||||
#define CON_TXFIFO2_EMPTY (1 << 15)
|
||||
#define CON_TXFIFO1_EMPTY (1 << 14)
|
||||
#define CON_TXFIFO2_FULL (1 << 13)
|
||||
#define CON_TXFIFO1_FULL (1 << 12)
|
||||
|
||||
#define CON_LRINDEX (1 << 11)
|
||||
#define CON_TXFIFO_EMPTY (1 << 10)
|
||||
#define CON_RXFIFO_EMPTY (1 << 9)
|
||||
#define CON_TXFIFO_FULL (1 << 8)
|
||||
#define CON_RXFIFO_FULL (1 << 7)
|
||||
#define CON_TXDMA_PAUSE (1 << 6)
|
||||
#define CON_RXDMA_PAUSE (1 << 5)
|
||||
#define CON_TXCH_PAUSE (1 << 4)
|
||||
#define CON_RXCH_PAUSE (1 << 3)
|
||||
#define CON_TXDMA_ACTIVE (1 << 2)
|
||||
#define CON_RXDMA_ACTIVE (1 << 1)
|
||||
#define CON_ACTIVE (1 << 0)
|
||||
|
||||
#define MOD_OPCLK_CDCLK_OUT (0 << 30)
|
||||
#define MOD_OPCLK_CDCLK_IN (1 << 30)
|
||||
#define MOD_OPCLK_BCLK_OUT (2 << 30)
|
||||
#define MOD_OPCLK_PCLK (3 << 30)
|
||||
#define MOD_OPCLK_MASK (3 << 30)
|
||||
#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
|
||||
|
||||
#define MOD_BLCS_SHIFT 26
|
||||
#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCP_SHIFT 24
|
||||
#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT)
|
||||
#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT)
|
||||
#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT)
|
||||
#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT)
|
||||
|
||||
#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
|
||||
#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
|
||||
#define MOD_C1DD_HHALF (1 << 19)
|
||||
#define MOD_C1DD_LHALF (1 << 18)
|
||||
#define MOD_DC2_EN (1 << 17)
|
||||
#define MOD_DC1_EN (1 << 16)
|
||||
#define MOD_BLC_16BIT (0 << 13)
|
||||
#define MOD_BLC_8BIT (1 << 13)
|
||||
#define MOD_BLC_24BIT (2 << 13)
|
||||
#define MOD_BLC_MASK (3 << 13)
|
||||
|
||||
#define MOD_IMS_SYSMUX (1 << 10)
|
||||
#define MOD_SLAVE (1 << 11)
|
||||
#define MOD_TXONLY (0 << 8)
|
||||
#define MOD_RXONLY (1 << 8)
|
||||
#define MOD_TXRX (2 << 8)
|
||||
#define MOD_MASK (3 << 8)
|
||||
#define MOD_LR_LLOW (0 << 7)
|
||||
#define MOD_LR_RLOW (1 << 7)
|
||||
#define MOD_SDF_IIS (0 << 5)
|
||||
#define MOD_SDF_MSB (1 << 5)
|
||||
#define MOD_SDF_LSB (2 << 5)
|
||||
#define MOD_SDF_MASK (3 << 5)
|
||||
#define MOD_RCLK_256FS (0 << 3)
|
||||
#define MOD_RCLK_512FS (1 << 3)
|
||||
#define MOD_RCLK_384FS (2 << 3)
|
||||
#define MOD_RCLK_768FS (3 << 3)
|
||||
#define MOD_RCLK_MASK (3 << 3)
|
||||
#define MOD_BCLK_32FS (0 << 1)
|
||||
#define MOD_BCLK_48FS (1 << 1)
|
||||
#define MOD_BCLK_16FS (2 << 1)
|
||||
#define MOD_BCLK_24FS (3 << 1)
|
||||
#define MOD_BCLK_MASK (3 << 1)
|
||||
#define MOD_8BIT (1 << 0)
|
||||
|
||||
#define MOD_CDCLKCON (1 << 12)
|
||||
|
||||
#define PSR_PSREN (1 << 15)
|
||||
|
||||
#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf)
|
||||
#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf)
|
||||
|
||||
#define FIC_TXFLUSH (1 << 15)
|
||||
#define FIC_RXFLUSH (1 << 7)
|
||||
|
||||
#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf)
|
||||
#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf)
|
||||
#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
|
||||
|
||||
#define AHB_INTENLVL0 (1 << 24)
|
||||
#define AHB_LVL0INT (1 << 20)
|
||||
#define AHB_CLRLVL0INT (1 << 16)
|
||||
#define AHB_DMARLD (1 << 5)
|
||||
#define AHB_INTMASK (1 << 3)
|
||||
#define AHB_DMAEN (1 << 0)
|
||||
#define AHB_LVLINTMASK (0xf << 20)
|
||||
|
||||
#define I2SSIZE_TRNMSK (0xffff)
|
||||
#define I2SSIZE_SHIFT (16)
|
||||
|
||||
#endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */
|
||||
|
||||
|
@ -22,109 +22,7 @@
|
||||
|
||||
#include "dma.h"
|
||||
#include "i2s.h"
|
||||
|
||||
#define I2SCON 0x0
|
||||
#define I2SMOD 0x4
|
||||
#define I2SFIC 0x8
|
||||
#define I2SPSR 0xc
|
||||
#define I2STXD 0x10
|
||||
#define I2SRXD 0x14
|
||||
#define I2SFICS 0x18
|
||||
#define I2STXDS 0x1c
|
||||
|
||||
#define CON_RSTCLR (1 << 31)
|
||||
#define CON_FRXOFSTATUS (1 << 26)
|
||||
#define CON_FRXORINTEN (1 << 25)
|
||||
#define CON_FTXSURSTAT (1 << 24)
|
||||
#define CON_FTXSURINTEN (1 << 23)
|
||||
#define CON_TXSDMA_PAUSE (1 << 20)
|
||||
#define CON_TXSDMA_ACTIVE (1 << 18)
|
||||
|
||||
#define CON_FTXURSTATUS (1 << 17)
|
||||
#define CON_FTXURINTEN (1 << 16)
|
||||
#define CON_TXFIFO2_EMPTY (1 << 15)
|
||||
#define CON_TXFIFO1_EMPTY (1 << 14)
|
||||
#define CON_TXFIFO2_FULL (1 << 13)
|
||||
#define CON_TXFIFO1_FULL (1 << 12)
|
||||
|
||||
#define CON_LRINDEX (1 << 11)
|
||||
#define CON_TXFIFO_EMPTY (1 << 10)
|
||||
#define CON_RXFIFO_EMPTY (1 << 9)
|
||||
#define CON_TXFIFO_FULL (1 << 8)
|
||||
#define CON_RXFIFO_FULL (1 << 7)
|
||||
#define CON_TXDMA_PAUSE (1 << 6)
|
||||
#define CON_RXDMA_PAUSE (1 << 5)
|
||||
#define CON_TXCH_PAUSE (1 << 4)
|
||||
#define CON_RXCH_PAUSE (1 << 3)
|
||||
#define CON_TXDMA_ACTIVE (1 << 2)
|
||||
#define CON_RXDMA_ACTIVE (1 << 1)
|
||||
#define CON_ACTIVE (1 << 0)
|
||||
|
||||
#define MOD_OPCLK_CDCLK_OUT (0 << 30)
|
||||
#define MOD_OPCLK_CDCLK_IN (1 << 30)
|
||||
#define MOD_OPCLK_BCLK_OUT (2 << 30)
|
||||
#define MOD_OPCLK_PCLK (3 << 30)
|
||||
#define MOD_OPCLK_MASK (3 << 30)
|
||||
#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
|
||||
|
||||
#define MOD_BLCS_SHIFT 26
|
||||
#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT)
|
||||
#define MOD_BLCP_SHIFT 24
|
||||
#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT)
|
||||
#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT)
|
||||
#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT)
|
||||
#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT)
|
||||
|
||||
#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
|
||||
#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
|
||||
#define MOD_C1DD_HHALF (1 << 19)
|
||||
#define MOD_C1DD_LHALF (1 << 18)
|
||||
#define MOD_DC2_EN (1 << 17)
|
||||
#define MOD_DC1_EN (1 << 16)
|
||||
#define MOD_BLC_16BIT (0 << 13)
|
||||
#define MOD_BLC_8BIT (1 << 13)
|
||||
#define MOD_BLC_24BIT (2 << 13)
|
||||
#define MOD_BLC_MASK (3 << 13)
|
||||
|
||||
#define MOD_IMS_SYSMUX (1 << 10)
|
||||
#define MOD_SLAVE (1 << 11)
|
||||
#define MOD_TXONLY (0 << 8)
|
||||
#define MOD_RXONLY (1 << 8)
|
||||
#define MOD_TXRX (2 << 8)
|
||||
#define MOD_MASK (3 << 8)
|
||||
#define MOD_LR_LLOW (0 << 7)
|
||||
#define MOD_LR_RLOW (1 << 7)
|
||||
#define MOD_SDF_IIS (0 << 5)
|
||||
#define MOD_SDF_MSB (1 << 5)
|
||||
#define MOD_SDF_LSB (2 << 5)
|
||||
#define MOD_SDF_MASK (3 << 5)
|
||||
#define MOD_RCLK_256FS (0 << 3)
|
||||
#define MOD_RCLK_512FS (1 << 3)
|
||||
#define MOD_RCLK_384FS (2 << 3)
|
||||
#define MOD_RCLK_768FS (3 << 3)
|
||||
#define MOD_RCLK_MASK (3 << 3)
|
||||
#define MOD_BCLK_32FS (0 << 1)
|
||||
#define MOD_BCLK_48FS (1 << 1)
|
||||
#define MOD_BCLK_16FS (2 << 1)
|
||||
#define MOD_BCLK_24FS (3 << 1)
|
||||
#define MOD_BCLK_MASK (3 << 1)
|
||||
#define MOD_8BIT (1 << 0)
|
||||
|
||||
#define MOD_CDCLKCON (1 << 12)
|
||||
|
||||
#define PSR_PSREN (1 << 15)
|
||||
|
||||
#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf)
|
||||
#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf)
|
||||
|
||||
#define FIC_TXFLUSH (1 << 15)
|
||||
#define FIC_RXFLUSH (1 << 7)
|
||||
#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf)
|
||||
#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf)
|
||||
#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
|
||||
#include "i2s-regs.h"
|
||||
|
||||
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "../codecs/wm8994.h"
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
/*
|
||||
* Default CFG switch settings to use this driver:
|
||||
@ -44,7 +45,9 @@ static int smdk_hw_params(struct snd_pcm_substream *substream,
|
||||
int ret;
|
||||
|
||||
/* AIF1CLK should be >=3MHz for optimal performance */
|
||||
if (params_rate(params) == 8000 || params_rate(params) == 11025)
|
||||
if (params_format(params) == SNDRV_PCM_FORMAT_S24_LE)
|
||||
pll_out = params_rate(params) * 384;
|
||||
else if (params_rate(params) == 8000 || params_rate(params) == 11025)
|
||||
pll_out = params_rate(params) * 512;
|
||||
else
|
||||
pll_out = params_rate(params) * 256;
|
||||
|
176
sound/soc/samsung/smdk_wm8994pcm.c
Normal file
176
sound/soc/samsung/smdk_wm8994pcm.c
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* sound/soc/samsung/smdk_wm8994pcm.c
|
||||
*
|
||||
* Copyright (c) 2011 Samsung Electronics Co., Ltd
|
||||
* http://www.samsung.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 <sound/soc.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "../codecs/wm8994.h"
|
||||
#include "dma.h"
|
||||
#include "pcm.h"
|
||||
|
||||
/*
|
||||
* Board Settings:
|
||||
* o '1' means 'ON'
|
||||
* o '0' means 'OFF'
|
||||
* o 'X' means 'Don't care'
|
||||
*
|
||||
* SMDKC210, SMDKV310: CFG3- 1001, CFG5-1000, CFG7-111111
|
||||
*/
|
||||
|
||||
/*
|
||||
* Configure audio route as :-
|
||||
* $ amixer sset 'DAC1' on,on
|
||||
* $ amixer sset 'Right Headphone Mux' 'DAC'
|
||||
* $ amixer sset 'Left Headphone Mux' 'DAC'
|
||||
* $ amixer sset 'DAC1R Mixer AIF1.1' on
|
||||
* $ amixer sset 'DAC1L Mixer AIF1.1' on
|
||||
* $ amixer sset 'IN2L' on
|
||||
* $ amixer sset 'IN2L PGA IN2LN' on
|
||||
* $ amixer sset 'MIXINL IN2L' on
|
||||
* $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
|
||||
* $ amixer sset 'IN2R' on
|
||||
* $ amixer sset 'IN2R PGA IN2RN' on
|
||||
* $ amixer sset 'MIXINR IN2R' on
|
||||
* $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
|
||||
*/
|
||||
|
||||
/* SMDK has a 16.9344MHZ crystal attached to WM8994 */
|
||||
#define SMDK_WM8994_FREQ 16934400
|
||||
|
||||
static int smdk_wm8994_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
unsigned long mclk_freq;
|
||||
int rfs, ret;
|
||||
|
||||
switch(params_rate(params)) {
|
||||
case 8000:
|
||||
rfs = 512;
|
||||
break;
|
||||
default:
|
||||
dev_err(cpu_dai->dev, "%s:%d Sampling Rate %u not supported!\n",
|
||||
__func__, __LINE__, params_rate(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mclk_freq = params_rate(params) * rfs;
|
||||
|
||||
/* Set the codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B
|
||||
| SND_SOC_DAIFMT_IB_NF
|
||||
| SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Set the cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B
|
||||
| SND_SOC_DAIFMT_IB_NF
|
||||
| SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
|
||||
mclk_freq, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
|
||||
SMDK_WM8994_FREQ, mclk_freq);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Set PCM source clock on CPU */
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
|
||||
mclk_freq, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Set SCLK_DIV for making bclk */
|
||||
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops smdk_wm8994_pcm_ops = {
|
||||
.hw_params = smdk_wm8994_pcm_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link smdk_dai[] = {
|
||||
{
|
||||
.name = "WM8994 PAIF PCM",
|
||||
.stream_name = "Primary PCM",
|
||||
.cpu_dai_name = "samsung-pcm.0",
|
||||
.codec_dai_name = "wm8994-aif1",
|
||||
.platform_name = "samsung-audio",
|
||||
.codec_name = "wm8994-codec",
|
||||
.ops = &smdk_wm8994_pcm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card smdk_pcm = {
|
||||
.name = "SMDK-PCM",
|
||||
.dai_link = smdk_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static int __devinit snd_smdk_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
smdk_pcm.dev = &pdev->dev;
|
||||
ret = snd_soc_register_card(&smdk_pcm);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit snd_smdk_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&smdk_pcm);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver snd_smdk_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "samsung-smdk-pcm",
|
||||
},
|
||||
.probe = snd_smdk_probe,
|
||||
.remove = __devexit_p(snd_smdk_remove),
|
||||
};
|
||||
|
||||
static int __init smdk_audio_init(void)
|
||||
{
|
||||
return platform_driver_register(&snd_smdk_driver);
|
||||
}
|
||||
|
||||
module_init(smdk_audio_init);
|
||||
|
||||
static void __exit smdk_audio_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&snd_smdk_driver);
|
||||
}
|
||||
|
||||
module_exit(smdk_audio_exit);
|
||||
|
||||
MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994 for PCM");
|
||||
MODULE_LICENSE("GPL");
|
@ -20,24 +20,29 @@
|
||||
#define WM8915_HPSEL_GPIO 214
|
||||
|
||||
static int speyside_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
int ret;
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8915_SYSCLK_MCLK1,
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8915_SYSCLK_MCLK2,
|
||||
32768, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8915_FLL_MCLK1,
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8915_FLL_MCLK2,
|
||||
0, 0, 0);
|
||||
if (ret < 0) {
|
||||
pr_err("Failed to stop FLL\n");
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
@ -46,6 +51,45 @@ static int speyside_set_bias_level(struct snd_soc_card *card,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int speyside_set_bias_level_post(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
int ret;
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
|
||||
ret = snd_soc_dai_set_pll(codec_dai, 0,
|
||||
WM8915_FLL_MCLK2,
|
||||
32768, 48000 * 256);
|
||||
if (ret < 0) {
|
||||
pr_err("Failed to start FLL\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai,
|
||||
WM8915_SYSCLK_FLL,
|
||||
48000 * 256,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
card->dapm.bias_level = level;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int speyside_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
@ -66,16 +110,6 @@ static int speyside_hw_params(struct snd_pcm_substream *substream,
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, 0, WM8915_FLL_MCLK1,
|
||||
32768, 256 * 48000);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8915_SYSCLK_FLL,
|
||||
256 * 48000, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -127,7 +161,7 @@ static int speyside_wm8915_init(struct snd_soc_pcm_runtime *rtd)
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(dai, WM8915_SYSCLK_MCLK1, 32768, 0);
|
||||
ret = snd_soc_dai_set_sysclk(dai, WM8915_SYSCLK_MCLK2, 32768, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@ -267,6 +301,7 @@ static struct snd_soc_card speyside = {
|
||||
.num_configs = ARRAY_SIZE(speyside_codec_conf),
|
||||
|
||||
.set_bias_level = speyside_set_bias_level,
|
||||
.set_bias_level_post = speyside_set_bias_level_post,
|
||||
|
||||
.controls = controls,
|
||||
.num_controls = ARRAY_SIZE(controls),
|
||||
|
264
sound/soc/samsung/speyside_wm8962.c
Normal file
264
sound/soc/samsung/speyside_wm8962.c
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Speyside with WM8962 audio support
|
||||
*
|
||||
* Copyright 2011 Wolfson Microelectronics
|
||||
*
|
||||
* 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 <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/jack.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "../codecs/wm8962.h"
|
||||
|
||||
static int speyside_wm8962_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
int ret;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
|
||||
WM8962_FLL_MCLK, 32768,
|
||||
44100 * 256);
|
||||
if (ret < 0)
|
||||
pr_err("Failed to start FLL: %d\n", ret);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai,
|
||||
WM8962_SYSCLK_FLL,
|
||||
44100 * 256,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
pr_err("Failed to set SYSCLK: %d\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int speyside_wm8962_set_bias_level_post(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
int ret;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
|
||||
32768, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
pr_err("Failed to switch away from FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
|
||||
0, 0, 0);
|
||||
if (ret < 0) {
|
||||
pr_err("Failed to stop FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dapm->bias_level = level;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int speyside_wm8962_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 *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops speyside_wm8962_ops = {
|
||||
.hw_params = speyside_wm8962_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link speyside_wm8962_dai[] = {
|
||||
{
|
||||
.name = "CPU",
|
||||
.stream_name = "CPU",
|
||||
.cpu_dai_name = "samsung-i2s.0",
|
||||
.codec_dai_name = "wm8962",
|
||||
.platform_name = "samsung-audio",
|
||||
.codec_name = "wm8962.1-001a",
|
||||
.ops = &speyside_wm8962_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new controls[] = {
|
||||
SOC_DAPM_PIN_SWITCH("Main Speaker"),
|
||||
};
|
||||
|
||||
static struct snd_soc_dapm_widget widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone", NULL),
|
||||
SND_SOC_DAPM_MIC("Headset Mic", NULL),
|
||||
|
||||
SND_SOC_DAPM_MIC("DMIC", NULL),
|
||||
|
||||
SND_SOC_DAPM_SPK("Main Speaker", NULL),
|
||||
};
|
||||
|
||||
static struct snd_soc_dapm_route audio_paths[] = {
|
||||
{ "Headphone", NULL, "HPOUTL" },
|
||||
{ "Headphone", NULL, "HPOUTR" },
|
||||
|
||||
{ "Main Speaker", NULL, "SPKOUTL" },
|
||||
{ "Main Speaker", NULL, "SPKOUTR" },
|
||||
|
||||
{ "MICBIAS", NULL, "Headset Mic" },
|
||||
{ "IN4L", NULL, "MICBIAS" },
|
||||
{ "IN4R", NULL, "MICBIAS" },
|
||||
|
||||
{ "MICBIAS", NULL, "DMIC" },
|
||||
{ "DMICDAT", NULL, "MICBIAS" },
|
||||
};
|
||||
|
||||
static struct snd_soc_jack speyside_wm8962_headset;
|
||||
|
||||
/* Headset jack detection DAPM pins */
|
||||
static struct snd_soc_jack_pin speyside_wm8962_headset_pins[] = {
|
||||
{
|
||||
.pin = "Headset Mic",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
},
|
||||
{
|
||||
.pin = "Headphone",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
},
|
||||
};
|
||||
|
||||
static int speyside_wm8962_late_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_codec *codec = card->rtd[0].codec;
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
|
||||
32768, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_jack_new(codec, "Headset",
|
||||
SND_JACK_HEADSET | SND_JACK_BTN_0,
|
||||
&speyside_wm8962_headset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_jack_add_pins(&speyside_wm8962_headset,
|
||||
ARRAY_SIZE(speyside_wm8962_headset_pins),
|
||||
speyside_wm8962_headset_pins);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
wm8962_mic_detect(codec, &speyside_wm8962_headset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_card speyside_wm8962 = {
|
||||
.name = "Speyside WM8962",
|
||||
.dai_link = speyside_wm8962_dai,
|
||||
.num_links = ARRAY_SIZE(speyside_wm8962_dai),
|
||||
|
||||
.set_bias_level = speyside_wm8962_set_bias_level,
|
||||
.set_bias_level_post = speyside_wm8962_set_bias_level_post,
|
||||
|
||||
.controls = controls,
|
||||
.num_controls = ARRAY_SIZE(controls),
|
||||
.dapm_widgets = widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(widgets),
|
||||
.dapm_routes = audio_paths,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_paths),
|
||||
|
||||
.late_probe = speyside_wm8962_late_probe,
|
||||
};
|
||||
|
||||
static __devinit int speyside_wm8962_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &speyside_wm8962;
|
||||
int ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit speyside_wm8962_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver speyside_wm8962_driver = {
|
||||
.driver = {
|
||||
.name = "speyside-wm8962",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
},
|
||||
.probe = speyside_wm8962_probe,
|
||||
.remove = __devexit_p(speyside_wm8962_remove),
|
||||
};
|
||||
|
||||
static int __init speyside_wm8962_audio_init(void)
|
||||
{
|
||||
return platform_driver_register(&speyside_wm8962_driver);
|
||||
}
|
||||
module_init(speyside_wm8962_audio_init);
|
||||
|
||||
static void __exit speyside_wm8962_audio_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&speyside_wm8962_driver);
|
||||
}
|
||||
module_exit(speyside_wm8962_audio_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Speyside WM8962 audio support");
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:speyside-wm8962");
|
@ -327,10 +327,10 @@ static void camelot_pcm_free(struct snd_pcm *pcm)
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
static int camelot_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int camelot_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
|
||||
/* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
|
||||
* in MMAP mode (i.e. aplay -M)
|
||||
*/
|
||||
|
@ -97,7 +97,7 @@ static int fsi_ak4642_remove(struct platform_device *pdev)
|
||||
|
||||
static struct fsi_ak4642_data fsi_a_ak4642 = {
|
||||
.name = "AK4642",
|
||||
.card = "FSIA (AK4642)",
|
||||
.card = "FSIA-AK4642",
|
||||
.cpu_dai = "fsia-dai",
|
||||
.codec = "ak4642-codec.0-0012",
|
||||
.platform = "sh_fsi.0",
|
||||
@ -106,7 +106,7 @@ static struct fsi_ak4642_data fsi_a_ak4642 = {
|
||||
|
||||
static struct fsi_ak4642_data fsi_b_ak4642 = {
|
||||
.name = "AK4642",
|
||||
.card = "FSIB (AK4642)",
|
||||
.card = "FSIB-AK4642",
|
||||
.cpu_dai = "fsib-dai",
|
||||
.codec = "ak4642-codec.0-0012",
|
||||
.platform = "sh_fsi.0",
|
||||
@ -115,7 +115,7 @@ static struct fsi_ak4642_data fsi_b_ak4642 = {
|
||||
|
||||
static struct fsi_ak4642_data fsi_a_ak4643 = {
|
||||
.name = "AK4643",
|
||||
.card = "FSIA (AK4643)",
|
||||
.card = "FSIA-AK4643",
|
||||
.cpu_dai = "fsia-dai",
|
||||
.codec = "ak4642-codec.0-0013",
|
||||
.platform = "sh_fsi.0",
|
||||
@ -124,7 +124,7 @@ static struct fsi_ak4642_data fsi_a_ak4643 = {
|
||||
|
||||
static struct fsi_ak4642_data fsi_b_ak4643 = {
|
||||
.name = "AK4643",
|
||||
.card = "FSIB (AK4643)",
|
||||
.card = "FSIB-AK4643",
|
||||
.cpu_dai = "fsib-dai",
|
||||
.codec = "ak4642-codec.0-0013",
|
||||
.platform = "sh_fsi.0",
|
||||
@ -133,7 +133,7 @@ static struct fsi_ak4642_data fsi_b_ak4643 = {
|
||||
|
||||
static struct fsi_ak4642_data fsi2_a_ak4642 = {
|
||||
.name = "AK4642",
|
||||
.card = "FSI2A (AK4642)",
|
||||
.card = "FSI2A-AK4642",
|
||||
.cpu_dai = "fsia-dai",
|
||||
.codec = "ak4642-codec.0-0012",
|
||||
.platform = "sh_fsi2",
|
||||
@ -142,7 +142,7 @@ static struct fsi_ak4642_data fsi2_a_ak4642 = {
|
||||
|
||||
static struct fsi_ak4642_data fsi2_b_ak4642 = {
|
||||
.name = "AK4642",
|
||||
.card = "FSI2B (AK4642)",
|
||||
.card = "FSI2B-AK4642",
|
||||
.cpu_dai = "fsib-dai",
|
||||
.codec = "ak4642-codec.0-0012",
|
||||
.platform = "sh_fsi2",
|
||||
@ -151,7 +151,7 @@ static struct fsi_ak4642_data fsi2_b_ak4642 = {
|
||||
|
||||
static struct fsi_ak4642_data fsi2_a_ak4643 = {
|
||||
.name = "AK4643",
|
||||
.card = "FSI2A (AK4643)",
|
||||
.card = "FSI2A-AK4643",
|
||||
.cpu_dai = "fsia-dai",
|
||||
.codec = "ak4642-codec.0-0013",
|
||||
.platform = "sh_fsi2",
|
||||
@ -160,7 +160,7 @@ static struct fsi_ak4642_data fsi2_a_ak4643 = {
|
||||
|
||||
static struct fsi_ak4642_data fsi2_b_ak4643 = {
|
||||
.name = "AK4643",
|
||||
.card = "FSI2B (AK4643)",
|
||||
.card = "FSI2B-AK4643",
|
||||
.cpu_dai = "fsib-dai",
|
||||
.codec = "ak4642-codec.0-0013",
|
||||
.platform = "sh_fsi2",
|
||||
|
@ -42,7 +42,7 @@ static struct snd_soc_dai_link fsi_da7210_dai = {
|
||||
};
|
||||
|
||||
static struct snd_soc_card fsi_soc_card = {
|
||||
.name = "FSI (DA7210)",
|
||||
.name = "FSI-DA7210",
|
||||
.dai_link = &fsi_da7210_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
@ -83,13 +83,13 @@ static int fsi_hdmi_remove(struct platform_device *pdev)
|
||||
|
||||
static struct fsi_hdmi_data fsi2_a_hdmi = {
|
||||
.cpu_dai = "fsia-dai",
|
||||
.card = "FSI2A (SH MOBILE HDMI)",
|
||||
.card = "FSI2A-HDMI",
|
||||
.id = FSI_PORT_A,
|
||||
};
|
||||
|
||||
static struct fsi_hdmi_data fsi2_b_hdmi = {
|
||||
.cpu_dai = "fsib-dai",
|
||||
.card = "FSI2B (SH MOBILE HDMI)",
|
||||
.card = "FSI2B-HDMI",
|
||||
.id = FSI_PORT_B,
|
||||
};
|
||||
|
||||
|
@ -118,10 +118,38 @@ typedef int (*set_rate_func)(struct device *dev, int is_porta, int rate, int ena
|
||||
/*
|
||||
* FSI driver use below type name for variable
|
||||
*
|
||||
* xxx_len : data length
|
||||
* xxx_width : data width
|
||||
* xxx_offset : data offset
|
||||
* xxx_num : number of data
|
||||
* xxx_pos : position of data
|
||||
* xxx_capa : capacity of data
|
||||
*/
|
||||
|
||||
/*
|
||||
* period/frame/sample image
|
||||
*
|
||||
* ex) PCM (2ch)
|
||||
*
|
||||
* period pos period pos
|
||||
* [n] [n + 1]
|
||||
* |<-------------------- period--------------------->|
|
||||
* ==|============================================ ... =|==
|
||||
* | |
|
||||
* ||<----- frame ----->|<------ frame ----->| ... |
|
||||
* |+--------------------+--------------------+- ... |
|
||||
* ||[ sample ][ sample ]|[ sample ][ sample ]| ... |
|
||||
* |+--------------------+--------------------+- ... |
|
||||
* ==|============================================ ... =|==
|
||||
*/
|
||||
|
||||
/*
|
||||
* FSI FIFO image
|
||||
*
|
||||
* | |
|
||||
* | |
|
||||
* | [ sample ] |
|
||||
* | [ sample ] |
|
||||
* | [ sample ] |
|
||||
* | [ sample ] |
|
||||
* --> go to codecs
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -131,12 +159,11 @@ typedef int (*set_rate_func)(struct device *dev, int is_porta, int rate, int ena
|
||||
struct fsi_stream {
|
||||
struct snd_pcm_substream *substream;
|
||||
|
||||
int fifo_max_num;
|
||||
|
||||
int buff_offset;
|
||||
int buff_len;
|
||||
int period_len;
|
||||
int period_num;
|
||||
int fifo_sample_capa; /* sample capacity of FSI FIFO */
|
||||
int buff_sample_capa; /* sample capacity of ALSA buffer */
|
||||
int buff_sample_pos; /* sample position of ALSA buffer */
|
||||
int period_samples; /* sample number / 1 period */
|
||||
int period_pos; /* current period position */
|
||||
|
||||
int uerr_num;
|
||||
int oerr_num;
|
||||
@ -149,17 +176,14 @@ struct fsi_priv {
|
||||
struct fsi_stream playback;
|
||||
struct fsi_stream capture;
|
||||
|
||||
u32 do_fmt;
|
||||
u32 di_fmt;
|
||||
|
||||
int chan_num:16;
|
||||
int clk_master:1;
|
||||
int spdif:1;
|
||||
|
||||
long rate;
|
||||
|
||||
/* for suspend/resume */
|
||||
u32 saved_do_fmt;
|
||||
u32 saved_di_fmt;
|
||||
u32 saved_ckg1;
|
||||
u32 saved_ckg2;
|
||||
u32 saved_out_sel;
|
||||
};
|
||||
|
||||
struct fsi_core {
|
||||
@ -180,14 +204,6 @@ struct fsi_master {
|
||||
struct fsi_core *core;
|
||||
struct sh_fsi_platform_info *info;
|
||||
spinlock_t lock;
|
||||
|
||||
/* for suspend/resume */
|
||||
u32 saved_a_mclk;
|
||||
u32 saved_b_mclk;
|
||||
u32 saved_iemsk;
|
||||
u32 saved_imsk;
|
||||
u32 saved_clk_rst;
|
||||
u32 saved_soft_rst;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -271,6 +287,11 @@ static int fsi_is_port_a(struct fsi_priv *fsi)
|
||||
return fsi->master->base == fsi->base;
|
||||
}
|
||||
|
||||
static int fsi_is_spdif(struct fsi_priv *fsi)
|
||||
{
|
||||
return fsi->spdif;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
@ -342,28 +363,59 @@ static u32 fsi_get_port_shift(struct fsi_priv *fsi, int is_play)
|
||||
return shift;
|
||||
}
|
||||
|
||||
static void fsi_stream_push(struct fsi_priv *fsi,
|
||||
int is_play,
|
||||
struct snd_pcm_substream *substream,
|
||||
u32 buffer_len,
|
||||
u32 period_len)
|
||||
static int fsi_frame2sample(struct fsi_priv *fsi, int frames)
|
||||
{
|
||||
return frames * fsi->chan_num;
|
||||
}
|
||||
|
||||
static int fsi_sample2frame(struct fsi_priv *fsi, int samples)
|
||||
{
|
||||
return samples / fsi->chan_num;
|
||||
}
|
||||
|
||||
static int fsi_stream_is_working(struct fsi_priv *fsi,
|
||||
int is_play)
|
||||
{
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, is_play);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&master->lock, flags);
|
||||
ret = !!io->substream;
|
||||
spin_unlock_irqrestore(&master->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fsi_stream_push(struct fsi_priv *fsi,
|
||||
int is_play,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, is_play);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&master->lock, flags);
|
||||
io->substream = substream;
|
||||
io->buff_len = buffer_len;
|
||||
io->buff_offset = 0;
|
||||
io->period_len = period_len;
|
||||
io->period_num = 0;
|
||||
io->buff_sample_capa = fsi_frame2sample(fsi, runtime->buffer_size);
|
||||
io->buff_sample_pos = 0;
|
||||
io->period_samples = fsi_frame2sample(fsi, runtime->period_size);
|
||||
io->period_pos = 0;
|
||||
io->oerr_num = -1; /* ignore 1st err */
|
||||
io->uerr_num = -1; /* ignore 1st err */
|
||||
spin_unlock_irqrestore(&master->lock, flags);
|
||||
}
|
||||
|
||||
static void fsi_stream_pop(struct fsi_priv *fsi, int is_play)
|
||||
{
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, is_play);
|
||||
struct snd_soc_dai *dai = fsi_get_dai(io->substream);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&master->lock, flags);
|
||||
|
||||
if (io->oerr_num > 0)
|
||||
dev_err(dai->dev, "over_run = %d\n", io->oerr_num);
|
||||
@ -372,47 +424,27 @@ static void fsi_stream_pop(struct fsi_priv *fsi, int is_play)
|
||||
dev_err(dai->dev, "under_run = %d\n", io->uerr_num);
|
||||
|
||||
io->substream = NULL;
|
||||
io->buff_len = 0;
|
||||
io->buff_offset = 0;
|
||||
io->period_len = 0;
|
||||
io->period_num = 0;
|
||||
io->buff_sample_capa = 0;
|
||||
io->buff_sample_pos = 0;
|
||||
io->period_samples = 0;
|
||||
io->period_pos = 0;
|
||||
io->oerr_num = 0;
|
||||
io->uerr_num = 0;
|
||||
spin_unlock_irqrestore(&master->lock, flags);
|
||||
}
|
||||
|
||||
static int fsi_get_fifo_data_num(struct fsi_priv *fsi, int is_play)
|
||||
static int fsi_get_current_fifo_samples(struct fsi_priv *fsi, int is_play)
|
||||
{
|
||||
u32 status;
|
||||
int data_num;
|
||||
int frames;
|
||||
|
||||
status = is_play ?
|
||||
fsi_reg_read(fsi, DOFF_ST) :
|
||||
fsi_reg_read(fsi, DIFF_ST);
|
||||
|
||||
data_num = 0x1ff & (status >> 8);
|
||||
data_num *= fsi->chan_num;
|
||||
frames = 0x1ff & (status >> 8);
|
||||
|
||||
return data_num;
|
||||
}
|
||||
|
||||
static int fsi_len2num(int len, int width)
|
||||
{
|
||||
return len / width;
|
||||
}
|
||||
|
||||
#define fsi_num2offset(a, b) fsi_num2len(a, b)
|
||||
static int fsi_num2len(int num, int width)
|
||||
{
|
||||
return num * width;
|
||||
}
|
||||
|
||||
static int fsi_get_frame_width(struct fsi_priv *fsi, int is_play)
|
||||
{
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, is_play);
|
||||
struct snd_pcm_substream *substream = io->substream;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
return frames_to_bytes(runtime, 1) / fsi->chan_num;
|
||||
return fsi_frame2sample(fsi, frames);
|
||||
}
|
||||
|
||||
static void fsi_count_fifo_err(struct fsi_priv *fsi)
|
||||
@ -444,8 +476,10 @@ static u8 *fsi_dma_get_area(struct fsi_priv *fsi, int stream)
|
||||
{
|
||||
int is_play = fsi_stream_is_play(stream);
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, is_play);
|
||||
struct snd_pcm_runtime *runtime = io->substream->runtime;
|
||||
|
||||
return io->substream->runtime->dma_area + io->buff_offset;
|
||||
return runtime->dma_area +
|
||||
samples_to_bytes(runtime, io->buff_sample_pos);
|
||||
}
|
||||
|
||||
static void fsi_dma_soft_push16(struct fsi_priv *fsi, int num)
|
||||
@ -559,37 +593,94 @@ static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable)
|
||||
/*
|
||||
* clock function
|
||||
*/
|
||||
#define fsi_module_init(m, d) __fsi_module_clk_ctrl(m, d, 1)
|
||||
#define fsi_module_kill(m, d) __fsi_module_clk_ctrl(m, d, 0)
|
||||
static void __fsi_module_clk_ctrl(struct fsi_master *master,
|
||||
struct device *dev,
|
||||
int enable)
|
||||
{
|
||||
pm_runtime_get_sync(dev);
|
||||
|
||||
if (enable) {
|
||||
/* enable only SR */
|
||||
fsi_master_mask_set(master, SOFT_RST, FSISR, FSISR);
|
||||
fsi_master_mask_set(master, SOFT_RST, PASR | PBSR, 0);
|
||||
} else {
|
||||
/* clear all registers */
|
||||
fsi_master_mask_set(master, SOFT_RST, FSISR, 0);
|
||||
}
|
||||
|
||||
pm_runtime_put_sync(dev);
|
||||
}
|
||||
|
||||
#define fsi_port_start(f) __fsi_port_clk_ctrl(f, 1)
|
||||
#define fsi_port_stop(f) __fsi_port_clk_ctrl(f, 0)
|
||||
static void __fsi_port_clk_ctrl(struct fsi_priv *fsi, int enable)
|
||||
static int fsi_set_master_clk(struct device *dev, struct fsi_priv *fsi,
|
||||
long rate, int enable)
|
||||
{
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
u32 soft = fsi_is_port_a(fsi) ? PASR : PBSR;
|
||||
u32 clk = fsi_is_port_a(fsi) ? CRA : CRB;
|
||||
int is_master = fsi_is_clk_master(fsi);
|
||||
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
||||
int fsi_ver = master->core->ver;
|
||||
int ret;
|
||||
|
||||
fsi_master_mask_set(master, SOFT_RST, soft, (enable) ? soft : 0);
|
||||
if (is_master)
|
||||
ret = set_rate(dev, fsi_is_port_a(fsi), rate, enable);
|
||||
if (ret < 0) /* error */
|
||||
return ret;
|
||||
|
||||
if (!enable)
|
||||
return 0;
|
||||
|
||||
if (ret > 0) {
|
||||
u32 data = 0;
|
||||
|
||||
switch (ret & SH_FSI_ACKMD_MASK) {
|
||||
default:
|
||||
/* FALL THROUGH */
|
||||
case SH_FSI_ACKMD_512:
|
||||
data |= (0x0 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_256:
|
||||
data |= (0x1 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_128:
|
||||
data |= (0x2 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_64:
|
||||
data |= (0x3 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_32:
|
||||
if (fsi_ver < 2)
|
||||
dev_err(dev, "unsupported ACKMD\n");
|
||||
else
|
||||
data |= (0x4 << 12);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ret & SH_FSI_BPFMD_MASK) {
|
||||
default:
|
||||
/* FALL THROUGH */
|
||||
case SH_FSI_BPFMD_32:
|
||||
data |= (0x0 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_64:
|
||||
data |= (0x1 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_128:
|
||||
data |= (0x2 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_256:
|
||||
data |= (0x3 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_512:
|
||||
data |= (0x4 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_16:
|
||||
if (fsi_ver < 2)
|
||||
dev_err(dev, "unsupported ACKMD\n");
|
||||
else
|
||||
data |= (0x7 << 8);
|
||||
break;
|
||||
}
|
||||
|
||||
fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data);
|
||||
udelay(10);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define fsi_port_start(f, i) __fsi_port_clk_ctrl(f, i, 1)
|
||||
#define fsi_port_stop(f, i) __fsi_port_clk_ctrl(f, i, 0)
|
||||
static void __fsi_port_clk_ctrl(struct fsi_priv *fsi, int is_play, int enable)
|
||||
{
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
u32 clk = fsi_is_port_a(fsi) ? CRA : CRB;
|
||||
|
||||
if (enable)
|
||||
fsi_irq_enable(fsi, is_play);
|
||||
else
|
||||
fsi_irq_disable(fsi, is_play);
|
||||
|
||||
if (fsi_is_clk_master(fsi))
|
||||
fsi_master_mask_set(master, CLK_RST, clk, (enable) ? clk : 0);
|
||||
}
|
||||
|
||||
@ -598,18 +689,19 @@ static void __fsi_port_clk_ctrl(struct fsi_priv *fsi, int enable)
|
||||
*/
|
||||
static void fsi_fifo_init(struct fsi_priv *fsi,
|
||||
int is_play,
|
||||
struct snd_soc_dai *dai)
|
||||
struct device *dev)
|
||||
{
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, is_play);
|
||||
u32 shift, i;
|
||||
int frame_capa;
|
||||
|
||||
/* get on-chip RAM capacity */
|
||||
shift = fsi_master_read(master, FIFO_SZ);
|
||||
shift >>= fsi_get_port_shift(fsi, is_play);
|
||||
shift &= FIFO_SZ_MASK;
|
||||
io->fifo_max_num = 256 << shift;
|
||||
dev_dbg(dai->dev, "fifo = %d words\n", io->fifo_max_num);
|
||||
frame_capa = 256 << shift;
|
||||
dev_dbg(dev, "fifo = %d words\n", frame_capa);
|
||||
|
||||
/*
|
||||
* The maximum number of sample data varies depending
|
||||
@ -631,9 +723,11 @@ static void fsi_fifo_init(struct fsi_priv *fsi,
|
||||
* 8 channels: 32 ( 32 x 8 = 256)
|
||||
*/
|
||||
for (i = 1; i < fsi->chan_num; i <<= 1)
|
||||
io->fifo_max_num >>= 1;
|
||||
dev_dbg(dai->dev, "%d channel %d store\n",
|
||||
fsi->chan_num, io->fifo_max_num);
|
||||
frame_capa >>= 1;
|
||||
dev_dbg(dev, "%d channel %d store\n",
|
||||
fsi->chan_num, frame_capa);
|
||||
|
||||
io->fifo_sample_capa = fsi_frame2sample(fsi, frame_capa);
|
||||
|
||||
/*
|
||||
* set interrupt generation factor
|
||||
@ -654,10 +748,10 @@ static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int stream)
|
||||
struct snd_pcm_substream *substream = NULL;
|
||||
int is_play = fsi_stream_is_play(stream);
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, is_play);
|
||||
int data_residue_num;
|
||||
int data_num;
|
||||
int data_num_max;
|
||||
int ch_width;
|
||||
int sample_residues;
|
||||
int sample_width;
|
||||
int samples;
|
||||
int samples_max;
|
||||
int over_period;
|
||||
void (*fn)(struct fsi_priv *fsi, int size);
|
||||
|
||||
@ -673,36 +767,35 @@ static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int stream)
|
||||
/* FSI FIFO has limit.
|
||||
* So, this driver can not send periods data at a time
|
||||
*/
|
||||
if (io->buff_offset >=
|
||||
fsi_num2offset(io->period_num + 1, io->period_len)) {
|
||||
if (io->buff_sample_pos >=
|
||||
io->period_samples * (io->period_pos + 1)) {
|
||||
|
||||
over_period = 1;
|
||||
io->period_num = (io->period_num + 1) % runtime->periods;
|
||||
io->period_pos = (io->period_pos + 1) % runtime->periods;
|
||||
|
||||
if (0 == io->period_num)
|
||||
io->buff_offset = 0;
|
||||
if (0 == io->period_pos)
|
||||
io->buff_sample_pos = 0;
|
||||
}
|
||||
|
||||
/* get 1 channel data width */
|
||||
ch_width = fsi_get_frame_width(fsi, is_play);
|
||||
/* get 1 sample data width */
|
||||
sample_width = samples_to_bytes(runtime, 1);
|
||||
|
||||
/* get residue data number of alsa */
|
||||
data_residue_num = fsi_len2num(io->buff_len - io->buff_offset,
|
||||
ch_width);
|
||||
/* get number of residue samples */
|
||||
sample_residues = io->buff_sample_capa - io->buff_sample_pos;
|
||||
|
||||
if (is_play) {
|
||||
/*
|
||||
* for play-back
|
||||
*
|
||||
* data_num_max : number of FSI fifo free space
|
||||
* data_num : number of ALSA residue data
|
||||
* samples_max : number of FSI fifo free samples space
|
||||
* samples : number of ALSA residue samples
|
||||
*/
|
||||
data_num_max = io->fifo_max_num * fsi->chan_num;
|
||||
data_num_max -= fsi_get_fifo_data_num(fsi, is_play);
|
||||
samples_max = io->fifo_sample_capa;
|
||||
samples_max -= fsi_get_current_fifo_samples(fsi, is_play);
|
||||
|
||||
data_num = data_residue_num;
|
||||
samples = sample_residues;
|
||||
|
||||
switch (ch_width) {
|
||||
switch (sample_width) {
|
||||
case 2:
|
||||
fn = fsi_dma_soft_push16;
|
||||
break;
|
||||
@ -716,13 +809,13 @@ static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int stream)
|
||||
/*
|
||||
* for capture
|
||||
*
|
||||
* data_num_max : number of ALSA free space
|
||||
* data_num : number of data in FSI fifo
|
||||
* samples_max : number of ALSA free samples space
|
||||
* samples : number of samples in FSI fifo
|
||||
*/
|
||||
data_num_max = data_residue_num;
|
||||
data_num = fsi_get_fifo_data_num(fsi, is_play);
|
||||
samples_max = sample_residues;
|
||||
samples = fsi_get_current_fifo_samples(fsi, is_play);
|
||||
|
||||
switch (ch_width) {
|
||||
switch (sample_width) {
|
||||
case 2:
|
||||
fn = fsi_dma_soft_pop16;
|
||||
break;
|
||||
@ -734,12 +827,12 @@ static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int stream)
|
||||
}
|
||||
}
|
||||
|
||||
data_num = min(data_num, data_num_max);
|
||||
samples = min(samples, samples_max);
|
||||
|
||||
fn(fsi, data_num);
|
||||
fn(fsi, samples);
|
||||
|
||||
/* update buff_offset */
|
||||
io->buff_offset += fsi_num2offset(data_num, ch_width);
|
||||
/* update buff_sample_pos */
|
||||
io->buff_sample_pos += samples;
|
||||
|
||||
if (over_period)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
@ -788,16 +881,20 @@ static irqreturn_t fsi_interrupt(int irq, void *data)
|
||||
* dai ops
|
||||
*/
|
||||
|
||||
static int fsi_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
static int fsi_hw_startup(struct fsi_priv *fsi,
|
||||
int is_play,
|
||||
struct device *dev)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
u32 flags = fsi_get_info_flags(fsi);
|
||||
u32 data;
|
||||
int is_play = fsi_is_play(substream);
|
||||
u32 data = 0;
|
||||
|
||||
pm_runtime_get_sync(dai->dev);
|
||||
pm_runtime_get_sync(dev);
|
||||
|
||||
/* clock setting */
|
||||
if (fsi_is_clk_master(fsi))
|
||||
data = DIMD | DOMD;
|
||||
|
||||
fsi_reg_mask_set(fsi, CKG1, (DIMD | DOMD), data);
|
||||
|
||||
/* clock inversion (CKG2) */
|
||||
data = 0;
|
||||
@ -812,54 +909,70 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream,
|
||||
|
||||
fsi_reg_write(fsi, CKG2, data);
|
||||
|
||||
/* set format */
|
||||
fsi_reg_write(fsi, DO_FMT, fsi->do_fmt);
|
||||
fsi_reg_write(fsi, DI_FMT, fsi->di_fmt);
|
||||
|
||||
/* spdif ? */
|
||||
if (fsi_is_spdif(fsi)) {
|
||||
fsi_spdif_clk_ctrl(fsi, 1);
|
||||
fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD);
|
||||
}
|
||||
|
||||
/* irq clear */
|
||||
fsi_irq_disable(fsi, is_play);
|
||||
fsi_irq_clear_status(fsi);
|
||||
|
||||
/* fifo init */
|
||||
fsi_fifo_init(fsi, is_play, dai);
|
||||
fsi_fifo_init(fsi, is_play, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fsi_hw_shutdown(struct fsi_priv *fsi,
|
||||
int is_play,
|
||||
struct device *dev)
|
||||
{
|
||||
if (fsi_is_clk_master(fsi))
|
||||
fsi_set_master_clk(dev, fsi, fsi->rate, 0);
|
||||
|
||||
pm_runtime_put_sync(dev);
|
||||
}
|
||||
|
||||
static int fsi_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
int is_play = fsi_is_play(substream);
|
||||
|
||||
return fsi_hw_startup(fsi, is_play, dai->dev);
|
||||
}
|
||||
|
||||
static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
int is_play = fsi_is_play(substream);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
||||
|
||||
fsi_irq_disable(fsi, is_play);
|
||||
|
||||
if (fsi_is_clk_master(fsi))
|
||||
set_rate(dai->dev, fsi_is_port_a(fsi), fsi->rate, 0);
|
||||
|
||||
fsi_hw_shutdown(fsi, is_play, dai->dev);
|
||||
fsi->rate = 0;
|
||||
|
||||
pm_runtime_put_sync(dai->dev);
|
||||
}
|
||||
|
||||
static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int is_play = fsi_is_play(substream);
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
fsi_stream_push(fsi, is_play, substream,
|
||||
frames_to_bytes(runtime, runtime->buffer_size),
|
||||
frames_to_bytes(runtime, runtime->period_size));
|
||||
fsi_stream_push(fsi, is_play, substream);
|
||||
ret = is_play ? fsi_data_push(fsi) : fsi_data_pop(fsi);
|
||||
fsi_irq_enable(fsi, is_play);
|
||||
fsi_port_start(fsi);
|
||||
fsi_port_start(fsi, is_play);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
fsi_port_stop(fsi);
|
||||
fsi_irq_disable(fsi, is_play);
|
||||
fsi_port_stop(fsi, is_play);
|
||||
fsi_stream_pop(fsi, is_play);
|
||||
break;
|
||||
}
|
||||
@ -884,8 +997,8 @@ static int fsi_set_fmt_dai(struct fsi_priv *fsi, unsigned int fmt)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fsi_reg_write(fsi, DO_FMT, data);
|
||||
fsi_reg_write(fsi, DI_FMT, data);
|
||||
fsi->do_fmt = data;
|
||||
fsi->di_fmt = data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -900,11 +1013,10 @@ static int fsi_set_fmt_spdif(struct fsi_priv *fsi)
|
||||
|
||||
data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM;
|
||||
fsi->chan_num = 2;
|
||||
fsi_spdif_clk_ctrl(fsi, 1);
|
||||
fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD);
|
||||
fsi->spdif = 1;
|
||||
|
||||
fsi_reg_write(fsi, DO_FMT, data);
|
||||
fsi_reg_write(fsi, DI_FMT, data);
|
||||
fsi->do_fmt = data;
|
||||
fsi->di_fmt = data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -915,32 +1027,24 @@ static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
||||
u32 flags = fsi_get_info_flags(fsi);
|
||||
u32 data = 0;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(dai->dev);
|
||||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
data = DIMD | DOMD;
|
||||
fsi->clk_master = 1;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto set_fmt_exit;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fsi_is_clk_master(fsi) && !set_rate) {
|
||||
dev_err(dai->dev, "platform doesn't have set_rate\n");
|
||||
ret = -EINVAL;
|
||||
goto set_fmt_exit;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fsi_reg_mask_set(fsi, CKG1, (DIMD | DOMD), data);
|
||||
|
||||
/* set format */
|
||||
switch (flags & SH_FSI_FMT_MASK) {
|
||||
case SH_FSI_FMT_DAI:
|
||||
@ -953,9 +1057,6 @@ static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
set_fmt_exit:
|
||||
pm_runtime_put_sync(dai->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -964,79 +1065,19 @@ static int fsi_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
||||
int fsi_ver = master->core->ver;
|
||||
long rate = params_rate(params);
|
||||
int ret;
|
||||
|
||||
if (!fsi_is_clk_master(fsi))
|
||||
return 0;
|
||||
|
||||
ret = set_rate(dai->dev, fsi_is_port_a(fsi), rate, 1);
|
||||
if (ret < 0) /* error */
|
||||
ret = fsi_set_master_clk(dai->dev, fsi, rate, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fsi->rate = rate;
|
||||
if (ret > 0) {
|
||||
u32 data = 0;
|
||||
|
||||
switch (ret & SH_FSI_ACKMD_MASK) {
|
||||
default:
|
||||
/* FALL THROUGH */
|
||||
case SH_FSI_ACKMD_512:
|
||||
data |= (0x0 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_256:
|
||||
data |= (0x1 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_128:
|
||||
data |= (0x2 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_64:
|
||||
data |= (0x3 << 12);
|
||||
break;
|
||||
case SH_FSI_ACKMD_32:
|
||||
if (fsi_ver < 2)
|
||||
dev_err(dai->dev, "unsupported ACKMD\n");
|
||||
else
|
||||
data |= (0x4 << 12);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ret & SH_FSI_BPFMD_MASK) {
|
||||
default:
|
||||
/* FALL THROUGH */
|
||||
case SH_FSI_BPFMD_32:
|
||||
data |= (0x0 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_64:
|
||||
data |= (0x1 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_128:
|
||||
data |= (0x2 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_256:
|
||||
data |= (0x3 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_512:
|
||||
data |= (0x4 << 8);
|
||||
break;
|
||||
case SH_FSI_BPFMD_16:
|
||||
if (fsi_ver < 2)
|
||||
dev_err(dai->dev, "unsupported ACKMD\n");
|
||||
else
|
||||
data |= (0x7 << 8);
|
||||
break;
|
||||
}
|
||||
|
||||
fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data);
|
||||
udelay(10);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops fsi_dai_ops = {
|
||||
@ -1097,16 +1138,14 @@ static int fsi_hw_free(struct snd_pcm_substream *substream)
|
||||
|
||||
static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
struct fsi_stream *io = fsi_get_stream(fsi, fsi_is_play(substream));
|
||||
long location;
|
||||
int samples_pos = io->buff_sample_pos - 1;
|
||||
|
||||
location = (io->buff_offset - 1);
|
||||
if (location < 0)
|
||||
location = 0;
|
||||
if (samples_pos < 0)
|
||||
samples_pos = 0;
|
||||
|
||||
return bytes_to_frames(runtime, location);
|
||||
return fsi_sample2frame(fsi, samples_pos);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops fsi_pcm_ops = {
|
||||
@ -1129,10 +1168,10 @@ static void fsi_pcm_free(struct snd_pcm *pcm)
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
static int fsi_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int fsi_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
|
||||
/*
|
||||
* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
|
||||
* in MMAP mode (i.e. aplay -M)
|
||||
@ -1246,8 +1285,6 @@ static int fsi_probe(struct platform_device *pdev)
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
dev_set_drvdata(&pdev->dev, master);
|
||||
|
||||
fsi_module_init(master, &pdev->dev);
|
||||
|
||||
ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED,
|
||||
id_entry->name, master);
|
||||
if (ret) {
|
||||
@ -1290,8 +1327,6 @@ static int fsi_remove(struct platform_device *pdev)
|
||||
|
||||
master = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
fsi_module_kill(master, &pdev->dev);
|
||||
|
||||
free_irq(master->irq, master);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
@ -1305,53 +1340,43 @@ static int fsi_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static void __fsi_suspend(struct fsi_priv *fsi,
|
||||
struct device *dev,
|
||||
set_rate_func set_rate)
|
||||
int is_play,
|
||||
struct device *dev)
|
||||
{
|
||||
fsi->saved_do_fmt = fsi_reg_read(fsi, DO_FMT);
|
||||
fsi->saved_di_fmt = fsi_reg_read(fsi, DI_FMT);
|
||||
fsi->saved_ckg1 = fsi_reg_read(fsi, CKG1);
|
||||
fsi->saved_ckg2 = fsi_reg_read(fsi, CKG2);
|
||||
fsi->saved_out_sel = fsi_reg_read(fsi, OUT_SEL);
|
||||
if (!fsi_stream_is_working(fsi, is_play))
|
||||
return;
|
||||
|
||||
if (fsi_is_clk_master(fsi))
|
||||
set_rate(dev, fsi_is_port_a(fsi), fsi->rate, 0);
|
||||
fsi_port_stop(fsi, is_play);
|
||||
fsi_hw_shutdown(fsi, is_play, dev);
|
||||
}
|
||||
|
||||
static void __fsi_resume(struct fsi_priv *fsi,
|
||||
struct device *dev,
|
||||
set_rate_func set_rate)
|
||||
int is_play,
|
||||
struct device *dev)
|
||||
{
|
||||
fsi_reg_write(fsi, DO_FMT, fsi->saved_do_fmt);
|
||||
fsi_reg_write(fsi, DI_FMT, fsi->saved_di_fmt);
|
||||
fsi_reg_write(fsi, CKG1, fsi->saved_ckg1);
|
||||
fsi_reg_write(fsi, CKG2, fsi->saved_ckg2);
|
||||
fsi_reg_write(fsi, OUT_SEL, fsi->saved_out_sel);
|
||||
if (!fsi_stream_is_working(fsi, is_play))
|
||||
return;
|
||||
|
||||
fsi_hw_startup(fsi, is_play, dev);
|
||||
|
||||
if (fsi_is_clk_master(fsi) && fsi->rate)
|
||||
fsi_set_master_clk(dev, fsi, fsi->rate, 1);
|
||||
|
||||
fsi_port_start(fsi, is_play);
|
||||
|
||||
if (fsi_is_clk_master(fsi))
|
||||
set_rate(dev, fsi_is_port_a(fsi), fsi->rate, 1);
|
||||
}
|
||||
|
||||
static int fsi_suspend(struct device *dev)
|
||||
{
|
||||
struct fsi_master *master = dev_get_drvdata(dev);
|
||||
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
||||
struct fsi_priv *fsia = &master->fsia;
|
||||
struct fsi_priv *fsib = &master->fsib;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
__fsi_suspend(fsia, 1, dev);
|
||||
__fsi_suspend(fsia, 0, dev);
|
||||
|
||||
__fsi_suspend(&master->fsia, dev, set_rate);
|
||||
__fsi_suspend(&master->fsib, dev, set_rate);
|
||||
|
||||
master->saved_a_mclk = fsi_core_read(master, a_mclk);
|
||||
master->saved_b_mclk = fsi_core_read(master, b_mclk);
|
||||
master->saved_iemsk = fsi_core_read(master, iemsk);
|
||||
master->saved_imsk = fsi_core_read(master, imsk);
|
||||
master->saved_clk_rst = fsi_master_read(master, CLK_RST);
|
||||
master->saved_soft_rst = fsi_master_read(master, SOFT_RST);
|
||||
|
||||
fsi_module_kill(master, dev);
|
||||
|
||||
pm_runtime_put_sync(dev);
|
||||
__fsi_suspend(fsib, 1, dev);
|
||||
__fsi_suspend(fsib, 0, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1359,23 +1384,14 @@ static int fsi_suspend(struct device *dev)
|
||||
static int fsi_resume(struct device *dev)
|
||||
{
|
||||
struct fsi_master *master = dev_get_drvdata(dev);
|
||||
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
||||
struct fsi_priv *fsia = &master->fsia;
|
||||
struct fsi_priv *fsib = &master->fsib;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
__fsi_resume(fsia, 1, dev);
|
||||
__fsi_resume(fsia, 0, dev);
|
||||
|
||||
fsi_module_init(master, dev);
|
||||
|
||||
fsi_master_mask_set(master, SOFT_RST, 0xffff, master->saved_soft_rst);
|
||||
fsi_master_mask_set(master, CLK_RST, 0xffff, master->saved_clk_rst);
|
||||
fsi_core_mask_set(master, a_mclk, 0xffff, master->saved_a_mclk);
|
||||
fsi_core_mask_set(master, b_mclk, 0xffff, master->saved_b_mclk);
|
||||
fsi_core_mask_set(master, iemsk, 0xffff, master->saved_iemsk);
|
||||
fsi_core_mask_set(master, imsk, 0xffff, master->saved_imsk);
|
||||
|
||||
__fsi_resume(&master->fsia, dev, set_rate);
|
||||
__fsi_resume(&master->fsib, dev, set_rate);
|
||||
|
||||
pm_runtime_put_sync(dev);
|
||||
__fsi_resume(fsib, 1, dev);
|
||||
__fsi_resume(fsib, 0, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -527,10 +527,11 @@ static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss)
|
||||
return bytes_to_frames(ss->runtime, ptr);
|
||||
}
|
||||
|
||||
static int siu_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int siu_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
/* card->dev == socdev->dev, see snd_soc_new_pcms() */
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
struct siu_info *info = siu_i2s_data;
|
||||
struct platform_device *pdev = to_platform_device(card->dev);
|
||||
int ret;
|
||||
|
@ -20,422 +20,6 @@
|
||||
|
||||
#include <trace/events/asoc.h>
|
||||
|
||||
#ifdef CONFIG_SPI_MASTER
|
||||
static int do_spi_write(void *control, const char *data, int len)
|
||||
{
|
||||
struct spi_device *spi = control;
|
||||
int ret;
|
||||
|
||||
ret = spi_write(spi, data, len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value, const void *data, int len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
||||
reg < codec->driver->reg_cache_size &&
|
||||
!codec->cache_bypass) {
|
||||
ret = snd_soc_cache_write(codec, reg, value);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (codec->cache_only) {
|
||||
codec->cache_sync = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = codec->hw_write(codec->control_data, data, len);
|
||||
if (ret == len)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static unsigned int do_hw_read(struct snd_soc_codec *codec, unsigned int reg)
|
||||
{
|
||||
int ret;
|
||||
unsigned int val;
|
||||
|
||||
if (reg >= codec->driver->reg_cache_size ||
|
||||
snd_soc_codec_volatile_register(codec, reg) ||
|
||||
codec->cache_bypass) {
|
||||
if (codec->cache_only)
|
||||
return -1;
|
||||
|
||||
BUG_ON(!codec->hw_read);
|
||||
return codec->hw_read(codec, reg);
|
||||
}
|
||||
|
||||
ret = snd_soc_cache_read(codec, reg, &val);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
return val;
|
||||
}
|
||||
|
||||
static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return do_hw_read(codec, reg);
|
||||
}
|
||||
|
||||
static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u16 data;
|
||||
|
||||
data = cpu_to_be16((reg << 12) | (value & 0xffffff));
|
||||
|
||||
return do_hw_write(codec, reg, value, &data, 2);
|
||||
}
|
||||
|
||||
static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return do_hw_read(codec, reg);
|
||||
}
|
||||
|
||||
static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 2);
|
||||
}
|
||||
|
||||
static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
reg &= 0xff;
|
||||
data[0] = reg;
|
||||
data[1] = value & 0xff;
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 2);
|
||||
}
|
||||
|
||||
static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return do_hw_read(codec, reg);
|
||||
}
|
||||
|
||||
static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
|
||||
data[0] = reg;
|
||||
data[1] = (value >> 8) & 0xff;
|
||||
data[2] = value & 0xff;
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 3);
|
||||
}
|
||||
|
||||
static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return do_hw_read(codec, reg);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int do_i2c_read(struct snd_soc_codec *codec,
|
||||
void *reg, int reglen,
|
||||
void *data, int datalen)
|
||||
{
|
||||
struct i2c_msg xfer[2];
|
||||
int ret;
|
||||
struct i2c_client *client = codec->control_data;
|
||||
|
||||
/* Write register */
|
||||
xfer[0].addr = client->addr;
|
||||
xfer[0].flags = 0;
|
||||
xfer[0].len = reglen;
|
||||
xfer[0].buf = reg;
|
||||
|
||||
/* Read data */
|
||||
xfer[1].addr = client->addr;
|
||||
xfer[1].flags = I2C_M_RD;
|
||||
xfer[1].len = datalen;
|
||||
xfer[1].buf = data;
|
||||
|
||||
ret = i2c_transfer(client->adapter, xfer, 2);
|
||||
if (ret == 2)
|
||||
return 0;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u8 reg = r;
|
||||
u8 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 1, &data, 1);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return data;
|
||||
}
|
||||
#else
|
||||
#define snd_soc_8_8_read_i2c NULL
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u8 reg = r;
|
||||
u16 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 1, &data, 2);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return (data >> 8) | ((data & 0xff) << 8);
|
||||
}
|
||||
#else
|
||||
#define snd_soc_8_16_read_i2c NULL
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u16 reg = r;
|
||||
u8 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 2, &data, 1);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return data;
|
||||
}
|
||||
#else
|
||||
#define snd_soc_16_8_read_i2c NULL
|
||||
#endif
|
||||
|
||||
static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return do_hw_read(codec, reg);
|
||||
}
|
||||
|
||||
static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
|
||||
data[0] = (reg >> 8) & 0xff;
|
||||
data[1] = reg & 0xff;
|
||||
data[2] = value;
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 3);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u16 reg = cpu_to_be16(r);
|
||||
u16 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 2, &data, 2);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return be16_to_cpu(data);
|
||||
}
|
||||
#else
|
||||
#define snd_soc_16_16_read_i2c NULL
|
||||
#endif
|
||||
|
||||
static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return do_hw_read(codec, reg);
|
||||
}
|
||||
|
||||
static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[4];
|
||||
|
||||
data[0] = (reg >> 8) & 0xff;
|
||||
data[1] = reg & 0xff;
|
||||
data[2] = (value >> 8) & 0xff;
|
||||
data[3] = value & 0xff;
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 4);
|
||||
}
|
||||
|
||||
/* Primitive bulk write support for soc-cache. The data pointed to by
|
||||
* `data' needs to already be in the form the hardware expects
|
||||
* including any leading register specific data. Any data written
|
||||
* through this function will not go through the cache as it only
|
||||
* handles writing to volatile or out of bounds registers.
|
||||
*/
|
||||
static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, unsigned int reg,
|
||||
const void *data, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* To ensure that we don't get out of sync with the cache, check
|
||||
* whether the base register is volatile or if we've directly asked
|
||||
* to bypass the cache. Out of bounds registers are considered
|
||||
* volatile.
|
||||
*/
|
||||
if (!codec->cache_bypass
|
||||
&& !snd_soc_codec_volatile_register(codec, reg)
|
||||
&& reg < codec->driver->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
switch (codec->control_type) {
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
case SND_SOC_I2C:
|
||||
ret = i2c_master_send(codec->control_data, data, len);
|
||||
break;
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
case SND_SOC_SPI:
|
||||
ret = spi_write(codec->control_data, data, len);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (ret == len)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static struct {
|
||||
int addr_bits;
|
||||
int data_bits;
|
||||
int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int);
|
||||
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
|
||||
unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
|
||||
} io_types[] = {
|
||||
{
|
||||
.addr_bits = 4, .data_bits = 12,
|
||||
.write = snd_soc_4_12_write, .read = snd_soc_4_12_read,
|
||||
},
|
||||
{
|
||||
.addr_bits = 7, .data_bits = 9,
|
||||
.write = snd_soc_7_9_write, .read = snd_soc_7_9_read,
|
||||
},
|
||||
{
|
||||
.addr_bits = 8, .data_bits = 8,
|
||||
.write = snd_soc_8_8_write, .read = snd_soc_8_8_read,
|
||||
.i2c_read = snd_soc_8_8_read_i2c,
|
||||
},
|
||||
{
|
||||
.addr_bits = 8, .data_bits = 16,
|
||||
.write = snd_soc_8_16_write, .read = snd_soc_8_16_read,
|
||||
.i2c_read = snd_soc_8_16_read_i2c,
|
||||
},
|
||||
{
|
||||
.addr_bits = 16, .data_bits = 8,
|
||||
.write = snd_soc_16_8_write, .read = snd_soc_16_8_read,
|
||||
.i2c_read = snd_soc_16_8_read_i2c,
|
||||
},
|
||||
{
|
||||
.addr_bits = 16, .data_bits = 16,
|
||||
.write = snd_soc_16_16_write, .read = snd_soc_16_16_read,
|
||||
.i2c_read = snd_soc_16_16_read_i2c,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* snd_soc_codec_set_cache_io: Set up standard I/O functions.
|
||||
*
|
||||
* @codec: CODEC to configure.
|
||||
* @addr_bits: Number of bits of register address data.
|
||||
* @data_bits: Number of bits of data per register.
|
||||
* @control: Control bus used.
|
||||
*
|
||||
* Register formats are frequently shared between many I2C and SPI
|
||||
* devices. In order to promote code reuse the ASoC core provides
|
||||
* some standard implementations of CODEC read and write operations
|
||||
* which can be set up using this function.
|
||||
*
|
||||
* The caller is responsible for allocating and initialising the
|
||||
* actual cache.
|
||||
*
|
||||
* Note that at present this code cannot be used by CODECs with
|
||||
* volatile registers.
|
||||
*/
|
||||
int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
|
||||
int addr_bits, int data_bits,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(io_types); i++)
|
||||
if (io_types[i].addr_bits == addr_bits &&
|
||||
io_types[i].data_bits == data_bits)
|
||||
break;
|
||||
if (i == ARRAY_SIZE(io_types)) {
|
||||
printk(KERN_ERR
|
||||
"No I/O functions for %d bit address %d bit data\n",
|
||||
addr_bits, data_bits);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec->write = io_types[i].write;
|
||||
codec->read = io_types[i].read;
|
||||
codec->bulk_write_raw = snd_soc_hw_bulk_write_raw;
|
||||
|
||||
switch (control) {
|
||||
case SND_SOC_I2C:
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
#endif
|
||||
if (io_types[i].i2c_read)
|
||||
codec->hw_read = io_types[i].i2c_read;
|
||||
|
||||
codec->control_data = container_of(codec->dev,
|
||||
struct i2c_client,
|
||||
dev);
|
||||
break;
|
||||
|
||||
case SND_SOC_SPI:
|
||||
#ifdef CONFIG_SPI_MASTER
|
||||
codec->hw_write = do_spi_write;
|
||||
#endif
|
||||
|
||||
codec->control_data = container_of(codec->dev,
|
||||
struct spi_device,
|
||||
dev);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
|
||||
|
||||
static bool snd_soc_set_cache_val(void *base, unsigned int idx,
|
||||
unsigned int val, unsigned int word_size)
|
||||
{
|
||||
@ -483,31 +67,86 @@ static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx,
|
||||
}
|
||||
|
||||
struct snd_soc_rbtree_node {
|
||||
struct rb_node node;
|
||||
unsigned int reg;
|
||||
unsigned int value;
|
||||
unsigned int defval;
|
||||
struct rb_node node; /* the actual rbtree node holding this block */
|
||||
unsigned int base_reg; /* base register handled by this block */
|
||||
unsigned int word_size; /* number of bytes needed to represent the register index */
|
||||
void *block; /* block of adjacent registers */
|
||||
unsigned int blklen; /* number of registers available in the block */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct snd_soc_rbtree_ctx {
|
||||
struct rb_root root;
|
||||
struct snd_soc_rbtree_node *cached_rbnode;
|
||||
};
|
||||
|
||||
static inline void snd_soc_rbtree_get_base_top_reg(
|
||||
struct snd_soc_rbtree_node *rbnode,
|
||||
unsigned int *base, unsigned int *top)
|
||||
{
|
||||
*base = rbnode->base_reg;
|
||||
*top = rbnode->base_reg + rbnode->blklen - 1;
|
||||
}
|
||||
|
||||
static unsigned int snd_soc_rbtree_get_register(
|
||||
struct snd_soc_rbtree_node *rbnode, unsigned int idx)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
switch (rbnode->word_size) {
|
||||
case 1: {
|
||||
u8 *p = rbnode->block;
|
||||
val = p[idx];
|
||||
return val;
|
||||
}
|
||||
case 2: {
|
||||
u16 *p = rbnode->block;
|
||||
val = p[idx];
|
||||
return val;
|
||||
}
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void snd_soc_rbtree_set_register(struct snd_soc_rbtree_node *rbnode,
|
||||
unsigned int idx, unsigned int val)
|
||||
{
|
||||
switch (rbnode->word_size) {
|
||||
case 1: {
|
||||
u8 *p = rbnode->block;
|
||||
p[idx] = val;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
u16 *p = rbnode->block;
|
||||
p[idx] = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct snd_soc_rbtree_node *snd_soc_rbtree_lookup(
|
||||
struct rb_root *root, unsigned int reg)
|
||||
{
|
||||
struct rb_node *node;
|
||||
struct snd_soc_rbtree_node *rbnode;
|
||||
unsigned int base_reg, top_reg;
|
||||
|
||||
node = root->rb_node;
|
||||
while (node) {
|
||||
rbnode = container_of(node, struct snd_soc_rbtree_node, node);
|
||||
if (rbnode->reg < reg)
|
||||
node = node->rb_left;
|
||||
else if (rbnode->reg > reg)
|
||||
node = node->rb_right;
|
||||
else
|
||||
snd_soc_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg);
|
||||
if (reg >= base_reg && reg <= top_reg)
|
||||
return rbnode;
|
||||
else if (reg > top_reg)
|
||||
node = node->rb_right;
|
||||
else if (reg < base_reg)
|
||||
node = node->rb_left;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -518,19 +157,28 @@ static int snd_soc_rbtree_insert(struct rb_root *root,
|
||||
{
|
||||
struct rb_node **new, *parent;
|
||||
struct snd_soc_rbtree_node *rbnode_tmp;
|
||||
unsigned int base_reg_tmp, top_reg_tmp;
|
||||
unsigned int base_reg;
|
||||
|
||||
parent = NULL;
|
||||
new = &root->rb_node;
|
||||
while (*new) {
|
||||
rbnode_tmp = container_of(*new, struct snd_soc_rbtree_node,
|
||||
node);
|
||||
/* base and top registers of the current rbnode */
|
||||
snd_soc_rbtree_get_base_top_reg(rbnode_tmp, &base_reg_tmp,
|
||||
&top_reg_tmp);
|
||||
/* base register of the rbnode to be added */
|
||||
base_reg = rbnode->base_reg;
|
||||
parent = *new;
|
||||
if (rbnode_tmp->reg < rbnode->reg)
|
||||
new = &((*new)->rb_left);
|
||||
else if (rbnode_tmp->reg > rbnode->reg)
|
||||
new = &((*new)->rb_right);
|
||||
else
|
||||
/* if this register has already been inserted, just return */
|
||||
if (base_reg >= base_reg_tmp &&
|
||||
base_reg <= top_reg_tmp)
|
||||
return 0;
|
||||
else if (base_reg > top_reg_tmp)
|
||||
new = &((*new)->rb_right);
|
||||
else if (base_reg < base_reg_tmp)
|
||||
new = &((*new)->rb_left);
|
||||
}
|
||||
|
||||
/* insert the node into the rbtree */
|
||||
@ -545,58 +193,146 @@ static int snd_soc_rbtree_cache_sync(struct snd_soc_codec *codec)
|
||||
struct snd_soc_rbtree_ctx *rbtree_ctx;
|
||||
struct rb_node *node;
|
||||
struct snd_soc_rbtree_node *rbnode;
|
||||
unsigned int val;
|
||||
unsigned int regtmp;
|
||||
unsigned int val, def;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
rbtree_ctx = codec->reg_cache;
|
||||
for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) {
|
||||
rbnode = rb_entry(node, struct snd_soc_rbtree_node, node);
|
||||
if (rbnode->value == rbnode->defval)
|
||||
continue;
|
||||
WARN_ON(codec->writable_register &&
|
||||
codec->writable_register(codec, rbnode->reg));
|
||||
ret = snd_soc_cache_read(codec, rbnode->reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
codec->cache_bypass = 1;
|
||||
ret = snd_soc_write(codec, rbnode->reg, val);
|
||||
codec->cache_bypass = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
|
||||
rbnode->reg, val);
|
||||
for (i = 0; i < rbnode->blklen; ++i) {
|
||||
regtmp = rbnode->base_reg + i;
|
||||
WARN_ON(codec->writable_register &&
|
||||
codec->writable_register(codec, regtmp));
|
||||
val = snd_soc_rbtree_get_register(rbnode, i);
|
||||
def = snd_soc_get_cache_val(codec->reg_def_copy, i,
|
||||
rbnode->word_size);
|
||||
if (val == def)
|
||||
continue;
|
||||
|
||||
codec->cache_bypass = 1;
|
||||
ret = snd_soc_write(codec, regtmp, val);
|
||||
codec->cache_bypass = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
|
||||
regtmp, val);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_soc_rbtree_insert_to_block(struct snd_soc_rbtree_node *rbnode,
|
||||
unsigned int pos, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 *blk;
|
||||
|
||||
blk = krealloc(rbnode->block,
|
||||
(rbnode->blklen + 1) * rbnode->word_size, GFP_KERNEL);
|
||||
if (!blk)
|
||||
return -ENOMEM;
|
||||
|
||||
/* insert the register value in the correct place in the rbnode block */
|
||||
memmove(blk + (pos + 1) * rbnode->word_size,
|
||||
blk + pos * rbnode->word_size,
|
||||
(rbnode->blklen - pos) * rbnode->word_size);
|
||||
|
||||
/* update the rbnode block, its size and the base register */
|
||||
rbnode->block = blk;
|
||||
rbnode->blklen++;
|
||||
if (!pos)
|
||||
rbnode->base_reg = reg;
|
||||
|
||||
snd_soc_rbtree_set_register(rbnode, pos, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_soc_rbtree_cache_write(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
struct snd_soc_rbtree_ctx *rbtree_ctx;
|
||||
struct snd_soc_rbtree_node *rbnode;
|
||||
struct snd_soc_rbtree_node *rbnode, *rbnode_tmp;
|
||||
struct rb_node *node;
|
||||
unsigned int val;
|
||||
unsigned int reg_tmp;
|
||||
unsigned int base_reg, top_reg;
|
||||
unsigned int pos;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
rbtree_ctx = codec->reg_cache;
|
||||
/* look up the required register in the cached rbnode */
|
||||
rbnode = rbtree_ctx->cached_rbnode;
|
||||
if (rbnode) {
|
||||
snd_soc_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg);
|
||||
if (reg >= base_reg && reg <= top_reg) {
|
||||
reg_tmp = reg - base_reg;
|
||||
val = snd_soc_rbtree_get_register(rbnode, reg_tmp);
|
||||
if (val == value)
|
||||
return 0;
|
||||
snd_soc_rbtree_set_register(rbnode, reg_tmp, value);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* if we can't locate it in the cached rbnode we'll have
|
||||
* to traverse the rbtree looking for it.
|
||||
*/
|
||||
rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg);
|
||||
if (rbnode) {
|
||||
if (rbnode->value == value)
|
||||
reg_tmp = reg - rbnode->base_reg;
|
||||
val = snd_soc_rbtree_get_register(rbnode, reg_tmp);
|
||||
if (val == value)
|
||||
return 0;
|
||||
rbnode->value = value;
|
||||
snd_soc_rbtree_set_register(rbnode, reg_tmp, value);
|
||||
rbtree_ctx->cached_rbnode = rbnode;
|
||||
} else {
|
||||
/* bail out early, no need to create the rbnode yet */
|
||||
if (!value)
|
||||
return 0;
|
||||
/*
|
||||
* for uninitialized registers whose value is changed
|
||||
* from the default zero, create an rbnode and insert
|
||||
* it into the tree.
|
||||
/* look for an adjacent register to the one we are about to add */
|
||||
for (node = rb_first(&rbtree_ctx->root); node;
|
||||
node = rb_next(node)) {
|
||||
rbnode_tmp = rb_entry(node, struct snd_soc_rbtree_node, node);
|
||||
for (i = 0; i < rbnode_tmp->blklen; ++i) {
|
||||
reg_tmp = rbnode_tmp->base_reg + i;
|
||||
if (abs(reg_tmp - reg) != 1)
|
||||
continue;
|
||||
/* decide where in the block to place our register */
|
||||
if (reg_tmp + 1 == reg)
|
||||
pos = i + 1;
|
||||
else
|
||||
pos = i;
|
||||
ret = snd_soc_rbtree_insert_to_block(rbnode_tmp, pos,
|
||||
reg, value);
|
||||
if (ret)
|
||||
return ret;
|
||||
rbtree_ctx->cached_rbnode = rbnode_tmp;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* we did not manage to find a place to insert it in an existing
|
||||
* block so create a new rbnode with a single register in its block.
|
||||
* This block will get populated further if any other adjacent
|
||||
* registers get modified in the future.
|
||||
*/
|
||||
rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL);
|
||||
if (!rbnode)
|
||||
return -ENOMEM;
|
||||
rbnode->reg = reg;
|
||||
rbnode->value = value;
|
||||
rbnode->blklen = 1;
|
||||
rbnode->base_reg = reg;
|
||||
rbnode->word_size = codec->driver->reg_word_size;
|
||||
rbnode->block = kmalloc(rbnode->blklen * rbnode->word_size,
|
||||
GFP_KERNEL);
|
||||
if (!rbnode->block) {
|
||||
kfree(rbnode);
|
||||
return -ENOMEM;
|
||||
}
|
||||
snd_soc_rbtree_set_register(rbnode, 0, value);
|
||||
snd_soc_rbtree_insert(&rbtree_ctx->root, rbnode);
|
||||
rbtree_ctx->cached_rbnode = rbnode;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -607,11 +343,28 @@ static int snd_soc_rbtree_cache_read(struct snd_soc_codec *codec,
|
||||
{
|
||||
struct snd_soc_rbtree_ctx *rbtree_ctx;
|
||||
struct snd_soc_rbtree_node *rbnode;
|
||||
unsigned int base_reg, top_reg;
|
||||
unsigned int reg_tmp;
|
||||
|
||||
rbtree_ctx = codec->reg_cache;
|
||||
/* look up the required register in the cached rbnode */
|
||||
rbnode = rbtree_ctx->cached_rbnode;
|
||||
if (rbnode) {
|
||||
snd_soc_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg);
|
||||
if (reg >= base_reg && reg <= top_reg) {
|
||||
reg_tmp = reg - base_reg;
|
||||
*value = snd_soc_rbtree_get_register(rbnode, reg_tmp);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* if we can't locate it in the cached rbnode we'll have
|
||||
* to traverse the rbtree looking for it.
|
||||
*/
|
||||
rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg);
|
||||
if (rbnode) {
|
||||
*value = rbnode->value;
|
||||
reg_tmp = reg - rbnode->base_reg;
|
||||
*value = snd_soc_rbtree_get_register(rbnode, reg_tmp);
|
||||
rbtree_ctx->cached_rbnode = rbnode;
|
||||
} else {
|
||||
/* uninitialized registers default to 0 */
|
||||
*value = 0;
|
||||
@ -637,6 +390,7 @@ static int snd_soc_rbtree_cache_exit(struct snd_soc_codec *codec)
|
||||
rbtree_node = rb_entry(next, struct snd_soc_rbtree_node, node);
|
||||
next = rb_next(&rbtree_node->node);
|
||||
rb_erase(&rbtree_node->node, &rbtree_ctx->root);
|
||||
kfree(rbtree_node->block);
|
||||
kfree(rbtree_node);
|
||||
}
|
||||
|
||||
@ -649,10 +403,9 @@ static int snd_soc_rbtree_cache_exit(struct snd_soc_codec *codec)
|
||||
|
||||
static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct snd_soc_rbtree_node *rbtree_node;
|
||||
struct snd_soc_rbtree_ctx *rbtree_ctx;
|
||||
unsigned int val;
|
||||
unsigned int word_size;
|
||||
unsigned int val;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
@ -662,32 +415,27 @@ static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec)
|
||||
|
||||
rbtree_ctx = codec->reg_cache;
|
||||
rbtree_ctx->root = RB_ROOT;
|
||||
rbtree_ctx->cached_rbnode = NULL;
|
||||
|
||||
if (!codec->reg_def_copy)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* populate the rbtree with the initialized registers. All other
|
||||
* registers will be inserted when they are first modified.
|
||||
*/
|
||||
word_size = codec->driver->reg_word_size;
|
||||
for (i = 0; i < codec->driver->reg_cache_size; ++i) {
|
||||
val = snd_soc_get_cache_val(codec->reg_def_copy, i, word_size);
|
||||
val = snd_soc_get_cache_val(codec->reg_def_copy, i,
|
||||
word_size);
|
||||
if (!val)
|
||||
continue;
|
||||
rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL);
|
||||
if (!rbtree_node) {
|
||||
ret = -ENOMEM;
|
||||
snd_soc_cache_exit(codec);
|
||||
break;
|
||||
}
|
||||
rbtree_node->reg = i;
|
||||
rbtree_node->value = val;
|
||||
rbtree_node->defval = val;
|
||||
snd_soc_rbtree_insert(&rbtree_ctx->root, rbtree_node);
|
||||
ret = snd_soc_rbtree_cache_write(codec, i, val);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
snd_soc_cache_exit(codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SND_SOC_CACHE_LZO
|
||||
|
@ -44,7 +44,6 @@
|
||||
|
||||
#define NAME_SIZE 32
|
||||
|
||||
static DEFINE_MUTEX(pcm_mutex);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
@ -58,7 +57,7 @@ static LIST_HEAD(dai_list);
|
||||
static LIST_HEAD(platform_list);
|
||||
static LIST_HEAD(codec_list);
|
||||
|
||||
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
|
||||
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
|
||||
|
||||
/*
|
||||
* This is a timeout to do a DAPM powerdown after a stream is closed().
|
||||
@ -485,552 +484,6 @@ static int soc_ac97_dev_register(struct snd_soc_codec *codec)
|
||||
}
|
||||
#endif
|
||||
|
||||
static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
if (!codec_dai->driver->symmetric_rates &&
|
||||
!cpu_dai->driver->symmetric_rates &&
|
||||
!rtd->dai_link->symmetric_rates)
|
||||
return 0;
|
||||
|
||||
/* This can happen if multiple streams are starting simultaneously -
|
||||
* the second can need to get its constraints before the first has
|
||||
* picked a rate. Complain and allow the application to carry on.
|
||||
*/
|
||||
if (!rtd->rate) {
|
||||
dev_warn(&rtd->dev,
|
||||
"Not enforcing symmetric_rates due to race\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n", rtd->rate);
|
||||
|
||||
ret = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
rtd->rate, rtd->rate);
|
||||
if (ret < 0) {
|
||||
dev_err(&rtd->dev,
|
||||
"Unable to apply rate symmetry constraint: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when a PCM substream is opened, the runtime->hw record is
|
||||
* then initialized and any private data can be allocated. This also calls
|
||||
* startup for the cpu DAI, platform, machine and codec DAI.
|
||||
*/
|
||||
static int soc_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
|
||||
struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&pcm_mutex);
|
||||
|
||||
/* startup the audio subsystem */
|
||||
if (cpu_dai->driver->ops->startup) {
|
||||
ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't open interface %s\n",
|
||||
cpu_dai->name);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->open) {
|
||||
ret = platform->driver->ops->open(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
|
||||
goto platform_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (codec_dai->driver->ops->startup) {
|
||||
ret = codec_dai->driver->ops->startup(substream, codec_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't open codec %s\n",
|
||||
codec_dai->name);
|
||||
goto codec_dai_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
|
||||
ret = rtd->dai_link->ops->startup(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: %s startup failed\n", rtd->dai_link->name);
|
||||
goto machine_err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that the codec and cpu DAIs are compatible */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
runtime->hw.rate_min =
|
||||
max(codec_dai_drv->playback.rate_min,
|
||||
cpu_dai_drv->playback.rate_min);
|
||||
runtime->hw.rate_max =
|
||||
min(codec_dai_drv->playback.rate_max,
|
||||
cpu_dai_drv->playback.rate_max);
|
||||
runtime->hw.channels_min =
|
||||
max(codec_dai_drv->playback.channels_min,
|
||||
cpu_dai_drv->playback.channels_min);
|
||||
runtime->hw.channels_max =
|
||||
min(codec_dai_drv->playback.channels_max,
|
||||
cpu_dai_drv->playback.channels_max);
|
||||
runtime->hw.formats =
|
||||
codec_dai_drv->playback.formats & cpu_dai_drv->playback.formats;
|
||||
runtime->hw.rates =
|
||||
codec_dai_drv->playback.rates & cpu_dai_drv->playback.rates;
|
||||
if (codec_dai_drv->playback.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= cpu_dai_drv->playback.rates;
|
||||
if (cpu_dai_drv->playback.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= codec_dai_drv->playback.rates;
|
||||
} else {
|
||||
runtime->hw.rate_min =
|
||||
max(codec_dai_drv->capture.rate_min,
|
||||
cpu_dai_drv->capture.rate_min);
|
||||
runtime->hw.rate_max =
|
||||
min(codec_dai_drv->capture.rate_max,
|
||||
cpu_dai_drv->capture.rate_max);
|
||||
runtime->hw.channels_min =
|
||||
max(codec_dai_drv->capture.channels_min,
|
||||
cpu_dai_drv->capture.channels_min);
|
||||
runtime->hw.channels_max =
|
||||
min(codec_dai_drv->capture.channels_max,
|
||||
cpu_dai_drv->capture.channels_max);
|
||||
runtime->hw.formats =
|
||||
codec_dai_drv->capture.formats & cpu_dai_drv->capture.formats;
|
||||
runtime->hw.rates =
|
||||
codec_dai_drv->capture.rates & cpu_dai_drv->capture.rates;
|
||||
if (codec_dai_drv->capture.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= cpu_dai_drv->capture.rates;
|
||||
if (cpu_dai_drv->capture.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= codec_dai_drv->capture.rates;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
snd_pcm_limit_hw_rates(runtime);
|
||||
if (!runtime->hw.rates) {
|
||||
printk(KERN_ERR "asoc: %s <-> %s No matching rates\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
goto config_err;
|
||||
}
|
||||
if (!runtime->hw.formats) {
|
||||
printk(KERN_ERR "asoc: %s <-> %s No matching formats\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
goto config_err;
|
||||
}
|
||||
if (!runtime->hw.channels_min || !runtime->hw.channels_max ||
|
||||
runtime->hw.channels_min > runtime->hw.channels_max) {
|
||||
printk(KERN_ERR "asoc: %s <-> %s No matching channels\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
goto config_err;
|
||||
}
|
||||
|
||||
/* Symmetry only applies if we've already got an active stream. */
|
||||
if (cpu_dai->active || codec_dai->active) {
|
||||
ret = soc_pcm_apply_symmetry(substream);
|
||||
if (ret != 0)
|
||||
goto config_err;
|
||||
}
|
||||
|
||||
pr_debug("asoc: %s <-> %s info:\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
pr_debug("asoc: rate mask 0x%x\n", runtime->hw.rates);
|
||||
pr_debug("asoc: min ch %d max ch %d\n", runtime->hw.channels_min,
|
||||
runtime->hw.channels_max);
|
||||
pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min,
|
||||
runtime->hw.rate_max);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
cpu_dai->playback_active++;
|
||||
codec_dai->playback_active++;
|
||||
} else {
|
||||
cpu_dai->capture_active++;
|
||||
codec_dai->capture_active++;
|
||||
}
|
||||
cpu_dai->active++;
|
||||
codec_dai->active++;
|
||||
rtd->codec->active++;
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return 0;
|
||||
|
||||
config_err:
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
|
||||
rtd->dai_link->ops->shutdown(substream);
|
||||
|
||||
machine_err:
|
||||
if (codec_dai->driver->ops->shutdown)
|
||||
codec_dai->driver->ops->shutdown(substream, codec_dai);
|
||||
|
||||
codec_dai_err:
|
||||
if (platform->driver->ops && platform->driver->ops->close)
|
||||
platform->driver->ops->close(substream);
|
||||
|
||||
platform_err:
|
||||
if (cpu_dai->driver->ops->shutdown)
|
||||
cpu_dai->driver->ops->shutdown(substream, cpu_dai);
|
||||
out:
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power down the audio subsystem pmdown_time msecs after close is called.
|
||||
* This is to ensure there are no pops or clicks in between any music tracks
|
||||
* due to DAPM power cycling.
|
||||
*/
|
||||
static void close_delayed_work(struct work_struct *work)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd =
|
||||
container_of(work, struct snd_soc_pcm_runtime, delayed_work.work);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
|
||||
mutex_lock(&pcm_mutex);
|
||||
|
||||
pr_debug("pop wq checking: %s status: %s waiting: %s\n",
|
||||
codec_dai->driver->playback.stream_name,
|
||||
codec_dai->playback_active ? "active" : "inactive",
|
||||
codec_dai->pop_wait ? "yes" : "no");
|
||||
|
||||
/* are we waiting on this codec DAI stream */
|
||||
if (codec_dai->pop_wait == 1) {
|
||||
codec_dai->pop_wait = 0;
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_STOP);
|
||||
}
|
||||
|
||||
mutex_unlock(&pcm_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when a PCM substream is closed. Private data can be
|
||||
* freed here. The cpu DAI, codec DAI, machine and platform are also
|
||||
* shutdown.
|
||||
*/
|
||||
static int soc_codec_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
|
||||
mutex_lock(&pcm_mutex);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
cpu_dai->playback_active--;
|
||||
codec_dai->playback_active--;
|
||||
} else {
|
||||
cpu_dai->capture_active--;
|
||||
codec_dai->capture_active--;
|
||||
}
|
||||
|
||||
cpu_dai->active--;
|
||||
codec_dai->active--;
|
||||
codec->active--;
|
||||
|
||||
/* Muting the DAC suppresses artifacts caused during digital
|
||||
* shutdown, for example from stopping clocks.
|
||||
*/
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dai_digital_mute(codec_dai, 1);
|
||||
|
||||
if (cpu_dai->driver->ops->shutdown)
|
||||
cpu_dai->driver->ops->shutdown(substream, cpu_dai);
|
||||
|
||||
if (codec_dai->driver->ops->shutdown)
|
||||
codec_dai->driver->ops->shutdown(substream, codec_dai);
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
|
||||
rtd->dai_link->ops->shutdown(substream);
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->close)
|
||||
platform->driver->ops->close(substream);
|
||||
cpu_dai->runtime = NULL;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/* start delayed pop wq here for playback streams */
|
||||
codec_dai->pop_wait = 1;
|
||||
schedule_delayed_work(&rtd->delayed_work,
|
||||
msecs_to_jiffies(rtd->pmdown_time));
|
||||
} else {
|
||||
/* capture streams can be powered down now */
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_STOP);
|
||||
}
|
||||
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when the PCM substream is prepared, can set format, sample
|
||||
* rate, etc. This function is non atomic and can be called multiple times,
|
||||
* it can refer to the runtime info.
|
||||
*/
|
||||
static int soc_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&pcm_mutex);
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->prepare) {
|
||||
ret = rtd->dai_link->ops->prepare(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: machine prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->prepare) {
|
||||
ret = platform->driver->ops->prepare(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: platform prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (codec_dai->driver->ops->prepare) {
|
||||
ret = codec_dai->driver->ops->prepare(substream, codec_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: codec DAI prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (cpu_dai->driver->ops->prepare) {
|
||||
ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: cpu DAI prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* cancel any delayed stream shutdown that is pending */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
codec_dai->pop_wait) {
|
||||
codec_dai->pop_wait = 0;
|
||||
cancel_delayed_work(&rtd->delayed_work);
|
||||
}
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
else
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
|
||||
snd_soc_dai_digital_mute(codec_dai, 0);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when the hardware params are set by application. This
|
||||
* function can also be called multiple times and can allocate buffers
|
||||
* (using snd_pcm_lib_* ). It's non-atomic.
|
||||
*/
|
||||
static int soc_pcm_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_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&pcm_mutex);
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
|
||||
ret = rtd->dai_link->ops->hw_params(substream, params);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: machine hw_params failed\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (codec_dai->driver->ops->hw_params) {
|
||||
ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't set codec %s hw params\n",
|
||||
codec_dai->name);
|
||||
goto codec_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (cpu_dai->driver->ops->hw_params) {
|
||||
ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: interface %s hw params failed\n",
|
||||
cpu_dai->name);
|
||||
goto interface_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->hw_params) {
|
||||
ret = platform->driver->ops->hw_params(substream, params);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: platform %s hw params failed\n",
|
||||
platform->name);
|
||||
goto platform_err;
|
||||
}
|
||||
}
|
||||
|
||||
rtd->rate = params_rate(params);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return ret;
|
||||
|
||||
platform_err:
|
||||
if (cpu_dai->driver->ops->hw_free)
|
||||
cpu_dai->driver->ops->hw_free(substream, cpu_dai);
|
||||
|
||||
interface_err:
|
||||
if (codec_dai->driver->ops->hw_free)
|
||||
codec_dai->driver->ops->hw_free(substream, codec_dai);
|
||||
|
||||
codec_err:
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
|
||||
rtd->dai_link->ops->hw_free(substream);
|
||||
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees resources allocated by hw_params, can be called multiple times
|
||||
*/
|
||||
static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
|
||||
mutex_lock(&pcm_mutex);
|
||||
|
||||
/* apply codec digital mute */
|
||||
if (!codec->active)
|
||||
snd_soc_dai_digital_mute(codec_dai, 1);
|
||||
|
||||
/* free any machine hw params */
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
|
||||
rtd->dai_link->ops->hw_free(substream);
|
||||
|
||||
/* free any DMA resources */
|
||||
if (platform->driver->ops && platform->driver->ops->hw_free)
|
||||
platform->driver->ops->hw_free(substream);
|
||||
|
||||
/* now free hw params for the DAIs */
|
||||
if (codec_dai->driver->ops->hw_free)
|
||||
codec_dai->driver->ops->hw_free(substream, codec_dai);
|
||||
|
||||
if (cpu_dai->driver->ops->hw_free)
|
||||
cpu_dai->driver->ops->hw_free(substream, cpu_dai);
|
||||
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
if (codec_dai->driver->ops->trigger) {
|
||||
ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->trigger) {
|
||||
ret = platform->driver->ops->trigger(substream, cmd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cpu_dai->driver->ops->trigger) {
|
||||
ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* soc level wrapper for pointer callback
|
||||
* If cpu_dai, codec_dai, platform driver has the delay callback, than
|
||||
* the runtime->delay will be updated accordingly.
|
||||
*/
|
||||
static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_pcm_uframes_t offset = 0;
|
||||
snd_pcm_sframes_t delay = 0;
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->pointer)
|
||||
offset = platform->driver->ops->pointer(substream);
|
||||
|
||||
if (cpu_dai->driver->ops->delay)
|
||||
delay += cpu_dai->driver->ops->delay(substream, cpu_dai);
|
||||
|
||||
if (codec_dai->driver->ops->delay)
|
||||
delay += codec_dai->driver->ops->delay(substream, codec_dai);
|
||||
|
||||
if (platform->driver->delay)
|
||||
delay += platform->driver->delay(substream, codec_dai);
|
||||
|
||||
runtime->delay = delay;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* ASoC PCM operations */
|
||||
static struct snd_pcm_ops soc_pcm_ops = {
|
||||
.open = soc_pcm_open,
|
||||
.close = soc_codec_close,
|
||||
.hw_params = soc_pcm_hw_params,
|
||||
.hw_free = soc_pcm_hw_free,
|
||||
.prepare = soc_pcm_prepare,
|
||||
.trigger = soc_pcm_trigger,
|
||||
.pointer = soc_pcm_pointer,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/* powers down audio subsystem for suspend */
|
||||
int snd_soc_suspend(struct device *dev)
|
||||
@ -1256,7 +709,7 @@ static void soc_resume_deferred(struct work_struct *work)
|
||||
int snd_soc_resume(struct device *dev)
|
||||
{
|
||||
struct snd_soc_card *card = dev_get_drvdata(dev);
|
||||
int i;
|
||||
int i, ac97_control = 0;
|
||||
|
||||
/* AC97 devices might have other drivers hanging off them so
|
||||
* need to resume immediately. Other drivers don't have that
|
||||
@ -1265,14 +718,15 @@ int snd_soc_resume(struct device *dev)
|
||||
*/
|
||||
for (i = 0; i < card->num_rtd; i++) {
|
||||
struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
|
||||
if (cpu_dai->driver->ac97_control) {
|
||||
dev_dbg(dev, "Resuming AC97 immediately\n");
|
||||
soc_resume_deferred(&card->deferred_resume_work);
|
||||
} else {
|
||||
dev_dbg(dev, "Scheduling resume work\n");
|
||||
if (!schedule_work(&card->deferred_resume_work))
|
||||
dev_err(dev, "resume work item may be lost\n");
|
||||
}
|
||||
ac97_control |= cpu_dai->driver->ac97_control;
|
||||
}
|
||||
if (ac97_control) {
|
||||
dev_dbg(dev, "Resuming AC97 immediately\n");
|
||||
soc_resume_deferred(&card->deferred_resume_work);
|
||||
} else {
|
||||
dev_dbg(dev, "Scheduling resume work\n");
|
||||
if (!schedule_work(&card->deferred_resume_work))
|
||||
dev_err(dev, "resume work item may be lost\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1393,7 +847,7 @@ static void soc_remove_codec(struct snd_soc_codec *codec)
|
||||
module_put(codec->dev->driver->owner);
|
||||
}
|
||||
|
||||
static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
||||
static void soc_remove_dai_link(struct snd_soc_card *card, int num, int order)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
@ -1410,7 +864,8 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
||||
}
|
||||
|
||||
/* remove the CODEC DAI */
|
||||
if (codec_dai && codec_dai->probed) {
|
||||
if (codec_dai && codec_dai->probed &&
|
||||
codec_dai->driver->remove_order == order) {
|
||||
if (codec_dai->driver->remove) {
|
||||
err = codec_dai->driver->remove(codec_dai);
|
||||
if (err < 0)
|
||||
@ -1421,7 +876,8 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
||||
}
|
||||
|
||||
/* remove the platform */
|
||||
if (platform && platform->probed) {
|
||||
if (platform && platform->probed &&
|
||||
platform->driver->remove_order == order) {
|
||||
if (platform->driver->remove) {
|
||||
err = platform->driver->remove(platform);
|
||||
if (err < 0)
|
||||
@ -1433,11 +889,13 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
||||
}
|
||||
|
||||
/* remove the CODEC */
|
||||
if (codec && codec->probed)
|
||||
if (codec && codec->probed &&
|
||||
codec->driver->remove_order == order)
|
||||
soc_remove_codec(codec);
|
||||
|
||||
/* remove the cpu_dai */
|
||||
if (cpu_dai && cpu_dai->probed) {
|
||||
if (cpu_dai && cpu_dai->probed &&
|
||||
cpu_dai->driver->remove_order == order) {
|
||||
if (cpu_dai->driver->remove) {
|
||||
err = cpu_dai->driver->remove(cpu_dai);
|
||||
if (err < 0)
|
||||
@ -1451,11 +909,13 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
||||
|
||||
static void soc_remove_dai_links(struct snd_soc_card *card)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < card->num_rtd; i++)
|
||||
soc_remove_dai_link(card, i);
|
||||
int dai, order;
|
||||
|
||||
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
|
||||
order++) {
|
||||
for (dai = 0; dai < card->num_rtd; dai++)
|
||||
soc_remove_dai_link(card, dai, order);
|
||||
}
|
||||
card->num_rtd = 0;
|
||||
}
|
||||
|
||||
@ -1526,6 +986,52 @@ err_probe:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int soc_probe_platform(struct snd_soc_card *card,
|
||||
struct snd_soc_platform *platform)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct snd_soc_platform_driver *driver = platform->driver;
|
||||
|
||||
platform->card = card;
|
||||
platform->dapm.card = card;
|
||||
|
||||
if (!try_module_get(platform->dev->driver->owner))
|
||||
return -ENODEV;
|
||||
|
||||
if (driver->dapm_widgets)
|
||||
snd_soc_dapm_new_controls(&platform->dapm,
|
||||
driver->dapm_widgets, driver->num_dapm_widgets);
|
||||
|
||||
if (driver->probe) {
|
||||
ret = driver->probe(platform);
|
||||
if (ret < 0) {
|
||||
dev_err(platform->dev,
|
||||
"asoc: failed to probe platform %s: %d\n",
|
||||
platform->name, ret);
|
||||
goto err_probe;
|
||||
}
|
||||
}
|
||||
|
||||
if (driver->controls)
|
||||
snd_soc_add_platform_controls(platform, driver->controls,
|
||||
driver->num_controls);
|
||||
if (driver->dapm_routes)
|
||||
snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes,
|
||||
driver->num_dapm_routes);
|
||||
|
||||
/* mark platform as probed and add to card platform list */
|
||||
platform->probed = 1;
|
||||
list_add(&platform->card_list, &card->platform_dev_list);
|
||||
list_add(&platform->dapm.list, &card->dapm_list);
|
||||
|
||||
return 0;
|
||||
|
||||
err_probe:
|
||||
module_put(platform->dev->driver->owner);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rtd_release(struct device *dev) {}
|
||||
|
||||
static int soc_post_component_init(struct snd_soc_card *card,
|
||||
@ -1572,6 +1078,7 @@ static int soc_post_component_init(struct snd_soc_card *card,
|
||||
rtd->dev.parent = card->dev;
|
||||
rtd->dev.release = rtd_release;
|
||||
rtd->dev.init_name = name;
|
||||
mutex_init(&rtd->pcm_mutex);
|
||||
ret = device_register(&rtd->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(card->dev,
|
||||
@ -1596,7 +1103,7 @@ static int soc_post_component_init(struct snd_soc_card *card,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
||||
static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)
|
||||
{
|
||||
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
|
||||
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
|
||||
@ -1605,7 +1112,8 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
|
||||
int ret;
|
||||
|
||||
dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num);
|
||||
dev_dbg(card->dev, "probe %s dai link %d late %d\n",
|
||||
card->name, num, order);
|
||||
|
||||
/* config components */
|
||||
codec_dai->codec = codec;
|
||||
@ -1617,7 +1125,8 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
||||
rtd->pmdown_time = pmdown_time;
|
||||
|
||||
/* probe the cpu_dai */
|
||||
if (!cpu_dai->probed) {
|
||||
if (!cpu_dai->probed &&
|
||||
cpu_dai->driver->probe_order == order) {
|
||||
if (!try_module_get(cpu_dai->dev->driver->owner))
|
||||
return -ENODEV;
|
||||
|
||||
@ -1636,33 +1145,23 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
||||
}
|
||||
|
||||
/* probe the CODEC */
|
||||
if (!codec->probed) {
|
||||
if (!codec->probed &&
|
||||
codec->driver->probe_order == order) {
|
||||
ret = soc_probe_codec(card, codec);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* probe the platform */
|
||||
if (!platform->probed) {
|
||||
if (!try_module_get(platform->dev->driver->owner))
|
||||
return -ENODEV;
|
||||
|
||||
if (platform->driver->probe) {
|
||||
ret = platform->driver->probe(platform);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: failed to probe platform %s\n",
|
||||
platform->name);
|
||||
module_put(platform->dev->driver->owner);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
/* mark platform as probed and add to card platform list */
|
||||
platform->probed = 1;
|
||||
list_add(&platform->card_list, &card->platform_dev_list);
|
||||
if (!platform->probed &&
|
||||
platform->driver->probe_order == order) {
|
||||
ret = soc_probe_platform(card, platform);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* probe the CODEC DAI */
|
||||
if (!codec_dai->probed) {
|
||||
if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
|
||||
if (codec_dai->driver->probe) {
|
||||
ret = codec_dai->driver->probe(codec_dai);
|
||||
if (ret < 0) {
|
||||
@ -1677,8 +1176,9 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
||||
list_add(&codec_dai->card_list, &card->dai_dev_list);
|
||||
}
|
||||
|
||||
/* DAPM dai link stream work */
|
||||
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
|
||||
/* complete DAI probe during last probe */
|
||||
if (order != SND_SOC_COMP_ORDER_LAST)
|
||||
return 0;
|
||||
|
||||
ret = soc_post_component_init(card, codec, num, 0);
|
||||
if (ret)
|
||||
@ -1817,7 +1317,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
||||
struct snd_soc_codec *codec;
|
||||
struct snd_soc_codec_conf *codec_conf;
|
||||
enum snd_soc_compress_type compress_type;
|
||||
int ret, i;
|
||||
int ret, i, order;
|
||||
|
||||
mutex_lock(&card->mutex);
|
||||
|
||||
@ -1895,12 +1395,16 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
||||
goto card_probe_error;
|
||||
}
|
||||
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
ret = soc_probe_dai_link(card, i);
|
||||
if (ret < 0) {
|
||||
pr_err("asoc: failed to instantiate card %s: %d\n",
|
||||
/* early DAI link probe */
|
||||
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
|
||||
order++) {
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
ret = soc_probe_dai_link(card, i, order);
|
||||
if (ret < 0) {
|
||||
pr_err("asoc: failed to instantiate card %s: %d\n",
|
||||
card->name, ret);
|
||||
goto probe_dai_err;
|
||||
goto probe_dai_err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2096,67 +1600,6 @@ static struct platform_driver soc_driver = {
|
||||
.remove = soc_remove,
|
||||
};
|
||||
|
||||
/* create a new pcm */
|
||||
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm;
|
||||
char new_name[64];
|
||||
int ret = 0, playback = 0, capture = 0;
|
||||
|
||||
/* check client and interface hw capabilities */
|
||||
snprintf(new_name, sizeof(new_name), "%s %s-%d",
|
||||
rtd->dai_link->stream_name, codec_dai->name, num);
|
||||
|
||||
if (codec_dai->driver->playback.channels_min)
|
||||
playback = 1;
|
||||
if (codec_dai->driver->capture.channels_min)
|
||||
capture = 1;
|
||||
|
||||
dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
|
||||
ret = snd_pcm_new(rtd->card->snd_card, new_name,
|
||||
num, playback, capture, &pcm);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rtd->pcm = pcm;
|
||||
pcm->private_data = rtd;
|
||||
if (platform->driver->ops) {
|
||||
soc_pcm_ops.mmap = platform->driver->ops->mmap;
|
||||
soc_pcm_ops.pointer = platform->driver->ops->pointer;
|
||||
soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
|
||||
soc_pcm_ops.copy = platform->driver->ops->copy;
|
||||
soc_pcm_ops.silence = platform->driver->ops->silence;
|
||||
soc_pcm_ops.ack = platform->driver->ops->ack;
|
||||
soc_pcm_ops.page = platform->driver->ops->page;
|
||||
}
|
||||
|
||||
if (playback)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
|
||||
|
||||
if (capture)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
|
||||
|
||||
if (platform->driver->pcm_new) {
|
||||
ret = platform->driver->pcm_new(rtd->card->snd_card,
|
||||
codec_dai, pcm);
|
||||
if (ret < 0) {
|
||||
pr_err("asoc: platform pcm constructor failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pcm->private_free = platform->driver->pcm_free;
|
||||
printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
|
||||
cpu_dai->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_codec_volatile_register: Report if a register is volatile.
|
||||
*
|
||||
@ -2211,6 +1654,38 @@ int snd_soc_codec_writable_register(struct snd_soc_codec *codec,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_codec_writable_register);
|
||||
|
||||
int snd_soc_platform_read(struct snd_soc_platform *platform,
|
||||
unsigned int reg)
|
||||
{
|
||||
unsigned int ret;
|
||||
|
||||
if (!platform->driver->read) {
|
||||
dev_err(platform->dev, "platform has no read back\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = platform->driver->read(platform, reg);
|
||||
dev_dbg(platform->dev, "read %x => %x\n", reg, ret);
|
||||
trace_snd_soc_preg_read(platform, reg, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_platform_read);
|
||||
|
||||
int snd_soc_platform_write(struct snd_soc_platform *platform,
|
||||
unsigned int reg, unsigned int val)
|
||||
{
|
||||
if (!platform->driver->write) {
|
||||
dev_err(platform->dev, "platform has no write back\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev_dbg(platform->dev, "write %x = %x\n", reg, val);
|
||||
trace_snd_soc_preg_write(platform, reg, val);
|
||||
return platform->driver->write(platform, reg, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_platform_write);
|
||||
|
||||
/**
|
||||
* snd_soc_new_ac97_codec - initailise AC97 device
|
||||
* @codec: audio codec
|
||||
@ -2323,7 +1798,7 @@ int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
|
||||
return ret;
|
||||
|
||||
old = ret;
|
||||
new = (old & ~mask) | value;
|
||||
new = (old & ~mask) | (value & mask);
|
||||
change = old != new;
|
||||
if (change) {
|
||||
ret = snd_soc_write(codec, reg, new);
|
||||
@ -2489,6 +1964,36 @@ int snd_soc_add_controls(struct snd_soc_codec *codec,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_add_controls);
|
||||
|
||||
/**
|
||||
* snd_soc_add_platform_controls - add an array of controls to a platform.
|
||||
* Convienience function to add a list of controls.
|
||||
*
|
||||
* @platform: platform to add controls to
|
||||
* @controls: array of controls to add
|
||||
* @num_controls: number of elements in the array
|
||||
*
|
||||
* Return 0 for success, else error.
|
||||
*/
|
||||
int snd_soc_add_platform_controls(struct snd_soc_platform *platform,
|
||||
const struct snd_kcontrol_new *controls, int num_controls)
|
||||
{
|
||||
struct snd_card *card = platform->card->snd_card;
|
||||
int err, i;
|
||||
|
||||
for (i = 0; i < num_controls; i++) {
|
||||
const struct snd_kcontrol_new *control = &controls[i];
|
||||
err = snd_ctl_add(card, snd_soc_cnew(control, platform,
|
||||
control->name, NULL));
|
||||
if (err < 0) {
|
||||
dev_err(platform->dev, "Failed to add %s %d\n",control->name, err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_add_platform_controls);
|
||||
|
||||
/**
|
||||
* snd_soc_info_enum_double - enumerated double mixer info callback
|
||||
* @kcontrol: mixer control
|
||||
@ -3633,6 +3138,8 @@ int snd_soc_register_platform(struct device *dev,
|
||||
|
||||
platform->dev = dev;
|
||||
platform->driver = platform_drv;
|
||||
platform->dapm.dev = dev;
|
||||
platform->dapm.platform = platform;
|
||||
|
||||
mutex_lock(&client_mutex);
|
||||
list_add(&platform->list, &platform_list);
|
||||
|
@ -124,6 +124,51 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
|
||||
return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);
|
||||
}
|
||||
|
||||
static int soc_widget_read(struct snd_soc_dapm_widget *w, int reg)
|
||||
{
|
||||
if (w->codec)
|
||||
return snd_soc_read(w->codec, reg);
|
||||
else if (w->platform)
|
||||
return snd_soc_platform_read(w->platform, reg);
|
||||
|
||||
dev_err(w->dapm->dev, "no valid widget read method\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int soc_widget_write(struct snd_soc_dapm_widget *w, int reg, int val)
|
||||
{
|
||||
if (w->codec)
|
||||
return snd_soc_write(w->codec, reg, val);
|
||||
else if (w->platform)
|
||||
return snd_soc_platform_write(w->platform, reg, val);
|
||||
|
||||
dev_err(w->dapm->dev, "no valid widget write method\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int soc_widget_update_bits(struct snd_soc_dapm_widget *w,
|
||||
unsigned short reg, unsigned int mask, unsigned int value)
|
||||
{
|
||||
int change;
|
||||
unsigned int old, new;
|
||||
int ret;
|
||||
|
||||
ret = soc_widget_read(w, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
old = ret;
|
||||
new = (old & ~mask) | (value & mask);
|
||||
change = old != new;
|
||||
if (change) {
|
||||
ret = soc_widget_write(w, reg, new);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_set_bias_level - set the bias level for the system
|
||||
* @dapm: DAPM context
|
||||
@ -139,39 +184,26 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm,
|
||||
struct snd_soc_card *card = dapm->card;
|
||||
int ret = 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
dev_dbg(dapm->dev, "Setting full bias\n");
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
dev_dbg(dapm->dev, "Setting bias prepare\n");
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
dev_dbg(dapm->dev, "Setting standby bias\n");
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
dev_dbg(dapm->dev, "Setting bias off\n");
|
||||
break;
|
||||
default:
|
||||
dev_err(dapm->dev, "Setting invalid bias %d\n", level);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
trace_snd_soc_bias_level_start(card, level);
|
||||
|
||||
if (card && card->set_bias_level)
|
||||
ret = card->set_bias_level(card, level);
|
||||
if (ret == 0) {
|
||||
if (dapm->codec && dapm->codec->driver->set_bias_level)
|
||||
ret = dapm->codec->driver->set_bias_level(dapm->codec, level);
|
||||
ret = card->set_bias_level(card, dapm, level);
|
||||
if (ret != 0)
|
||||
goto out;
|
||||
|
||||
if (dapm->codec) {
|
||||
if (dapm->codec->driver->set_bias_level)
|
||||
ret = dapm->codec->driver->set_bias_level(dapm->codec,
|
||||
level);
|
||||
else
|
||||
dapm->bias_level = level;
|
||||
}
|
||||
if (ret == 0) {
|
||||
if (card && card->set_bias_level_post)
|
||||
ret = card->set_bias_level_post(card, level);
|
||||
}
|
||||
if (ret != 0)
|
||||
goto out;
|
||||
|
||||
if (card && card->set_bias_level_post)
|
||||
ret = card->set_bias_level_post(card, dapm, level);
|
||||
out:
|
||||
trace_snd_soc_bias_level_done(card, level);
|
||||
|
||||
return ret;
|
||||
@ -194,7 +226,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
|
||||
unsigned int mask = (1 << fls(max)) - 1;
|
||||
unsigned int invert = mc->invert;
|
||||
|
||||
val = snd_soc_read(w->codec, reg);
|
||||
val = soc_widget_read(w, reg);
|
||||
val = (val >> shift) & mask;
|
||||
|
||||
if ((invert && !val) || (!invert && val))
|
||||
@ -209,8 +241,8 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
|
||||
int val, item, bitmask;
|
||||
|
||||
for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
|
||||
;
|
||||
val = snd_soc_read(w->codec, e->reg);
|
||||
;
|
||||
val = soc_widget_read(w, e->reg);
|
||||
item = (val >> e->shift_l) & (bitmask - 1);
|
||||
|
||||
p->connect = 0;
|
||||
@ -240,7 +272,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
|
||||
w->kcontrol_news[i].private_value;
|
||||
int val, item;
|
||||
|
||||
val = snd_soc_read(w->codec, e->reg);
|
||||
val = soc_widget_read(w, e->reg);
|
||||
val = (val >> e->shift_l) & e->mask;
|
||||
for (item = 0; item < e->max; item++) {
|
||||
if (val == e->values[item])
|
||||
@ -606,6 +638,9 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
|
||||
}
|
||||
|
||||
list_for_each_entry(path, &widget->sinks, list_source) {
|
||||
if (path->weak)
|
||||
continue;
|
||||
|
||||
if (path->walked)
|
||||
continue;
|
||||
|
||||
@ -656,6 +691,9 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
|
||||
}
|
||||
|
||||
list_for_each_entry(path, &widget->sources, list_sink) {
|
||||
if (path->weak)
|
||||
continue;
|
||||
|
||||
if (path->walked)
|
||||
continue;
|
||||
|
||||
@ -681,7 +719,7 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w,
|
||||
else
|
||||
val = w->off_val;
|
||||
|
||||
snd_soc_update_bits(w->codec, -(w->reg + 1),
|
||||
soc_widget_update_bits(w, -(w->reg + 1),
|
||||
w->mask << w->shift, val << w->shift);
|
||||
|
||||
return 0;
|
||||
@ -737,6 +775,9 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
|
||||
|
||||
/* Check if one of our outputs is connected */
|
||||
list_for_each_entry(path, &w->sinks, list_source) {
|
||||
if (path->weak)
|
||||
continue;
|
||||
|
||||
if (path->connected &&
|
||||
!path->connected(path->source, path->sink))
|
||||
continue;
|
||||
@ -885,11 +926,17 @@ static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm,
|
||||
}
|
||||
|
||||
if (reg >= 0) {
|
||||
/* Any widget will do, they should all be updating the
|
||||
* same register.
|
||||
*/
|
||||
w = list_first_entry(pending, struct snd_soc_dapm_widget,
|
||||
power_list);
|
||||
|
||||
pop_dbg(dapm->dev, card->pop_time,
|
||||
"pop test : Applying 0x%x/0x%x to %x in %dms\n",
|
||||
value, mask, reg, card->pop_time);
|
||||
pop_wait(card->pop_time);
|
||||
snd_soc_update_bits(dapm->codec, reg, mask, value);
|
||||
soc_widget_update_bits(w, reg, mask, value);
|
||||
}
|
||||
|
||||
list_for_each_entry(w, pending, power_list) {
|
||||
@ -942,7 +989,7 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm,
|
||||
|
||||
INIT_LIST_HEAD(&pending);
|
||||
cur_sort = -1;
|
||||
cur_subseq = -1;
|
||||
cur_subseq = INT_MIN;
|
||||
cur_reg = SND_SOC_NOPM;
|
||||
cur_dapm = NULL;
|
||||
}
|
||||
@ -1041,16 +1088,17 @@ static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
|
||||
struct snd_soc_dapm_context *d = data;
|
||||
int ret;
|
||||
|
||||
if (d->dev_power && d->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* If we're off and we're not supposed to be go into STANDBY */
|
||||
if (d->bias_level == SND_SOC_BIAS_OFF &&
|
||||
d->target_bias_level != SND_SOC_BIAS_OFF) {
|
||||
ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
|
||||
if (ret != 0)
|
||||
dev_err(d->dev,
|
||||
"Failed to turn on bias: %d\n", ret);
|
||||
}
|
||||
|
||||
/* If we're changing to all on or all off then prepare */
|
||||
if ((d->dev_power && d->bias_level == SND_SOC_BIAS_STANDBY) ||
|
||||
(!d->dev_power && d->bias_level == SND_SOC_BIAS_ON)) {
|
||||
/* Prepare for a STADDBY->ON or ON->STANDBY transition */
|
||||
if (d->bias_level != d->target_bias_level) {
|
||||
ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
|
||||
if (ret != 0)
|
||||
dev_err(d->dev,
|
||||
@ -1067,7 +1115,9 @@ static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
|
||||
int ret;
|
||||
|
||||
/* If we just powered the last thing off drop to standby bias */
|
||||
if (d->bias_level == SND_SOC_BIAS_PREPARE && !d->dev_power) {
|
||||
if (d->bias_level == SND_SOC_BIAS_PREPARE &&
|
||||
(d->target_bias_level == SND_SOC_BIAS_STANDBY ||
|
||||
d->target_bias_level == SND_SOC_BIAS_OFF)) {
|
||||
ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
|
||||
if (ret != 0)
|
||||
dev_err(d->dev, "Failed to apply standby bias: %d\n",
|
||||
@ -1075,14 +1125,16 @@ static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
|
||||
}
|
||||
|
||||
/* If we're in standby and can support bias off then do that */
|
||||
if (d->bias_level == SND_SOC_BIAS_STANDBY && d->idle_bias_off) {
|
||||
if (d->bias_level == SND_SOC_BIAS_STANDBY &&
|
||||
d->target_bias_level == SND_SOC_BIAS_OFF) {
|
||||
ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF);
|
||||
if (ret != 0)
|
||||
dev_err(d->dev, "Failed to turn off bias: %d\n", ret);
|
||||
}
|
||||
|
||||
/* If we just powered up then move to active bias */
|
||||
if (d->bias_level == SND_SOC_BIAS_PREPARE && d->dev_power) {
|
||||
if (d->bias_level == SND_SOC_BIAS_PREPARE &&
|
||||
d->target_bias_level == SND_SOC_BIAS_ON) {
|
||||
ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);
|
||||
if (ret != 0)
|
||||
dev_err(d->dev, "Failed to apply active bias: %d\n",
|
||||
@ -1107,13 +1159,19 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
|
||||
LIST_HEAD(up_list);
|
||||
LIST_HEAD(down_list);
|
||||
LIST_HEAD(async_domain);
|
||||
enum snd_soc_bias_level bias;
|
||||
int power;
|
||||
|
||||
trace_snd_soc_dapm_start(card);
|
||||
|
||||
list_for_each_entry(d, &card->dapm_list, list)
|
||||
if (d->n_widgets || d->codec == NULL)
|
||||
d->dev_power = 0;
|
||||
list_for_each_entry(d, &card->dapm_list, list) {
|
||||
if (d->n_widgets || d->codec == NULL) {
|
||||
if (d->idle_bias_off)
|
||||
d->target_bias_level = SND_SOC_BIAS_OFF;
|
||||
else
|
||||
d->target_bias_level = SND_SOC_BIAS_STANDBY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check which widgets we need to power and store them in
|
||||
* lists indicating if they should be powered up or down.
|
||||
@ -1135,8 +1193,27 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
|
||||
power = w->power_check(w);
|
||||
else
|
||||
power = 1;
|
||||
if (power)
|
||||
w->dapm->dev_power = 1;
|
||||
|
||||
if (power) {
|
||||
d = w->dapm;
|
||||
|
||||
/* Supplies and micbiases only bring
|
||||
* the context up to STANDBY as unless
|
||||
* something else is active and
|
||||
* passing audio they generally don't
|
||||
* require full power.
|
||||
*/
|
||||
switch (w->id) {
|
||||
case snd_soc_dapm_supply:
|
||||
case snd_soc_dapm_micbias:
|
||||
if (d->target_bias_level < SND_SOC_BIAS_STANDBY)
|
||||
d->target_bias_level = SND_SOC_BIAS_STANDBY;
|
||||
break;
|
||||
default:
|
||||
d->target_bias_level = SND_SOC_BIAS_ON;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (w->power == power)
|
||||
continue;
|
||||
@ -1160,24 +1237,19 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_STREAM_START:
|
||||
case SND_SOC_DAPM_STREAM_RESUME:
|
||||
dapm->dev_power = 1;
|
||||
dapm->target_bias_level = SND_SOC_BIAS_ON;
|
||||
break;
|
||||
case SND_SOC_DAPM_STREAM_STOP:
|
||||
dapm->dev_power = !!dapm->codec->active;
|
||||
if (dapm->codec->active)
|
||||
dapm->target_bias_level = SND_SOC_BIAS_ON;
|
||||
else
|
||||
dapm->target_bias_level = SND_SOC_BIAS_STANDBY;
|
||||
break;
|
||||
case SND_SOC_DAPM_STREAM_SUSPEND:
|
||||
dapm->dev_power = 0;
|
||||
dapm->target_bias_level = SND_SOC_BIAS_STANDBY;
|
||||
break;
|
||||
case SND_SOC_DAPM_STREAM_NOP:
|
||||
switch (dapm->bias_level) {
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
case SND_SOC_BIAS_OFF:
|
||||
dapm->dev_power = 0;
|
||||
break;
|
||||
default:
|
||||
dapm->dev_power = 1;
|
||||
break;
|
||||
}
|
||||
dapm->target_bias_level = dapm->bias_level;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -1185,12 +1257,12 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
|
||||
}
|
||||
|
||||
/* Force all contexts in the card to the same bias state */
|
||||
power = 0;
|
||||
bias = SND_SOC_BIAS_OFF;
|
||||
list_for_each_entry(d, &card->dapm_list, list)
|
||||
if (d->dev_power)
|
||||
power = 1;
|
||||
if (d->target_bias_level > bias)
|
||||
bias = d->target_bias_level;
|
||||
list_for_each_entry(d, &card->dapm_list, list)
|
||||
d->dev_power = power;
|
||||
d->target_bias_level = bias;
|
||||
|
||||
|
||||
/* Run all the bias changes in parallel */
|
||||
@ -1794,6 +1866,84 @@ int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes);
|
||||
|
||||
static int snd_soc_dapm_weak_route(struct snd_soc_dapm_context *dapm,
|
||||
const struct snd_soc_dapm_route *route)
|
||||
{
|
||||
struct snd_soc_dapm_widget *source = dapm_find_widget(dapm,
|
||||
route->source,
|
||||
true);
|
||||
struct snd_soc_dapm_widget *sink = dapm_find_widget(dapm,
|
||||
route->sink,
|
||||
true);
|
||||
struct snd_soc_dapm_path *path;
|
||||
int count = 0;
|
||||
|
||||
if (!source) {
|
||||
dev_err(dapm->dev, "Unable to find source %s for weak route\n",
|
||||
route->source);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!sink) {
|
||||
dev_err(dapm->dev, "Unable to find sink %s for weak route\n",
|
||||
route->sink);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (route->control || route->connected)
|
||||
dev_warn(dapm->dev, "Ignoring control for weak route %s->%s\n",
|
||||
route->source, route->sink);
|
||||
|
||||
list_for_each_entry(path, &source->sinks, list_source) {
|
||||
if (path->sink == sink) {
|
||||
path->weak = 1;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
dev_err(dapm->dev, "No path found for weak route %s->%s\n",
|
||||
route->source, route->sink);
|
||||
if (count > 1)
|
||||
dev_warn(dapm->dev, "%d paths found for weak route %s->%s\n",
|
||||
count, route->source, route->sink);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_weak_routes - Mark routes between DAPM widgets as weak
|
||||
* @dapm: DAPM context
|
||||
* @route: audio routes
|
||||
* @num: number of routes
|
||||
*
|
||||
* Mark existing routes matching those specified in the passed array
|
||||
* as being weak, meaning that they are ignored for the purpose of
|
||||
* power decisions. The main intended use case is for sidetone paths
|
||||
* which couple audio between other independent paths if they are both
|
||||
* active in order to make the combination work better at the user
|
||||
* level but which aren't intended to be "used".
|
||||
*
|
||||
* Note that CODEC drivers should not use this as sidetone type paths
|
||||
* can frequently also be used as bypass paths.
|
||||
*/
|
||||
int snd_soc_dapm_weak_routes(struct snd_soc_dapm_context *dapm,
|
||||
const struct snd_soc_dapm_route *route, int num)
|
||||
{
|
||||
int i, err;
|
||||
int ret = 0;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
err = snd_soc_dapm_weak_route(dapm, route);
|
||||
if (err)
|
||||
ret = err;
|
||||
route++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_dapm_weak_routes);
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_new_widgets - add new dapm widgets
|
||||
* @dapm: DAPM context
|
||||
@ -1865,7 +2015,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
|
||||
|
||||
/* Read the initial power state from the device */
|
||||
if (w->reg >= 0) {
|
||||
val = snd_soc_read(w->codec, w->reg);
|
||||
val = soc_widget_read(w, w->reg);
|
||||
val &= 1 << w->shift;
|
||||
if (w->invert)
|
||||
val = !val;
|
||||
@ -2353,6 +2503,7 @@ int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
|
||||
dapm->n_widgets++;
|
||||
w->dapm = dapm;
|
||||
w->codec = dapm->codec;
|
||||
w->platform = dapm->platform;
|
||||
INIT_LIST_HEAD(&w->sources);
|
||||
INIT_LIST_HEAD(&w->sinks);
|
||||
INIT_LIST_HEAD(&w->list);
|
||||
|
396
sound/soc/soc-io.c
Normal file
396
sound/soc/soc-io.c
Normal file
@ -0,0 +1,396 @@
|
||||
/*
|
||||
* soc-io.c -- ASoC register I/O helpers
|
||||
*
|
||||
* Copyright 2009-2011 Wolfson Microelectronics PLC.
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.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/i2c.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <trace/events/asoc.h>
|
||||
|
||||
#ifdef CONFIG_SPI_MASTER
|
||||
static int do_spi_write(void *control, const char *data, int len)
|
||||
{
|
||||
struct spi_device *spi = control;
|
||||
int ret;
|
||||
|
||||
ret = spi_write(spi, data, len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value, const void *data, int len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
||||
reg < codec->driver->reg_cache_size &&
|
||||
!codec->cache_bypass) {
|
||||
ret = snd_soc_cache_write(codec, reg, value);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (codec->cache_only) {
|
||||
codec->cache_sync = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = codec->hw_write(codec->control_data, data, len);
|
||||
if (ret == len)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static unsigned int hw_read(struct snd_soc_codec *codec, unsigned int reg)
|
||||
{
|
||||
int ret;
|
||||
unsigned int val;
|
||||
|
||||
if (reg >= codec->driver->reg_cache_size ||
|
||||
snd_soc_codec_volatile_register(codec, reg) ||
|
||||
codec->cache_bypass) {
|
||||
if (codec->cache_only)
|
||||
return -1;
|
||||
|
||||
BUG_ON(!codec->hw_read);
|
||||
return codec->hw_read(codec, reg);
|
||||
}
|
||||
|
||||
ret = snd_soc_cache_read(codec, reg, &val);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
return val;
|
||||
}
|
||||
|
||||
static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u16 data;
|
||||
|
||||
data = cpu_to_be16((reg << 12) | (value & 0xffffff));
|
||||
|
||||
return do_hw_write(codec, reg, value, &data, 2);
|
||||
}
|
||||
|
||||
static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u16 data;
|
||||
|
||||
data = cpu_to_be16((reg << 9) | (value & 0x1ff));
|
||||
|
||||
return do_hw_write(codec, reg, value, &data, 2);
|
||||
}
|
||||
|
||||
static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
reg &= 0xff;
|
||||
data[0] = reg;
|
||||
data[1] = value & 0xff;
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 2);
|
||||
}
|
||||
|
||||
static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
u16 val = cpu_to_be16(value);
|
||||
|
||||
data[0] = reg;
|
||||
memcpy(&data[1], &val, sizeof(val));
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 3);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int do_i2c_read(struct snd_soc_codec *codec,
|
||||
void *reg, int reglen,
|
||||
void *data, int datalen)
|
||||
{
|
||||
struct i2c_msg xfer[2];
|
||||
int ret;
|
||||
struct i2c_client *client = codec->control_data;
|
||||
|
||||
/* Write register */
|
||||
xfer[0].addr = client->addr;
|
||||
xfer[0].flags = 0;
|
||||
xfer[0].len = reglen;
|
||||
xfer[0].buf = reg;
|
||||
|
||||
/* Read data */
|
||||
xfer[1].addr = client->addr;
|
||||
xfer[1].flags = I2C_M_RD;
|
||||
xfer[1].len = datalen;
|
||||
xfer[1].buf = data;
|
||||
|
||||
ret = i2c_transfer(client->adapter, xfer, 2);
|
||||
if (ret == 2)
|
||||
return 0;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u8 reg = r;
|
||||
u8 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 1, &data, 1);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return data;
|
||||
}
|
||||
#else
|
||||
#define snd_soc_8_8_read_i2c NULL
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u8 reg = r;
|
||||
u16 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 1, &data, 2);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return (data >> 8) | ((data & 0xff) << 8);
|
||||
}
|
||||
#else
|
||||
#define snd_soc_8_16_read_i2c NULL
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u16 reg = r;
|
||||
u8 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 2, &data, 1);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return data;
|
||||
}
|
||||
#else
|
||||
#define snd_soc_16_8_read_i2c NULL
|
||||
#endif
|
||||
|
||||
static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
u16 rval = cpu_to_be16(reg);
|
||||
|
||||
memcpy(data, &rval, sizeof(rval));
|
||||
data[2] = value;
|
||||
|
||||
return do_hw_write(codec, reg, value, data, 3);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec,
|
||||
unsigned int r)
|
||||
{
|
||||
u16 reg = cpu_to_be16(r);
|
||||
u16 data;
|
||||
int ret;
|
||||
|
||||
ret = do_i2c_read(codec, ®, 2, &data, 2);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
return be16_to_cpu(data);
|
||||
}
|
||||
#else
|
||||
#define snd_soc_16_16_read_i2c NULL
|
||||
#endif
|
||||
|
||||
static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u16 data[2];
|
||||
|
||||
data[0] = cpu_to_be16(reg);
|
||||
data[1] = cpu_to_be16(value);
|
||||
|
||||
return do_hw_write(codec, reg, value, data, sizeof(data));
|
||||
}
|
||||
|
||||
/* Primitive bulk write support for soc-cache. The data pointed to by
|
||||
* `data' needs to already be in the form the hardware expects
|
||||
* including any leading register specific data. Any data written
|
||||
* through this function will not go through the cache as it only
|
||||
* handles writing to volatile or out of bounds registers.
|
||||
*/
|
||||
static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, unsigned int reg,
|
||||
const void *data, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* To ensure that we don't get out of sync with the cache, check
|
||||
* whether the base register is volatile or if we've directly asked
|
||||
* to bypass the cache. Out of bounds registers are considered
|
||||
* volatile.
|
||||
*/
|
||||
if (!codec->cache_bypass
|
||||
&& !snd_soc_codec_volatile_register(codec, reg)
|
||||
&& reg < codec->driver->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
switch (codec->control_type) {
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
case SND_SOC_I2C:
|
||||
ret = i2c_master_send(to_i2c_client(codec->dev), data, len);
|
||||
break;
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
case SND_SOC_SPI:
|
||||
ret = spi_write(to_spi_device(codec->dev), data, len);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (ret == len)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static struct {
|
||||
int addr_bits;
|
||||
int data_bits;
|
||||
int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int);
|
||||
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
|
||||
unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
|
||||
} io_types[] = {
|
||||
{
|
||||
.addr_bits = 4, .data_bits = 12,
|
||||
.write = snd_soc_4_12_write,
|
||||
},
|
||||
{
|
||||
.addr_bits = 7, .data_bits = 9,
|
||||
.write = snd_soc_7_9_write,
|
||||
},
|
||||
{
|
||||
.addr_bits = 8, .data_bits = 8,
|
||||
.write = snd_soc_8_8_write,
|
||||
.i2c_read = snd_soc_8_8_read_i2c,
|
||||
},
|
||||
{
|
||||
.addr_bits = 8, .data_bits = 16,
|
||||
.write = snd_soc_8_16_write,
|
||||
.i2c_read = snd_soc_8_16_read_i2c,
|
||||
},
|
||||
{
|
||||
.addr_bits = 16, .data_bits = 8,
|
||||
.write = snd_soc_16_8_write,
|
||||
.i2c_read = snd_soc_16_8_read_i2c,
|
||||
},
|
||||
{
|
||||
.addr_bits = 16, .data_bits = 16,
|
||||
.write = snd_soc_16_16_write,
|
||||
.i2c_read = snd_soc_16_16_read_i2c,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* snd_soc_codec_set_cache_io: Set up standard I/O functions.
|
||||
*
|
||||
* @codec: CODEC to configure.
|
||||
* @addr_bits: Number of bits of register address data.
|
||||
* @data_bits: Number of bits of data per register.
|
||||
* @control: Control bus used.
|
||||
*
|
||||
* Register formats are frequently shared between many I2C and SPI
|
||||
* devices. In order to promote code reuse the ASoC core provides
|
||||
* some standard implementations of CODEC read and write operations
|
||||
* which can be set up using this function.
|
||||
*
|
||||
* The caller is responsible for allocating and initialising the
|
||||
* actual cache.
|
||||
*
|
||||
* Note that at present this code cannot be used by CODECs with
|
||||
* volatile registers.
|
||||
*/
|
||||
int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
|
||||
int addr_bits, int data_bits,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(io_types); i++)
|
||||
if (io_types[i].addr_bits == addr_bits &&
|
||||
io_types[i].data_bits == data_bits)
|
||||
break;
|
||||
if (i == ARRAY_SIZE(io_types)) {
|
||||
printk(KERN_ERR
|
||||
"No I/O functions for %d bit address %d bit data\n",
|
||||
addr_bits, data_bits);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec->write = io_types[i].write;
|
||||
codec->read = hw_read;
|
||||
codec->bulk_write_raw = snd_soc_hw_bulk_write_raw;
|
||||
|
||||
switch (control) {
|
||||
case SND_SOC_I2C:
|
||||
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
#endif
|
||||
if (io_types[i].i2c_read)
|
||||
codec->hw_read = io_types[i].i2c_read;
|
||||
|
||||
codec->control_data = container_of(codec->dev,
|
||||
struct i2c_client,
|
||||
dev);
|
||||
break;
|
||||
|
||||
case SND_SOC_SPI:
|
||||
#ifdef CONFIG_SPI_MASTER
|
||||
codec->hw_write = do_spi_write;
|
||||
#endif
|
||||
|
||||
codec->control_data = container_of(codec->dev,
|
||||
struct spi_device,
|
||||
dev);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
|
||||
|
639
sound/soc/soc-pcm.c
Normal file
639
sound/soc/soc-pcm.c
Normal file
@ -0,0 +1,639 @@
|
||||
/*
|
||||
* soc-pcm.c -- ALSA SoC PCM
|
||||
*
|
||||
* Copyright 2005 Wolfson Microelectronics PLC.
|
||||
* Copyright 2005 Openedhand Ltd.
|
||||
* Copyright (C) 2010 Slimlogic Ltd.
|
||||
* Copyright (C) 2010 Texas Instruments Inc.
|
||||
*
|
||||
* Authors: Liam Girdwood <lrg@ti.com>
|
||||
* Mark Brown <broonie@opensource.wolfsonmicro.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/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
static DEFINE_MUTEX(pcm_mutex);
|
||||
|
||||
static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
if (!codec_dai->driver->symmetric_rates &&
|
||||
!cpu_dai->driver->symmetric_rates &&
|
||||
!rtd->dai_link->symmetric_rates)
|
||||
return 0;
|
||||
|
||||
/* This can happen if multiple streams are starting simultaneously -
|
||||
* the second can need to get its constraints before the first has
|
||||
* picked a rate. Complain and allow the application to carry on.
|
||||
*/
|
||||
if (!rtd->rate) {
|
||||
dev_warn(&rtd->dev,
|
||||
"Not enforcing symmetric_rates due to race\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n", rtd->rate);
|
||||
|
||||
ret = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
rtd->rate, rtd->rate);
|
||||
if (ret < 0) {
|
||||
dev_err(&rtd->dev,
|
||||
"Unable to apply rate symmetry constraint: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when a PCM substream is opened, the runtime->hw record is
|
||||
* then initialized and any private data can be allocated. This also calls
|
||||
* startup for the cpu DAI, platform, machine and codec DAI.
|
||||
*/
|
||||
static int soc_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
|
||||
struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
|
||||
|
||||
/* startup the audio subsystem */
|
||||
if (cpu_dai->driver->ops->startup) {
|
||||
ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't open interface %s\n",
|
||||
cpu_dai->name);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->open) {
|
||||
ret = platform->driver->ops->open(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
|
||||
goto platform_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (codec_dai->driver->ops->startup) {
|
||||
ret = codec_dai->driver->ops->startup(substream, codec_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't open codec %s\n",
|
||||
codec_dai->name);
|
||||
goto codec_dai_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
|
||||
ret = rtd->dai_link->ops->startup(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: %s startup failed\n", rtd->dai_link->name);
|
||||
goto machine_err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that the codec and cpu DAIs are compatible */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
runtime->hw.rate_min =
|
||||
max(codec_dai_drv->playback.rate_min,
|
||||
cpu_dai_drv->playback.rate_min);
|
||||
runtime->hw.rate_max =
|
||||
min(codec_dai_drv->playback.rate_max,
|
||||
cpu_dai_drv->playback.rate_max);
|
||||
runtime->hw.channels_min =
|
||||
max(codec_dai_drv->playback.channels_min,
|
||||
cpu_dai_drv->playback.channels_min);
|
||||
runtime->hw.channels_max =
|
||||
min(codec_dai_drv->playback.channels_max,
|
||||
cpu_dai_drv->playback.channels_max);
|
||||
runtime->hw.formats =
|
||||
codec_dai_drv->playback.formats & cpu_dai_drv->playback.formats;
|
||||
runtime->hw.rates =
|
||||
codec_dai_drv->playback.rates & cpu_dai_drv->playback.rates;
|
||||
if (codec_dai_drv->playback.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= cpu_dai_drv->playback.rates;
|
||||
if (cpu_dai_drv->playback.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= codec_dai_drv->playback.rates;
|
||||
} else {
|
||||
runtime->hw.rate_min =
|
||||
max(codec_dai_drv->capture.rate_min,
|
||||
cpu_dai_drv->capture.rate_min);
|
||||
runtime->hw.rate_max =
|
||||
min(codec_dai_drv->capture.rate_max,
|
||||
cpu_dai_drv->capture.rate_max);
|
||||
runtime->hw.channels_min =
|
||||
max(codec_dai_drv->capture.channels_min,
|
||||
cpu_dai_drv->capture.channels_min);
|
||||
runtime->hw.channels_max =
|
||||
min(codec_dai_drv->capture.channels_max,
|
||||
cpu_dai_drv->capture.channels_max);
|
||||
runtime->hw.formats =
|
||||
codec_dai_drv->capture.formats & cpu_dai_drv->capture.formats;
|
||||
runtime->hw.rates =
|
||||
codec_dai_drv->capture.rates & cpu_dai_drv->capture.rates;
|
||||
if (codec_dai_drv->capture.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= cpu_dai_drv->capture.rates;
|
||||
if (cpu_dai_drv->capture.rates
|
||||
& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
|
||||
runtime->hw.rates |= codec_dai_drv->capture.rates;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
snd_pcm_limit_hw_rates(runtime);
|
||||
if (!runtime->hw.rates) {
|
||||
printk(KERN_ERR "asoc: %s <-> %s No matching rates\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
goto config_err;
|
||||
}
|
||||
if (!runtime->hw.formats) {
|
||||
printk(KERN_ERR "asoc: %s <-> %s No matching formats\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
goto config_err;
|
||||
}
|
||||
if (!runtime->hw.channels_min || !runtime->hw.channels_max ||
|
||||
runtime->hw.channels_min > runtime->hw.channels_max) {
|
||||
printk(KERN_ERR "asoc: %s <-> %s No matching channels\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
goto config_err;
|
||||
}
|
||||
|
||||
/* Symmetry only applies if we've already got an active stream. */
|
||||
if (cpu_dai->active || codec_dai->active) {
|
||||
ret = soc_pcm_apply_symmetry(substream);
|
||||
if (ret != 0)
|
||||
goto config_err;
|
||||
}
|
||||
|
||||
pr_debug("asoc: %s <-> %s info:\n",
|
||||
codec_dai->name, cpu_dai->name);
|
||||
pr_debug("asoc: rate mask 0x%x\n", runtime->hw.rates);
|
||||
pr_debug("asoc: min ch %d max ch %d\n", runtime->hw.channels_min,
|
||||
runtime->hw.channels_max);
|
||||
pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min,
|
||||
runtime->hw.rate_max);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
cpu_dai->playback_active++;
|
||||
codec_dai->playback_active++;
|
||||
} else {
|
||||
cpu_dai->capture_active++;
|
||||
codec_dai->capture_active++;
|
||||
}
|
||||
cpu_dai->active++;
|
||||
codec_dai->active++;
|
||||
rtd->codec->active++;
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
return 0;
|
||||
|
||||
config_err:
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
|
||||
rtd->dai_link->ops->shutdown(substream);
|
||||
|
||||
machine_err:
|
||||
if (codec_dai->driver->ops->shutdown)
|
||||
codec_dai->driver->ops->shutdown(substream, codec_dai);
|
||||
|
||||
codec_dai_err:
|
||||
if (platform->driver->ops && platform->driver->ops->close)
|
||||
platform->driver->ops->close(substream);
|
||||
|
||||
platform_err:
|
||||
if (cpu_dai->driver->ops->shutdown)
|
||||
cpu_dai->driver->ops->shutdown(substream, cpu_dai);
|
||||
out:
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power down the audio subsystem pmdown_time msecs after close is called.
|
||||
* This is to ensure there are no pops or clicks in between any music tracks
|
||||
* due to DAPM power cycling.
|
||||
*/
|
||||
static void close_delayed_work(struct work_struct *work)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd =
|
||||
container_of(work, struct snd_soc_pcm_runtime, delayed_work.work);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
|
||||
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
|
||||
|
||||
pr_debug("pop wq checking: %s status: %s waiting: %s\n",
|
||||
codec_dai->driver->playback.stream_name,
|
||||
codec_dai->playback_active ? "active" : "inactive",
|
||||
codec_dai->pop_wait ? "yes" : "no");
|
||||
|
||||
/* are we waiting on this codec DAI stream */
|
||||
if (codec_dai->pop_wait == 1) {
|
||||
codec_dai->pop_wait = 0;
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_STOP);
|
||||
}
|
||||
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when a PCM substream is closed. Private data can be
|
||||
* freed here. The cpu DAI, codec DAI, machine and platform are also
|
||||
* shutdown.
|
||||
*/
|
||||
static int soc_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
|
||||
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
cpu_dai->playback_active--;
|
||||
codec_dai->playback_active--;
|
||||
} else {
|
||||
cpu_dai->capture_active--;
|
||||
codec_dai->capture_active--;
|
||||
}
|
||||
|
||||
cpu_dai->active--;
|
||||
codec_dai->active--;
|
||||
codec->active--;
|
||||
|
||||
/* Muting the DAC suppresses artifacts caused during digital
|
||||
* shutdown, for example from stopping clocks.
|
||||
*/
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dai_digital_mute(codec_dai, 1);
|
||||
|
||||
if (cpu_dai->driver->ops->shutdown)
|
||||
cpu_dai->driver->ops->shutdown(substream, cpu_dai);
|
||||
|
||||
if (codec_dai->driver->ops->shutdown)
|
||||
codec_dai->driver->ops->shutdown(substream, codec_dai);
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
|
||||
rtd->dai_link->ops->shutdown(substream);
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->close)
|
||||
platform->driver->ops->close(substream);
|
||||
cpu_dai->runtime = NULL;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/* start delayed pop wq here for playback streams */
|
||||
codec_dai->pop_wait = 1;
|
||||
schedule_delayed_work(&rtd->delayed_work,
|
||||
msecs_to_jiffies(rtd->pmdown_time));
|
||||
} else {
|
||||
/* capture streams can be powered down now */
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_STOP);
|
||||
}
|
||||
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when the PCM substream is prepared, can set format, sample
|
||||
* rate, etc. This function is non atomic and can be called multiple times,
|
||||
* it can refer to the runtime info.
|
||||
*/
|
||||
static int soc_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->prepare) {
|
||||
ret = rtd->dai_link->ops->prepare(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: machine prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->prepare) {
|
||||
ret = platform->driver->ops->prepare(substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: platform prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (codec_dai->driver->ops->prepare) {
|
||||
ret = codec_dai->driver->ops->prepare(substream, codec_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: codec DAI prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (cpu_dai->driver->ops->prepare) {
|
||||
ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: cpu DAI prepare error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* cancel any delayed stream shutdown that is pending */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
codec_dai->pop_wait) {
|
||||
codec_dai->pop_wait = 0;
|
||||
cancel_delayed_work(&rtd->delayed_work);
|
||||
}
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
else
|
||||
snd_soc_dapm_stream_event(rtd,
|
||||
codec_dai->driver->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
|
||||
snd_soc_dai_digital_mute(codec_dai, 0);
|
||||
|
||||
out:
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by ALSA when the hardware params are set by application. This
|
||||
* function can also be called multiple times and can allocate buffers
|
||||
* (using snd_pcm_lib_* ). It's non-atomic.
|
||||
*/
|
||||
static int soc_pcm_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_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
|
||||
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
|
||||
ret = rtd->dai_link->ops->hw_params(substream, params);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: machine hw_params failed\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (codec_dai->driver->ops->hw_params) {
|
||||
ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't set codec %s hw params\n",
|
||||
codec_dai->name);
|
||||
goto codec_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (cpu_dai->driver->ops->hw_params) {
|
||||
ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: interface %s hw params failed\n",
|
||||
cpu_dai->name);
|
||||
goto interface_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->hw_params) {
|
||||
ret = platform->driver->ops->hw_params(substream, params);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: platform %s hw params failed\n",
|
||||
platform->name);
|
||||
goto platform_err;
|
||||
}
|
||||
}
|
||||
|
||||
rtd->rate = params_rate(params);
|
||||
|
||||
out:
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
return ret;
|
||||
|
||||
platform_err:
|
||||
if (cpu_dai->driver->ops->hw_free)
|
||||
cpu_dai->driver->ops->hw_free(substream, cpu_dai);
|
||||
|
||||
interface_err:
|
||||
if (codec_dai->driver->ops->hw_free)
|
||||
codec_dai->driver->ops->hw_free(substream, codec_dai);
|
||||
|
||||
codec_err:
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
|
||||
rtd->dai_link->ops->hw_free(substream);
|
||||
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees resources allocated by hw_params, can be called multiple times
|
||||
*/
|
||||
static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
|
||||
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
|
||||
|
||||
/* apply codec digital mute */
|
||||
if (!codec->active)
|
||||
snd_soc_dai_digital_mute(codec_dai, 1);
|
||||
|
||||
/* free any machine hw params */
|
||||
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
|
||||
rtd->dai_link->ops->hw_free(substream);
|
||||
|
||||
/* free any DMA resources */
|
||||
if (platform->driver->ops && platform->driver->ops->hw_free)
|
||||
platform->driver->ops->hw_free(substream);
|
||||
|
||||
/* now free hw params for the DAIs */
|
||||
if (codec_dai->driver->ops->hw_free)
|
||||
codec_dai->driver->ops->hw_free(substream, codec_dai);
|
||||
|
||||
if (cpu_dai->driver->ops->hw_free)
|
||||
cpu_dai->driver->ops->hw_free(substream, cpu_dai);
|
||||
|
||||
mutex_unlock(&rtd->pcm_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
if (codec_dai->driver->ops->trigger) {
|
||||
ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->trigger) {
|
||||
ret = platform->driver->ops->trigger(substream, cmd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cpu_dai->driver->ops->trigger) {
|
||||
ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* soc level wrapper for pointer callback
|
||||
* If cpu_dai, codec_dai, platform driver has the delay callback, than
|
||||
* the runtime->delay will be updated accordingly.
|
||||
*/
|
||||
static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_pcm_uframes_t offset = 0;
|
||||
snd_pcm_sframes_t delay = 0;
|
||||
|
||||
if (platform->driver->ops && platform->driver->ops->pointer)
|
||||
offset = platform->driver->ops->pointer(substream);
|
||||
|
||||
if (cpu_dai->driver->ops->delay)
|
||||
delay += cpu_dai->driver->ops->delay(substream, cpu_dai);
|
||||
|
||||
if (codec_dai->driver->ops->delay)
|
||||
delay += codec_dai->driver->ops->delay(substream, codec_dai);
|
||||
|
||||
if (platform->driver->delay)
|
||||
delay += platform->driver->delay(substream, codec_dai);
|
||||
|
||||
runtime->delay = delay;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* ASoC PCM operations */
|
||||
static struct snd_pcm_ops soc_pcm_ops = {
|
||||
.open = soc_pcm_open,
|
||||
.close = soc_pcm_close,
|
||||
.hw_params = soc_pcm_hw_params,
|
||||
.hw_free = soc_pcm_hw_free,
|
||||
.prepare = soc_pcm_prepare,
|
||||
.trigger = soc_pcm_trigger,
|
||||
.pointer = soc_pcm_pointer,
|
||||
};
|
||||
|
||||
/* create a new pcm */
|
||||
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_platform *platform = rtd->platform;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm;
|
||||
char new_name[64];
|
||||
int ret = 0, playback = 0, capture = 0;
|
||||
|
||||
/* check client and interface hw capabilities */
|
||||
snprintf(new_name, sizeof(new_name), "%s %s-%d",
|
||||
rtd->dai_link->stream_name, codec_dai->name, num);
|
||||
|
||||
if (codec_dai->driver->playback.channels_min)
|
||||
playback = 1;
|
||||
if (codec_dai->driver->capture.channels_min)
|
||||
capture = 1;
|
||||
|
||||
dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
|
||||
ret = snd_pcm_new(rtd->card->snd_card, new_name,
|
||||
num, playback, capture, &pcm);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* DAPM dai link stream work */
|
||||
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
|
||||
|
||||
rtd->pcm = pcm;
|
||||
pcm->private_data = rtd;
|
||||
if (platform->driver->ops) {
|
||||
soc_pcm_ops.mmap = platform->driver->ops->mmap;
|
||||
soc_pcm_ops.pointer = platform->driver->ops->pointer;
|
||||
soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
|
||||
soc_pcm_ops.copy = platform->driver->ops->copy;
|
||||
soc_pcm_ops.silence = platform->driver->ops->silence;
|
||||
soc_pcm_ops.ack = platform->driver->ops->ack;
|
||||
soc_pcm_ops.page = platform->driver->ops->page;
|
||||
}
|
||||
|
||||
if (playback)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
|
||||
|
||||
if (capture)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
|
||||
|
||||
if (platform->driver->pcm_new) {
|
||||
ret = platform->driver->pcm_new(rtd);
|
||||
if (ret < 0) {
|
||||
pr_err("asoc: platform pcm constructor failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pcm->private_free = platform->driver->pcm_free;
|
||||
printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
|
||||
cpu_dai->name);
|
||||
return ret;
|
||||
}
|
@ -12,6 +12,15 @@ config SND_SOC_TEGRA_I2S
|
||||
Tegra I2S interface. You will also need to select the individual
|
||||
machine drivers to support below.
|
||||
|
||||
config SND_SOC_TEGRA_SPDIF
|
||||
tristate
|
||||
depends on SND_SOC_TEGRA
|
||||
default m
|
||||
help
|
||||
Say Y or M if you want to add support for the SPDIF interface.
|
||||
You will also need to select the individual machine drivers to support
|
||||
below.
|
||||
|
||||
config MACH_HAS_SND_SOC_TEGRA_WM8903
|
||||
bool
|
||||
help
|
||||
|
@ -2,12 +2,14 @@
|
||||
snd-soc-tegra-das-objs := tegra_das.o
|
||||
snd-soc-tegra-pcm-objs := tegra_pcm.o
|
||||
snd-soc-tegra-i2s-objs := tegra_i2s.o
|
||||
snd-soc-tegra-spdif-objs := tegra_spdif.o
|
||||
snd-soc-tegra-utils-objs += tegra_asoc_utils.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o
|
||||
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-das.o
|
||||
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o
|
||||
obj-$(CONFIG_SND_SOC_TEGRA_I2S) += snd-soc-tegra-i2s.o
|
||||
obj-$(CONFIG_SND_SOC_TEGRA_SPDIF) += snd-soc-tegra-spdif.o
|
||||
|
||||
# Tegra machine Support
|
||||
snd-soc-tegra-wm8903-objs := tegra_wm8903.o
|
||||
|
@ -354,7 +354,6 @@ struct snd_soc_dai_driver tegra_i2s_dai[] = {
|
||||
static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_i2s * i2s;
|
||||
char clk_name[12]; /* tegra-i2s.0 */
|
||||
struct resource *mem, *memregion, *dmareq;
|
||||
int ret;
|
||||
|
||||
@ -389,8 +388,7 @@ static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev)
|
||||
}
|
||||
dev_set_drvdata(&pdev->dev, i2s);
|
||||
|
||||
snprintf(clk_name, sizeof(clk_name), DRV_NAME ".%d", pdev->id);
|
||||
i2s->clk_i2s = clk_get_sys(clk_name, NULL);
|
||||
i2s->clk_i2s = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(i2s->clk_i2s)) {
|
||||
dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
|
||||
ret = PTR_ERR(i2s->clk_i2s);
|
||||
|
@ -322,9 +322,11 @@ static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
|
||||
static u64 tegra_dma_mask = DMA_BIT_MASK(32);
|
||||
|
||||
static int tegra_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
static int tegra_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
|
371
sound/soc/tegra/tegra_spdif.c
Normal file
371
sound/soc/tegra/tegra_spdif.c
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
* tegra_spdif.c - Tegra SPDIF driver
|
||||
*
|
||||
* Author: Stephen Warren <swarren@nvidia.com>
|
||||
* Copyright (C) 2011 - NVIDIA, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/io.h>
|
||||
#include <mach/iomap.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "tegra_spdif.h"
|
||||
|
||||
#define DRV_NAME "tegra-spdif"
|
||||
|
||||
static inline void tegra_spdif_write(struct tegra_spdif *spdif, u32 reg,
|
||||
u32 val)
|
||||
{
|
||||
__raw_writel(val, spdif->regs + reg);
|
||||
}
|
||||
|
||||
static inline u32 tegra_spdif_read(struct tegra_spdif *spdif, u32 reg)
|
||||
{
|
||||
return __raw_readl(spdif->regs + reg);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int tegra_spdif_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
#define REG(r) { r, #r }
|
||||
static const struct {
|
||||
int offset;
|
||||
const char *name;
|
||||
} regs[] = {
|
||||
REG(TEGRA_SPDIF_CTRL),
|
||||
REG(TEGRA_SPDIF_STATUS),
|
||||
REG(TEGRA_SPDIF_STROBE_CTRL),
|
||||
REG(TEGRA_SPDIF_DATA_FIFO_CSR),
|
||||
REG(TEGRA_SPDIF_CH_STA_RX_A),
|
||||
REG(TEGRA_SPDIF_CH_STA_RX_B),
|
||||
REG(TEGRA_SPDIF_CH_STA_RX_C),
|
||||
REG(TEGRA_SPDIF_CH_STA_RX_D),
|
||||
REG(TEGRA_SPDIF_CH_STA_RX_E),
|
||||
REG(TEGRA_SPDIF_CH_STA_RX_F),
|
||||
REG(TEGRA_SPDIF_CH_STA_TX_A),
|
||||
REG(TEGRA_SPDIF_CH_STA_TX_B),
|
||||
REG(TEGRA_SPDIF_CH_STA_TX_C),
|
||||
REG(TEGRA_SPDIF_CH_STA_TX_D),
|
||||
REG(TEGRA_SPDIF_CH_STA_TX_E),
|
||||
REG(TEGRA_SPDIF_CH_STA_TX_F),
|
||||
};
|
||||
#undef REG
|
||||
|
||||
struct tegra_spdif *spdif = s->private;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(regs); i++) {
|
||||
u32 val = tegra_spdif_read(spdif, regs[i].offset);
|
||||
seq_printf(s, "%s = %08x\n", regs[i].name, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_spdif_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, tegra_spdif_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations tegra_spdif_debug_fops = {
|
||||
.open = tegra_spdif_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static void tegra_spdif_debug_add(struct tegra_spdif *spdif)
|
||||
{
|
||||
spdif->debug = debugfs_create_file(DRV_NAME, S_IRUGO,
|
||||
snd_soc_debugfs_root, spdif,
|
||||
&tegra_spdif_debug_fops);
|
||||
}
|
||||
|
||||
static void tegra_spdif_debug_remove(struct tegra_spdif *spdif)
|
||||
{
|
||||
if (spdif->debug)
|
||||
debugfs_remove(spdif->debug);
|
||||
}
|
||||
#else
|
||||
static inline void tegra_spdif_debug_add(struct tegra_spdif *spdif)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void tegra_spdif_debug_remove(struct tegra_spdif *spdif)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static int tegra_spdif_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct device *dev = substream->pcm->card->dev;
|
||||
struct tegra_spdif *spdif = snd_soc_dai_get_drvdata(dai);
|
||||
int ret, srate, spdifclock;
|
||||
|
||||
spdif->reg_ctrl &= ~TEGRA_SPDIF_CTRL_PACK;
|
||||
spdif->reg_ctrl &= ~TEGRA_SPDIF_CTRL_BIT_MODE_MASK;
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
spdif->reg_ctrl |= TEGRA_SPDIF_CTRL_PACK;
|
||||
spdif->reg_ctrl |= TEGRA_SPDIF_CTRL_BIT_MODE_16BIT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
srate = params_rate(params);
|
||||
switch (params_rate(params)) {
|
||||
case 32000:
|
||||
spdifclock = 4096000;
|
||||
break;
|
||||
case 44100:
|
||||
spdifclock = 5644800;
|
||||
break;
|
||||
case 48000:
|
||||
spdifclock = 6144000;
|
||||
break;
|
||||
case 88200:
|
||||
spdifclock = 11289600;
|
||||
break;
|
||||
case 96000:
|
||||
spdifclock = 12288000;
|
||||
break;
|
||||
case 176400:
|
||||
spdifclock = 22579200;
|
||||
break;
|
||||
case 192000:
|
||||
spdifclock = 24576000;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_set_rate(spdif->clk_spdif_out, spdifclock);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_spdif_start_playback(struct tegra_spdif *spdif)
|
||||
{
|
||||
spdif->reg_ctrl |= TEGRA_SPDIF_CTRL_TX_EN;
|
||||
tegra_spdif_write(spdif, TEGRA_SPDIF_CTRL, spdif->reg_ctrl);
|
||||
}
|
||||
|
||||
static void tegra_spdif_stop_playback(struct tegra_spdif *spdif)
|
||||
{
|
||||
spdif->reg_ctrl &= ~TEGRA_SPDIF_CTRL_TX_EN;
|
||||
tegra_spdif_write(spdif, TEGRA_SPDIF_CTRL, spdif->reg_ctrl);
|
||||
}
|
||||
|
||||
static int tegra_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct tegra_spdif *spdif = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
if (!spdif->clk_refs)
|
||||
clk_enable(spdif->clk_spdif_out);
|
||||
spdif->clk_refs++;
|
||||
tegra_spdif_start_playback(spdif);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
tegra_spdif_stop_playback(spdif);
|
||||
spdif->clk_refs--;
|
||||
if (!spdif->clk_refs)
|
||||
clk_disable(spdif->clk_spdif_out);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_spdif_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct tegra_spdif *spdif = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
dai->capture_dma_data = NULL;
|
||||
dai->playback_dma_data = &spdif->playback_dma_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops tegra_spdif_dai_ops = {
|
||||
.hw_params = tegra_spdif_hw_params,
|
||||
.trigger = tegra_spdif_trigger,
|
||||
};
|
||||
|
||||
struct snd_soc_dai_driver tegra_spdif_dai = {
|
||||
.name = DRV_NAME,
|
||||
.probe = tegra_spdif_probe,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
|
||||
SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &tegra_spdif_dai_ops,
|
||||
};
|
||||
|
||||
static __devinit int tegra_spdif_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_spdif *spdif;
|
||||
struct resource *mem, *memregion, *dmareq;
|
||||
int ret;
|
||||
|
||||
spdif = kzalloc(sizeof(struct tegra_spdif), GFP_KERNEL);
|
||||
if (!spdif) {
|
||||
dev_err(&pdev->dev, "Can't allocate tegra_spdif\n");
|
||||
ret = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
dev_set_drvdata(&pdev->dev, spdif);
|
||||
|
||||
spdif->clk_spdif_out = clk_get(&pdev->dev, "spdif_out");
|
||||
if (IS_ERR(spdif->clk_spdif_out)) {
|
||||
pr_err("Can't retrieve spdif clock\n");
|
||||
ret = PTR_ERR(spdif->clk_spdif_out);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!mem) {
|
||||
dev_err(&pdev->dev, "No memory resource\n");
|
||||
ret = -ENODEV;
|
||||
goto err_clk_put;
|
||||
}
|
||||
|
||||
dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!dmareq) {
|
||||
dev_err(&pdev->dev, "No DMA resource\n");
|
||||
ret = -ENODEV;
|
||||
goto err_clk_put;
|
||||
}
|
||||
|
||||
memregion = request_mem_region(mem->start, resource_size(mem),
|
||||
DRV_NAME);
|
||||
if (!memregion) {
|
||||
dev_err(&pdev->dev, "Memory region already claimed\n");
|
||||
ret = -EBUSY;
|
||||
goto err_clk_put;
|
||||
}
|
||||
|
||||
spdif->regs = ioremap(mem->start, resource_size(mem));
|
||||
if (!spdif->regs) {
|
||||
dev_err(&pdev->dev, "ioremap failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_release;
|
||||
}
|
||||
|
||||
spdif->playback_dma_data.addr = mem->start + TEGRA_SPDIF_DATA_OUT;
|
||||
spdif->playback_dma_data.wrap = 4;
|
||||
spdif->playback_dma_data.width = 32;
|
||||
spdif->playback_dma_data.req_sel = dmareq->start;
|
||||
|
||||
ret = snd_soc_register_dai(&pdev->dev, &tegra_spdif_dai);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
|
||||
ret = -ENOMEM;
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
tegra_spdif_debug_add(spdif);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unmap:
|
||||
iounmap(spdif->regs);
|
||||
err_release:
|
||||
release_mem_region(mem->start, resource_size(mem));
|
||||
err_clk_put:
|
||||
clk_put(spdif->clk_spdif_out);
|
||||
err_free:
|
||||
kfree(spdif);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit tegra_spdif_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_spdif *spdif = dev_get_drvdata(&pdev->dev);
|
||||
struct resource *res;
|
||||
|
||||
snd_soc_unregister_dai(&pdev->dev);
|
||||
|
||||
tegra_spdif_debug_remove(spdif);
|
||||
|
||||
iounmap(spdif->regs);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
|
||||
clk_put(spdif->clk_spdif_out);
|
||||
|
||||
kfree(spdif);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver tegra_spdif_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = tegra_spdif_platform_probe,
|
||||
.remove = __devexit_p(tegra_spdif_platform_remove),
|
||||
};
|
||||
|
||||
static int __init snd_tegra_spdif_init(void)
|
||||
{
|
||||
return platform_driver_register(&tegra_spdif_driver);
|
||||
}
|
||||
module_init(snd_tegra_spdif_init);
|
||||
|
||||
static void __exit snd_tegra_spdif_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&tegra_spdif_driver);
|
||||
}
|
||||
module_exit(snd_tegra_spdif_exit);
|
||||
|
||||
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
|
||||
MODULE_DESCRIPTION("Tegra SPDIF ASoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
473
sound/soc/tegra/tegra_spdif.h
Normal file
473
sound/soc/tegra/tegra_spdif.h
Normal file
@ -0,0 +1,473 @@
|
||||
/*
|
||||
* tegra_spdif.h - Definitions for Tegra SPDIF driver
|
||||
*
|
||||
* Author: Stephen Warren <swarren@nvidia.com>
|
||||
* Copyright (C) 2011 - NVIDIA, Inc.
|
||||
*
|
||||
* Based on code copyright/by:
|
||||
* Copyright (c) 2008-2009, NVIDIA Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __TEGRA_SPDIF_H__
|
||||
#define __TEGRA_SPDIF_H__
|
||||
|
||||
#include "tegra_pcm.h"
|
||||
|
||||
/* Offsets from TEGRA_SPDIF_BASE */
|
||||
|
||||
#define TEGRA_SPDIF_CTRL 0x0
|
||||
#define TEGRA_SPDIF_STATUS 0x4
|
||||
#define TEGRA_SPDIF_STROBE_CTRL 0x8
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR 0x0C
|
||||
#define TEGRA_SPDIF_DATA_OUT 0x40
|
||||
#define TEGRA_SPDIF_DATA_IN 0x80
|
||||
#define TEGRA_SPDIF_CH_STA_RX_A 0x100
|
||||
#define TEGRA_SPDIF_CH_STA_RX_B 0x104
|
||||
#define TEGRA_SPDIF_CH_STA_RX_C 0x108
|
||||
#define TEGRA_SPDIF_CH_STA_RX_D 0x10C
|
||||
#define TEGRA_SPDIF_CH_STA_RX_E 0x110
|
||||
#define TEGRA_SPDIF_CH_STA_RX_F 0x114
|
||||
#define TEGRA_SPDIF_CH_STA_TX_A 0x140
|
||||
#define TEGRA_SPDIF_CH_STA_TX_B 0x144
|
||||
#define TEGRA_SPDIF_CH_STA_TX_C 0x148
|
||||
#define TEGRA_SPDIF_CH_STA_TX_D 0x14C
|
||||
#define TEGRA_SPDIF_CH_STA_TX_E 0x150
|
||||
#define TEGRA_SPDIF_CH_STA_TX_F 0x154
|
||||
#define TEGRA_SPDIF_USR_STA_RX_A 0x180
|
||||
#define TEGRA_SPDIF_USR_DAT_TX_A 0x1C0
|
||||
|
||||
/* Fields in TEGRA_SPDIF_CTRL */
|
||||
|
||||
/* Start capturing from 0=right, 1=left channel */
|
||||
#define TEGRA_SPDIF_CTRL_CAP_LC (1 << 30)
|
||||
|
||||
/* SPDIF receiver(RX) enable */
|
||||
#define TEGRA_SPDIF_CTRL_RX_EN (1 << 29)
|
||||
|
||||
/* SPDIF Transmitter(TX) enable */
|
||||
#define TEGRA_SPDIF_CTRL_TX_EN (1 << 28)
|
||||
|
||||
/* Transmit Channel status */
|
||||
#define TEGRA_SPDIF_CTRL_TC_EN (1 << 27)
|
||||
|
||||
/* Transmit user Data */
|
||||
#define TEGRA_SPDIF_CTRL_TU_EN (1 << 26)
|
||||
|
||||
/* Interrupt on transmit error */
|
||||
#define TEGRA_SPDIF_CTRL_IE_TXE (1 << 25)
|
||||
|
||||
/* Interrupt on receive error */
|
||||
#define TEGRA_SPDIF_CTRL_IE_RXE (1 << 24)
|
||||
|
||||
/* Interrupt on invalid preamble */
|
||||
#define TEGRA_SPDIF_CTRL_IE_P (1 << 23)
|
||||
|
||||
/* Interrupt on "B" preamble */
|
||||
#define TEGRA_SPDIF_CTRL_IE_B (1 << 22)
|
||||
|
||||
/* Interrupt when block of channel status received */
|
||||
#define TEGRA_SPDIF_CTRL_IE_C (1 << 21)
|
||||
|
||||
/* Interrupt when a valid information unit (IU) is received */
|
||||
#define TEGRA_SPDIF_CTRL_IE_U (1 << 20)
|
||||
|
||||
/* Interrupt when RX user FIFO attention level is reached */
|
||||
#define TEGRA_SPDIF_CTRL_QE_RU (1 << 19)
|
||||
|
||||
/* Interrupt when TX user FIFO attention level is reached */
|
||||
#define TEGRA_SPDIF_CTRL_QE_TU (1 << 18)
|
||||
|
||||
/* Interrupt when RX data FIFO attention level is reached */
|
||||
#define TEGRA_SPDIF_CTRL_QE_RX (1 << 17)
|
||||
|
||||
/* Interrupt when TX data FIFO attention level is reached */
|
||||
#define TEGRA_SPDIF_CTRL_QE_TX (1 << 16)
|
||||
|
||||
/* Loopback test mode enable */
|
||||
#define TEGRA_SPDIF_CTRL_LBK_EN (1 << 15)
|
||||
|
||||
/*
|
||||
* Pack data mode:
|
||||
* 0 = Single data (16 bit needs to be padded to match the
|
||||
* interface data bit size).
|
||||
* 1 = Packeted left/right channel data into a single word.
|
||||
*/
|
||||
#define TEGRA_SPDIF_CTRL_PACK (1 << 14)
|
||||
|
||||
/*
|
||||
* 00 = 16bit data
|
||||
* 01 = 20bit data
|
||||
* 10 = 24bit data
|
||||
* 11 = raw data
|
||||
*/
|
||||
#define TEGRA_SPDIF_BIT_MODE_16BIT 0
|
||||
#define TEGRA_SPDIF_BIT_MODE_20BIT 1
|
||||
#define TEGRA_SPDIF_BIT_MODE_24BIT 2
|
||||
#define TEGRA_SPDIF_BIT_MODE_RAW 3
|
||||
|
||||
#define TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT 12
|
||||
#define TEGRA_SPDIF_CTRL_BIT_MODE_MASK (3 << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT)
|
||||
#define TEGRA_SPDIF_CTRL_BIT_MODE_16BIT (TEGRA_SPDIF_BIT_MODE_16BIT << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT)
|
||||
#define TEGRA_SPDIF_CTRL_BIT_MODE_20BIT (TEGRA_SPDIF_BIT_MODE_20BIT << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT)
|
||||
#define TEGRA_SPDIF_CTRL_BIT_MODE_24BIT (TEGRA_SPDIF_BIT_MODE_24BIT << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT)
|
||||
#define TEGRA_SPDIF_CTRL_BIT_MODE_RAW (TEGRA_SPDIF_BIT_MODE_RAW << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT)
|
||||
|
||||
/* Fields in TEGRA_SPDIF_STATUS */
|
||||
|
||||
/*
|
||||
* Note: IS_P, IS_B, IS_C, and IS_U are sticky bits. Software must
|
||||
* write a 1 to the corresponding bit location to clear the status.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Receiver(RX) shifter is busy receiving data.
|
||||
* This bit is asserted when the receiver first locked onto the
|
||||
* preamble of the data stream after RX_EN is asserted. This bit is
|
||||
* deasserted when either,
|
||||
* (a) the end of a frame is reached after RX_EN is deeasserted, or
|
||||
* (b) the SPDIF data stream becomes inactive.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_RX_BSY (1 << 29)
|
||||
|
||||
/*
|
||||
* Transmitter(TX) shifter is busy transmitting data.
|
||||
* This bit is asserted when TX_EN is asserted.
|
||||
* This bit is deasserted when the end of a frame is reached after
|
||||
* TX_EN is deasserted.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_TX_BSY (1 << 28)
|
||||
|
||||
/*
|
||||
* TX is busy shifting out channel status.
|
||||
* This bit is asserted when both TX_EN and TC_EN are asserted and
|
||||
* data from CH_STA_TX_A register is loaded into the internal shifter.
|
||||
* This bit is deasserted when either,
|
||||
* (a) the end of a frame is reached after TX_EN is deasserted, or
|
||||
* (b) CH_STA_TX_F register is loaded into the internal shifter.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_TC_BSY (1 << 27)
|
||||
|
||||
/*
|
||||
* TX User data FIFO busy.
|
||||
* This bit is asserted when TX_EN and TXU_EN are asserted and
|
||||
* there's data in the TX user FIFO. This bit is deassert when either,
|
||||
* (a) the end of a frame is reached after TX_EN is deasserted, or
|
||||
* (b) there's no data left in the TX user FIFO.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_TU_BSY (1 << 26)
|
||||
|
||||
/* TX FIFO Underrun error status */
|
||||
#define TEGRA_SPDIF_STATUS_TX_ERR (1 << 25)
|
||||
|
||||
/* RX FIFO Overrun error status */
|
||||
#define TEGRA_SPDIF_STATUS_RX_ERR (1 << 24)
|
||||
|
||||
/* Preamble status: 0=Preamble OK, 1=bad/missing preamble */
|
||||
#define TEGRA_SPDIF_STATUS_IS_P (1 << 23)
|
||||
|
||||
/* B-preamble detection status: 0=not detected, 1=B-preamble detected */
|
||||
#define TEGRA_SPDIF_STATUS_IS_B (1 << 22)
|
||||
|
||||
/*
|
||||
* RX channel block data receive status:
|
||||
* 0=entire block not recieved yet.
|
||||
* 1=received entire block of channel status,
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_IS_C (1 << 21)
|
||||
|
||||
/* RX User Data Valid flag: 1=valid IU detected, 0 = no IU detected. */
|
||||
#define TEGRA_SPDIF_STATUS_IS_U (1 << 20)
|
||||
|
||||
/*
|
||||
* RX User FIFO Status:
|
||||
* 1=attention level reached, 0=attention level not reached.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_QS_RU (1 << 19)
|
||||
|
||||
/*
|
||||
* TX User FIFO Status:
|
||||
* 1=attention level reached, 0=attention level not reached.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_QS_TU (1 << 18)
|
||||
|
||||
/*
|
||||
* RX Data FIFO Status:
|
||||
* 1=attention level reached, 0=attention level not reached.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_QS_RX (1 << 17)
|
||||
|
||||
/*
|
||||
* TX Data FIFO Status:
|
||||
* 1=attention level reached, 0=attention level not reached.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STATUS_QS_TX (1 << 16)
|
||||
|
||||
/* Fields in TEGRA_SPDIF_STROBE_CTRL */
|
||||
|
||||
/*
|
||||
* Indicates the approximate number of detected SPDIFIN clocks within a
|
||||
* bi-phase period.
|
||||
*/
|
||||
#define TEGRA_SPDIF_STROBE_CTRL_PERIOD_SHIFT 16
|
||||
#define TEGRA_SPDIF_STROBE_CTRL_PERIOD_MASK (0xff << TEGRA_SPDIF_STROBE_CTRL_PERIOD_SHIFT)
|
||||
|
||||
/* Data strobe mode: 0=Auto-locked 1=Manual locked */
|
||||
#define TEGRA_SPDIF_STROBE_CTRL_STROBE (1 << 15)
|
||||
|
||||
/*
|
||||
* Manual data strobe time within the bi-phase clock period (in terms of
|
||||
* the number of over-sampling clocks).
|
||||
*/
|
||||
#define TEGRA_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT 8
|
||||
#define TEGRA_SPDIF_STROBE_CTRL_DATA_STROBES_MASK (0x1f << TEGRA_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT)
|
||||
|
||||
/*
|
||||
* Manual SPDIFIN bi-phase clock period (in terms of the number of
|
||||
* over-sampling clocks).
|
||||
*/
|
||||
#define TEGRA_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT 0
|
||||
#define TEGRA_SPDIF_STROBE_CTRL_CLOCK_PERIOD_MASK (0x3f << TEGRA_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT)
|
||||
|
||||
/* Fields in SPDIF_DATA_FIFO_CSR */
|
||||
|
||||
/* Clear Receiver User FIFO (RX USR.FIFO) */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_CLR (1 << 31)
|
||||
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT 0
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS 1
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS 2
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS 3
|
||||
|
||||
/* RU FIFO attention level */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT 29
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_MASK \
|
||||
(0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU1_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU2_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU3_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU4_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
|
||||
|
||||
/* Number of RX USR.FIFO levels with valid data. */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT 24
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_MASK (0x1f << TEGRA_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT)
|
||||
|
||||
/* Clear Transmitter User FIFO (TX USR.FIFO) */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_CLR (1 << 23)
|
||||
|
||||
/* TU FIFO attention level */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT 21
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_MASK \
|
||||
(0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU1_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU2_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU3_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU4_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
|
||||
|
||||
/* Number of TX USR.FIFO levels that could be filled. */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT 16
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT)
|
||||
|
||||
/* Clear Receiver Data FIFO (RX DATA.FIFO) */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_CLR (1 << 15)
|
||||
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT 0
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS 1
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS 2
|
||||
#define TEGRA_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS 3
|
||||
|
||||
/* RU FIFO attention level */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT 13
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_MASK \
|
||||
(0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU1_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU4_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU8_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU12_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
|
||||
|
||||
/* Number of RX DATA.FIFO levels with valid data. */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT 8
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_MASK (0x1f << TEGRA_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT)
|
||||
|
||||
/* Clear Transmitter Data FIFO (TX DATA.FIFO) */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_CLR (1 << 7)
|
||||
|
||||
/* TU FIFO attention level */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT 5
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK \
|
||||
(0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU1_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU8_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU12_WORD_FULL \
|
||||
(TEGRA_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
|
||||
|
||||
/* Number of TX DATA.FIFO levels that could be filled. */
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT)
|
||||
|
||||
/* Fields in TEGRA_SPDIF_DATA_OUT */
|
||||
|
||||
/*
|
||||
* This register has 5 different formats:
|
||||
* 16-bit (BIT_MODE=00, PACK=0)
|
||||
* 20-bit (BIT_MODE=01, PACK=0)
|
||||
* 24-bit (BIT_MODE=10, PACK=0)
|
||||
* raw (BIT_MODE=11, PACK=0)
|
||||
* 16-bit packed (BIT_MODE=00, PACK=1)
|
||||
*/
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_16_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_16_MASK (0xffff << TEGRA_SPDIF_DATA_OUT_DATA_16_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_20_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_20_MASK (0xfffff << TEGRA_SPDIF_DATA_OUT_DATA_20_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_24_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_24_MASK (0xffffff << TEGRA_SPDIF_DATA_OUT_DATA_24_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_P (1 << 31)
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_C (1 << 30)
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_U (1 << 29)
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_V (1 << 28)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT 8
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_DATA_MASK (0xfffff << TEGRA_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT 4
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_AUX_MASK (0xf << TEGRA_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT 16
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT)
|
||||
|
||||
/* Fields in TEGRA_SPDIF_DATA_IN */
|
||||
|
||||
/*
|
||||
* This register has 5 different formats:
|
||||
* 16-bit (BIT_MODE=00, PACK=0)
|
||||
* 20-bit (BIT_MODE=01, PACK=0)
|
||||
* 24-bit (BIT_MODE=10, PACK=0)
|
||||
* raw (BIT_MODE=11, PACK=0)
|
||||
* 16-bit packed (BIT_MODE=00, PACK=1)
|
||||
*
|
||||
* Bits 31:24 are common to all modes except 16-bit packed
|
||||
*/
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_P (1 << 31)
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_C (1 << 30)
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_U (1 << 29)
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_V (1 << 28)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT 24
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_PREAMBLE_MASK (0xf << TEGRA_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_16_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_16_MASK (0xffff << TEGRA_SPDIF_DATA_IN_DATA_16_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_20_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_20_MASK (0xfffff << TEGRA_SPDIF_DATA_IN_DATA_20_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_24_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_24_MASK (0xffffff << TEGRA_SPDIF_DATA_IN_DATA_24_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT 8
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_RAW_DATA_MASK (0xfffff << TEGRA_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT 4
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_RAW_AUX_MASK (0xf << TEGRA_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT 16
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT)
|
||||
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT 0
|
||||
#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT)
|
||||
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_RX_A */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_RX_B */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_RX_C */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_RX_D */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_RX_E */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_RX_F */
|
||||
|
||||
/*
|
||||
* The 6-word receive channel data page buffer holds a block (192 frames) of
|
||||
* channel status information. The order of receive is from LSB to MSB
|
||||
* bit, and from CH_STA_RX_A to CH_STA_RX_F then back to CH_STA_RX_A.
|
||||
*/
|
||||
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_TX_A */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_TX_B */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_TX_C */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_TX_D */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_TX_E */
|
||||
/* Fields in TEGRA_SPDIF_CH_STA_TX_F */
|
||||
|
||||
/*
|
||||
* The 6-word transmit channel data page buffer holds a block (192 frames) of
|
||||
* channel status information. The order of transmission is from LSB to MSB
|
||||
* bit, and from CH_STA_TX_A to CH_STA_TX_F then back to CH_STA_TX_A.
|
||||
*/
|
||||
|
||||
/* Fields in TEGRA_SPDIF_USR_STA_RX_A */
|
||||
|
||||
/*
|
||||
* This 4-word deep FIFO receives user FIFO field information. The order of
|
||||
* receive is from LSB to MSB bit.
|
||||
*/
|
||||
|
||||
/* Fields in TEGRA_SPDIF_USR_DAT_TX_A */
|
||||
|
||||
/*
|
||||
* This 4-word deep FIFO transmits user FIFO field information. The order of
|
||||
* transmission is from LSB to MSB bit.
|
||||
*/
|
||||
|
||||
struct tegra_spdif {
|
||||
struct clk *clk_spdif_out;
|
||||
int clk_refs;
|
||||
struct tegra_pcm_dma_params capture_dma_data;
|
||||
struct tegra_pcm_dma_params playback_dma_data;
|
||||
void __iomem *regs;
|
||||
struct dentry *debug;
|
||||
u32 reg_ctrl;
|
||||
};
|
||||
|
||||
#endif
|
@ -267,7 +267,7 @@ static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd)
|
||||
}
|
||||
machine->gpio_requested |= GPIO_HP_MUTE;
|
||||
|
||||
gpio_direction_output(pdata->gpio_hp_mute, 0);
|
||||
gpio_direction_output(pdata->gpio_hp_mute, 1);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(pdata->gpio_int_mic_en)) {
|
||||
|
@ -288,9 +288,10 @@ static void txx9aclc_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
static int txx9aclc_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
struct platform_device *pdev = to_platform_device(dai->platform->dev);
|
||||
struct txx9aclc_soc_device *dev;
|
||||
struct resource *r;
|
||||
|
Loading…
Reference in New Issue
Block a user