forked from Minki/linux
Merge branch 'topic/asoc' into for-linus
* topic/asoc: (135 commits) ASoC: Apostrophe patrol ASoC: codec tlv320aic23 fix bogus divide by 0 message ASoC: fix NULL pointer dereference in soc_suspend() ASoC: Fix build error in twl4030.c ASoC: SSM2602: assign last substream to the master when shutting down ASoC: Blackfin: document how anomaly 05000250 is handled ASoC: Blackfin: set the transfer size according the ac97_frame size ASoC: SSM2602: remove unsupported sample rates ASoC: TWL4030: Check the interface format for 4 channel mode ASoC: TWL4030: Use reg_cache in twl4030_init_chip ASoC: Initialise dev for the dummy S/PDIF DAI ASoC: Add dummy S/PDIF codec support ASoC: correct print specifiers for unsigneds ASoC: Modify mpc5200 AC97 driver to use V9 of spin_event_timeout() ASoC: Switch FSL SSI DAI over to symmetric_rates ASoC: Mark MPC5200 AC97 as BROKEN until PowerPC merge issues are resolved ASoC: Fabric bindings for STAC9766 on the Efika ASoC: Support for AC97 on Phytec pmc030 base board. ASoC: AC97 driver for mpc5200 ASoC: Main rewite of the mpc5200 audio DMA code ...
This commit is contained in:
commit
ba252af8d6
@ -62,6 +62,7 @@ Audio DAPM widgets fall into a number of types:-
|
||||
o Mic - Mic (and optional Jack)
|
||||
o Line - Line Input/Output (and optional Jack)
|
||||
o Speaker - Speaker
|
||||
o Supply - Power or clock supply widget used by other widgets.
|
||||
o Pre - Special PRE widget (exec before all others)
|
||||
o Post - Special POST widget (exec after all others)
|
||||
|
||||
|
@ -4574,7 +4574,8 @@ F: drivers/pcmcia/pxa2xx*
|
||||
F: drivers/spi/pxa2xx*
|
||||
F: drivers/usb/gadget/pxa2*
|
||||
F: include/sound/pxa2xx-lib.h
|
||||
F: sound/soc/pxa/pxa2xx*
|
||||
F: sound/arm/pxa*
|
||||
F: sound/soc/pxa
|
||||
|
||||
PXA168 SUPPORT
|
||||
P: Eric Miao
|
||||
@ -5302,11 +5303,12 @@ P: Liam Girdwood
|
||||
M: lrg@slimlogic.co.uk
|
||||
P: Mark Brown
|
||||
M: broonie@opensource.wolfsonmicro.com
|
||||
T: git git://opensource.wolfsonmicro.com/linux-2.6-asoc
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
|
||||
L: alsa-devel@alsa-project.org (subscribers-only)
|
||||
W: http://alsa-project.org/main/index.php/ASoC
|
||||
S: Supported
|
||||
F: sound/soc/
|
||||
F: include/sound/soc*
|
||||
|
||||
SPARC + UltraSPARC (sparc/sparc64)
|
||||
P: David S. Miller
|
||||
|
@ -28,6 +28,10 @@
|
||||
#define MPC52xx_PSC_MAXNUM 6
|
||||
|
||||
/* Programmable Serial Controller (PSC) status register bits */
|
||||
#define MPC52xx_PSC_SR_UNEX_RX 0x0001
|
||||
#define MPC52xx_PSC_SR_DATA_VAL 0x0002
|
||||
#define MPC52xx_PSC_SR_DATA_OVR 0x0004
|
||||
#define MPC52xx_PSC_SR_CMDSEND 0x0008
|
||||
#define MPC52xx_PSC_SR_CDE 0x0080
|
||||
#define MPC52xx_PSC_SR_RXRDY 0x0100
|
||||
#define MPC52xx_PSC_SR_RXFULL 0x0200
|
||||
@ -61,6 +65,12 @@
|
||||
#define MPC52xx_PSC_RXTX_FIFO_EMPTY 0x0001
|
||||
|
||||
/* PSC interrupt status/mask bits */
|
||||
#define MPC52xx_PSC_IMR_UNEX_RX_SLOT 0x0001
|
||||
#define MPC52xx_PSC_IMR_DATA_VALID 0x0002
|
||||
#define MPC52xx_PSC_IMR_DATA_OVR 0x0004
|
||||
#define MPC52xx_PSC_IMR_CMD_SEND 0x0008
|
||||
#define MPC52xx_PSC_IMR_ERROR 0x0040
|
||||
#define MPC52xx_PSC_IMR_DEOF 0x0080
|
||||
#define MPC52xx_PSC_IMR_TXRDY 0x0100
|
||||
#define MPC52xx_PSC_IMR_RXRDY 0x0200
|
||||
#define MPC52xx_PSC_IMR_DB 0x0400
|
||||
@ -117,6 +127,7 @@
|
||||
#define MPC52xx_PSC_SICR_SIM_FIR (0x6 << 24)
|
||||
#define MPC52xx_PSC_SICR_SIM_CODEC_24 (0x7 << 24)
|
||||
#define MPC52xx_PSC_SICR_SIM_CODEC_32 (0xf << 24)
|
||||
#define MPC52xx_PSC_SICR_AWR (1 << 30)
|
||||
#define MPC52xx_PSC_SICR_GENCLK (1 << 23)
|
||||
#define MPC52xx_PSC_SICR_I2S (1 << 22)
|
||||
#define MPC52xx_PSC_SICR_CLKPOL (1 << 21)
|
||||
|
@ -44,24 +44,6 @@ struct snd_pcm_substream;
|
||||
#define SND_SOC_DAIFMT_CONT (0 << 4) /* continuous clock */
|
||||
#define SND_SOC_DAIFMT_GATED (1 << 4) /* clock is gated */
|
||||
|
||||
/*
|
||||
* DAI Left/Right Clocks.
|
||||
*
|
||||
* Specifies whether the DAI can support different samples for similtanious
|
||||
* playback and capture. This usually requires a seperate physical frame
|
||||
* clock for playback and capture.
|
||||
*/
|
||||
#define SND_SOC_DAIFMT_SYNC (0 << 5) /* Tx FRM = Rx FRM */
|
||||
#define SND_SOC_DAIFMT_ASYNC (1 << 5) /* Tx FRM ~ Rx FRM */
|
||||
|
||||
/*
|
||||
* TDM
|
||||
*
|
||||
* Time Division Multiplexing. Allows PCM data to be multplexed with other
|
||||
* data on the DAI.
|
||||
*/
|
||||
#define SND_SOC_DAIFMT_TDM (1 << 6)
|
||||
|
||||
/*
|
||||
* DAI hardware signal inversions.
|
||||
*
|
||||
@ -96,6 +78,10 @@ struct snd_pcm_substream;
|
||||
#define SND_SOC_CLOCK_IN 0
|
||||
#define SND_SOC_CLOCK_OUT 1
|
||||
|
||||
#define SND_SOC_STD_AC97_FMTS (SNDRV_PCM_FMTBIT_S16_LE |\
|
||||
SNDRV_PCM_FMTBIT_S32_LE |\
|
||||
SNDRV_PCM_FMTBIT_S32_BE)
|
||||
|
||||
struct snd_soc_dai_ops;
|
||||
struct snd_soc_dai;
|
||||
struct snd_ac97_bus_ops;
|
||||
@ -208,6 +194,7 @@ struct snd_soc_dai {
|
||||
/* DAI capabilities */
|
||||
struct snd_soc_pcm_stream capture;
|
||||
struct snd_soc_pcm_stream playback;
|
||||
unsigned int symmetric_rates:1;
|
||||
|
||||
/* DAI runtime info */
|
||||
struct snd_pcm_runtime *runtime;
|
||||
@ -219,11 +206,8 @@ struct snd_soc_dai {
|
||||
/* DAI private data */
|
||||
void *private_data;
|
||||
|
||||
/* parent codec/platform */
|
||||
union {
|
||||
struct snd_soc_codec *codec;
|
||||
struct snd_soc_platform *platform;
|
||||
};
|
||||
/* parent platform */
|
||||
struct snd_soc_platform *platform;
|
||||
|
||||
struct list_head list;
|
||||
};
|
||||
|
@ -140,16 +140,30 @@
|
||||
#define SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, .reg = wreg, \
|
||||
.shift = wshift, .invert = winvert}
|
||||
#define SND_SOC_DAPM_DAC_E(wname, stname, wreg, wshift, winvert, \
|
||||
wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, .reg = wreg, \
|
||||
.shift = wshift, .invert = winvert, \
|
||||
.event = wevent, .event_flags = wflags}
|
||||
#define SND_SOC_DAPM_ADC(wname, stname, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_adc, .name = wname, .sname = stname, .reg = wreg, \
|
||||
.shift = wshift, .invert = winvert}
|
||||
#define SND_SOC_DAPM_ADC_E(wname, stname, wreg, wshift, winvert, \
|
||||
wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_adc, .name = wname, .sname = stname, .reg = wreg, \
|
||||
.shift = wshift, .invert = winvert, \
|
||||
.event = wevent, .event_flags = wflags}
|
||||
|
||||
/* generic register modifier widget */
|
||||
/* generic widgets */
|
||||
#define SND_SOC_DAPM_REG(wid, wname, wreg, wshift, wmask, won_val, woff_val) \
|
||||
{ .id = wid, .name = wname, .kcontrols = NULL, .num_kcontrols = 0, \
|
||||
.reg = -((wreg) + 1), .shift = wshift, .mask = wmask, \
|
||||
.on_val = won_val, .off_val = woff_val, .event = dapm_reg_event, \
|
||||
.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
|
||||
#define SND_SOC_DAPM_SUPPLY(wname, wreg, wshift, winvert, wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_supply, .name = wname, .reg = wreg, \
|
||||
.shift = wshift, .invert = winvert, .event = wevent, \
|
||||
.event_flags = wflags}
|
||||
|
||||
/* dapm kcontrol types */
|
||||
#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \
|
||||
@ -265,8 +279,6 @@ int snd_soc_dapm_add_routes(struct snd_soc_codec *codec,
|
||||
/* dapm events */
|
||||
int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, char *stream,
|
||||
int event);
|
||||
int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev,
|
||||
enum snd_soc_bias_level level);
|
||||
|
||||
/* dapm sys fs - used by the core */
|
||||
int snd_soc_dapm_sys_add(struct device *dev);
|
||||
@ -298,6 +310,7 @@ enum snd_soc_dapm_type {
|
||||
snd_soc_dapm_vmid, /* codec bias/vmid - to minimise pops */
|
||||
snd_soc_dapm_pre, /* machine specific pre widget - exec first */
|
||||
snd_soc_dapm_post, /* machine specific post widget - exec last */
|
||||
snd_soc_dapm_supply, /* power/clock supply */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -357,6 +370,8 @@ struct snd_soc_dapm_widget {
|
||||
unsigned char suspend:1; /* was active before suspend */
|
||||
unsigned char pmdown:1; /* waiting for timeout */
|
||||
|
||||
int (*power_check)(struct snd_soc_dapm_widget *w);
|
||||
|
||||
/* external events */
|
||||
unsigned short event_flags; /* flags to specify event types */
|
||||
int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
|
||||
@ -368,6 +383,9 @@ struct snd_soc_dapm_widget {
|
||||
/* widget input and outputs */
|
||||
struct list_head sources;
|
||||
struct list_head sinks;
|
||||
|
||||
/* used during DAPM updates */
|
||||
struct list_head power_list;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -118,6 +118,14 @@
|
||||
.info = snd_soc_info_volsw, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) }
|
||||
#define SOC_DOUBLE_EXT(xname, xreg, shift_left, shift_right, xmax, xinvert,\
|
||||
xhandler_get, xhandler_put) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
|
||||
.info = snd_soc_info_volsw, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = (unsigned long)&(struct soc_mixer_control) \
|
||||
{.reg = xreg, .shift = shift_left, .rshift = shift_right, \
|
||||
.max = xmax, .invert = xinvert} }
|
||||
#define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmax, xinvert,\
|
||||
xhandler_get, xhandler_put, tlv_array) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
@ -206,10 +214,6 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
|
||||
struct snd_soc_jack_gpio *gpios);
|
||||
#endif
|
||||
|
||||
/* codec IO */
|
||||
#define snd_soc_read(codec, reg) codec->read(codec, reg)
|
||||
#define snd_soc_write(codec, reg, value) codec->write(codec, reg, value)
|
||||
|
||||
/* codec register bit access */
|
||||
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
|
||||
unsigned short mask, unsigned short value);
|
||||
@ -331,6 +335,7 @@ struct snd_soc_codec {
|
||||
struct module *owner;
|
||||
struct mutex mutex;
|
||||
struct device *dev;
|
||||
struct snd_soc_device *socdev;
|
||||
|
||||
struct list_head list;
|
||||
|
||||
@ -364,6 +369,8 @@ struct snd_soc_codec {
|
||||
enum snd_soc_bias_level bias_level;
|
||||
enum snd_soc_bias_level suspend_bias_level;
|
||||
struct delayed_work delayed_work;
|
||||
struct list_head up_list;
|
||||
struct list_head down_list;
|
||||
|
||||
/* codec DAI's */
|
||||
struct snd_soc_dai *dai;
|
||||
@ -417,6 +424,12 @@ struct snd_soc_dai_link {
|
||||
/* codec/machine specific init - e.g. add machine controls */
|
||||
int (*init)(struct snd_soc_codec *codec);
|
||||
|
||||
/* Symmetry requirements */
|
||||
unsigned int symmetric_rates:1;
|
||||
|
||||
/* Symmetry data - only valid if symmetry is being enforced */
|
||||
unsigned int rate;
|
||||
|
||||
/* DAI pcm */
|
||||
struct snd_pcm *pcm;
|
||||
};
|
||||
@ -490,6 +503,19 @@ struct soc_enum {
|
||||
void *dapm;
|
||||
};
|
||||
|
||||
/* codec IO */
|
||||
static inline unsigned int snd_soc_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return codec->read(codec, reg);
|
||||
}
|
||||
|
||||
static inline unsigned int snd_soc_write(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int val)
|
||||
{
|
||||
return codec->write(codec, reg, val);
|
||||
}
|
||||
|
||||
#include <sound/soc-dai.h>
|
||||
|
||||
#endif
|
||||
|
25
include/sound/wm9081.h
Normal file
25
include/sound/wm9081.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* linux/sound/wm9081.h -- Platform data for WM9081
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics. PLC.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_SND_WM_9081_H
|
||||
#define __LINUX_SND_WM_9081_H
|
||||
|
||||
struct wm9081_retune_mobile_setting {
|
||||
const char *name;
|
||||
unsigned int rate;
|
||||
u16 config[20];
|
||||
};
|
||||
|
||||
struct wm9081_retune_mobile_config {
|
||||
struct wm9081_retune_mobile_setting *configs;
|
||||
int num_configs;
|
||||
};
|
||||
|
||||
#endif
|
@ -1037,7 +1037,7 @@ static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
|
||||
}
|
||||
ldev->selfptr_headphone.ptr = ldev;
|
||||
ldev->selfptr_lineout.ptr = ldev;
|
||||
sdev->ofdev.dev.driver_data = ldev;
|
||||
dev_set_drvdata(&sdev->ofdev.dev, ldev);
|
||||
list_add(&ldev->list, &layouts_list);
|
||||
layouts_list_items++;
|
||||
|
||||
@ -1081,7 +1081,7 @@ static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
|
||||
|
||||
static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
|
||||
{
|
||||
struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
|
||||
struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
|
||||
int i;
|
||||
|
||||
for (i=0; i<MAX_CODECS_PER_BUS; i++) {
|
||||
@ -1114,7 +1114,7 @@ static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
|
||||
#ifdef CONFIG_PM
|
||||
static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state)
|
||||
{
|
||||
struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
|
||||
struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
|
||||
|
||||
if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
|
||||
ldev->gpio.methods->all_amps_off(&ldev->gpio);
|
||||
@ -1124,7 +1124,7 @@ static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t sta
|
||||
|
||||
static int aoa_fabric_layout_resume(struct soundbus_dev *sdev)
|
||||
{
|
||||
struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
|
||||
struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
|
||||
|
||||
if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
|
||||
ldev->gpio.methods->all_amps_restore(&ldev->gpio);
|
||||
|
@ -358,14 +358,14 @@ static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev->ofdev.dev.driver_data = control;
|
||||
dev_set_drvdata(&dev->ofdev.dev, control);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2sbus_remove(struct macio_dev* dev)
|
||||
{
|
||||
struct i2sbus_control *control = dev->ofdev.dev.driver_data;
|
||||
struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
|
||||
struct i2sbus_dev *i2sdev, *tmp;
|
||||
|
||||
list_for_each_entry_safe(i2sdev, tmp, &control->list, item)
|
||||
@ -377,7 +377,7 @@ static int i2sbus_remove(struct macio_dev* dev)
|
||||
#ifdef CONFIG_PM
|
||||
static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
|
||||
{
|
||||
struct i2sbus_control *control = dev->ofdev.dev.driver_data;
|
||||
struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
|
||||
struct codec_info_item *cii;
|
||||
struct i2sbus_dev* i2sdev;
|
||||
int err, ret = 0;
|
||||
@ -407,7 +407,7 @@ static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
|
||||
|
||||
static int i2sbus_resume(struct macio_dev* dev)
|
||||
{
|
||||
struct i2sbus_control *control = dev->ofdev.dev.driver_data;
|
||||
struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
|
||||
struct codec_info_item *cii;
|
||||
struct i2sbus_dev* i2sdev;
|
||||
int err, ret = 0;
|
||||
|
@ -32,7 +32,9 @@ source "sound/soc/fsl/Kconfig"
|
||||
source "sound/soc/omap/Kconfig"
|
||||
source "sound/soc/pxa/Kconfig"
|
||||
source "sound/soc/s3c24xx/Kconfig"
|
||||
source "sound/soc/s6000/Kconfig"
|
||||
source "sound/soc/sh/Kconfig"
|
||||
source "sound/soc/txx9/Kconfig"
|
||||
|
||||
# Supported codecs
|
||||
source "sound/soc/codecs/Kconfig"
|
||||
|
@ -10,4 +10,6 @@ obj-$(CONFIG_SND_SOC) += fsl/
|
||||
obj-$(CONFIG_SND_SOC) += omap/
|
||||
obj-$(CONFIG_SND_SOC) += pxa/
|
||||
obj-$(CONFIG_SND_SOC) += s3c24xx/
|
||||
obj-$(CONFIG_SND_SOC) += s6000/
|
||||
obj-$(CONFIG_SND_SOC) += sh/
|
||||
obj-$(CONFIG_SND_SOC) += txx9/
|
||||
|
@ -41,3 +41,11 @@ config SND_AT32_SOC_PLAYPAQ_SLAVE
|
||||
and FRAME signals on the PlayPaq. Unless you want to play
|
||||
with the AT32 as the SSC master, you probably want to say N here,
|
||||
as this will give you better sound quality.
|
||||
|
||||
config SND_AT91_SOC_AFEB9260
|
||||
tristate "SoC Audio support for AFEB9260 board"
|
||||
depends on ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
|
||||
select SND_ATMEL_SOC_SSC
|
||||
select SND_SOC_TLV320AIC23
|
||||
help
|
||||
Say Y here to support sound on AFEB9260 board.
|
||||
|
@ -13,3 +13,4 @@ snd-soc-playpaq-objs := playpaq_wm8510.o
|
||||
|
||||
obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
|
||||
obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o
|
||||
obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o
|
||||
|
@ -117,7 +117,7 @@ static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
|
||||
* Find actual rate, compare to requested rate
|
||||
*/
|
||||
actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
|
||||
pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n",
|
||||
pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
|
||||
rate, actual_rate);
|
||||
|
||||
|
||||
|
203
sound/soc/atmel/snd-soc-afeb9260.c
Normal file
203
sound/soc/atmel/snd-soc-afeb9260.c
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
* afeb9260.c -- SoC audio for AFEB9260
|
||||
*
|
||||
* Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/atmel-ssc.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "../codecs/tlv320aic23.h"
|
||||
#include "atmel-pcm.h"
|
||||
#include "atmel_ssc_dai.h"
|
||||
|
||||
#define CODEC_CLOCK 12000000
|
||||
|
||||
static int afeb9260_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
int err;
|
||||
|
||||
/* Set codec DAI configuration */
|
||||
err = snd_soc_dai_set_fmt(codec_dai,
|
||||
SND_SOC_DAIFMT_I2S|
|
||||
SND_SOC_DAIFMT_NB_IF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "can't set codec DAI configuration\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Set cpu DAI configuration */
|
||||
err = snd_soc_dai_set_fmt(cpu_dai,
|
||||
SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_IF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "can't set cpu DAI configuration\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
err =
|
||||
snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
|
||||
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "can't set codec system clock\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops afeb9260_ops = {
|
||||
.hw_params = afeb9260_hw_params,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic Jack", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"Headphone Jack", NULL, "LHPOUT"},
|
||||
{"Headphone Jack", NULL, "RHPOUT"},
|
||||
|
||||
{"LLINEIN", NULL, "Line In"},
|
||||
{"RLINEIN", NULL, "Line In"},
|
||||
|
||||
{"MICIN", NULL, "Mic Jack"},
|
||||
};
|
||||
|
||||
static int afeb9260_tlv320aic23_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
|
||||
/* Add afeb9260 specific widgets */
|
||||
snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
|
||||
ARRAY_SIZE(tlv320aic23_dapm_widgets));
|
||||
|
||||
/* Set up afeb9260 specific audio path audio_map */
|
||||
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
|
||||
|
||||
snd_soc_dapm_enable_pin(codec, "Headphone Jack");
|
||||
snd_soc_dapm_enable_pin(codec, "Line In");
|
||||
snd_soc_dapm_enable_pin(codec, "Mic Jack");
|
||||
|
||||
snd_soc_dapm_sync(codec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link afeb9260_dai = {
|
||||
.name = "TLV320AIC23",
|
||||
.stream_name = "AIC23",
|
||||
.cpu_dai = &atmel_ssc_dai[0],
|
||||
.codec_dai = &tlv320aic23_dai,
|
||||
.init = afeb9260_tlv320aic23_init,
|
||||
.ops = &afeb9260_ops,
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card snd_soc_machine_afeb9260 = {
|
||||
.name = "AFEB9260",
|
||||
.platform = &atmel_soc_platform,
|
||||
.dai_link = &afeb9260_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
/* Audio subsystem */
|
||||
static struct snd_soc_device afeb9260_snd_devdata = {
|
||||
.card = &snd_soc_machine_afeb9260,
|
||||
.codec_dev = &soc_codec_dev_tlv320aic23,
|
||||
};
|
||||
|
||||
static struct platform_device *afeb9260_snd_device;
|
||||
|
||||
static int __init afeb9260_soc_init(void)
|
||||
{
|
||||
int err;
|
||||
struct device *dev;
|
||||
struct atmel_ssc_info *ssc_p = afeb9260_dai.cpu_dai->private_data;
|
||||
struct ssc_device *ssc = NULL;
|
||||
|
||||
if (!(machine_is_afeb9260()))
|
||||
return -ENODEV;
|
||||
|
||||
ssc = ssc_request(0);
|
||||
if (IS_ERR(ssc)) {
|
||||
printk(KERN_ERR "ASoC: Failed to request SSC 0\n");
|
||||
err = PTR_ERR(ssc);
|
||||
ssc = NULL;
|
||||
goto err_ssc;
|
||||
}
|
||||
ssc_p->ssc = ssc;
|
||||
|
||||
afeb9260_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!afeb9260_snd_device) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(afeb9260_snd_device, &afeb9260_snd_devdata);
|
||||
afeb9260_snd_devdata.dev = &afeb9260_snd_device->dev;
|
||||
err = platform_device_add(afeb9260_snd_device);
|
||||
if (err)
|
||||
goto err1;
|
||||
|
||||
dev = &afeb9260_snd_device->dev;
|
||||
|
||||
return 0;
|
||||
err1:
|
||||
platform_device_del(afeb9260_snd_device);
|
||||
platform_device_put(afeb9260_snd_device);
|
||||
err_ssc:
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
static void __exit afeb9260_soc_exit(void)
|
||||
{
|
||||
platform_device_unregister(afeb9260_snd_device);
|
||||
}
|
||||
|
||||
module_init(afeb9260_soc_init);
|
||||
module_exit(afeb9260_soc_exit);
|
||||
|
||||
MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>");
|
||||
MODULE_DESCRIPTION("ALSA SoC for AFEB9260");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -31,6 +31,15 @@
|
||||
#include "bf5xx-sport.h"
|
||||
#include "bf5xx-ac97.h"
|
||||
|
||||
/* Anomaly notes:
|
||||
* 05000250 - AD1980 is running in TDM mode and RFS/TFS are generated by SPORT
|
||||
* contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1,
|
||||
* while the max AC97 data size is 13*16. The DIV is always larger
|
||||
* than data size. AD73311 and ad2602 are not running in TDM mode.
|
||||
* AD1836 and AD73322 depend on external RFS/TFS only. So, this
|
||||
* anomaly does not affect blackfin sound drivers.
|
||||
*/
|
||||
|
||||
static int *cmd_count;
|
||||
static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
|
||||
|
||||
|
@ -190,7 +190,7 @@ static inline int sport_hook_rx_dummy(struct sport_device *sport)
|
||||
desc = get_dma_next_desc_ptr(sport->dma_rx_chan);
|
||||
/* Copy the descriptor which will be damaged to backup */
|
||||
temp_desc = *desc;
|
||||
desc->x_count = 0xa;
|
||||
desc->x_count = sport->dummy_count / 2;
|
||||
desc->y_count = 0;
|
||||
desc->next_desc_addr = sport->dummy_rx_desc;
|
||||
local_irq_restore(flags);
|
||||
@ -309,7 +309,7 @@ static inline int sport_hook_tx_dummy(struct sport_device *sport)
|
||||
desc = get_dma_next_desc_ptr(sport->dma_tx_chan);
|
||||
/* Store the descriptor which will be damaged */
|
||||
temp_desc = *desc;
|
||||
desc->x_count = 0xa;
|
||||
desc->x_count = sport->dummy_count / 2;
|
||||
desc->y_count = 0;
|
||||
desc->next_desc_addr = sport->dummy_tx_desc;
|
||||
local_irq_restore(flags);
|
||||
|
@ -18,7 +18,9 @@ config SND_SOC_ALL_CODECS
|
||||
select SND_SOC_AK4535 if I2C
|
||||
select SND_SOC_CS4270 if I2C
|
||||
select SND_SOC_PCM3008
|
||||
select SND_SOC_SPDIF
|
||||
select SND_SOC_SSM2602 if I2C
|
||||
select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_TLV320AIC23 if I2C
|
||||
select SND_SOC_TLV320AIC26 if SPI_MASTER
|
||||
select SND_SOC_TLV320AIC3X if I2C
|
||||
@ -35,8 +37,12 @@ config SND_SOC_ALL_CODECS
|
||||
select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8900 if I2C
|
||||
select SND_SOC_WM8903 if I2C
|
||||
select SND_SOC_WM8940 if I2C
|
||||
select SND_SOC_WM8960 if I2C
|
||||
select SND_SOC_WM8971 if I2C
|
||||
select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8990 if I2C
|
||||
select SND_SOC_WM9081 if I2C
|
||||
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
|
||||
@ -86,9 +92,15 @@ config SND_SOC_L3
|
||||
config SND_SOC_PCM3008
|
||||
tristate
|
||||
|
||||
config SND_SOC_SPDIF
|
||||
tristate
|
||||
|
||||
config SND_SOC_SSM2602
|
||||
tristate
|
||||
|
||||
config SND_SOC_STAC9766
|
||||
tristate
|
||||
|
||||
config SND_SOC_TLV320AIC23
|
||||
tristate
|
||||
|
||||
@ -138,12 +150,24 @@ config SND_SOC_WM8900
|
||||
config SND_SOC_WM8903
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8940
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8960
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8971
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8988
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8990
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM9081
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM9705
|
||||
tristate
|
||||
|
||||
|
@ -6,7 +6,9 @@ snd-soc-ak4535-objs := ak4535.o
|
||||
snd-soc-cs4270-objs := cs4270.o
|
||||
snd-soc-l3-objs := l3.o
|
||||
snd-soc-pcm3008-objs := pcm3008.o
|
||||
snd-soc-spdif-objs := spdif_transciever.o
|
||||
snd-soc-ssm2602-objs := ssm2602.o
|
||||
snd-soc-stac9766-objs := stac9766.o
|
||||
snd-soc-tlv320aic23-objs := tlv320aic23.o
|
||||
snd-soc-tlv320aic26-objs := tlv320aic26.o
|
||||
snd-soc-tlv320aic3x-objs := tlv320aic3x.o
|
||||
@ -23,8 +25,12 @@ snd-soc-wm8750-objs := wm8750.o
|
||||
snd-soc-wm8753-objs := wm8753.o
|
||||
snd-soc-wm8900-objs := wm8900.o
|
||||
snd-soc-wm8903-objs := wm8903.o
|
||||
snd-soc-wm8940-objs := wm8940.o
|
||||
snd-soc-wm8960-objs := wm8960.o
|
||||
snd-soc-wm8971-objs := wm8971.o
|
||||
snd-soc-wm8988-objs := wm8988.o
|
||||
snd-soc-wm8990-objs := wm8990.o
|
||||
snd-soc-wm9081-objs := wm9081.o
|
||||
snd-soc-wm9705-objs := wm9705.o
|
||||
snd-soc-wm9712-objs := wm9712.o
|
||||
snd-soc-wm9713-objs := wm9713.o
|
||||
@ -37,7 +43,9 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
|
||||
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
|
||||
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
|
||||
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
|
||||
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
|
||||
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.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
|
||||
obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
|
||||
@ -55,7 +63,11 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
|
||||
obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
|
||||
obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
|
||||
obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
|
||||
obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o
|
||||
obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o
|
||||
obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
|
||||
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
|
||||
obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o
|
||||
obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
|
||||
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
|
||||
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
|
||||
|
@ -53,13 +53,13 @@ struct snd_soc_dai ac97_dai = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = STD_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.capture = {
|
||||
.stream_name = "AC97 Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = STD_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.ops = &ac97_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(ac97_dai);
|
||||
|
@ -137,13 +137,13 @@ struct snd_soc_dai ad1980_dai = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 6,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
|
||||
.formats = SND_SOC_STD_AC97_FMTS, },
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
|
||||
.formats = SND_SOC_STD_AC97_FMTS, },
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(ad1980_dai);
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
* - The machine driver's 'startup' function must call
|
||||
* cs4270_set_dai_sysclk() with the value of MCLK.
|
||||
* - Only I2S and left-justified modes are supported
|
||||
* - Power management is not supported
|
||||
* - Power management is supported
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
@ -27,6 +27,7 @@
|
||||
#include <sound/soc.h>
|
||||
#include <sound/initval.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "cs4270.h"
|
||||
|
||||
@ -56,6 +57,7 @@
|
||||
#define CS4270_FIRSTREG 0x01
|
||||
#define CS4270_LASTREG 0x08
|
||||
#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1)
|
||||
#define CS4270_I2C_INCR 0x80
|
||||
|
||||
/* Bit masks for the CS4270 registers */
|
||||
#define CS4270_CHIPID_ID 0xF0
|
||||
@ -64,6 +66,8 @@
|
||||
#define CS4270_PWRCTL_PDN_ADC 0x20
|
||||
#define CS4270_PWRCTL_PDN_DAC 0x02
|
||||
#define CS4270_PWRCTL_PDN 0x01
|
||||
#define CS4270_PWRCTL_PDN_ALL \
|
||||
(CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN)
|
||||
#define CS4270_MODE_SPEED_MASK 0x30
|
||||
#define CS4270_MODE_1X 0x00
|
||||
#define CS4270_MODE_2X 0x10
|
||||
@ -109,6 +113,7 @@ struct cs4270_private {
|
||||
unsigned int mclk; /* Input frequency of the MCLK pin */
|
||||
unsigned int mode; /* The mode (I2S or left-justified) */
|
||||
unsigned int slave_mode;
|
||||
unsigned int manual_mute;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -295,7 +300,7 @@ static int cs4270_fill_cache(struct snd_soc_codec *codec)
|
||||
s32 length;
|
||||
|
||||
length = i2c_smbus_read_i2c_block_data(i2c_client,
|
||||
CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache);
|
||||
CS4270_FIRSTREG | CS4270_I2C_INCR, CS4270_NUMREGS, cache);
|
||||
|
||||
if (length != CS4270_NUMREGS) {
|
||||
dev_err(codec->dev, "i2c read failure, addr=0x%x\n",
|
||||
@ -453,7 +458,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream,
|
||||
}
|
||||
|
||||
/**
|
||||
* cs4270_mute - enable/disable the CS4270 external mute
|
||||
* cs4270_dai_mute - enable/disable the CS4270 external mute
|
||||
* @dai: the SOC DAI
|
||||
* @mute: 0 = disable mute, 1 = enable mute
|
||||
*
|
||||
@ -462,21 +467,52 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream,
|
||||
* board does not have the MUTEA or MUTEB pins connected to such circuitry,
|
||||
* then this function will do nothing.
|
||||
*/
|
||||
static int cs4270_mute(struct snd_soc_dai *dai, int mute)
|
||||
static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct cs4270_private *cs4270 = codec->private_data;
|
||||
int reg6;
|
||||
|
||||
reg6 = snd_soc_read(codec, CS4270_MUTE);
|
||||
|
||||
if (mute)
|
||||
reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
|
||||
else
|
||||
else {
|
||||
reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
|
||||
reg6 |= cs4270->manual_mute;
|
||||
}
|
||||
|
||||
return snd_soc_write(codec, CS4270_MUTE, reg6);
|
||||
}
|
||||
|
||||
/**
|
||||
* cs4270_soc_put_mute - put callback for the 'Master Playback switch'
|
||||
* alsa control.
|
||||
* @kcontrol: mixer control
|
||||
* @ucontrol: control element information
|
||||
*
|
||||
* This function basically passes the arguments on to the generic
|
||||
* snd_soc_put_volsw() function and saves the mute information in
|
||||
* our private data structure. This is because we want to prevent
|
||||
* cs4270_dai_mute() neglecting the user's decision to manually
|
||||
* mute the codec's output.
|
||||
*
|
||||
* Returns 0 for success.
|
||||
*/
|
||||
static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct cs4270_private *cs4270 = codec->private_data;
|
||||
int left = !ucontrol->value.integer.value[0];
|
||||
int right = !ucontrol->value.integer.value[1];
|
||||
|
||||
cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) |
|
||||
(right ? CS4270_MUTE_DAC_B : 0);
|
||||
|
||||
return snd_soc_put_volsw(kcontrol, ucontrol);
|
||||
}
|
||||
|
||||
/* A list of non-DAPM controls that the CS4270 supports */
|
||||
static const struct snd_kcontrol_new cs4270_snd_controls[] = {
|
||||
SOC_DOUBLE_R("Master Playback Volume",
|
||||
@ -486,7 +522,9 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = {
|
||||
SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0),
|
||||
SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1),
|
||||
SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0),
|
||||
SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 0)
|
||||
SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1),
|
||||
SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1,
|
||||
snd_soc_get_volsw, cs4270_soc_put_mute),
|
||||
};
|
||||
|
||||
/*
|
||||
@ -506,7 +544,7 @@ static struct snd_soc_dai_ops cs4270_dai_ops = {
|
||||
.hw_params = cs4270_hw_params,
|
||||
.set_sysclk = cs4270_set_dai_sysclk,
|
||||
.set_fmt = cs4270_set_dai_fmt,
|
||||
.digital_mute = cs4270_mute,
|
||||
.digital_mute = cs4270_dai_mute,
|
||||
};
|
||||
|
||||
struct snd_soc_dai cs4270_dai = {
|
||||
@ -753,6 +791,57 @@ static struct i2c_device_id cs4270_id[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, cs4270_id);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/* This suspend/resume implementation can handle both - a simple standby
|
||||
* where the codec remains powered, and a full suspend, where the voltage
|
||||
* domain the codec is connected to is teared down and/or any other hardware
|
||||
* reset condition is asserted.
|
||||
*
|
||||
* The codec's own power saving features are enabled in the suspend callback,
|
||||
* and all registers are written back to the hardware when resuming.
|
||||
*/
|
||||
|
||||
static int cs4270_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
|
||||
{
|
||||
struct cs4270_private *cs4270 = i2c_get_clientdata(client);
|
||||
struct snd_soc_codec *codec = &cs4270->codec;
|
||||
int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
|
||||
|
||||
return snd_soc_write(codec, CS4270_PWRCTL, reg);
|
||||
}
|
||||
|
||||
static int cs4270_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
struct cs4270_private *cs4270 = i2c_get_clientdata(client);
|
||||
struct snd_soc_codec *codec = &cs4270->codec;
|
||||
int reg;
|
||||
|
||||
/* In case the device was put to hard reset during sleep, we need to
|
||||
* wait 500ns here before any I2C communication. */
|
||||
ndelay(500);
|
||||
|
||||
/* first restore the entire register cache ... */
|
||||
for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) {
|
||||
u8 val = snd_soc_read(codec, reg);
|
||||
|
||||
if (i2c_smbus_write_byte_data(client, reg, val)) {
|
||||
dev_err(codec->dev, "i2c write failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
/* ... then disable the power-down bits */
|
||||
reg = snd_soc_read(codec, CS4270_PWRCTL);
|
||||
reg &= ~CS4270_PWRCTL_PDN_ALL;
|
||||
|
||||
return snd_soc_write(codec, CS4270_PWRCTL, reg);
|
||||
}
|
||||
#else
|
||||
#define cs4270_i2c_suspend NULL
|
||||
#define cs4270_i2c_resume NULL
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
/*
|
||||
* cs4270_i2c_driver - I2C device identification
|
||||
*
|
||||
@ -767,6 +856,8 @@ static struct i2c_driver cs4270_i2c_driver = {
|
||||
.id_table = cs4270_id,
|
||||
.probe = cs4270_i2c_probe,
|
||||
.remove = cs4270_i2c_remove,
|
||||
.suspend = cs4270_i2c_suspend,
|
||||
.resume = cs4270_i2c_resume,
|
||||
};
|
||||
|
||||
/*
|
||||
|
71
sound/soc/codecs/spdif_transciever.c
Normal file
71
sound/soc/codecs/spdif_transciever.c
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* ALSA SoC SPDIF DIT driver
|
||||
*
|
||||
* This driver is used by controllers which can operate in DIT (SPDI/F) where
|
||||
* no codec is needed. This file provides stub codec that can be used
|
||||
* in these configurations. TI DaVinci Audio controller uses this driver.
|
||||
*
|
||||
* Author: Steve Chen, <schen@mvista.com>
|
||||
* Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
|
||||
* Copyright: (C) 2009 Texas Instruments, India
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm.h>
|
||||
|
||||
#include "spdif_transciever.h"
|
||||
|
||||
#define STUB_RATES SNDRV_PCM_RATE_8000_96000
|
||||
#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE
|
||||
|
||||
struct snd_soc_dai dit_stub_dai = {
|
||||
.name = "DIT",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 384,
|
||||
.rates = STUB_RATES,
|
||||
.formats = STUB_FORMATS,
|
||||
},
|
||||
};
|
||||
|
||||
static int spdif_dit_probe(struct platform_device *pdev)
|
||||
{
|
||||
dit_stub_dai.dev = &pdev->dev;
|
||||
return snd_soc_register_dai(&dit_stub_dai);
|
||||
}
|
||||
|
||||
static int spdif_dit_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_dai(&dit_stub_dai);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver spdif_dit_driver = {
|
||||
.probe = spdif_dit_probe,
|
||||
.remove = spdif_dit_remove,
|
||||
.driver = {
|
||||
.name = "spdif-dit",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init dit_modinit(void)
|
||||
{
|
||||
return platform_driver_register(&spdif_dit_driver);
|
||||
}
|
||||
|
||||
static void __exit dit_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&spdif_dit_driver);
|
||||
}
|
||||
|
||||
module_init(dit_modinit);
|
||||
module_exit(dit_exit);
|
||||
|
17
sound/soc/codecs/spdif_transciever.h
Normal file
17
sound/soc/codecs/spdif_transciever.h
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* ALSA SoC DIT/DIR driver header
|
||||
*
|
||||
* Author: Steve Chen, <schen@mvista.com>
|
||||
* Copyright: (C) 2008 MontaVista Software, Inc., <source@mvista.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef CODEC_STUBS_H
|
||||
#define CODEC_STUBS_H
|
||||
|
||||
extern struct snd_soc_dai dit_stub_dai;
|
||||
|
||||
#endif /* CODEC_STUBS_H */
|
@ -336,15 +336,17 @@ static int ssm2602_startup(struct snd_pcm_substream *substream,
|
||||
master_runtime->sample_bits,
|
||||
master_runtime->rate);
|
||||
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
master_runtime->rate,
|
||||
master_runtime->rate);
|
||||
if (master_runtime->rate != 0)
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
master_runtime->rate,
|
||||
master_runtime->rate);
|
||||
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
||||
master_runtime->sample_bits,
|
||||
master_runtime->sample_bits);
|
||||
if (master_runtime->sample_bits != 0)
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
||||
master_runtime->sample_bits,
|
||||
master_runtime->sample_bits);
|
||||
|
||||
ssm2602->slave_substream = substream;
|
||||
} else
|
||||
@ -372,6 +374,11 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct ssm2602_priv *ssm2602 = codec->private_data;
|
||||
|
||||
if (ssm2602->master_substream == substream)
|
||||
ssm2602->master_substream = ssm2602->slave_substream;
|
||||
|
||||
ssm2602->slave_substream = NULL;
|
||||
/* deactivate */
|
||||
if (!codec->active)
|
||||
ssm2602_write(codec, SSM2602_ACTIVE, 0);
|
||||
@ -497,11 +504,9 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
|
||||
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
|
||||
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
|
||||
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
|
||||
SNDRV_PCM_RATE_96000)
|
||||
#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
|
||||
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
|
||||
|
||||
#define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
463
sound/soc/codecs/stac9766.c
Normal file
463
sound/soc/codecs/stac9766.c
Normal file
@ -0,0 +1,463 @@
|
||||
/*
|
||||
* stac9766.c -- ALSA SoC STAC9766 codec support
|
||||
*
|
||||
* Copyright 2009 Jon Smirl, Digispeaker
|
||||
* Author: Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Features:-
|
||||
*
|
||||
* o Support for AC97 Codec, S/PDIF
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/ac97_codec.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <sound/soc-of-simple.h>
|
||||
|
||||
#include "stac9766.h"
|
||||
|
||||
#define STAC9766_VERSION "0.10"
|
||||
|
||||
/*
|
||||
* STAC9766 register cache
|
||||
*/
|
||||
static const u16 stac9766_reg[] = {
|
||||
0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */
|
||||
0x0000, 0x0000, 0x8008, 0x8008, /* e */
|
||||
0x8808, 0x8808, 0x8808, 0x8808, /* 16 */
|
||||
0x8808, 0x0000, 0x8000, 0x0000, /* 1e */
|
||||
0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
|
||||
0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */
|
||||
0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
|
||||
0x0000, 0x2000, 0x0000, 0x0100, /* 3e */
|
||||
0x0000, 0x0000, 0x0080, 0x0000, /* 46 */
|
||||
0x0000, 0x0000, 0x0003, 0xffff, /* 4e */
|
||||
0x0000, 0x0000, 0x0000, 0x0000, /* 56 */
|
||||
0x4000, 0x0000, 0x0000, 0x0000, /* 5e */
|
||||
0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */
|
||||
0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
|
||||
0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
|
||||
0x0000, 0x0000, 0x0000, 0x0000, /* 7e */
|
||||
};
|
||||
|
||||
static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX",
|
||||
"Line", "Stereo Mix", "Mono Mix", "Phone"};
|
||||
static const char *stac9766_mono_mux[] = {"Mix", "Mic"};
|
||||
static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"};
|
||||
static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"};
|
||||
static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"};
|
||||
static const char *stac9766_record_all_mux[] = {"All analog",
|
||||
"Analog plus DAC"};
|
||||
static const char *stac9766_boost1[] = {"0dB", "10dB"};
|
||||
static const char *stac9766_boost2[] = {"0dB", "20dB"};
|
||||
static const char *stac9766_stereo_mic[] = {"Off", "On"};
|
||||
|
||||
static const struct soc_enum stac9766_record_enum =
|
||||
SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux);
|
||||
static const struct soc_enum stac9766_mono_enum =
|
||||
SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux);
|
||||
static const struct soc_enum stac9766_mic_enum =
|
||||
SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux);
|
||||
static const struct soc_enum stac9766_SPDIF_enum =
|
||||
SOC_ENUM_SINGLE(AC97_STAC_DA_CONTROL, 1, 2, stac9766_SPDIF_mux);
|
||||
static const struct soc_enum stac9766_popbypass_enum =
|
||||
SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux);
|
||||
static const struct soc_enum stac9766_record_all_enum =
|
||||
SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2,
|
||||
stac9766_record_all_mux);
|
||||
static const struct soc_enum stac9766_boost1_enum =
|
||||
SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */
|
||||
static const struct soc_enum stac9766_boost2_enum =
|
||||
SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 2, 2, stac9766_boost2); /* 0/20dB */
|
||||
static const struct soc_enum stac9766_stereo_mic_enum =
|
||||
SOC_ENUM_SINGLE(AC97_STAC_STEREO_MIC, 2, 1, stac9766_stereo_mic);
|
||||
|
||||
static const DECLARE_TLV_DB_LINEAR(master_tlv, -4600, 0);
|
||||
static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250);
|
||||
static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);
|
||||
static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200);
|
||||
|
||||
static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = {
|
||||
SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv),
|
||||
SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1),
|
||||
SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1,
|
||||
master_tlv),
|
||||
SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1),
|
||||
SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1,
|
||||
master_tlv),
|
||||
SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1),
|
||||
|
||||
SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv),
|
||||
SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1),
|
||||
|
||||
|
||||
SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv),
|
||||
SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1),
|
||||
SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1),
|
||||
SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv),
|
||||
SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1),
|
||||
|
||||
SOC_ENUM("Mic Boost1", stac9766_boost1_enum),
|
||||
SOC_ENUM("Mic Boost2", stac9766_boost2_enum),
|
||||
SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv),
|
||||
SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
|
||||
SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum),
|
||||
|
||||
SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv),
|
||||
SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1),
|
||||
SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv),
|
||||
SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1),
|
||||
SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv),
|
||||
SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1),
|
||||
SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv),
|
||||
SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1),
|
||||
|
||||
SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv),
|
||||
SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1),
|
||||
SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0),
|
||||
SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1),
|
||||
SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0),
|
||||
|
||||
SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum),
|
||||
SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum),
|
||||
SOC_ENUM("Record All Mux", stac9766_record_all_enum),
|
||||
SOC_ENUM("Record Mux", stac9766_record_enum),
|
||||
SOC_ENUM("Mono Mux", stac9766_mono_enum),
|
||||
SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum),
|
||||
};
|
||||
|
||||
static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
if (reg > AC97_STAC_PAGE0) {
|
||||
stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
|
||||
soc_ac97_ops.write(codec->ac97, reg, val);
|
||||
stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
|
||||
return 0;
|
||||
}
|
||||
if (reg / 2 > ARRAY_SIZE(stac9766_reg))
|
||||
return -EIO;
|
||||
|
||||
soc_ac97_ops.write(codec->ac97, reg, val);
|
||||
cache[reg / 2] = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 val = 0, *cache = codec->reg_cache;
|
||||
|
||||
if (reg > AC97_STAC_PAGE0) {
|
||||
stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
|
||||
val = soc_ac97_ops.read(codec->ac97, reg - AC97_STAC_PAGE0);
|
||||
stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
|
||||
return val;
|
||||
}
|
||||
if (reg / 2 > ARRAY_SIZE(stac9766_reg))
|
||||
return -EIO;
|
||||
|
||||
if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
|
||||
reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 ||
|
||||
reg == AC97_VENDOR_ID2) {
|
||||
|
||||
val = soc_ac97_ops.read(codec->ac97, reg);
|
||||
return val;
|
||||
}
|
||||
return cache[reg / 2];
|
||||
}
|
||||
|
||||
static int ac97_analog_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned short reg, vra;
|
||||
|
||||
vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
|
||||
|
||||
vra |= 0x1; /* enable variable rate audio */
|
||||
|
||||
stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
reg = AC97_PCM_FRONT_DAC_RATE;
|
||||
else
|
||||
reg = AC97_PCM_LR_ADC_RATE;
|
||||
|
||||
return stac9766_ac97_write(codec, reg, runtime->rate);
|
||||
}
|
||||
|
||||
static int ac97_digital_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned short reg, vra;
|
||||
|
||||
stac9766_ac97_write(codec, AC97_SPDIF, 0x2002);
|
||||
|
||||
vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
|
||||
vra |= 0x5; /* Enable VRA and SPDIF out */
|
||||
|
||||
stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
|
||||
|
||||
reg = AC97_PCM_FRONT_DAC_RATE;
|
||||
|
||||
return stac9766_ac97_write(codec, reg, runtime->rate);
|
||||
}
|
||||
|
||||
static int ac97_digital_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
unsigned short vra;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
|
||||
vra &= !0x04;
|
||||
stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stac9766_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON: /* full On */
|
||||
case SND_SOC_BIAS_PREPARE: /* partial On */
|
||||
case SND_SOC_BIAS_STANDBY: /* Off, with power */
|
||||
stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF: /* Off, without power */
|
||||
/* disable everything including AC link */
|
||||
stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stac9766_reset(struct snd_soc_codec *codec, int try_warm)
|
||||
{
|
||||
if (try_warm && soc_ac97_ops.warm_reset) {
|
||||
soc_ac97_ops.warm_reset(codec->ac97);
|
||||
if (stac9766_ac97_read(codec, 0) == stac9766_reg[0])
|
||||
return 1;
|
||||
}
|
||||
|
||||
soc_ac97_ops.reset(codec->ac97);
|
||||
if (soc_ac97_ops.warm_reset)
|
||||
soc_ac97_ops.warm_reset(codec->ac97);
|
||||
if (stac9766_ac97_read(codec, 0) != stac9766_reg[0])
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stac9766_codec_suspend(struct platform_device *pdev,
|
||||
pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stac9766_codec_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 id, reset;
|
||||
|
||||
reset = 0;
|
||||
/* give the codec an AC97 warm reset to start the link */
|
||||
reset:
|
||||
if (reset > 5) {
|
||||
printk(KERN_ERR "stac9766 failed to resume");
|
||||
return -EIO;
|
||||
}
|
||||
codec->ac97->bus->ops->warm_reset(codec->ac97);
|
||||
id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2);
|
||||
if (id != 0x4c13) {
|
||||
stac9766_reset(codec, 0);
|
||||
reset++;
|
||||
goto reset;
|
||||
}
|
||||
stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
|
||||
stac9766_set_bias_level(codec, SND_SOC_BIAS_ON);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops stac9766_dai_ops_analog = {
|
||||
.prepare = ac97_analog_prepare,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops stac9766_dai_ops_digital = {
|
||||
.prepare = ac97_digital_prepare,
|
||||
.trigger = ac97_digital_trigger,
|
||||
};
|
||||
|
||||
struct snd_soc_dai stac9766_dai[] = {
|
||||
{
|
||||
.name = "stac9766 analog",
|
||||
.id = 0,
|
||||
.ac97_control = 1,
|
||||
|
||||
/* stream cababilities */
|
||||
.playback = {
|
||||
.stream_name = "stac9766 analog",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SND_SOC_STD_AC97_FMTS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "stac9766 analog",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SND_SOC_STD_AC97_FMTS,
|
||||
},
|
||||
/* alsa ops */
|
||||
.ops = &stac9766_dai_ops_analog,
|
||||
},
|
||||
{
|
||||
.name = "stac9766 IEC958",
|
||||
.id = 1,
|
||||
.ac97_control = 1,
|
||||
|
||||
/* stream cababilities */
|
||||
.playback = {
|
||||
.stream_name = "stac9766 IEC958",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE,
|
||||
},
|
||||
/* alsa ops */
|
||||
.ops = &stac9766_dai_ops_digital,
|
||||
}
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(stac9766_dai);
|
||||
|
||||
static int stac9766_codec_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION);
|
||||
|
||||
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
||||
if (socdev->card->codec == NULL)
|
||||
return -ENOMEM;
|
||||
codec = socdev->card->codec;
|
||||
mutex_init(&codec->mutex);
|
||||
|
||||
codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg),
|
||||
GFP_KERNEL);
|
||||
if (codec->reg_cache == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto cache_err;
|
||||
}
|
||||
codec->reg_cache_size = sizeof(stac9766_reg);
|
||||
codec->reg_cache_step = 2;
|
||||
|
||||
codec->name = "STAC9766";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->dai = stac9766_dai;
|
||||
codec->num_dai = ARRAY_SIZE(stac9766_dai);
|
||||
codec->write = stac9766_ac97_write;
|
||||
codec->read = stac9766_ac97_read;
|
||||
codec->set_bias_level = stac9766_set_bias_level;
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
|
||||
if (ret < 0)
|
||||
goto codec_err;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0)
|
||||
goto pcm_err;
|
||||
|
||||
/* do a cold reset for the controller and then try
|
||||
* a warm reset followed by an optional cold reset for codec */
|
||||
stac9766_reset(codec, 0);
|
||||
ret = stac9766_reset(codec, 1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n");
|
||||
goto reset_err;
|
||||
}
|
||||
|
||||
stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
snd_soc_add_controls(codec, stac9766_snd_ac97_controls,
|
||||
ARRAY_SIZE(stac9766_snd_ac97_controls));
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0)
|
||||
goto reset_err;
|
||||
return 0;
|
||||
|
||||
reset_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
pcm_err:
|
||||
snd_soc_free_ac97_codec(codec);
|
||||
codec_err:
|
||||
kfree(codec->private_data);
|
||||
cache_err:
|
||||
kfree(socdev->card->codec);
|
||||
socdev->card->codec = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stac9766_codec_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
if (codec == NULL)
|
||||
return 0;
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_free_ac97_codec(codec);
|
||||
kfree(codec->reg_cache);
|
||||
kfree(codec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_stac9766 = {
|
||||
.probe = stac9766_codec_probe,
|
||||
.remove = stac9766_codec_remove,
|
||||
.suspend = stac9766_codec_suspend,
|
||||
.resume = stac9766_codec_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC stac9766 driver");
|
||||
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
||||
MODULE_LICENSE("GPL");
|
21
sound/soc/codecs/stac9766.h
Normal file
21
sound/soc/codecs/stac9766.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* stac9766.h -- STAC9766 Soc Audio driver
|
||||
*/
|
||||
|
||||
#ifndef _STAC9766_H
|
||||
#define _STAC9766_H
|
||||
|
||||
#define AC97_STAC_PAGE0 0x1000
|
||||
#define AC97_STAC_DA_CONTROL (AC97_STAC_PAGE0 | 0x6A)
|
||||
#define AC97_STAC_ANALOG_SPECIAL (AC97_STAC_PAGE0 | 0x6E)
|
||||
#define AC97_STAC_STEREO_MIC 0x78
|
||||
|
||||
/* STAC9766 DAI ID's */
|
||||
#define STAC9766_DAI_AC97_ANALOG 0
|
||||
#define STAC9766_DAI_AC97_DIGITAL 1
|
||||
|
||||
extern struct snd_soc_dai stac9766_dai[];
|
||||
extern struct snd_soc_codec_device soc_codec_dev_stac9766;
|
||||
|
||||
|
||||
#endif
|
@ -86,7 +86,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
*/
|
||||
|
||||
if ((reg < 0 || reg > 9) && (reg != 15)) {
|
||||
printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg);
|
||||
printk(KERN_WARNING "%s Invalid register R%u\n", __func__, reg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
|
||||
printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__,
|
||||
printk(KERN_ERR "%s cannot write %03x to register R%u\n", __func__,
|
||||
value, reg);
|
||||
|
||||
return -EIO;
|
||||
@ -273,14 +273,14 @@ static const unsigned short sr_valid_mask[] = {
|
||||
* Every divisor is a factor of 11*12
|
||||
*/
|
||||
#define SR_MULT (11*12)
|
||||
#define A(x) (x) ? (SR_MULT/x) : 0
|
||||
#define A(x) (SR_MULT/x)
|
||||
static const unsigned char sr_adc_mult_table[] = {
|
||||
A(2), A(2), A(12), A(12), A(0), A(0), A(3), A(1),
|
||||
A(2), A(2), A(11), A(11), A(0), A(0), A(0), A(1)
|
||||
A(2), A(2), A(12), A(12), 0, 0, A(3), A(1),
|
||||
A(2), A(2), A(11), A(11), 0, 0, 0, A(1)
|
||||
};
|
||||
static const unsigned char sr_dac_mult_table[] = {
|
||||
A(2), A(12), A(2), A(12), A(0), A(0), A(3), A(1),
|
||||
A(2), A(11), A(2), A(11), A(0), A(0), A(0), A(1)
|
||||
A(2), A(12), A(2), A(12), 0, 0, A(3), A(1),
|
||||
A(2), A(11), A(2), A(11), 0, 0, 0, A(1)
|
||||
};
|
||||
|
||||
static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc,
|
||||
@ -523,6 +523,8 @@ static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
iface_reg |= TLV320AIC23_FOR_I2S;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
iface_reg |= TLV320AIC23_LRP_ON;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
iface_reg |= TLV320AIC23_FOR_DSP;
|
||||
break;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -92,8 +92,9 @@
|
||||
#define TWL4030_REG_VIBRA_PWM_SET 0x47
|
||||
#define TWL4030_REG_ANAMIC_GAIN 0x48
|
||||
#define TWL4030_REG_MISC_SET_2 0x49
|
||||
#define TWL4030_REG_SW_SHADOW 0x4A
|
||||
|
||||
#define TWL4030_CACHEREGNUM (TWL4030_REG_MISC_SET_2 + 1)
|
||||
#define TWL4030_CACHEREGNUM (TWL4030_REG_SW_SHADOW + 1)
|
||||
|
||||
/* Bitfield Definitions */
|
||||
|
||||
@ -110,9 +111,22 @@
|
||||
#define TWL4030_APLL_RATE_44100 0x90
|
||||
#define TWL4030_APLL_RATE_48000 0xA0
|
||||
#define TWL4030_APLL_RATE_96000 0xE0
|
||||
#define TWL4030_SEL_16K 0x04
|
||||
#define TWL4030_SEL_16K 0x08
|
||||
#define TWL4030_CODECPDZ 0x02
|
||||
#define TWL4030_OPT_MODE 0x01
|
||||
#define TWL4030_OPTION_1 (1 << 0)
|
||||
#define TWL4030_OPTION_2 (0 << 0)
|
||||
|
||||
/* TWL4030_OPTION (0x02) Fields */
|
||||
|
||||
#define TWL4030_ATXL1_EN (1 << 0)
|
||||
#define TWL4030_ATXR1_EN (1 << 1)
|
||||
#define TWL4030_ATXL2_VTXL_EN (1 << 2)
|
||||
#define TWL4030_ATXR2_VTXR_EN (1 << 3)
|
||||
#define TWL4030_ARXL1_VRX_EN (1 << 4)
|
||||
#define TWL4030_ARXR1_EN (1 << 5)
|
||||
#define TWL4030_ARXL2_EN (1 << 6)
|
||||
#define TWL4030_ARXR2_EN (1 << 7)
|
||||
|
||||
/* TWL4030_REG_MICBIAS_CTL (0x04) Fields */
|
||||
|
||||
@ -171,6 +185,17 @@
|
||||
#define TWL4030_CLK256FS_EN 0x02
|
||||
#define TWL4030_AIF_EN 0x01
|
||||
|
||||
/* VOICE_IF (0x0F) Fields */
|
||||
|
||||
#define TWL4030_VIF_SLAVE_EN 0x80
|
||||
#define TWL4030_VIF_DIN_EN 0x40
|
||||
#define TWL4030_VIF_DOUT_EN 0x20
|
||||
#define TWL4030_VIF_SWAP 0x10
|
||||
#define TWL4030_VIF_FORMAT 0x08
|
||||
#define TWL4030_VIF_TRI_EN 0x04
|
||||
#define TWL4030_VIF_SUB_EN 0x02
|
||||
#define TWL4030_VIF_EN 0x01
|
||||
|
||||
/* EAR_CTL (0x21) */
|
||||
#define TWL4030_EAR_GAIN 0x30
|
||||
|
||||
@ -236,7 +261,19 @@
|
||||
#define TWL4030_SMOOTH_ANAVOL_EN 0x02
|
||||
#define TWL4030_DIGMIC_LR_SWAP_EN 0x01
|
||||
|
||||
extern struct snd_soc_dai twl4030_dai;
|
||||
/* TWL4030_REG_SW_SHADOW (0x4A) Fields */
|
||||
#define TWL4030_HFL_EN 0x01
|
||||
#define TWL4030_HFR_EN 0x02
|
||||
|
||||
#define TWL4030_DAI_HIFI 0
|
||||
#define TWL4030_DAI_VOICE 1
|
||||
|
||||
extern struct snd_soc_dai twl4030_dai[2];
|
||||
extern struct snd_soc_codec_device soc_codec_dev_twl4030;
|
||||
|
||||
struct twl4030_setup_data {
|
||||
unsigned int ramp_delay_value;
|
||||
unsigned int sysclk;
|
||||
};
|
||||
|
||||
#endif /* End of __TWL4030_AUDIO_H__ */
|
||||
|
@ -101,7 +101,7 @@ static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
pr_debug("%s reg: %02X, value:%02X\n", __func__, reg, value);
|
||||
|
||||
if (reg >= UDA134X_REGS_NUM) {
|
||||
printk(KERN_ERR "%s unkown register: reg: %d",
|
||||
printk(KERN_ERR "%s unkown register: reg: %u",
|
||||
__func__, reg);
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -296,7 +296,7 @@ static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct uda134x_priv *uda134x = codec->private_data;
|
||||
|
||||
pr_debug("%s clk_id: %d, freq: %d, dir: %d\n", __func__,
|
||||
pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__,
|
||||
clk_id, freq, dir);
|
||||
|
||||
/* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable
|
||||
|
@ -1108,7 +1108,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dev_dbg(wm8350->dev,
|
||||
"FLL in %d FLL out %d N 0x%x K 0x%x div %d ratio %d",
|
||||
"FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d",
|
||||
freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div,
|
||||
fll_div.ratio);
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define _WM8350_H
|
||||
|
||||
#include <sound/soc.h>
|
||||
#include <linux/mfd/wm8350/audio.h>
|
||||
|
||||
extern struct snd_soc_dai wm8350_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8350;
|
||||
|
@ -954,7 +954,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
|
||||
factors->outdiv *= 2;
|
||||
if (factors->outdiv > 32) {
|
||||
dev_err(wm8400->wm8400->dev,
|
||||
"Unsupported FLL output frequency %dHz\n",
|
||||
"Unsupported FLL output frequency %uHz\n",
|
||||
Fout);
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -1003,7 +1003,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
|
||||
factors->k = K / 10;
|
||||
|
||||
dev_dbg(wm8400->wm8400->dev,
|
||||
"FLL: Fref=%d Fout=%d N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
|
||||
"FLL: Fref=%u Fout=%u N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
|
||||
Fref, Fout,
|
||||
factors->n, factors->k, factors->fratio, factors->outdiv);
|
||||
|
||||
@ -1473,8 +1473,8 @@ static int wm8400_codec_probe(struct platform_device *dev)
|
||||
|
||||
codec = &priv->codec;
|
||||
codec->private_data = priv;
|
||||
codec->control_data = dev->dev.driver_data;
|
||||
priv->wm8400 = dev->dev.driver_data;
|
||||
codec->control_data = dev_get_drvdata(&dev->dev);
|
||||
priv->wm8400 = dev_get_drvdata(&dev->dev);
|
||||
|
||||
ret = regulator_bulk_get(priv->wm8400->dev,
|
||||
ARRAY_SIZE(power), &power[0]);
|
||||
|
@ -298,7 +298,7 @@ static void pll_factors(unsigned int target, unsigned int source)
|
||||
|
||||
if ((Ndiv < 6) || (Ndiv > 12))
|
||||
printk(KERN_WARNING
|
||||
"WM8510 N value %d outwith recommended range!d\n",
|
||||
"WM8510 N value %u outwith recommended range!d\n",
|
||||
Ndiv);
|
||||
|
||||
pll_div.n = Ndiv;
|
||||
|
@ -415,7 +415,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target,
|
||||
unsigned int K, Ndiv, Nmod;
|
||||
int i;
|
||||
|
||||
pr_debug("wm8580: PLL %dHz->%dHz\n", source, target);
|
||||
pr_debug("wm8580: PLL %uHz->%uHz\n", source, target);
|
||||
|
||||
/* Scale the output frequency up; the PLL should run in the
|
||||
* region of 90-100MHz.
|
||||
@ -447,7 +447,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target,
|
||||
|
||||
if ((Ndiv < 5) || (Ndiv > 13)) {
|
||||
printk(KERN_ERR
|
||||
"WM8580 N=%d outside supported range\n", Ndiv);
|
||||
"WM8580 N=%u outside supported range\n", Ndiv);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -666,14 +666,14 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi)
|
||||
codec->hw_write = (hw_write_t)wm8731_spi_write;
|
||||
codec->dev = &spi->dev;
|
||||
|
||||
spi->dev.driver_data = wm8731;
|
||||
dev_set_drvdata(&spi->dev, wm8731);
|
||||
|
||||
return wm8731_register(wm8731);
|
||||
}
|
||||
|
||||
static int __devexit wm8731_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct wm8731_priv *wm8731 = spi->dev.driver_data;
|
||||
struct wm8731_priv *wm8731 = dev_get_drvdata(&spi->dev);
|
||||
|
||||
wm8731_unregister(wm8731);
|
||||
|
||||
|
@ -703,7 +703,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target,
|
||||
|
||||
if ((Ndiv < 6) || (Ndiv > 12))
|
||||
printk(KERN_WARNING
|
||||
"wm8753: unsupported N = %d\n", Ndiv);
|
||||
"wm8753: unsupported N = %u\n", Ndiv);
|
||||
|
||||
pll_div->n = Ndiv;
|
||||
Nmod = target % source;
|
||||
@ -1822,14 +1822,14 @@ static int __devinit wm8753_spi_probe(struct spi_device *spi)
|
||||
codec->hw_write = (hw_write_t)wm8753_spi_write;
|
||||
codec->dev = &spi->dev;
|
||||
|
||||
spi->dev.driver_data = wm8753;
|
||||
dev_set_drvdata(&spi->dev, wm8753);
|
||||
|
||||
return wm8753_register(wm8753);
|
||||
}
|
||||
|
||||
static int __devexit wm8753_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct wm8753_priv *wm8753 = spi->dev.driver_data;
|
||||
struct wm8753_priv *wm8753 = dev_get_drvdata(&spi->dev);
|
||||
wm8753_unregister(wm8753);
|
||||
return 0;
|
||||
}
|
||||
|
@ -778,11 +778,11 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
|
||||
}
|
||||
|
||||
if (target > 100000000)
|
||||
printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d"
|
||||
" Fout=%d\n", target, Fref, Fout);
|
||||
printk(KERN_WARNING "wm8900: FLL rate %u out of range, Fref=%u"
|
||||
" Fout=%u\n", target, Fref, Fout);
|
||||
if (div > 32) {
|
||||
printk(KERN_ERR "wm8900: Invalid FLL division rate %u, "
|
||||
"Fref=%d, Fout=%d, target=%d\n",
|
||||
"Fref=%u, Fout=%u, target=%u\n",
|
||||
div, Fref, Fout, target);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -217,7 +217,6 @@ struct wm8903_priv {
|
||||
int sysclk;
|
||||
|
||||
/* Reference counts */
|
||||
int charge_pump_users;
|
||||
int class_w_users;
|
||||
int playback_active;
|
||||
int capture_active;
|
||||
@ -373,6 +372,15 @@ static void wm8903_reset(struct snd_soc_codec *codec)
|
||||
#define WM8903_OUTPUT_INT 0x2
|
||||
#define WM8903_OUTPUT_IN 0x1
|
||||
|
||||
static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
WARN_ON(event != SND_SOC_DAPM_POST_PMU);
|
||||
mdelay(4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Event for headphone and line out amplifier power changes. Special
|
||||
* power up/down sequences are required in order to maximise pop/click
|
||||
@ -382,19 +390,20 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
struct wm8903_priv *wm8903 = codec->private_data;
|
||||
struct i2c_client *i2c = codec->control_data;
|
||||
u16 val;
|
||||
u16 reg;
|
||||
u16 dcs_reg;
|
||||
u16 dcs_bit;
|
||||
int shift;
|
||||
u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0);
|
||||
|
||||
switch (w->reg) {
|
||||
case WM8903_POWER_MANAGEMENT_2:
|
||||
reg = WM8903_ANALOGUE_HP_0;
|
||||
dcs_bit = 0 + w->shift;
|
||||
break;
|
||||
case WM8903_POWER_MANAGEMENT_3:
|
||||
reg = WM8903_ANALOGUE_LINEOUT_0;
|
||||
dcs_bit = 2 + w->shift;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
@ -419,18 +428,6 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
|
||||
/* Short the output */
|
||||
val &= ~(WM8903_OUTPUT_SHORT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
|
||||
wm8903->charge_pump_users++;
|
||||
|
||||
dev_dbg(&i2c->dev, "Charge pump use count now %d\n",
|
||||
wm8903->charge_pump_users);
|
||||
|
||||
if (wm8903->charge_pump_users == 1) {
|
||||
dev_dbg(&i2c->dev, "Enabling charge pump\n");
|
||||
wm8903_write(codec, WM8903_CHARGE_PUMP_0,
|
||||
cp_reg | WM8903_CP_ENA);
|
||||
mdelay(4);
|
||||
}
|
||||
}
|
||||
|
||||
if (event & SND_SOC_DAPM_POST_PMU) {
|
||||
@ -446,6 +443,11 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
|
||||
val |= (WM8903_OUTPUT_OUT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
|
||||
/* Enable the DC servo */
|
||||
dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0);
|
||||
dcs_reg |= dcs_bit;
|
||||
wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg);
|
||||
|
||||
/* Remove the short */
|
||||
val |= (WM8903_OUTPUT_SHORT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
@ -458,25 +460,17 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
|
||||
val &= ~(WM8903_OUTPUT_SHORT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
|
||||
/* Disable the DC servo */
|
||||
dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0);
|
||||
dcs_reg &= ~dcs_bit;
|
||||
wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg);
|
||||
|
||||
/* Then disable the intermediate and output stages */
|
||||
val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT |
|
||||
WM8903_OUTPUT_IN) << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
}
|
||||
|
||||
if (event & SND_SOC_DAPM_POST_PMD) {
|
||||
wm8903->charge_pump_users--;
|
||||
|
||||
dev_dbg(&i2c->dev, "Charge pump use count now %d\n",
|
||||
wm8903->charge_pump_users);
|
||||
|
||||
if (wm8903->charge_pump_users == 0) {
|
||||
dev_dbg(&i2c->dev, "Disabling charge pump\n");
|
||||
wm8903_write(codec, WM8903_CHARGE_PUMP_0,
|
||||
cp_reg & ~WM8903_CP_ENA);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -539,6 +533,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
|
||||
/* ALSA can only do steps of .01dB */
|
||||
static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(digital_sidetone_tlv, -3600, 300, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0);
|
||||
@ -657,6 +652,16 @@ static const struct soc_enum rinput_inv_enum =
|
||||
SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text);
|
||||
|
||||
|
||||
static const char *sidetone_text[] = {
|
||||
"None", "Left", "Right"
|
||||
};
|
||||
|
||||
static const struct soc_enum lsidetone_enum =
|
||||
SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 2, 3, sidetone_text);
|
||||
|
||||
static const struct soc_enum rsidetone_enum =
|
||||
SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text);
|
||||
|
||||
static const struct snd_kcontrol_new wm8903_snd_controls[] = {
|
||||
|
||||
/* Input PGAs - No TLV since the scale depends on PGA mode */
|
||||
@ -700,6 +705,9 @@ SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT,
|
||||
SOC_ENUM("ADC Companding Mode", adc_companding),
|
||||
SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0),
|
||||
|
||||
SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8903_DAC_DIGITAL_0, 4, 8,
|
||||
12, 0, digital_sidetone_tlv),
|
||||
|
||||
/* DAC */
|
||||
SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT,
|
||||
WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv),
|
||||
@ -762,6 +770,12 @@ static const struct snd_kcontrol_new rinput_mux =
|
||||
static const struct snd_kcontrol_new rinput_inv_mux =
|
||||
SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum);
|
||||
|
||||
static const struct snd_kcontrol_new lsidetone_mux =
|
||||
SOC_DAPM_ENUM("DACL Sidetone Mux", lsidetone_enum);
|
||||
|
||||
static const struct snd_kcontrol_new rsidetone_mux =
|
||||
SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum);
|
||||
|
||||
static const struct snd_kcontrol_new left_output_mixer[] = {
|
||||
SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0),
|
||||
SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0),
|
||||
@ -828,6 +842,9 @@ SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0),
|
||||
SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0),
|
||||
|
||||
SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux),
|
||||
SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux),
|
||||
|
||||
SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0),
|
||||
SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0),
|
||||
|
||||
@ -844,26 +861,29 @@ SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
|
||||
SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
|
||||
1, 0, NULL, 0, wm8903_output_event,
|
||||
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
|
||||
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_PRE_PMD),
|
||||
SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
|
||||
0, 0, NULL, 0, wm8903_output_event,
|
||||
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
|
||||
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_PRE_PMD),
|
||||
|
||||
SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0,
|
||||
NULL, 0, wm8903_output_event,
|
||||
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
|
||||
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_PRE_PMD),
|
||||
SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0,
|
||||
NULL, 0, wm8903_output_event,
|
||||
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
|
||||
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_PRE_PMD),
|
||||
|
||||
SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
|
||||
NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0,
|
||||
NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0,
|
||||
wm8903_cp_event, SND_SOC_DAPM_POST_PMU),
|
||||
SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route intercon[] = {
|
||||
@ -909,7 +929,19 @@ static const struct snd_soc_dapm_route intercon[] = {
|
||||
{ "Right Input PGA", NULL, "Right Input Mode Mux" },
|
||||
|
||||
{ "ADCL", NULL, "Left Input PGA" },
|
||||
{ "ADCL", NULL, "CLK_DSP" },
|
||||
{ "ADCR", NULL, "Right Input PGA" },
|
||||
{ "ADCR", NULL, "CLK_DSP" },
|
||||
|
||||
{ "DACL Sidetone", "Left", "ADCL" },
|
||||
{ "DACL Sidetone", "Right", "ADCR" },
|
||||
{ "DACR Sidetone", "Left", "ADCL" },
|
||||
{ "DACR Sidetone", "Right", "ADCR" },
|
||||
|
||||
{ "DACL", NULL, "DACL Sidetone" },
|
||||
{ "DACL", NULL, "CLK_DSP" },
|
||||
{ "DACR", NULL, "DACR Sidetone" },
|
||||
{ "DACR", NULL, "CLK_DSP" },
|
||||
|
||||
{ "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" },
|
||||
{ "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" },
|
||||
@ -951,6 +983,11 @@ static const struct snd_soc_dapm_route intercon[] = {
|
||||
|
||||
{ "ROP", NULL, "Right Speaker PGA" },
|
||||
{ "RON", NULL, "Right Speaker PGA" },
|
||||
|
||||
{ "Left Headphone Output PGA", NULL, "Charge Pump" },
|
||||
{ "Right Headphone Output PGA", NULL, "Charge Pump" },
|
||||
{ "Left Line Output PGA", NULL, "Charge Pump" },
|
||||
{ "Right Line Output PGA", NULL, "Charge Pump" },
|
||||
};
|
||||
|
||||
static int wm8903_add_widgets(struct snd_soc_codec *codec)
|
||||
@ -985,6 +1022,11 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec,
|
||||
wm8903_write(codec, WM8903_CLOCK_RATES_2,
|
||||
WM8903_CLK_SYS_ENA);
|
||||
|
||||
/* Change DC servo dither level in startup sequence */
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11);
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257);
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2);
|
||||
|
||||
wm8903_run_sequence(codec, 0);
|
||||
wm8903_sync_reg_cache(codec, codec->reg_cache);
|
||||
|
||||
@ -1277,14 +1319,8 @@ static int wm8903_startup(struct snd_pcm_substream *substream,
|
||||
if (wm8903->master_substream) {
|
||||
master_runtime = wm8903->master_substream->runtime;
|
||||
|
||||
dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n",
|
||||
master_runtime->sample_bits,
|
||||
master_runtime->rate);
|
||||
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
master_runtime->rate,
|
||||
master_runtime->rate);
|
||||
dev_dbg(&i2c->dev, "Constraining to %d bits\n",
|
||||
master_runtime->sample_bits);
|
||||
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
||||
@ -1523,6 +1559,7 @@ struct snd_soc_dai wm8903_dai = {
|
||||
.formats = WM8903_FORMATS,
|
||||
},
|
||||
.ops = &wm8903_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8903_dai);
|
||||
|
||||
|
955
sound/soc/codecs/wm8940.c
Normal file
955
sound/soc/codecs/wm8940.c
Normal file
@ -0,0 +1,955 @@
|
||||
/*
|
||||
* wm8940.c -- WM8940 ALSA Soc Audio driver
|
||||
*
|
||||
* Author: Jonathan Cameron <jic23@cam.ac.uk>
|
||||
*
|
||||
* Based on wm8510.c
|
||||
* Copyright 2006 Wolfson Microelectronics PLC.
|
||||
* Author: Liam Girdwood <lrg@slimlogic.co.uk>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Not currently handled:
|
||||
* Notch filter control
|
||||
* AUXMode (inverting vs mixer)
|
||||
* No means to obtain current gain if alc enabled.
|
||||
* No use made of gpio
|
||||
* Fast VMID discharge for power down
|
||||
* Soft Start
|
||||
* DLR and ALR Swaps not enabled
|
||||
* Digital Sidetone not supported
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi.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 "wm8940.h"
|
||||
|
||||
struct wm8940_priv {
|
||||
unsigned int sysclk;
|
||||
u16 reg_cache[WM8940_CACHEREGNUM];
|
||||
struct snd_soc_codec codec;
|
||||
};
|
||||
|
||||
static u16 wm8940_reg_defaults[] = {
|
||||
0x8940, /* Soft Reset */
|
||||
0x0000, /* Power 1 */
|
||||
0x0000, /* Power 2 */
|
||||
0x0000, /* Power 3 */
|
||||
0x0010, /* Interface Control */
|
||||
0x0000, /* Companding Control */
|
||||
0x0140, /* Clock Control */
|
||||
0x0000, /* Additional Controls */
|
||||
0x0000, /* GPIO Control */
|
||||
0x0002, /* Auto Increment Control */
|
||||
0x0000, /* DAC Control */
|
||||
0x00FF, /* DAC Volume */
|
||||
0,
|
||||
0,
|
||||
0x0100, /* ADC Control */
|
||||
0x00FF, /* ADC Volume */
|
||||
0x0000, /* Notch Filter 1 Control 1 */
|
||||
0x0000, /* Notch Filter 1 Control 2 */
|
||||
0x0000, /* Notch Filter 2 Control 1 */
|
||||
0x0000, /* Notch Filter 2 Control 2 */
|
||||
0x0000, /* Notch Filter 3 Control 1 */
|
||||
0x0000, /* Notch Filter 3 Control 2 */
|
||||
0x0000, /* Notch Filter 4 Control 1 */
|
||||
0x0000, /* Notch Filter 4 Control 2 */
|
||||
0x0032, /* DAC Limit Control 1 */
|
||||
0x0000, /* DAC Limit Control 2 */
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0x0038, /* ALC Control 1 */
|
||||
0x000B, /* ALC Control 2 */
|
||||
0x0032, /* ALC Control 3 */
|
||||
0x0000, /* Noise Gate */
|
||||
0x0041, /* PLLN */
|
||||
0x000C, /* PLLK1 */
|
||||
0x0093, /* PLLK2 */
|
||||
0x00E9, /* PLLK3 */
|
||||
0,
|
||||
0,
|
||||
0x0030, /* ALC Control 4 */
|
||||
0,
|
||||
0x0002, /* Input Control */
|
||||
0x0050, /* PGA Gain */
|
||||
0,
|
||||
0x0002, /* ADC Boost Control */
|
||||
0,
|
||||
0x0002, /* Output Control */
|
||||
0x0000, /* Speaker Mixer Control */
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0x0079, /* Speaker Volume */
|
||||
0,
|
||||
0x0000, /* Mono Mixer Control */
|
||||
};
|
||||
|
||||
static inline unsigned int wm8940_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
if (reg >= ARRAY_SIZE(wm8940_reg_defaults))
|
||||
return -1;
|
||||
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
static inline int wm8940_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
if (reg >= ARRAY_SIZE(wm8940_reg_defaults))
|
||||
return -1;
|
||||
|
||||
cache[reg] = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8940_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
int ret;
|
||||
u8 data[3] = { reg,
|
||||
(value & 0xff00) >> 8,
|
||||
(value & 0x00ff)
|
||||
};
|
||||
|
||||
wm8940_write_reg_cache(codec, reg, value);
|
||||
|
||||
ret = codec->hw_write(codec->control_data, data, 3);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret != 3)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" };
|
||||
static const struct soc_enum wm8940_adc_companding_enum
|
||||
= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding);
|
||||
static const struct soc_enum wm8940_dac_companding_enum
|
||||
= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 3, 4, wm8940_companding);
|
||||
|
||||
static const char *wm8940_alc_mode_text[] = {"ALC", "Limiter"};
|
||||
static const struct soc_enum wm8940_alc_mode_enum
|
||||
= SOC_ENUM_SINGLE(WM8940_ALC3, 8, 2, wm8940_alc_mode_text);
|
||||
|
||||
static const char *wm8940_mic_bias_level_text[] = {"0.9", "0.65"};
|
||||
static const struct soc_enum wm8940_mic_bias_level_enum
|
||||
= SOC_ENUM_SINGLE(WM8940_INPUTCTL, 8, 2, wm8940_mic_bias_level_text);
|
||||
|
||||
static const char *wm8940_filter_mode_text[] = {"Audio", "Application"};
|
||||
static const struct soc_enum wm8940_filter_mode_enum
|
||||
= SOC_ENUM_SINGLE(WM8940_ADC, 7, 2, wm8940_filter_mode_text);
|
||||
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1);
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0);
|
||||
|
||||
static const struct snd_kcontrol_new wm8940_snd_controls[] = {
|
||||
SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL,
|
||||
6, 1, 0),
|
||||
SOC_ENUM("DAC Companding", wm8940_dac_companding_enum),
|
||||
SOC_ENUM("ADC Companding", wm8940_adc_companding_enum),
|
||||
|
||||
SOC_ENUM("ALC Mode", wm8940_alc_mode_enum),
|
||||
SOC_SINGLE("ALC Switch", WM8940_ALC1, 8, 1, 0),
|
||||
SOC_SINGLE_TLV("ALC Capture Max Gain", WM8940_ALC1,
|
||||
3, 7, 1, wm8940_alc_max_tlv),
|
||||
SOC_SINGLE_TLV("ALC Capture Min Gain", WM8940_ALC1,
|
||||
0, 7, 0, wm8940_alc_min_tlv),
|
||||
SOC_SINGLE_TLV("ALC Capture Target", WM8940_ALC2,
|
||||
0, 14, 0, wm8940_alc_tar_tlv),
|
||||
SOC_SINGLE("ALC Capture Hold", WM8940_ALC2, 4, 10, 0),
|
||||
SOC_SINGLE("ALC Capture Decay", WM8940_ALC3, 4, 10, 0),
|
||||
SOC_SINGLE("ALC Capture Attach", WM8940_ALC3, 0, 10, 0),
|
||||
SOC_SINGLE("ALC ZC Switch", WM8940_ALC4, 1, 1, 0),
|
||||
SOC_SINGLE("ALC Capture Noise Gate Switch", WM8940_NOISEGATE,
|
||||
3, 1, 0),
|
||||
SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8940_NOISEGATE,
|
||||
0, 7, 0),
|
||||
|
||||
SOC_SINGLE("DAC Playback Limiter Switch", WM8940_DACLIM1, 8, 1, 0),
|
||||
SOC_SINGLE("DAC Playback Limiter Attack", WM8940_DACLIM1, 0, 9, 0),
|
||||
SOC_SINGLE("DAC Playback Limiter Decay", WM8940_DACLIM1, 4, 11, 0),
|
||||
SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8940_DACLIM2,
|
||||
4, 9, 1, wm8940_lim_thresh_tlv),
|
||||
SOC_SINGLE_TLV("DAC Playback Limiter Boost", WM8940_DACLIM2,
|
||||
0, 12, 0, wm8940_lim_boost_tlv),
|
||||
|
||||
SOC_SINGLE("Capture PGA ZC Switch", WM8940_PGAGAIN, 7, 1, 0),
|
||||
SOC_SINGLE_TLV("Capture PGA Volume", WM8940_PGAGAIN,
|
||||
0, 63, 0, wm8940_pga_vol_tlv),
|
||||
SOC_SINGLE_TLV("Digital Playback Volume", WM8940_DACVOL,
|
||||
0, 255, 0, wm8940_adc_tlv),
|
||||
SOC_SINGLE_TLV("Digital Capture Volume", WM8940_ADCVOL,
|
||||
0, 255, 0, wm8940_adc_tlv),
|
||||
SOC_ENUM("Mic Bias Level", wm8940_mic_bias_level_enum),
|
||||
SOC_SINGLE_TLV("Capture Boost Volue", WM8940_ADCBOOST,
|
||||
8, 1, 0, wm8940_capture_boost_vol_tlv),
|
||||
SOC_SINGLE_TLV("Speaker Playback Volume", WM8940_SPKVOL,
|
||||
0, 63, 0, wm8940_spk_vol_tlv),
|
||||
SOC_SINGLE("Speaker Playback Switch", WM8940_SPKVOL, 6, 1, 1),
|
||||
|
||||
SOC_SINGLE_TLV("Speaker Mixer Line Bypass Volume", WM8940_SPKVOL,
|
||||
8, 1, 1, wm8940_att_tlv),
|
||||
SOC_SINGLE("Speaker Playback ZC Switch", WM8940_SPKVOL, 7, 1, 0),
|
||||
|
||||
SOC_SINGLE("Mono Out Switch", WM8940_MONOMIX, 6, 1, 1),
|
||||
SOC_SINGLE_TLV("Mono Mixer Line Bypass Volume", WM8940_MONOMIX,
|
||||
7, 1, 1, wm8940_att_tlv),
|
||||
|
||||
SOC_SINGLE("High Pass Filter Switch", WM8940_ADC, 8, 1, 0),
|
||||
SOC_ENUM("High Pass Filter Mode", wm8940_filter_mode_enum),
|
||||
SOC_SINGLE("High Pass Filter Cut Off", WM8940_ADC, 4, 7, 0),
|
||||
SOC_SINGLE("ADC Inversion Switch", WM8940_ADC, 0, 1, 0),
|
||||
SOC_SINGLE("DAC Inversion Switch", WM8940_DAC, 0, 1, 0),
|
||||
SOC_SINGLE("DAC Auto Mute Switch", WM8940_DAC, 2, 1, 0),
|
||||
SOC_SINGLE("ZC Timeout Clock Switch", WM8940_ADDCNTRL, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8940_speaker_mixer_controls[] = {
|
||||
SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_SPKMIX, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_SPKMIX, 5, 1, 0),
|
||||
SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_SPKMIX, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = {
|
||||
SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_MONOMIX, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_MONOMIX, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0),
|
||||
};
|
||||
|
||||
static DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1);
|
||||
static const struct snd_kcontrol_new wm8940_input_boost_controls[] = {
|
||||
SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1),
|
||||
SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST,
|
||||
0, 7, 0, wm8940_boost_vol_tlv),
|
||||
SOC_DAPM_SINGLE_TLV("Mic Volume", WM8940_ADCBOOST,
|
||||
4, 7, 0, wm8940_boost_vol_tlv),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8940_micpga_controls[] = {
|
||||
SOC_DAPM_SINGLE("AUX Switch", WM8940_INPUTCTL, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("MICP Switch", WM8940_INPUTCTL, 0, 1, 0),
|
||||
SOC_DAPM_SINGLE("MICN Switch", WM8940_INPUTCTL, 1, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8940_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_MIXER("Speaker Mixer", WM8940_POWER3, 2, 0,
|
||||
&wm8940_speaker_mixer_controls[0],
|
||||
ARRAY_SIZE(wm8940_speaker_mixer_controls)),
|
||||
SND_SOC_DAPM_MIXER("Mono Mixer", WM8940_POWER3, 3, 0,
|
||||
&wm8940_mono_mixer_controls[0],
|
||||
ARRAY_SIZE(wm8940_mono_mixer_controls)),
|
||||
SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8940_POWER3, 0, 0),
|
||||
|
||||
SND_SOC_DAPM_PGA("SpkN Out", WM8940_POWER3, 5, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("SpkP Out", WM8940_POWER3, 6, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Mono Out", WM8940_POWER3, 7, 0, NULL, 0),
|
||||
SND_SOC_DAPM_OUTPUT("MONOOUT"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTP"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTN"),
|
||||
|
||||
SND_SOC_DAPM_PGA("Aux Input", WM8940_POWER1, 6, 0, NULL, 0),
|
||||
SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8940_POWER2, 0, 0),
|
||||
SND_SOC_DAPM_MIXER("Mic PGA", WM8940_POWER2, 2, 0,
|
||||
&wm8940_micpga_controls[0],
|
||||
ARRAY_SIZE(wm8940_micpga_controls)),
|
||||
SND_SOC_DAPM_MIXER("Boost Mixer", WM8940_POWER2, 4, 0,
|
||||
&wm8940_input_boost_controls[0],
|
||||
ARRAY_SIZE(wm8940_input_boost_controls)),
|
||||
SND_SOC_DAPM_MICBIAS("Mic Bias", WM8940_POWER1, 4, 0),
|
||||
|
||||
SND_SOC_DAPM_INPUT("MICN"),
|
||||
SND_SOC_DAPM_INPUT("MICP"),
|
||||
SND_SOC_DAPM_INPUT("AUX"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Mono output mixer */
|
||||
{"Mono Mixer", "PCM Playback Switch", "DAC"},
|
||||
{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
|
||||
{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
|
||||
|
||||
/* Speaker output mixer */
|
||||
{"Speaker Mixer", "PCM Playback Switch", "DAC"},
|
||||
{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
|
||||
{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
|
||||
|
||||
/* Outputs */
|
||||
{"Mono Out", NULL, "Mono Mixer"},
|
||||
{"MONOOUT", NULL, "Mono Out"},
|
||||
{"SpkN Out", NULL, "Speaker Mixer"},
|
||||
{"SpkP Out", NULL, "Speaker Mixer"},
|
||||
{"SPKOUTN", NULL, "SpkN Out"},
|
||||
{"SPKOUTP", NULL, "SpkP Out"},
|
||||
|
||||
/* Microphone PGA */
|
||||
{"Mic PGA", "MICN Switch", "MICN"},
|
||||
{"Mic PGA", "MICP Switch", "MICP"},
|
||||
{"Mic PGA", "AUX Switch", "AUX"},
|
||||
|
||||
/* Boost Mixer */
|
||||
{"Boost Mixer", "Mic PGA Switch", "Mic PGA"},
|
||||
{"Boost Mixer", "Mic Volume", "MICP"},
|
||||
{"Boost Mixer", "Aux Volume", "Aux Input"},
|
||||
|
||||
{"ADC", NULL, "Boost Mixer"},
|
||||
};
|
||||
|
||||
static int wm8940_add_widgets(struct snd_soc_codec *codec)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dapm_new_controls(codec, wm8940_dapm_widgets,
|
||||
ARRAY_SIZE(wm8940_dapm_widgets));
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = snd_soc_dapm_new_widgets(codec);
|
||||
|
||||
error_ret:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define wm8940_reset(c) wm8940_write(c, WM8940_SOFTRESET, 0);
|
||||
|
||||
static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFE67;
|
||||
u16 clk = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0x1fe;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
clk |= 1;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
wm8940_write(codec, WM8940_CLOCK, clk);
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
iface |= (2 << 3);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
iface |= (1 << 3);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
iface |= (3 << 3);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
iface |= (3 << 3) | (1 << 7);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
iface |= (1 << 7);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
iface |= (1 << 8);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
iface |= (1 << 8) | (1 << 7);
|
||||
break;
|
||||
}
|
||||
|
||||
wm8940_write(codec, WM8940_IFACE, iface);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFD9F;
|
||||
u16 addcntrl = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFF1;
|
||||
u16 companding = wm8940_read_reg_cache(codec,
|
||||
WM8940_COMPANDINGCTL) & 0xFFDF;
|
||||
int ret;
|
||||
|
||||
/* LoutR control */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
|
||||
&& params_channels(params) == 2)
|
||||
iface |= (1 << 9);
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case SNDRV_PCM_RATE_8000:
|
||||
addcntrl |= (0x5 << 1);
|
||||
break;
|
||||
case SNDRV_PCM_RATE_11025:
|
||||
addcntrl |= (0x4 << 1);
|
||||
break;
|
||||
case SNDRV_PCM_RATE_16000:
|
||||
addcntrl |= (0x3 << 1);
|
||||
break;
|
||||
case SNDRV_PCM_RATE_22050:
|
||||
addcntrl |= (0x2 << 1);
|
||||
break;
|
||||
case SNDRV_PCM_RATE_32000:
|
||||
addcntrl |= (0x1 << 1);
|
||||
break;
|
||||
case SNDRV_PCM_RATE_44100:
|
||||
case SNDRV_PCM_RATE_48000:
|
||||
break;
|
||||
}
|
||||
ret = wm8940_write(codec, WM8940_ADDCNTRL, addcntrl);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S8:
|
||||
companding = companding | (1 << 5);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
iface |= (1 << 5);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
iface |= (2 << 5);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
iface |= (3 << 5);
|
||||
break;
|
||||
}
|
||||
ret = wm8940_write(codec, WM8940_COMPANDINGCTL, companding);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = wm8940_write(codec, WM8940_IFACE, iface);
|
||||
|
||||
error_ret:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm8940_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8940_read_reg_cache(codec, WM8940_DAC) & 0xffbf;
|
||||
|
||||
if (mute)
|
||||
mute_reg |= 0x40;
|
||||
|
||||
return wm8940_write(codec, WM8940_DAC, mute_reg);
|
||||
}
|
||||
|
||||
static int wm8940_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
u16 val;
|
||||
u16 pwr_reg = wm8940_read_reg_cache(codec, WM8940_POWER1) & 0x1F0;
|
||||
int ret = 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
/* ensure bufioen and biasen */
|
||||
pwr_reg |= (1 << 2) | (1 << 3);
|
||||
/* Enable thermal shutdown */
|
||||
val = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL);
|
||||
ret = wm8940_write(codec, WM8940_OUTPUTCTL, val | 0x2);
|
||||
if (ret)
|
||||
break;
|
||||
/* set vmid to 75k */
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1);
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* ensure bufioen and biasen */
|
||||
pwr_reg |= (1 << 2) | (1 << 3);
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1);
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
/* ensure bufioen and biasen */
|
||||
pwr_reg |= (1 << 2) | (1 << 3);
|
||||
/* set vmid to 300k for standby */
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x2);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct pll_ {
|
||||
unsigned int pre_scale:2;
|
||||
unsigned int n:4;
|
||||
unsigned int k;
|
||||
};
|
||||
|
||||
static struct pll_ pll_div;
|
||||
|
||||
/* The size in bits of the pll divide multiplied by 10
|
||||
* to allow rounding later */
|
||||
#define FIXED_PLL_SIZE ((1 << 24) * 10)
|
||||
static void pll_factors(unsigned int target, unsigned int source)
|
||||
{
|
||||
unsigned long long Kpart;
|
||||
unsigned int K, Ndiv, Nmod;
|
||||
/* The left shift ist to avoid accuracy loss when right shifting */
|
||||
Ndiv = target / source;
|
||||
|
||||
if (Ndiv > 12) {
|
||||
source <<= 1;
|
||||
/* Multiply by 2 */
|
||||
pll_div.pre_scale = 0;
|
||||
Ndiv = target / source;
|
||||
} else if (Ndiv < 3) {
|
||||
source >>= 2;
|
||||
/* Divide by 4 */
|
||||
pll_div.pre_scale = 3;
|
||||
Ndiv = target / source;
|
||||
} else if (Ndiv < 6) {
|
||||
source >>= 1;
|
||||
/* divide by 2 */
|
||||
pll_div.pre_scale = 2;
|
||||
Ndiv = target / source;
|
||||
} else
|
||||
pll_div.pre_scale = 1;
|
||||
|
||||
if ((Ndiv < 6) || (Ndiv > 12))
|
||||
printk(KERN_WARNING
|
||||
"WM8940 N value %d outwith recommended range!d\n",
|
||||
Ndiv);
|
||||
|
||||
pll_div.n = Ndiv;
|
||||
Nmod = target % source;
|
||||
Kpart = FIXED_PLL_SIZE * (long long)Nmod;
|
||||
|
||||
do_div(Kpart, source);
|
||||
|
||||
K = Kpart & 0xFFFFFFFF;
|
||||
|
||||
/* Check if we need to round */
|
||||
if ((K % 10) >= 5)
|
||||
K += 5;
|
||||
|
||||
/* Move down to proper range now rounding is done */
|
||||
K /= 10;
|
||||
|
||||
pll_div.k = K;
|
||||
}
|
||||
|
||||
/* Untested at the moment */
|
||||
static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai,
|
||||
int pll_id, unsigned int freq_in, unsigned int freq_out)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
|
||||
/* Turn off PLL */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_POWER1);
|
||||
wm8940_write(codec, WM8940_POWER1, reg & 0x1df);
|
||||
|
||||
if (freq_in == 0 || freq_out == 0) {
|
||||
/* Clock CODEC directly from MCLK */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK);
|
||||
wm8940_write(codec, WM8940_CLOCK, reg & 0x0ff);
|
||||
/* Pll power down */
|
||||
wm8940_write(codec, WM8940_PLLN, (1 << 7));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pll is followed by a frequency divide by 4 */
|
||||
pll_factors(freq_out*4, freq_in);
|
||||
if (pll_div.k)
|
||||
wm8940_write(codec, WM8940_PLLN,
|
||||
(pll_div.pre_scale << 4) | pll_div.n | (1 << 6));
|
||||
else /* No factional component */
|
||||
wm8940_write(codec, WM8940_PLLN,
|
||||
(pll_div.pre_scale << 4) | pll_div.n);
|
||||
wm8940_write(codec, WM8940_PLLK1, pll_div.k >> 18);
|
||||
wm8940_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff);
|
||||
wm8940_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff);
|
||||
/* Enable the PLL */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_POWER1);
|
||||
wm8940_write(codec, WM8940_POWER1, reg | 0x020);
|
||||
|
||||
/* Run CODEC from PLL instead of MCLK */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK);
|
||||
wm8940_write(codec, WM8940_CLOCK, reg | 0x100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8940_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 wm8940_priv *wm8940 = codec->private_data;
|
||||
|
||||
switch (freq) {
|
||||
case 11289600:
|
||||
case 12000000:
|
||||
case 12288000:
|
||||
case 16934400:
|
||||
case 18432000:
|
||||
wm8940->sysclk = freq;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
int ret = 0;
|
||||
|
||||
switch (div_id) {
|
||||
case WM8940_BCLKDIV:
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFFEF3;
|
||||
ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 2));
|
||||
break;
|
||||
case WM8940_MCLKDIV:
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFF1F;
|
||||
ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 5));
|
||||
break;
|
||||
case WM8940_OPCLKDIV:
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFCF;
|
||||
ret = wm8940_write(codec, WM8940_ADDCNTRL, reg | (div << 4));
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define WM8940_RATES SNDRV_PCM_RATE_8000_48000
|
||||
|
||||
#define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
|
||||
SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE | \
|
||||
SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
static struct snd_soc_dai_ops wm8940_dai_ops = {
|
||||
.hw_params = wm8940_i2s_hw_params,
|
||||
.set_sysclk = wm8940_set_dai_sysclk,
|
||||
.digital_mute = wm8940_mute,
|
||||
.set_fmt = wm8940_set_dai_fmt,
|
||||
.set_clkdiv = wm8940_set_dai_clkdiv,
|
||||
.set_pll = wm8940_set_dai_pll,
|
||||
};
|
||||
|
||||
struct snd_soc_dai wm8940_dai = {
|
||||
.name = "WM8940",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM8940_RATES,
|
||||
.formats = WM8940_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM8940_RATES,
|
||||
.formats = WM8940_FORMATS,
|
||||
},
|
||||
.ops = &wm8940_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8940_dai);
|
||||
|
||||
static int wm8940_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
return wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
}
|
||||
|
||||
static int wm8940_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int i;
|
||||
int ret;
|
||||
u8 data[3];
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
/* Sync reg_cache with the hardware
|
||||
* Could use auto incremented writes to speed this up
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(wm8940_reg_defaults); i++) {
|
||||
data[0] = i;
|
||||
data[1] = (cache[i] & 0xFF00) >> 8;
|
||||
data[2] = cache[i] & 0x00FF;
|
||||
ret = codec->hw_write(codec->control_data, data, 3);
|
||||
if (ret < 0)
|
||||
goto error_ret;
|
||||
else if (ret != 3) {
|
||||
ret = -EIO;
|
||||
goto error_ret;
|
||||
}
|
||||
}
|
||||
ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = wm8940_set_bias_level(codec, codec->suspend_bias_level);
|
||||
|
||||
error_ret:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_codec *wm8940_codec;
|
||||
|
||||
static int wm8940_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
if (wm8940_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = wm8940_codec;
|
||||
codec = wm8940_codec;
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
ret = snd_soc_add_controls(codec, wm8940_snd_controls,
|
||||
ARRAY_SIZE(wm8940_snd_controls));
|
||||
if (ret)
|
||||
goto error_free_pcms;
|
||||
ret = wm8940_add_widgets(codec);
|
||||
if (ret)
|
||||
goto error_free_pcms;
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto error_free_pcms;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
error_free_pcms:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm8940_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8940 = {
|
||||
.probe = wm8940_probe,
|
||||
.remove = wm8940_remove,
|
||||
.suspend = wm8940_suspend,
|
||||
.resume = wm8940_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8940);
|
||||
|
||||
static int wm8940_register(struct wm8940_priv *wm8940)
|
||||
{
|
||||
struct wm8940_setup_data *pdata = wm8940->codec.dev->platform_data;
|
||||
struct snd_soc_codec *codec = &wm8940->codec;
|
||||
int ret;
|
||||
u16 reg;
|
||||
if (wm8940_codec) {
|
||||
dev_err(codec->dev, "Another WM8940 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = wm8940;
|
||||
codec->name = "WM8940";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8940_read_reg_cache;
|
||||
codec->write = wm8940_write;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8940_set_bias_level;
|
||||
codec->dai = &wm8940_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults);
|
||||
codec->reg_cache = &wm8940->reg_cache;
|
||||
|
||||
memcpy(codec->reg_cache, wm8940_reg_defaults,
|
||||
sizeof(wm8940_reg_defaults));
|
||||
|
||||
ret = wm8940_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
wm8940_dai.dev = codec->dev;
|
||||
|
||||
wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
ret = wm8940_write(codec, WM8940_POWER1, 0x180);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!pdata)
|
||||
dev_warn(codec->dev, "No platform data supplied\n");
|
||||
else {
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL);
|
||||
ret = wm8940_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
wm8940_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8940_dai);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm8940_unregister(struct wm8940_priv *wm8940)
|
||||
{
|
||||
wm8940_set_bias_level(&wm8940->codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_unregister_dai(&wm8940_dai);
|
||||
snd_soc_unregister_codec(&wm8940->codec);
|
||||
kfree(wm8940);
|
||||
wm8940_codec = NULL;
|
||||
}
|
||||
|
||||
static int wm8940_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct wm8940_priv *wm8940;
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
wm8940 = kzalloc(sizeof *wm8940, GFP_KERNEL);
|
||||
if (wm8940 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &wm8940->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
i2c_set_clientdata(i2c, wm8940);
|
||||
codec->control_data = i2c;
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8940_register(wm8940);
|
||||
}
|
||||
|
||||
static int __devexit wm8940_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct wm8940_priv *wm8940 = i2c_get_clientdata(client);
|
||||
|
||||
wm8940_unregister(wm8940);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id wm8940_i2c_id[] = {
|
||||
{ "wm8940", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm8940_i2c_id);
|
||||
|
||||
static struct i2c_driver wm8940_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "WM8940 I2C Codec",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8940_i2c_probe,
|
||||
.remove = __devexit_p(wm8940_i2c_remove),
|
||||
.id_table = wm8940_i2c_id,
|
||||
};
|
||||
|
||||
static int __init wm8940_modinit(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_add_driver(&wm8940_i2c_driver);
|
||||
if (ret)
|
||||
printk(KERN_ERR "Failed to register WM8940 I2C driver: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
module_init(wm8940_modinit);
|
||||
|
||||
static void __exit wm8940_exit(void)
|
||||
{
|
||||
i2c_del_driver(&wm8940_i2c_driver);
|
||||
}
|
||||
module_exit(wm8940_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC WM8940 driver");
|
||||
MODULE_AUTHOR("Jonathan Cameron");
|
||||
MODULE_LICENSE("GPL");
|
104
sound/soc/codecs/wm8940.h
Normal file
104
sound/soc/codecs/wm8940.h
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* wm8940.h -- WM8940 Soc Audio driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _WM8940_H
|
||||
#define _WM8940_H
|
||||
|
||||
struct wm8940_setup_data {
|
||||
/* Vref to analogue output resistance */
|
||||
#define WM8940_VROI_1K 0
|
||||
#define WM8940_VROI_30K 1
|
||||
unsigned int vroi:1;
|
||||
};
|
||||
extern struct snd_soc_dai wm8940_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8940;
|
||||
|
||||
/* WM8940 register space */
|
||||
#define WM8940_SOFTRESET 0x00
|
||||
#define WM8940_POWER1 0x01
|
||||
#define WM8940_POWER2 0x02
|
||||
#define WM8940_POWER3 0x03
|
||||
#define WM8940_IFACE 0x04
|
||||
#define WM8940_COMPANDINGCTL 0x05
|
||||
#define WM8940_CLOCK 0x06
|
||||
#define WM8940_ADDCNTRL 0x07
|
||||
#define WM8940_GPIO 0x08
|
||||
#define WM8940_CTLINT 0x09
|
||||
#define WM8940_DAC 0x0A
|
||||
#define WM8940_DACVOL 0x0B
|
||||
|
||||
#define WM8940_ADC 0x0E
|
||||
#define WM8940_ADCVOL 0x0F
|
||||
#define WM8940_NOTCH1 0x10
|
||||
#define WM8940_NOTCH2 0x11
|
||||
#define WM8940_NOTCH3 0x12
|
||||
#define WM8940_NOTCH4 0x13
|
||||
#define WM8940_NOTCH5 0x14
|
||||
#define WM8940_NOTCH6 0x15
|
||||
#define WM8940_NOTCH7 0x16
|
||||
#define WM8940_NOTCH8 0x17
|
||||
#define WM8940_DACLIM1 0x18
|
||||
#define WM8940_DACLIM2 0x19
|
||||
|
||||
#define WM8940_ALC1 0x20
|
||||
#define WM8940_ALC2 0x21
|
||||
#define WM8940_ALC3 0x22
|
||||
#define WM8940_NOISEGATE 0x23
|
||||
#define WM8940_PLLN 0x24
|
||||
#define WM8940_PLLK1 0x25
|
||||
#define WM8940_PLLK2 0x26
|
||||
#define WM8940_PLLK3 0x27
|
||||
|
||||
#define WM8940_ALC4 0x2A
|
||||
|
||||
#define WM8940_INPUTCTL 0x2C
|
||||
#define WM8940_PGAGAIN 0x2D
|
||||
|
||||
#define WM8940_ADCBOOST 0x2F
|
||||
|
||||
#define WM8940_OUTPUTCTL 0x31
|
||||
#define WM8940_SPKMIX 0x32
|
||||
|
||||
#define WM8940_SPKVOL 0x36
|
||||
|
||||
#define WM8940_MONOMIX 0x38
|
||||
|
||||
#define WM8940_CACHEREGNUM 0x57
|
||||
|
||||
|
||||
/* Clock divider Id's */
|
||||
#define WM8940_BCLKDIV 0
|
||||
#define WM8940_MCLKDIV 1
|
||||
#define WM8940_OPCLKDIV 2
|
||||
|
||||
/* MCLK clock dividers */
|
||||
#define WM8940_MCLKDIV_1 0
|
||||
#define WM8940_MCLKDIV_1_5 1
|
||||
#define WM8940_MCLKDIV_2 2
|
||||
#define WM8940_MCLKDIV_3 3
|
||||
#define WM8940_MCLKDIV_4 4
|
||||
#define WM8940_MCLKDIV_6 5
|
||||
#define WM8940_MCLKDIV_8 6
|
||||
#define WM8940_MCLKDIV_12 7
|
||||
|
||||
/* BCLK clock dividers */
|
||||
#define WM8940_BCLKDIV_1 0
|
||||
#define WM8940_BCLKDIV_2 1
|
||||
#define WM8940_BCLKDIV_4 2
|
||||
#define WM8940_BCLKDIV_8 3
|
||||
#define WM8940_BCLKDIV_16 4
|
||||
#define WM8940_BCLKDIV_32 5
|
||||
|
||||
/* PLL Out Dividers */
|
||||
#define WM8940_OPCLKDIV_1 0
|
||||
#define WM8940_OPCLKDIV_2 1
|
||||
#define WM8940_OPCLKDIV_3 2
|
||||
#define WM8940_OPCLKDIV_4 3
|
||||
|
||||
#endif /* _WM8940_H */
|
||||
|
969
sound/soc/codecs/wm8960.c
Normal file
969
sound/soc/codecs/wm8960.c
Normal file
@ -0,0 +1,969 @@
|
||||
/*
|
||||
* wm8960.c -- WM8960 ALSA SoC Audio driver
|
||||
*
|
||||
* Author: Liam Girdwood
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "wm8960.h"
|
||||
|
||||
#define AUDIO_NAME "wm8960"
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8960;
|
||||
|
||||
/* R25 - Power 1 */
|
||||
#define WM8960_VREF 0x40
|
||||
|
||||
/* R28 - Anti-pop 1 */
|
||||
#define WM8960_POBCTRL 0x80
|
||||
#define WM8960_BUFDCOPEN 0x10
|
||||
#define WM8960_BUFIOEN 0x08
|
||||
#define WM8960_SOFT_ST 0x04
|
||||
#define WM8960_HPSTBY 0x01
|
||||
|
||||
/* R29 - Anti-pop 2 */
|
||||
#define WM8960_DISOP 0x40
|
||||
|
||||
/*
|
||||
* wm8960 register cache
|
||||
* We can't read the WM8960 register space when we are
|
||||
* using 2 wire for device control, so we cache them instead.
|
||||
*/
|
||||
static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
|
||||
0x0097, 0x0097, 0x0000, 0x0000,
|
||||
0x0000, 0x0008, 0x0000, 0x000a,
|
||||
0x01c0, 0x0000, 0x00ff, 0x00ff,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x007b, 0x0100, 0x0032,
|
||||
0x0000, 0x00c3, 0x00c3, 0x01c0,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0100, 0x0100, 0x0050, 0x0050,
|
||||
0x0050, 0x0050, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0040, 0x0000,
|
||||
0x0000, 0x0050, 0x0050, 0x0000,
|
||||
0x0002, 0x0037, 0x004d, 0x0080,
|
||||
0x0008, 0x0031, 0x0026, 0x00e9,
|
||||
};
|
||||
|
||||
struct wm8960_priv {
|
||||
u16 reg_cache[WM8960_CACHEREGNUM];
|
||||
struct snd_soc_codec codec;
|
||||
};
|
||||
|
||||
/*
|
||||
* read wm8960 register cache
|
||||
*/
|
||||
static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg == WM8960_RESET)
|
||||
return 0;
|
||||
if (reg >= WM8960_CACHEREGNUM)
|
||||
return -1;
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8960 register cache
|
||||
*/
|
||||
static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg >= WM8960_CACHEREGNUM)
|
||||
return;
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
static inline unsigned int wm8960_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return wm8960_read_reg_cache(codec, reg);
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the WM8960 register space
|
||||
*/
|
||||
static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8960 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8960_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8960_reset(c) wm8960_write(c, WM8960_RESET, 0)
|
||||
|
||||
/* enumerated controls */
|
||||
static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
|
||||
static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted",
|
||||
"Right Inverted", "Stereo Inversion"};
|
||||
static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"};
|
||||
static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"};
|
||||
static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
|
||||
static const char *wm8960_alcmode[] = {"ALC", "Limiter"};
|
||||
|
||||
static const struct soc_enum wm8960_enum[] = {
|
||||
SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph),
|
||||
SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
|
||||
SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
|
||||
SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
|
||||
SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff),
|
||||
SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc),
|
||||
SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
|
||||
};
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_snd_controls[] = {
|
||||
SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
|
||||
0, 63, 0, adc_tlv),
|
||||
SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
|
||||
6, 1, 0),
|
||||
SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
|
||||
7, 1, 0),
|
||||
|
||||
SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
|
||||
0, 255, 0, dac_tlv),
|
||||
|
||||
SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
|
||||
0, 127, 0, out_tlv),
|
||||
SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
|
||||
7, 1, 0),
|
||||
|
||||
SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
|
||||
0, 127, 0, out_tlv),
|
||||
SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
|
||||
7, 1, 0),
|
||||
SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
|
||||
SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
|
||||
|
||||
SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
|
||||
SOC_ENUM("ADC Polarity", wm8960_enum[1]),
|
||||
SOC_ENUM("Playback De-emphasis", wm8960_enum[0]),
|
||||
SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
|
||||
|
||||
SOC_ENUM("DAC Polarity", wm8960_enum[2]),
|
||||
|
||||
SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]),
|
||||
SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]),
|
||||
SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
|
||||
SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
|
||||
|
||||
SOC_ENUM("ALC Function", wm8960_enum[5]),
|
||||
SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
|
||||
SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
|
||||
SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
|
||||
SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
|
||||
SOC_ENUM("ALC Mode", wm8960_enum[6]),
|
||||
SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
|
||||
SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
|
||||
|
||||
SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
|
||||
SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),
|
||||
|
||||
SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH,
|
||||
0, 127, 0),
|
||||
|
||||
SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
|
||||
WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
|
||||
WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
|
||||
WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
|
||||
WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_lin_boost[] = {
|
||||
SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
|
||||
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_lin[] = {
|
||||
SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_rin_boost[] = {
|
||||
SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
|
||||
SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_rin[] = {
|
||||
SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
|
||||
SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
|
||||
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
|
||||
SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
|
||||
SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8960_mono_out[] = {
|
||||
SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_INPUT("LINPUT1"),
|
||||
SND_SOC_DAPM_INPUT("RINPUT1"),
|
||||
SND_SOC_DAPM_INPUT("LINPUT2"),
|
||||
SND_SOC_DAPM_INPUT("RINPUT2"),
|
||||
SND_SOC_DAPM_INPUT("LINPUT3"),
|
||||
SND_SOC_DAPM_INPUT("RINPUT3"),
|
||||
|
||||
SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
|
||||
wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
|
||||
SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
|
||||
wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
|
||||
wm8960_lin, ARRAY_SIZE(wm8960_lin)),
|
||||
SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
|
||||
wm8960_rin, ARRAY_SIZE(wm8960_rin)),
|
||||
|
||||
SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0),
|
||||
SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0),
|
||||
|
||||
SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
|
||||
SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
|
||||
&wm8960_loutput_mixer[0],
|
||||
ARRAY_SIZE(wm8960_loutput_mixer)),
|
||||
SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
|
||||
&wm8960_routput_mixer[0],
|
||||
ARRAY_SIZE(wm8960_routput_mixer)),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
|
||||
&wm8960_mono_out[0],
|
||||
ARRAY_SIZE(wm8960_mono_out)),
|
||||
|
||||
SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_OUTPUT("SPK_LP"),
|
||||
SND_SOC_DAPM_OUTPUT("SPK_LN"),
|
||||
SND_SOC_DAPM_OUTPUT("HP_L"),
|
||||
SND_SOC_DAPM_OUTPUT("HP_R"),
|
||||
SND_SOC_DAPM_OUTPUT("SPK_RP"),
|
||||
SND_SOC_DAPM_OUTPUT("SPK_RN"),
|
||||
SND_SOC_DAPM_OUTPUT("OUT3"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_paths[] = {
|
||||
{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
|
||||
{ "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
|
||||
{ "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },
|
||||
|
||||
{ "Left Input Mixer", "Boost Switch", "Left Boost Mixer", },
|
||||
{ "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */
|
||||
{ "Left Input Mixer", NULL, "LINPUT2" },
|
||||
{ "Left Input Mixer", NULL, "LINPUT3" },
|
||||
|
||||
{ "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
|
||||
{ "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
|
||||
{ "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },
|
||||
|
||||
{ "Right Input Mixer", "Boost Switch", "Right Boost Mixer", },
|
||||
{ "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */
|
||||
{ "Right Input Mixer", NULL, "RINPUT2" },
|
||||
{ "Right Input Mixer", NULL, "LINPUT3" },
|
||||
|
||||
{ "Left ADC", NULL, "Left Input Mixer" },
|
||||
{ "Right ADC", NULL, "Right Input Mixer" },
|
||||
|
||||
{ "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
|
||||
{ "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} ,
|
||||
{ "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
|
||||
|
||||
{ "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
|
||||
{ "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } ,
|
||||
{ "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
|
||||
|
||||
{ "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
|
||||
{ "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
|
||||
|
||||
{ "LOUT1 PGA", NULL, "Left Output Mixer" },
|
||||
{ "ROUT1 PGA", NULL, "Right Output Mixer" },
|
||||
|
||||
{ "HP_L", NULL, "LOUT1 PGA" },
|
||||
{ "HP_R", NULL, "ROUT1 PGA" },
|
||||
|
||||
{ "Left Speaker PGA", NULL, "Left Output Mixer" },
|
||||
{ "Right Speaker PGA", NULL, "Right Output Mixer" },
|
||||
|
||||
{ "Left Speaker Output", NULL, "Left Speaker PGA" },
|
||||
{ "Right Speaker Output", NULL, "Right Speaker PGA" },
|
||||
|
||||
{ "SPK_LN", NULL, "Left Speaker Output" },
|
||||
{ "SPK_LP", NULL, "Left Speaker Output" },
|
||||
{ "SPK_RN", NULL, "Right Speaker Output" },
|
||||
{ "SPK_RP", NULL, "Right Speaker Output" },
|
||||
|
||||
{ "OUT3", NULL, "Mono Output Mixer", }
|
||||
};
|
||||
|
||||
static int wm8960_add_widgets(struct snd_soc_codec *codec)
|
||||
{
|
||||
snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets,
|
||||
ARRAY_SIZE(wm8960_dapm_widgets));
|
||||
|
||||
snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
|
||||
|
||||
snd_soc_dapm_new_widgets(codec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 iface = 0;
|
||||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
iface |= 0x0040;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* interface format */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
iface |= 0x0002;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
iface |= 0x0001;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
iface |= 0x0003;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
iface |= 0x0013;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
iface |= 0x0090;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
iface |= 0x0080;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
iface |= 0x0010;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set iface */
|
||||
wm8960_write(codec, WM8960_IFACE1, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 iface = wm8960_read(codec, WM8960_IFACE1) & 0xfff3;
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
iface |= 0x0004;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
iface |= 0x0008;
|
||||
break;
|
||||
}
|
||||
|
||||
/* set iface */
|
||||
wm8960_write(codec, WM8960_IFACE1, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8960_read(codec, WM8960_DACCTL1) & 0xfff7;
|
||||
|
||||
if (mute)
|
||||
wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
|
||||
else
|
||||
wm8960_write(codec, WM8960_DACCTL1, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct wm8960_data *pdata = codec->dev->platform_data;
|
||||
u16 reg;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* Set VMID to 2x50k */
|
||||
reg = wm8960_read(codec, WM8960_POWER1);
|
||||
reg &= ~0x180;
|
||||
reg |= 0x80;
|
||||
wm8960_write(codec, WM8960_POWER1, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Enable anti-pop features */
|
||||
wm8960_write(codec, WM8960_APOP1,
|
||||
WM8960_POBCTRL | WM8960_SOFT_ST |
|
||||
WM8960_BUFDCOPEN | WM8960_BUFIOEN);
|
||||
|
||||
/* Discharge HP output */
|
||||
reg = WM8960_DISOP;
|
||||
if (pdata)
|
||||
reg |= pdata->dres << 4;
|
||||
wm8960_write(codec, WM8960_APOP2, reg);
|
||||
|
||||
msleep(400);
|
||||
|
||||
wm8960_write(codec, WM8960_APOP2, 0);
|
||||
|
||||
/* Enable & ramp VMID at 2x50k */
|
||||
reg = wm8960_read(codec, WM8960_POWER1);
|
||||
reg |= 0x80;
|
||||
wm8960_write(codec, WM8960_POWER1, reg);
|
||||
msleep(100);
|
||||
|
||||
/* Enable VREF */
|
||||
wm8960_write(codec, WM8960_POWER1, reg | WM8960_VREF);
|
||||
|
||||
/* Disable anti-pop features */
|
||||
wm8960_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
|
||||
}
|
||||
|
||||
/* Set VMID to 2x250k */
|
||||
reg = wm8960_read(codec, WM8960_POWER1);
|
||||
reg &= ~0x180;
|
||||
reg |= 0x100;
|
||||
wm8960_write(codec, WM8960_POWER1, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* Enable anti-pop features */
|
||||
wm8960_write(codec, WM8960_APOP1,
|
||||
WM8960_POBCTRL | WM8960_SOFT_ST |
|
||||
WM8960_BUFDCOPEN | WM8960_BUFIOEN);
|
||||
|
||||
/* Disable VMID and VREF, let them discharge */
|
||||
wm8960_write(codec, WM8960_POWER1, 0);
|
||||
msleep(600);
|
||||
|
||||
wm8960_write(codec, WM8960_APOP1, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
codec->bias_level = level;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* PLL divisors */
|
||||
struct _pll_div {
|
||||
u32 pre_div:1;
|
||||
u32 n:4;
|
||||
u32 k:24;
|
||||
};
|
||||
|
||||
/* The size in bits of the pll divide multiplied by 10
|
||||
* to allow rounding later */
|
||||
#define FIXED_PLL_SIZE ((1 << 24) * 10)
|
||||
|
||||
static int pll_factors(unsigned int source, unsigned int target,
|
||||
struct _pll_div *pll_div)
|
||||
{
|
||||
unsigned long long Kpart;
|
||||
unsigned int K, Ndiv, Nmod;
|
||||
|
||||
pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);
|
||||
|
||||
/* Scale up target to PLL operating frequency */
|
||||
target *= 4;
|
||||
|
||||
Ndiv = target / source;
|
||||
if (Ndiv < 6) {
|
||||
source >>= 1;
|
||||
pll_div->pre_div = 1;
|
||||
Ndiv = target / source;
|
||||
} else
|
||||
pll_div->pre_div = 0;
|
||||
|
||||
if ((Ndiv < 6) || (Ndiv > 12)) {
|
||||
pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pll_div->n = Ndiv;
|
||||
Nmod = target % source;
|
||||
Kpart = FIXED_PLL_SIZE * (long long)Nmod;
|
||||
|
||||
do_div(Kpart, source);
|
||||
|
||||
K = Kpart & 0xFFFFFFFF;
|
||||
|
||||
/* Check if we need to round */
|
||||
if ((K % 10) >= 5)
|
||||
K += 5;
|
||||
|
||||
/* Move down to proper range now rounding is done */
|
||||
K /= 10;
|
||||
|
||||
pll_div->k = K;
|
||||
|
||||
pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
|
||||
pll_div->n, pll_div->k, pll_div->pre_div);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai,
|
||||
int pll_id, unsigned int freq_in, unsigned int freq_out)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
static struct _pll_div pll_div;
|
||||
int ret;
|
||||
|
||||
if (freq_in && freq_out) {
|
||||
ret = pll_factors(freq_in, freq_out, &pll_div);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable the PLL: even if we are changing the frequency the
|
||||
* PLL needs to be disabled while we do so. */
|
||||
wm8960_write(codec, WM8960_CLOCK1,
|
||||
wm8960_read(codec, WM8960_CLOCK1) & ~1);
|
||||
wm8960_write(codec, WM8960_POWER2,
|
||||
wm8960_read(codec, WM8960_POWER2) & ~1);
|
||||
|
||||
if (!freq_in || !freq_out)
|
||||
return 0;
|
||||
|
||||
reg = wm8960_read(codec, WM8960_PLL1) & ~0x3f;
|
||||
reg |= pll_div.pre_div << 4;
|
||||
reg |= pll_div.n;
|
||||
|
||||
if (pll_div.k) {
|
||||
reg |= 0x20;
|
||||
|
||||
wm8960_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
|
||||
wm8960_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
|
||||
wm8960_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
|
||||
}
|
||||
wm8960_write(codec, WM8960_PLL1, reg);
|
||||
|
||||
/* Turn it on */
|
||||
wm8960_write(codec, WM8960_POWER2,
|
||||
wm8960_read(codec, WM8960_POWER2) | 1);
|
||||
msleep(250);
|
||||
wm8960_write(codec, WM8960_CLOCK1,
|
||||
wm8960_read(codec, WM8960_CLOCK1) | 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
|
||||
switch (div_id) {
|
||||
case WM8960_SYSCLKSEL:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1fe;
|
||||
wm8960_write(codec, WM8960_CLOCK1, reg | div);
|
||||
break;
|
||||
case WM8960_SYSCLKDIV:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1f9;
|
||||
wm8960_write(codec, WM8960_CLOCK1, reg | div);
|
||||
break;
|
||||
case WM8960_DACDIV:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1c7;
|
||||
wm8960_write(codec, WM8960_CLOCK1, reg | div);
|
||||
break;
|
||||
case WM8960_OPCLKDIV:
|
||||
reg = wm8960_read(codec, WM8960_PLL1) & 0x03f;
|
||||
wm8960_write(codec, WM8960_PLL1, reg | div);
|
||||
break;
|
||||
case WM8960_DCLKDIV:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK2) & 0x03f;
|
||||
wm8960_write(codec, WM8960_CLOCK2, reg | div);
|
||||
break;
|
||||
case WM8960_TOCLKSEL:
|
||||
reg = wm8960_read(codec, WM8960_ADDCTL1) & 0x1fd;
|
||||
wm8960_write(codec, WM8960_ADDCTL1, reg | div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define WM8960_RATES SNDRV_PCM_RATE_8000_48000
|
||||
|
||||
#define WM8960_FORMATS \
|
||||
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
static struct snd_soc_dai_ops wm8960_dai_ops = {
|
||||
.hw_params = wm8960_hw_params,
|
||||
.digital_mute = wm8960_mute,
|
||||
.set_fmt = wm8960_set_dai_fmt,
|
||||
.set_clkdiv = wm8960_set_dai_clkdiv,
|
||||
.set_pll = wm8960_set_dai_pll,
|
||||
};
|
||||
|
||||
struct snd_soc_dai wm8960_dai = {
|
||||
.name = "WM8960",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM8960_RATES,
|
||||
.formats = WM8960_FORMATS,},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM8960_RATES,
|
||||
.formats = WM8960_FORMATS,},
|
||||
.ops = &wm8960_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8960_dai);
|
||||
|
||||
static int wm8960_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int i;
|
||||
u8 data[2];
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
/* Sync reg_cache with the hardware */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) {
|
||||
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
|
||||
data[1] = cache[i] & 0x00ff;
|
||||
codec->hw_write(codec->control_data, data, 2);
|
||||
}
|
||||
|
||||
wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
wm8960_set_bias_level(codec, codec->suspend_bias_level);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_codec *wm8960_codec;
|
||||
|
||||
static int wm8960_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
if (wm8960_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = wm8960_codec;
|
||||
codec = wm8960_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, wm8960_snd_controls,
|
||||
ARRAY_SIZE(wm8960_snd_controls));
|
||||
wm8960_add_widgets(codec);
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int wm8960_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8960 = {
|
||||
.probe = wm8960_probe,
|
||||
.remove = wm8960_remove,
|
||||
.suspend = wm8960_suspend,
|
||||
.resume = wm8960_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960);
|
||||
|
||||
static int wm8960_register(struct wm8960_priv *wm8960)
|
||||
{
|
||||
struct wm8960_data *pdata = wm8960->codec.dev->platform_data;
|
||||
struct snd_soc_codec *codec = &wm8960->codec;
|
||||
int ret;
|
||||
u16 reg;
|
||||
|
||||
if (wm8960_codec) {
|
||||
dev_err(codec->dev, "Another WM8960 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!pdata) {
|
||||
dev_warn(codec->dev, "No platform data supplied\n");
|
||||
} else {
|
||||
if (pdata->dres > WM8960_DRES_MAX) {
|
||||
dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres);
|
||||
pdata->dres = 0;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = wm8960;
|
||||
codec->name = "WM8960";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8960_read_reg_cache;
|
||||
codec->write = wm8960_write;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8960_set_bias_level;
|
||||
codec->dai = &wm8960_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->reg_cache_size = WM8960_CACHEREGNUM;
|
||||
codec->reg_cache = &wm8960->reg_cache;
|
||||
|
||||
memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg));
|
||||
|
||||
ret = wm8960_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
wm8960_dai.dev = codec->dev;
|
||||
|
||||
wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Latch the update bits */
|
||||
reg = wm8960_read(codec, WM8960_LINVOL);
|
||||
wm8960_write(codec, WM8960_LINVOL, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_RINVOL);
|
||||
wm8960_write(codec, WM8960_RINVOL, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LADC);
|
||||
wm8960_write(codec, WM8960_LADC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_RADC);
|
||||
wm8960_write(codec, WM8960_RADC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LDAC);
|
||||
wm8960_write(codec, WM8960_LDAC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_RDAC);
|
||||
wm8960_write(codec, WM8960_RDAC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LOUT1);
|
||||
wm8960_write(codec, WM8960_LOUT1, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_ROUT1);
|
||||
wm8960_write(codec, WM8960_ROUT1, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LOUT2);
|
||||
wm8960_write(codec, WM8960_LOUT2, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_ROUT2);
|
||||
wm8960_write(codec, WM8960_ROUT2, reg | 0x100);
|
||||
|
||||
wm8960_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8960_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm8960_unregister(struct wm8960_priv *wm8960)
|
||||
{
|
||||
wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_unregister_dai(&wm8960_dai);
|
||||
snd_soc_unregister_codec(&wm8960->codec);
|
||||
kfree(wm8960);
|
||||
wm8960_codec = NULL;
|
||||
}
|
||||
|
||||
static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct wm8960_priv *wm8960;
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
|
||||
if (wm8960 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &wm8960->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8960);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8960_register(wm8960);
|
||||
}
|
||||
|
||||
static __devexit int wm8960_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct wm8960_priv *wm8960 = i2c_get_clientdata(client);
|
||||
wm8960_unregister(wm8960);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id wm8960_i2c_id[] = {
|
||||
{ "wm8960", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);
|
||||
|
||||
static struct i2c_driver wm8960_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "WM8960 I2C Codec",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8960_i2c_probe,
|
||||
.remove = __devexit_p(wm8960_i2c_remove),
|
||||
.id_table = wm8960_i2c_id,
|
||||
};
|
||||
|
||||
static int __init wm8960_modinit(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_add_driver(&wm8960_i2c_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(wm8960_modinit);
|
||||
|
||||
static void __exit wm8960_exit(void)
|
||||
{
|
||||
i2c_del_driver(&wm8960_i2c_driver);
|
||||
}
|
||||
module_exit(wm8960_exit);
|
||||
|
||||
|
||||
MODULE_DESCRIPTION("ASoC WM8960 driver");
|
||||
MODULE_AUTHOR("Liam Girdwood");
|
||||
MODULE_LICENSE("GPL");
|
127
sound/soc/codecs/wm8960.h
Normal file
127
sound/soc/codecs/wm8960.h
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* wm8960.h -- WM8960 Soc Audio driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _WM8960_H
|
||||
#define _WM8960_H
|
||||
|
||||
/* WM8960 register space */
|
||||
|
||||
|
||||
#define WM8960_CACHEREGNUM 56
|
||||
|
||||
#define WM8960_LINVOL 0x0
|
||||
#define WM8960_RINVOL 0x1
|
||||
#define WM8960_LOUT1 0x2
|
||||
#define WM8960_ROUT1 0x3
|
||||
#define WM8960_CLOCK1 0x4
|
||||
#define WM8960_DACCTL1 0x5
|
||||
#define WM8960_DACCTL2 0x6
|
||||
#define WM8960_IFACE1 0x7
|
||||
#define WM8960_CLOCK2 0x8
|
||||
#define WM8960_IFACE2 0x9
|
||||
#define WM8960_LDAC 0xa
|
||||
#define WM8960_RDAC 0xb
|
||||
|
||||
#define WM8960_RESET 0xf
|
||||
#define WM8960_3D 0x10
|
||||
#define WM8960_ALC1 0x11
|
||||
#define WM8960_ALC2 0x12
|
||||
#define WM8960_ALC3 0x13
|
||||
#define WM8960_NOISEG 0x14
|
||||
#define WM8960_LADC 0x15
|
||||
#define WM8960_RADC 0x16
|
||||
#define WM8960_ADDCTL1 0x17
|
||||
#define WM8960_ADDCTL2 0x18
|
||||
#define WM8960_POWER1 0x19
|
||||
#define WM8960_POWER2 0x1a
|
||||
#define WM8960_ADDCTL3 0x1b
|
||||
#define WM8960_APOP1 0x1c
|
||||
#define WM8960_APOP2 0x1d
|
||||
|
||||
#define WM8960_LINPATH 0x20
|
||||
#define WM8960_RINPATH 0x21
|
||||
#define WM8960_LOUTMIX 0x22
|
||||
|
||||
#define WM8960_ROUTMIX 0x25
|
||||
#define WM8960_MONOMIX1 0x26
|
||||
#define WM8960_MONOMIX2 0x27
|
||||
#define WM8960_LOUT2 0x28
|
||||
#define WM8960_ROUT2 0x29
|
||||
#define WM8960_MONO 0x2a
|
||||
#define WM8960_INBMIX1 0x2b
|
||||
#define WM8960_INBMIX2 0x2c
|
||||
#define WM8960_BYPASS1 0x2d
|
||||
#define WM8960_BYPASS2 0x2e
|
||||
#define WM8960_POWER3 0x2f
|
||||
#define WM8960_ADDCTL4 0x30
|
||||
#define WM8960_CLASSD1 0x31
|
||||
|
||||
#define WM8960_CLASSD3 0x33
|
||||
#define WM8960_PLL1 0x34
|
||||
#define WM8960_PLL2 0x35
|
||||
#define WM8960_PLL3 0x36
|
||||
#define WM8960_PLL4 0x37
|
||||
|
||||
|
||||
/*
|
||||
* WM8960 Clock dividers
|
||||
*/
|
||||
#define WM8960_SYSCLKDIV 0
|
||||
#define WM8960_DACDIV 1
|
||||
#define WM8960_OPCLKDIV 2
|
||||
#define WM8960_DCLKDIV 3
|
||||
#define WM8960_TOCLKSEL 4
|
||||
#define WM8960_SYSCLKSEL 5
|
||||
|
||||
#define WM8960_SYSCLK_DIV_1 (0 << 1)
|
||||
#define WM8960_SYSCLK_DIV_2 (2 << 1)
|
||||
|
||||
#define WM8960_SYSCLK_MCLK (0 << 0)
|
||||
#define WM8960_SYSCLK_PLL (1 << 0)
|
||||
|
||||
#define WM8960_DAC_DIV_1 (0 << 3)
|
||||
#define WM8960_DAC_DIV_1_5 (1 << 3)
|
||||
#define WM8960_DAC_DIV_2 (2 << 3)
|
||||
#define WM8960_DAC_DIV_3 (3 << 3)
|
||||
#define WM8960_DAC_DIV_4 (4 << 3)
|
||||
#define WM8960_DAC_DIV_5_5 (5 << 3)
|
||||
#define WM8960_DAC_DIV_6 (6 << 3)
|
||||
|
||||
#define WM8960_DCLK_DIV_1_5 (0 << 6)
|
||||
#define WM8960_DCLK_DIV_2 (1 << 6)
|
||||
#define WM8960_DCLK_DIV_3 (2 << 6)
|
||||
#define WM8960_DCLK_DIV_4 (3 << 6)
|
||||
#define WM8960_DCLK_DIV_6 (4 << 6)
|
||||
#define WM8960_DCLK_DIV_8 (5 << 6)
|
||||
#define WM8960_DCLK_DIV_12 (6 << 6)
|
||||
#define WM8960_DCLK_DIV_16 (7 << 6)
|
||||
|
||||
#define WM8960_TOCLK_F19 (0 << 1)
|
||||
#define WM8960_TOCLK_F21 (1 << 1)
|
||||
|
||||
#define WM8960_OPCLK_DIV_1 (0 << 0)
|
||||
#define WM8960_OPCLK_DIV_2 (1 << 0)
|
||||
#define WM8960_OPCLK_DIV_3 (2 << 0)
|
||||
#define WM8960_OPCLK_DIV_4 (3 << 0)
|
||||
#define WM8960_OPCLK_DIV_5_5 (4 << 0)
|
||||
#define WM8960_OPCLK_DIV_6 (5 << 0)
|
||||
|
||||
extern struct snd_soc_dai wm8960_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8960;
|
||||
|
||||
#define WM8960_DRES_400R 0
|
||||
#define WM8960_DRES_200R 1
|
||||
#define WM8960_DRES_600R 2
|
||||
#define WM8960_DRES_150R 3
|
||||
#define WM8960_DRES_MAX 3
|
||||
|
||||
struct wm8960_data {
|
||||
int dres;
|
||||
};
|
||||
|
||||
#endif
|
1097
sound/soc/codecs/wm8988.c
Normal file
1097
sound/soc/codecs/wm8988.c
Normal file
File diff suppressed because it is too large
Load Diff
60
sound/soc/codecs/wm8988.h
Normal file
60
sound/soc/codecs/wm8988.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2005 Openedhand Ltd.
|
||||
*
|
||||
* Author: Richard Purdie <richard@openedhand.com>
|
||||
*
|
||||
* Based on WM8753.h
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _WM8988_H
|
||||
#define _WM8988_H
|
||||
|
||||
/* WM8988 register space */
|
||||
|
||||
#define WM8988_LINVOL 0x00
|
||||
#define WM8988_RINVOL 0x01
|
||||
#define WM8988_LOUT1V 0x02
|
||||
#define WM8988_ROUT1V 0x03
|
||||
#define WM8988_ADCDAC 0x05
|
||||
#define WM8988_IFACE 0x07
|
||||
#define WM8988_SRATE 0x08
|
||||
#define WM8988_LDAC 0x0a
|
||||
#define WM8988_RDAC 0x0b
|
||||
#define WM8988_BASS 0x0c
|
||||
#define WM8988_TREBLE 0x0d
|
||||
#define WM8988_RESET 0x0f
|
||||
#define WM8988_3D 0x10
|
||||
#define WM8988_ALC1 0x11
|
||||
#define WM8988_ALC2 0x12
|
||||
#define WM8988_ALC3 0x13
|
||||
#define WM8988_NGATE 0x14
|
||||
#define WM8988_LADC 0x15
|
||||
#define WM8988_RADC 0x16
|
||||
#define WM8988_ADCTL1 0x17
|
||||
#define WM8988_ADCTL2 0x18
|
||||
#define WM8988_PWR1 0x19
|
||||
#define WM8988_PWR2 0x1a
|
||||
#define WM8988_ADCTL3 0x1b
|
||||
#define WM8988_ADCIN 0x1f
|
||||
#define WM8988_LADCIN 0x20
|
||||
#define WM8988_RADCIN 0x21
|
||||
#define WM8988_LOUTM1 0x22
|
||||
#define WM8988_LOUTM2 0x23
|
||||
#define WM8988_ROUTM1 0x24
|
||||
#define WM8988_ROUTM2 0x25
|
||||
#define WM8988_LOUT2V 0x28
|
||||
#define WM8988_ROUT2V 0x29
|
||||
#define WM8988_LPPB 0x43
|
||||
#define WM8988_NUM_REG 0x44
|
||||
|
||||
#define WM8988_SYSCLK 0
|
||||
|
||||
extern struct snd_soc_dai wm8988_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8988;
|
||||
|
||||
#endif
|
@ -998,7 +998,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target,
|
||||
|
||||
if ((Ndiv < 6) || (Ndiv > 12))
|
||||
printk(KERN_WARNING
|
||||
"WM8990 N value outwith recommended range! N = %d\n", Ndiv);
|
||||
"WM8990 N value outwith recommended range! N = %u\n", Ndiv);
|
||||
|
||||
pll_div->n = Ndiv;
|
||||
Nmod = target % source;
|
||||
|
1534
sound/soc/codecs/wm9081.c
Normal file
1534
sound/soc/codecs/wm9081.c
Normal file
File diff suppressed because it is too large
Load Diff
787
sound/soc/codecs/wm9081.h
Normal file
787
sound/soc/codecs/wm9081.h
Normal file
@ -0,0 +1,787 @@
|
||||
#ifndef WM9081_H
|
||||
#define WM9081_H
|
||||
|
||||
/*
|
||||
* wm9081.c -- WM9081 ALSA SoC Audio driver
|
||||
*
|
||||
* Author: Mark Brown
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
extern struct snd_soc_dai wm9081_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm9081;
|
||||
|
||||
/*
|
||||
* SYSCLK sources
|
||||
*/
|
||||
#define WM9081_SYSCLK_MCLK 1 /* Use MCLK without FLL */
|
||||
#define WM9081_SYSCLK_FLL_MCLK 2 /* Use MCLK, enabling FLL if required */
|
||||
|
||||
/*
|
||||
* Register values.
|
||||
*/
|
||||
#define WM9081_SOFTWARE_RESET 0x00
|
||||
#define WM9081_ANALOGUE_LINEOUT 0x02
|
||||
#define WM9081_ANALOGUE_SPEAKER_PGA 0x03
|
||||
#define WM9081_VMID_CONTROL 0x04
|
||||
#define WM9081_BIAS_CONTROL_1 0x05
|
||||
#define WM9081_ANALOGUE_MIXER 0x07
|
||||
#define WM9081_ANTI_POP_CONTROL 0x08
|
||||
#define WM9081_ANALOGUE_SPEAKER_1 0x09
|
||||
#define WM9081_ANALOGUE_SPEAKER_2 0x0A
|
||||
#define WM9081_POWER_MANAGEMENT 0x0B
|
||||
#define WM9081_CLOCK_CONTROL_1 0x0C
|
||||
#define WM9081_CLOCK_CONTROL_2 0x0D
|
||||
#define WM9081_CLOCK_CONTROL_3 0x0E
|
||||
#define WM9081_FLL_CONTROL_1 0x10
|
||||
#define WM9081_FLL_CONTROL_2 0x11
|
||||
#define WM9081_FLL_CONTROL_3 0x12
|
||||
#define WM9081_FLL_CONTROL_4 0x13
|
||||
#define WM9081_FLL_CONTROL_5 0x14
|
||||
#define WM9081_AUDIO_INTERFACE_1 0x16
|
||||
#define WM9081_AUDIO_INTERFACE_2 0x17
|
||||
#define WM9081_AUDIO_INTERFACE_3 0x18
|
||||
#define WM9081_AUDIO_INTERFACE_4 0x19
|
||||
#define WM9081_INTERRUPT_STATUS 0x1A
|
||||
#define WM9081_INTERRUPT_STATUS_MASK 0x1B
|
||||
#define WM9081_INTERRUPT_POLARITY 0x1C
|
||||
#define WM9081_INTERRUPT_CONTROL 0x1D
|
||||
#define WM9081_DAC_DIGITAL_1 0x1E
|
||||
#define WM9081_DAC_DIGITAL_2 0x1F
|
||||
#define WM9081_DRC_1 0x20
|
||||
#define WM9081_DRC_2 0x21
|
||||
#define WM9081_DRC_3 0x22
|
||||
#define WM9081_DRC_4 0x23
|
||||
#define WM9081_WRITE_SEQUENCER_1 0x26
|
||||
#define WM9081_WRITE_SEQUENCER_2 0x27
|
||||
#define WM9081_MW_SLAVE_1 0x28
|
||||
#define WM9081_EQ_1 0x2A
|
||||
#define WM9081_EQ_2 0x2B
|
||||
#define WM9081_EQ_3 0x2C
|
||||
#define WM9081_EQ_4 0x2D
|
||||
#define WM9081_EQ_5 0x2E
|
||||
#define WM9081_EQ_6 0x2F
|
||||
#define WM9081_EQ_7 0x30
|
||||
#define WM9081_EQ_8 0x31
|
||||
#define WM9081_EQ_9 0x32
|
||||
#define WM9081_EQ_10 0x33
|
||||
#define WM9081_EQ_11 0x34
|
||||
#define WM9081_EQ_12 0x35
|
||||
#define WM9081_EQ_13 0x36
|
||||
#define WM9081_EQ_14 0x37
|
||||
#define WM9081_EQ_15 0x38
|
||||
#define WM9081_EQ_16 0x39
|
||||
#define WM9081_EQ_17 0x3A
|
||||
#define WM9081_EQ_18 0x3B
|
||||
#define WM9081_EQ_19 0x3C
|
||||
#define WM9081_EQ_20 0x3D
|
||||
|
||||
#define WM9081_REGISTER_COUNT 55
|
||||
#define WM9081_MAX_REGISTER 0x3D
|
||||
|
||||
/*
|
||||
* Field Definitions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* R0 (0x00) - Software Reset
|
||||
*/
|
||||
#define WM9081_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */
|
||||
#define WM9081_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */
|
||||
#define WM9081_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */
|
||||
|
||||
/*
|
||||
* R2 (0x02) - Analogue Lineout
|
||||
*/
|
||||
#define WM9081_LINEOUT_MUTE 0x0080 /* LINEOUT_MUTE */
|
||||
#define WM9081_LINEOUT_MUTE_MASK 0x0080 /* LINEOUT_MUTE */
|
||||
#define WM9081_LINEOUT_MUTE_SHIFT 7 /* LINEOUT_MUTE */
|
||||
#define WM9081_LINEOUT_MUTE_WIDTH 1 /* LINEOUT_MUTE */
|
||||
#define WM9081_LINEOUTZC 0x0040 /* LINEOUTZC */
|
||||
#define WM9081_LINEOUTZC_MASK 0x0040 /* LINEOUTZC */
|
||||
#define WM9081_LINEOUTZC_SHIFT 6 /* LINEOUTZC */
|
||||
#define WM9081_LINEOUTZC_WIDTH 1 /* LINEOUTZC */
|
||||
#define WM9081_LINEOUT_VOL_MASK 0x003F /* LINEOUT_VOL - [5:0] */
|
||||
#define WM9081_LINEOUT_VOL_SHIFT 0 /* LINEOUT_VOL - [5:0] */
|
||||
#define WM9081_LINEOUT_VOL_WIDTH 6 /* LINEOUT_VOL - [5:0] */
|
||||
|
||||
/*
|
||||
* R3 (0x03) - Analogue Speaker PGA
|
||||
*/
|
||||
#define WM9081_SPKPGA_MUTE 0x0080 /* SPKPGA_MUTE */
|
||||
#define WM9081_SPKPGA_MUTE_MASK 0x0080 /* SPKPGA_MUTE */
|
||||
#define WM9081_SPKPGA_MUTE_SHIFT 7 /* SPKPGA_MUTE */
|
||||
#define WM9081_SPKPGA_MUTE_WIDTH 1 /* SPKPGA_MUTE */
|
||||
#define WM9081_SPKPGAZC 0x0040 /* SPKPGAZC */
|
||||
#define WM9081_SPKPGAZC_MASK 0x0040 /* SPKPGAZC */
|
||||
#define WM9081_SPKPGAZC_SHIFT 6 /* SPKPGAZC */
|
||||
#define WM9081_SPKPGAZC_WIDTH 1 /* SPKPGAZC */
|
||||
#define WM9081_SPKPGA_VOL_MASK 0x003F /* SPKPGA_VOL - [5:0] */
|
||||
#define WM9081_SPKPGA_VOL_SHIFT 0 /* SPKPGA_VOL - [5:0] */
|
||||
#define WM9081_SPKPGA_VOL_WIDTH 6 /* SPKPGA_VOL - [5:0] */
|
||||
|
||||
/*
|
||||
* R4 (0x04) - VMID Control
|
||||
*/
|
||||
#define WM9081_VMID_BUF_ENA 0x0020 /* VMID_BUF_ENA */
|
||||
#define WM9081_VMID_BUF_ENA_MASK 0x0020 /* VMID_BUF_ENA */
|
||||
#define WM9081_VMID_BUF_ENA_SHIFT 5 /* VMID_BUF_ENA */
|
||||
#define WM9081_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
|
||||
#define WM9081_VMID_RAMP 0x0008 /* VMID_RAMP */
|
||||
#define WM9081_VMID_RAMP_MASK 0x0008 /* VMID_RAMP */
|
||||
#define WM9081_VMID_RAMP_SHIFT 3 /* VMID_RAMP */
|
||||
#define WM9081_VMID_RAMP_WIDTH 1 /* VMID_RAMP */
|
||||
#define WM9081_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */
|
||||
#define WM9081_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */
|
||||
#define WM9081_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */
|
||||
#define WM9081_VMID_FAST_ST 0x0001 /* VMID_FAST_ST */
|
||||
#define WM9081_VMID_FAST_ST_MASK 0x0001 /* VMID_FAST_ST */
|
||||
#define WM9081_VMID_FAST_ST_SHIFT 0 /* VMID_FAST_ST */
|
||||
#define WM9081_VMID_FAST_ST_WIDTH 1 /* VMID_FAST_ST */
|
||||
|
||||
/*
|
||||
* R5 (0x05) - Bias Control 1
|
||||
*/
|
||||
#define WM9081_BIAS_SRC 0x0040 /* BIAS_SRC */
|
||||
#define WM9081_BIAS_SRC_MASK 0x0040 /* BIAS_SRC */
|
||||
#define WM9081_BIAS_SRC_SHIFT 6 /* BIAS_SRC */
|
||||
#define WM9081_BIAS_SRC_WIDTH 1 /* BIAS_SRC */
|
||||
#define WM9081_STBY_BIAS_LVL 0x0020 /* STBY_BIAS_LVL */
|
||||
#define WM9081_STBY_BIAS_LVL_MASK 0x0020 /* STBY_BIAS_LVL */
|
||||
#define WM9081_STBY_BIAS_LVL_SHIFT 5 /* STBY_BIAS_LVL */
|
||||
#define WM9081_STBY_BIAS_LVL_WIDTH 1 /* STBY_BIAS_LVL */
|
||||
#define WM9081_STBY_BIAS_ENA 0x0010 /* STBY_BIAS_ENA */
|
||||
#define WM9081_STBY_BIAS_ENA_MASK 0x0010 /* STBY_BIAS_ENA */
|
||||
#define WM9081_STBY_BIAS_ENA_SHIFT 4 /* STBY_BIAS_ENA */
|
||||
#define WM9081_STBY_BIAS_ENA_WIDTH 1 /* STBY_BIAS_ENA */
|
||||
#define WM9081_BIAS_LVL_MASK 0x000C /* BIAS_LVL - [3:2] */
|
||||
#define WM9081_BIAS_LVL_SHIFT 2 /* BIAS_LVL - [3:2] */
|
||||
#define WM9081_BIAS_LVL_WIDTH 2 /* BIAS_LVL - [3:2] */
|
||||
#define WM9081_BIAS_ENA 0x0002 /* BIAS_ENA */
|
||||
#define WM9081_BIAS_ENA_MASK 0x0002 /* BIAS_ENA */
|
||||
#define WM9081_BIAS_ENA_SHIFT 1 /* BIAS_ENA */
|
||||
#define WM9081_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
|
||||
#define WM9081_STARTUP_BIAS_ENA 0x0001 /* STARTUP_BIAS_ENA */
|
||||
#define WM9081_STARTUP_BIAS_ENA_MASK 0x0001 /* STARTUP_BIAS_ENA */
|
||||
#define WM9081_STARTUP_BIAS_ENA_SHIFT 0 /* STARTUP_BIAS_ENA */
|
||||
#define WM9081_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
|
||||
|
||||
/*
|
||||
* R7 (0x07) - Analogue Mixer
|
||||
*/
|
||||
#define WM9081_DAC_SEL 0x0010 /* DAC_SEL */
|
||||
#define WM9081_DAC_SEL_MASK 0x0010 /* DAC_SEL */
|
||||
#define WM9081_DAC_SEL_SHIFT 4 /* DAC_SEL */
|
||||
#define WM9081_DAC_SEL_WIDTH 1 /* DAC_SEL */
|
||||
#define WM9081_IN2_VOL 0x0008 /* IN2_VOL */
|
||||
#define WM9081_IN2_VOL_MASK 0x0008 /* IN2_VOL */
|
||||
#define WM9081_IN2_VOL_SHIFT 3 /* IN2_VOL */
|
||||
#define WM9081_IN2_VOL_WIDTH 1 /* IN2_VOL */
|
||||
#define WM9081_IN2_ENA 0x0004 /* IN2_ENA */
|
||||
#define WM9081_IN2_ENA_MASK 0x0004 /* IN2_ENA */
|
||||
#define WM9081_IN2_ENA_SHIFT 2 /* IN2_ENA */
|
||||
#define WM9081_IN2_ENA_WIDTH 1 /* IN2_ENA */
|
||||
#define WM9081_IN1_VOL 0x0002 /* IN1_VOL */
|
||||
#define WM9081_IN1_VOL_MASK 0x0002 /* IN1_VOL */
|
||||
#define WM9081_IN1_VOL_SHIFT 1 /* IN1_VOL */
|
||||
#define WM9081_IN1_VOL_WIDTH 1 /* IN1_VOL */
|
||||
#define WM9081_IN1_ENA 0x0001 /* IN1_ENA */
|
||||
#define WM9081_IN1_ENA_MASK 0x0001 /* IN1_ENA */
|
||||
#define WM9081_IN1_ENA_SHIFT 0 /* IN1_ENA */
|
||||
#define WM9081_IN1_ENA_WIDTH 1 /* IN1_ENA */
|
||||
|
||||
/*
|
||||
* R8 (0x08) - Anti Pop Control
|
||||
*/
|
||||
#define WM9081_LINEOUT_DISCH 0x0004 /* LINEOUT_DISCH */
|
||||
#define WM9081_LINEOUT_DISCH_MASK 0x0004 /* LINEOUT_DISCH */
|
||||
#define WM9081_LINEOUT_DISCH_SHIFT 2 /* LINEOUT_DISCH */
|
||||
#define WM9081_LINEOUT_DISCH_WIDTH 1 /* LINEOUT_DISCH */
|
||||
#define WM9081_LINEOUT_VROI 0x0002 /* LINEOUT_VROI */
|
||||
#define WM9081_LINEOUT_VROI_MASK 0x0002 /* LINEOUT_VROI */
|
||||
#define WM9081_LINEOUT_VROI_SHIFT 1 /* LINEOUT_VROI */
|
||||
#define WM9081_LINEOUT_VROI_WIDTH 1 /* LINEOUT_VROI */
|
||||
#define WM9081_LINEOUT_CLAMP 0x0001 /* LINEOUT_CLAMP */
|
||||
#define WM9081_LINEOUT_CLAMP_MASK 0x0001 /* LINEOUT_CLAMP */
|
||||
#define WM9081_LINEOUT_CLAMP_SHIFT 0 /* LINEOUT_CLAMP */
|
||||
#define WM9081_LINEOUT_CLAMP_WIDTH 1 /* LINEOUT_CLAMP */
|
||||
|
||||
/*
|
||||
* R9 (0x09) - Analogue Speaker 1
|
||||
*/
|
||||
#define WM9081_SPK_DCGAIN_MASK 0x0038 /* SPK_DCGAIN - [5:3] */
|
||||
#define WM9081_SPK_DCGAIN_SHIFT 3 /* SPK_DCGAIN - [5:3] */
|
||||
#define WM9081_SPK_DCGAIN_WIDTH 3 /* SPK_DCGAIN - [5:3] */
|
||||
#define WM9081_SPK_ACGAIN_MASK 0x0007 /* SPK_ACGAIN - [2:0] */
|
||||
#define WM9081_SPK_ACGAIN_SHIFT 0 /* SPK_ACGAIN - [2:0] */
|
||||
#define WM9081_SPK_ACGAIN_WIDTH 3 /* SPK_ACGAIN - [2:0] */
|
||||
|
||||
/*
|
||||
* R10 (0x0A) - Analogue Speaker 2
|
||||
*/
|
||||
#define WM9081_SPK_MODE 0x0040 /* SPK_MODE */
|
||||
#define WM9081_SPK_MODE_MASK 0x0040 /* SPK_MODE */
|
||||
#define WM9081_SPK_MODE_SHIFT 6 /* SPK_MODE */
|
||||
#define WM9081_SPK_MODE_WIDTH 1 /* SPK_MODE */
|
||||
#define WM9081_SPK_INV_MUTE 0x0010 /* SPK_INV_MUTE */
|
||||
#define WM9081_SPK_INV_MUTE_MASK 0x0010 /* SPK_INV_MUTE */
|
||||
#define WM9081_SPK_INV_MUTE_SHIFT 4 /* SPK_INV_MUTE */
|
||||
#define WM9081_SPK_INV_MUTE_WIDTH 1 /* SPK_INV_MUTE */
|
||||
#define WM9081_OUT_SPK_CTRL 0x0008 /* OUT_SPK_CTRL */
|
||||
#define WM9081_OUT_SPK_CTRL_MASK 0x0008 /* OUT_SPK_CTRL */
|
||||
#define WM9081_OUT_SPK_CTRL_SHIFT 3 /* OUT_SPK_CTRL */
|
||||
#define WM9081_OUT_SPK_CTRL_WIDTH 1 /* OUT_SPK_CTRL */
|
||||
|
||||
/*
|
||||
* R11 (0x0B) - Power Management
|
||||
*/
|
||||
#define WM9081_TSHUT_ENA 0x0100 /* TSHUT_ENA */
|
||||
#define WM9081_TSHUT_ENA_MASK 0x0100 /* TSHUT_ENA */
|
||||
#define WM9081_TSHUT_ENA_SHIFT 8 /* TSHUT_ENA */
|
||||
#define WM9081_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */
|
||||
#define WM9081_TSENSE_ENA 0x0080 /* TSENSE_ENA */
|
||||
#define WM9081_TSENSE_ENA_MASK 0x0080 /* TSENSE_ENA */
|
||||
#define WM9081_TSENSE_ENA_SHIFT 7 /* TSENSE_ENA */
|
||||
#define WM9081_TSENSE_ENA_WIDTH 1 /* TSENSE_ENA */
|
||||
#define WM9081_TEMP_SHUT 0x0040 /* TEMP_SHUT */
|
||||
#define WM9081_TEMP_SHUT_MASK 0x0040 /* TEMP_SHUT */
|
||||
#define WM9081_TEMP_SHUT_SHIFT 6 /* TEMP_SHUT */
|
||||
#define WM9081_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */
|
||||
#define WM9081_LINEOUT_ENA 0x0010 /* LINEOUT_ENA */
|
||||
#define WM9081_LINEOUT_ENA_MASK 0x0010 /* LINEOUT_ENA */
|
||||
#define WM9081_LINEOUT_ENA_SHIFT 4 /* LINEOUT_ENA */
|
||||
#define WM9081_LINEOUT_ENA_WIDTH 1 /* LINEOUT_ENA */
|
||||
#define WM9081_SPKPGA_ENA 0x0004 /* SPKPGA_ENA */
|
||||
#define WM9081_SPKPGA_ENA_MASK 0x0004 /* SPKPGA_ENA */
|
||||
#define WM9081_SPKPGA_ENA_SHIFT 2 /* SPKPGA_ENA */
|
||||
#define WM9081_SPKPGA_ENA_WIDTH 1 /* SPKPGA_ENA */
|
||||
#define WM9081_SPK_ENA 0x0002 /* SPK_ENA */
|
||||
#define WM9081_SPK_ENA_MASK 0x0002 /* SPK_ENA */
|
||||
#define WM9081_SPK_ENA_SHIFT 1 /* SPK_ENA */
|
||||
#define WM9081_SPK_ENA_WIDTH 1 /* SPK_ENA */
|
||||
#define WM9081_DAC_ENA 0x0001 /* DAC_ENA */
|
||||
#define WM9081_DAC_ENA_MASK 0x0001 /* DAC_ENA */
|
||||
#define WM9081_DAC_ENA_SHIFT 0 /* DAC_ENA */
|
||||
#define WM9081_DAC_ENA_WIDTH 1 /* DAC_ENA */
|
||||
|
||||
/*
|
||||
* R12 (0x0C) - Clock Control 1
|
||||
*/
|
||||
#define WM9081_CLK_OP_DIV_MASK 0x1C00 /* CLK_OP_DIV - [12:10] */
|
||||
#define WM9081_CLK_OP_DIV_SHIFT 10 /* CLK_OP_DIV - [12:10] */
|
||||
#define WM9081_CLK_OP_DIV_WIDTH 3 /* CLK_OP_DIV - [12:10] */
|
||||
#define WM9081_CLK_TO_DIV_MASK 0x0300 /* CLK_TO_DIV - [9:8] */
|
||||
#define WM9081_CLK_TO_DIV_SHIFT 8 /* CLK_TO_DIV - [9:8] */
|
||||
#define WM9081_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [9:8] */
|
||||
#define WM9081_MCLKDIV2 0x0080 /* MCLKDIV2 */
|
||||
#define WM9081_MCLKDIV2_MASK 0x0080 /* MCLKDIV2 */
|
||||
#define WM9081_MCLKDIV2_SHIFT 7 /* MCLKDIV2 */
|
||||
#define WM9081_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
|
||||
|
||||
/*
|
||||
* R13 (0x0D) - Clock Control 2
|
||||
*/
|
||||
#define WM9081_CLK_SYS_RATE_MASK 0x00F0 /* CLK_SYS_RATE - [7:4] */
|
||||
#define WM9081_CLK_SYS_RATE_SHIFT 4 /* CLK_SYS_RATE - [7:4] */
|
||||
#define WM9081_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [7:4] */
|
||||
#define WM9081_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */
|
||||
#define WM9081_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */
|
||||
#define WM9081_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */
|
||||
|
||||
/*
|
||||
* R14 (0x0E) - Clock Control 3
|
||||
*/
|
||||
#define WM9081_CLK_SRC_SEL 0x2000 /* CLK_SRC_SEL */
|
||||
#define WM9081_CLK_SRC_SEL_MASK 0x2000 /* CLK_SRC_SEL */
|
||||
#define WM9081_CLK_SRC_SEL_SHIFT 13 /* CLK_SRC_SEL */
|
||||
#define WM9081_CLK_SRC_SEL_WIDTH 1 /* CLK_SRC_SEL */
|
||||
#define WM9081_CLK_OP_ENA 0x0020 /* CLK_OP_ENA */
|
||||
#define WM9081_CLK_OP_ENA_MASK 0x0020 /* CLK_OP_ENA */
|
||||
#define WM9081_CLK_OP_ENA_SHIFT 5 /* CLK_OP_ENA */
|
||||
#define WM9081_CLK_OP_ENA_WIDTH 1 /* CLK_OP_ENA */
|
||||
#define WM9081_CLK_TO_ENA 0x0004 /* CLK_TO_ENA */
|
||||
#define WM9081_CLK_TO_ENA_MASK 0x0004 /* CLK_TO_ENA */
|
||||
#define WM9081_CLK_TO_ENA_SHIFT 2 /* CLK_TO_ENA */
|
||||
#define WM9081_CLK_TO_ENA_WIDTH 1 /* CLK_TO_ENA */
|
||||
#define WM9081_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */
|
||||
#define WM9081_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */
|
||||
#define WM9081_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */
|
||||
#define WM9081_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
|
||||
#define WM9081_CLK_SYS_ENA 0x0001 /* CLK_SYS_ENA */
|
||||
#define WM9081_CLK_SYS_ENA_MASK 0x0001 /* CLK_SYS_ENA */
|
||||
#define WM9081_CLK_SYS_ENA_SHIFT 0 /* CLK_SYS_ENA */
|
||||
#define WM9081_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
|
||||
|
||||
/*
|
||||
* R16 (0x10) - FLL Control 1
|
||||
*/
|
||||
#define WM9081_FLL_HOLD 0x0008 /* FLL_HOLD */
|
||||
#define WM9081_FLL_HOLD_MASK 0x0008 /* FLL_HOLD */
|
||||
#define WM9081_FLL_HOLD_SHIFT 3 /* FLL_HOLD */
|
||||
#define WM9081_FLL_HOLD_WIDTH 1 /* FLL_HOLD */
|
||||
#define WM9081_FLL_FRAC 0x0004 /* FLL_FRAC */
|
||||
#define WM9081_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */
|
||||
#define WM9081_FLL_FRAC_SHIFT 2 /* FLL_FRAC */
|
||||
#define WM9081_FLL_FRAC_WIDTH 1 /* FLL_FRAC */
|
||||
#define WM9081_FLL_ENA 0x0001 /* FLL_ENA */
|
||||
#define WM9081_FLL_ENA_MASK 0x0001 /* FLL_ENA */
|
||||
#define WM9081_FLL_ENA_SHIFT 0 /* FLL_ENA */
|
||||
#define WM9081_FLL_ENA_WIDTH 1 /* FLL_ENA */
|
||||
|
||||
/*
|
||||
* R17 (0x11) - FLL Control 2
|
||||
*/
|
||||
#define WM9081_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */
|
||||
#define WM9081_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */
|
||||
#define WM9081_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */
|
||||
#define WM9081_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */
|
||||
#define WM9081_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */
|
||||
#define WM9081_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */
|
||||
#define WM9081_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */
|
||||
#define WM9081_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */
|
||||
#define WM9081_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */
|
||||
|
||||
/*
|
||||
* R18 (0x12) - FLL Control 3
|
||||
*/
|
||||
#define WM9081_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */
|
||||
#define WM9081_FLL_K_SHIFT 0 /* FLL_K - [15:0] */
|
||||
#define WM9081_FLL_K_WIDTH 16 /* FLL_K - [15:0] */
|
||||
|
||||
/*
|
||||
* R19 (0x13) - FLL Control 4
|
||||
*/
|
||||
#define WM9081_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */
|
||||
#define WM9081_FLL_N_SHIFT 5 /* FLL_N - [14:5] */
|
||||
#define WM9081_FLL_N_WIDTH 10 /* FLL_N - [14:5] */
|
||||
#define WM9081_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */
|
||||
#define WM9081_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */
|
||||
#define WM9081_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */
|
||||
|
||||
/*
|
||||
* R20 (0x14) - FLL Control 5
|
||||
*/
|
||||
#define WM9081_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */
|
||||
#define WM9081_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */
|
||||
#define WM9081_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */
|
||||
#define WM9081_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */
|
||||
#define WM9081_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */
|
||||
#define WM9081_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */
|
||||
|
||||
/*
|
||||
* R22 (0x16) - Audio Interface 1
|
||||
*/
|
||||
#define WM9081_AIFDAC_CHAN 0x0040 /* AIFDAC_CHAN */
|
||||
#define WM9081_AIFDAC_CHAN_MASK 0x0040 /* AIFDAC_CHAN */
|
||||
#define WM9081_AIFDAC_CHAN_SHIFT 6 /* AIFDAC_CHAN */
|
||||
#define WM9081_AIFDAC_CHAN_WIDTH 1 /* AIFDAC_CHAN */
|
||||
#define WM9081_AIFDAC_TDM_SLOT_MASK 0x0030 /* AIFDAC_TDM_SLOT - [5:4] */
|
||||
#define WM9081_AIFDAC_TDM_SLOT_SHIFT 4 /* AIFDAC_TDM_SLOT - [5:4] */
|
||||
#define WM9081_AIFDAC_TDM_SLOT_WIDTH 2 /* AIFDAC_TDM_SLOT - [5:4] */
|
||||
#define WM9081_AIFDAC_TDM_MODE_MASK 0x000C /* AIFDAC_TDM_MODE - [3:2] */
|
||||
#define WM9081_AIFDAC_TDM_MODE_SHIFT 2 /* AIFDAC_TDM_MODE - [3:2] */
|
||||
#define WM9081_AIFDAC_TDM_MODE_WIDTH 2 /* AIFDAC_TDM_MODE - [3:2] */
|
||||
#define WM9081_DAC_COMP 0x0002 /* DAC_COMP */
|
||||
#define WM9081_DAC_COMP_MASK 0x0002 /* DAC_COMP */
|
||||
#define WM9081_DAC_COMP_SHIFT 1 /* DAC_COMP */
|
||||
#define WM9081_DAC_COMP_WIDTH 1 /* DAC_COMP */
|
||||
#define WM9081_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */
|
||||
#define WM9081_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */
|
||||
#define WM9081_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */
|
||||
#define WM9081_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
|
||||
|
||||
/*
|
||||
* R23 (0x17) - Audio Interface 2
|
||||
*/
|
||||
#define WM9081_AIF_TRIS 0x0200 /* AIF_TRIS */
|
||||
#define WM9081_AIF_TRIS_MASK 0x0200 /* AIF_TRIS */
|
||||
#define WM9081_AIF_TRIS_SHIFT 9 /* AIF_TRIS */
|
||||
#define WM9081_AIF_TRIS_WIDTH 1 /* AIF_TRIS */
|
||||
#define WM9081_DAC_DAT_INV 0x0100 /* DAC_DAT_INV */
|
||||
#define WM9081_DAC_DAT_INV_MASK 0x0100 /* DAC_DAT_INV */
|
||||
#define WM9081_DAC_DAT_INV_SHIFT 8 /* DAC_DAT_INV */
|
||||
#define WM9081_DAC_DAT_INV_WIDTH 1 /* DAC_DAT_INV */
|
||||
#define WM9081_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */
|
||||
#define WM9081_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */
|
||||
#define WM9081_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */
|
||||
#define WM9081_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
|
||||
#define WM9081_BCLK_DIR 0x0040 /* BCLK_DIR */
|
||||
#define WM9081_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */
|
||||
#define WM9081_BCLK_DIR_SHIFT 6 /* BCLK_DIR */
|
||||
#define WM9081_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
|
||||
#define WM9081_LRCLK_DIR 0x0020 /* LRCLK_DIR */
|
||||
#define WM9081_LRCLK_DIR_MASK 0x0020 /* LRCLK_DIR */
|
||||
#define WM9081_LRCLK_DIR_SHIFT 5 /* LRCLK_DIR */
|
||||
#define WM9081_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
|
||||
#define WM9081_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */
|
||||
#define WM9081_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */
|
||||
#define WM9081_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */
|
||||
#define WM9081_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
|
||||
#define WM9081_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */
|
||||
#define WM9081_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */
|
||||
#define WM9081_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */
|
||||
#define WM9081_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */
|
||||
#define WM9081_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */
|
||||
#define WM9081_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */
|
||||
|
||||
/*
|
||||
* R24 (0x18) - Audio Interface 3
|
||||
*/
|
||||
#define WM9081_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */
|
||||
#define WM9081_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */
|
||||
#define WM9081_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */
|
||||
|
||||
/*
|
||||
* R25 (0x19) - Audio Interface 4
|
||||
*/
|
||||
#define WM9081_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
|
||||
#define WM9081_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
|
||||
#define WM9081_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
|
||||
|
||||
/*
|
||||
* R26 (0x1A) - Interrupt Status
|
||||
*/
|
||||
#define WM9081_WSEQ_BUSY_EINT 0x0004 /* WSEQ_BUSY_EINT */
|
||||
#define WM9081_WSEQ_BUSY_EINT_MASK 0x0004 /* WSEQ_BUSY_EINT */
|
||||
#define WM9081_WSEQ_BUSY_EINT_SHIFT 2 /* WSEQ_BUSY_EINT */
|
||||
#define WM9081_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */
|
||||
#define WM9081_TSHUT_EINT 0x0001 /* TSHUT_EINT */
|
||||
#define WM9081_TSHUT_EINT_MASK 0x0001 /* TSHUT_EINT */
|
||||
#define WM9081_TSHUT_EINT_SHIFT 0 /* TSHUT_EINT */
|
||||
#define WM9081_TSHUT_EINT_WIDTH 1 /* TSHUT_EINT */
|
||||
|
||||
/*
|
||||
* R27 (0x1B) - Interrupt Status Mask
|
||||
*/
|
||||
#define WM9081_IM_WSEQ_BUSY_EINT 0x0004 /* IM_WSEQ_BUSY_EINT */
|
||||
#define WM9081_IM_WSEQ_BUSY_EINT_MASK 0x0004 /* IM_WSEQ_BUSY_EINT */
|
||||
#define WM9081_IM_WSEQ_BUSY_EINT_SHIFT 2 /* IM_WSEQ_BUSY_EINT */
|
||||
#define WM9081_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */
|
||||
#define WM9081_IM_TSHUT_EINT 0x0001 /* IM_TSHUT_EINT */
|
||||
#define WM9081_IM_TSHUT_EINT_MASK 0x0001 /* IM_TSHUT_EINT */
|
||||
#define WM9081_IM_TSHUT_EINT_SHIFT 0 /* IM_TSHUT_EINT */
|
||||
#define WM9081_IM_TSHUT_EINT_WIDTH 1 /* IM_TSHUT_EINT */
|
||||
|
||||
/*
|
||||
* R28 (0x1C) - Interrupt Polarity
|
||||
*/
|
||||
#define WM9081_TSHUT_INV 0x0001 /* TSHUT_INV */
|
||||
#define WM9081_TSHUT_INV_MASK 0x0001 /* TSHUT_INV */
|
||||
#define WM9081_TSHUT_INV_SHIFT 0 /* TSHUT_INV */
|
||||
#define WM9081_TSHUT_INV_WIDTH 1 /* TSHUT_INV */
|
||||
|
||||
/*
|
||||
* R29 (0x1D) - Interrupt Control
|
||||
*/
|
||||
#define WM9081_IRQ_POL 0x8000 /* IRQ_POL */
|
||||
#define WM9081_IRQ_POL_MASK 0x8000 /* IRQ_POL */
|
||||
#define WM9081_IRQ_POL_SHIFT 15 /* IRQ_POL */
|
||||
#define WM9081_IRQ_POL_WIDTH 1 /* IRQ_POL */
|
||||
#define WM9081_IRQ_OP_CTRL 0x0001 /* IRQ_OP_CTRL */
|
||||
#define WM9081_IRQ_OP_CTRL_MASK 0x0001 /* IRQ_OP_CTRL */
|
||||
#define WM9081_IRQ_OP_CTRL_SHIFT 0 /* IRQ_OP_CTRL */
|
||||
#define WM9081_IRQ_OP_CTRL_WIDTH 1 /* IRQ_OP_CTRL */
|
||||
|
||||
/*
|
||||
* R30 (0x1E) - DAC Digital 1
|
||||
*/
|
||||
#define WM9081_DAC_VOL_MASK 0x00FF /* DAC_VOL - [7:0] */
|
||||
#define WM9081_DAC_VOL_SHIFT 0 /* DAC_VOL - [7:0] */
|
||||
#define WM9081_DAC_VOL_WIDTH 8 /* DAC_VOL - [7:0] */
|
||||
|
||||
/*
|
||||
* R31 (0x1F) - DAC Digital 2
|
||||
*/
|
||||
#define WM9081_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */
|
||||
#define WM9081_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */
|
||||
#define WM9081_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */
|
||||
#define WM9081_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
|
||||
#define WM9081_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */
|
||||
#define WM9081_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */
|
||||
#define WM9081_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */
|
||||
#define WM9081_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */
|
||||
#define WM9081_DAC_MUTE 0x0008 /* DAC_MUTE */
|
||||
#define WM9081_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
|
||||
#define WM9081_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
|
||||
#define WM9081_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
|
||||
#define WM9081_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
|
||||
#define WM9081_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
|
||||
#define WM9081_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
|
||||
|
||||
/*
|
||||
* R32 (0x20) - DRC 1
|
||||
*/
|
||||
#define WM9081_DRC_ENA 0x8000 /* DRC_ENA */
|
||||
#define WM9081_DRC_ENA_MASK 0x8000 /* DRC_ENA */
|
||||
#define WM9081_DRC_ENA_SHIFT 15 /* DRC_ENA */
|
||||
#define WM9081_DRC_ENA_WIDTH 1 /* DRC_ENA */
|
||||
#define WM9081_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */
|
||||
#define WM9081_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */
|
||||
#define WM9081_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */
|
||||
#define WM9081_DRC_FF_DLY 0x0020 /* DRC_FF_DLY */
|
||||
#define WM9081_DRC_FF_DLY_MASK 0x0020 /* DRC_FF_DLY */
|
||||
#define WM9081_DRC_FF_DLY_SHIFT 5 /* DRC_FF_DLY */
|
||||
#define WM9081_DRC_FF_DLY_WIDTH 1 /* DRC_FF_DLY */
|
||||
#define WM9081_DRC_QR 0x0004 /* DRC_QR */
|
||||
#define WM9081_DRC_QR_MASK 0x0004 /* DRC_QR */
|
||||
#define WM9081_DRC_QR_SHIFT 2 /* DRC_QR */
|
||||
#define WM9081_DRC_QR_WIDTH 1 /* DRC_QR */
|
||||
#define WM9081_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */
|
||||
#define WM9081_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */
|
||||
#define WM9081_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */
|
||||
#define WM9081_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */
|
||||
|
||||
/*
|
||||
* R33 (0x21) - DRC 2
|
||||
*/
|
||||
#define WM9081_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */
|
||||
#define WM9081_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */
|
||||
#define WM9081_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */
|
||||
#define WM9081_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */
|
||||
#define WM9081_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */
|
||||
#define WM9081_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */
|
||||
#define WM9081_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */
|
||||
#define WM9081_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */
|
||||
#define WM9081_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */
|
||||
#define WM9081_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */
|
||||
#define WM9081_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */
|
||||
#define WM9081_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */
|
||||
#define WM9081_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
|
||||
#define WM9081_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
|
||||
#define WM9081_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
|
||||
#define WM9081_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
|
||||
#define WM9081_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
|
||||
#define WM9081_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
|
||||
|
||||
/*
|
||||
* R34 (0x22) - DRC 3
|
||||
*/
|
||||
#define WM9081_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */
|
||||
#define WM9081_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */
|
||||
#define WM9081_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */
|
||||
#define WM9081_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */
|
||||
#define WM9081_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */
|
||||
#define WM9081_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */
|
||||
|
||||
/*
|
||||
* R35 (0x23) - DRC 4
|
||||
*/
|
||||
#define WM9081_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */
|
||||
#define WM9081_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */
|
||||
#define WM9081_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */
|
||||
#define WM9081_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */
|
||||
#define WM9081_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */
|
||||
#define WM9081_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */
|
||||
|
||||
/*
|
||||
* R38 (0x26) - Write Sequencer 1
|
||||
*/
|
||||
#define WM9081_WSEQ_ENA 0x8000 /* WSEQ_ENA */
|
||||
#define WM9081_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */
|
||||
#define WM9081_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */
|
||||
#define WM9081_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
|
||||
#define WM9081_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
|
||||
#define WM9081_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
|
||||
#define WM9081_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
|
||||
#define WM9081_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
|
||||
#define WM9081_WSEQ_START 0x0100 /* WSEQ_START */
|
||||
#define WM9081_WSEQ_START_MASK 0x0100 /* WSEQ_START */
|
||||
#define WM9081_WSEQ_START_SHIFT 8 /* WSEQ_START */
|
||||
#define WM9081_WSEQ_START_WIDTH 1 /* WSEQ_START */
|
||||
#define WM9081_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */
|
||||
#define WM9081_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */
|
||||
#define WM9081_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */
|
||||
|
||||
/*
|
||||
* R39 (0x27) - Write Sequencer 2
|
||||
*/
|
||||
#define WM9081_WSEQ_CURRENT_INDEX_MASK 0x07F0 /* WSEQ_CURRENT_INDEX - [10:4] */
|
||||
#define WM9081_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [10:4] */
|
||||
#define WM9081_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [10:4] */
|
||||
#define WM9081_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
|
||||
#define WM9081_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
|
||||
#define WM9081_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
|
||||
#define WM9081_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
|
||||
|
||||
/*
|
||||
* R40 (0x28) - MW Slave 1
|
||||
*/
|
||||
#define WM9081_SPI_CFG 0x0020 /* SPI_CFG */
|
||||
#define WM9081_SPI_CFG_MASK 0x0020 /* SPI_CFG */
|
||||
#define WM9081_SPI_CFG_SHIFT 5 /* SPI_CFG */
|
||||
#define WM9081_SPI_CFG_WIDTH 1 /* SPI_CFG */
|
||||
#define WM9081_SPI_4WIRE 0x0010 /* SPI_4WIRE */
|
||||
#define WM9081_SPI_4WIRE_MASK 0x0010 /* SPI_4WIRE */
|
||||
#define WM9081_SPI_4WIRE_SHIFT 4 /* SPI_4WIRE */
|
||||
#define WM9081_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */
|
||||
#define WM9081_ARA_ENA 0x0008 /* ARA_ENA */
|
||||
#define WM9081_ARA_ENA_MASK 0x0008 /* ARA_ENA */
|
||||
#define WM9081_ARA_ENA_SHIFT 3 /* ARA_ENA */
|
||||
#define WM9081_ARA_ENA_WIDTH 1 /* ARA_ENA */
|
||||
#define WM9081_AUTO_INC 0x0002 /* AUTO_INC */
|
||||
#define WM9081_AUTO_INC_MASK 0x0002 /* AUTO_INC */
|
||||
#define WM9081_AUTO_INC_SHIFT 1 /* AUTO_INC */
|
||||
#define WM9081_AUTO_INC_WIDTH 1 /* AUTO_INC */
|
||||
|
||||
/*
|
||||
* R42 (0x2A) - EQ 1
|
||||
*/
|
||||
#define WM9081_EQ_B1_GAIN_MASK 0xF800 /* EQ_B1_GAIN - [15:11] */
|
||||
#define WM9081_EQ_B1_GAIN_SHIFT 11 /* EQ_B1_GAIN - [15:11] */
|
||||
#define WM9081_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [15:11] */
|
||||
#define WM9081_EQ_B2_GAIN_MASK 0x07C0 /* EQ_B2_GAIN - [10:6] */
|
||||
#define WM9081_EQ_B2_GAIN_SHIFT 6 /* EQ_B2_GAIN - [10:6] */
|
||||
#define WM9081_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [10:6] */
|
||||
#define WM9081_EQ_B4_GAIN_MASK 0x003E /* EQ_B4_GAIN - [5:1] */
|
||||
#define WM9081_EQ_B4_GAIN_SHIFT 1 /* EQ_B4_GAIN - [5:1] */
|
||||
#define WM9081_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [5:1] */
|
||||
#define WM9081_EQ_ENA 0x0001 /* EQ_ENA */
|
||||
#define WM9081_EQ_ENA_MASK 0x0001 /* EQ_ENA */
|
||||
#define WM9081_EQ_ENA_SHIFT 0 /* EQ_ENA */
|
||||
#define WM9081_EQ_ENA_WIDTH 1 /* EQ_ENA */
|
||||
|
||||
/*
|
||||
* R43 (0x2B) - EQ 2
|
||||
*/
|
||||
#define WM9081_EQ_B3_GAIN_MASK 0xF800 /* EQ_B3_GAIN - [15:11] */
|
||||
#define WM9081_EQ_B3_GAIN_SHIFT 11 /* EQ_B3_GAIN - [15:11] */
|
||||
#define WM9081_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [15:11] */
|
||||
#define WM9081_EQ_B5_GAIN_MASK 0x07C0 /* EQ_B5_GAIN - [10:6] */
|
||||
#define WM9081_EQ_B5_GAIN_SHIFT 6 /* EQ_B5_GAIN - [10:6] */
|
||||
#define WM9081_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [10:6] */
|
||||
|
||||
/*
|
||||
* R44 (0x2C) - EQ 3
|
||||
*/
|
||||
#define WM9081_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */
|
||||
#define WM9081_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */
|
||||
#define WM9081_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */
|
||||
|
||||
/*
|
||||
* R45 (0x2D) - EQ 4
|
||||
*/
|
||||
#define WM9081_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */
|
||||
#define WM9081_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */
|
||||
#define WM9081_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */
|
||||
|
||||
/*
|
||||
* R46 (0x2E) - EQ 5
|
||||
*/
|
||||
#define WM9081_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */
|
||||
#define WM9081_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */
|
||||
#define WM9081_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */
|
||||
|
||||
/*
|
||||
* R47 (0x2F) - EQ 6
|
||||
*/
|
||||
#define WM9081_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */
|
||||
#define WM9081_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */
|
||||
#define WM9081_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */
|
||||
|
||||
/*
|
||||
* R48 (0x30) - EQ 7
|
||||
*/
|
||||
#define WM9081_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */
|
||||
#define WM9081_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */
|
||||
#define WM9081_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */
|
||||
|
||||
/*
|
||||
* R49 (0x31) - EQ 8
|
||||
*/
|
||||
#define WM9081_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */
|
||||
#define WM9081_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */
|
||||
#define WM9081_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */
|
||||
|
||||
/*
|
||||
* R50 (0x32) - EQ 9
|
||||
*/
|
||||
#define WM9081_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */
|
||||
#define WM9081_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */
|
||||
#define WM9081_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */
|
||||
|
||||
/*
|
||||
* R51 (0x33) - EQ 10
|
||||
*/
|
||||
#define WM9081_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */
|
||||
#define WM9081_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */
|
||||
#define WM9081_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */
|
||||
|
||||
/*
|
||||
* R52 (0x34) - EQ 11
|
||||
*/
|
||||
#define WM9081_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */
|
||||
#define WM9081_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */
|
||||
#define WM9081_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */
|
||||
|
||||
/*
|
||||
* R53 (0x35) - EQ 12
|
||||
*/
|
||||
#define WM9081_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */
|
||||
#define WM9081_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */
|
||||
#define WM9081_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */
|
||||
|
||||
/*
|
||||
* R54 (0x36) - EQ 13
|
||||
*/
|
||||
#define WM9081_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */
|
||||
#define WM9081_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */
|
||||
#define WM9081_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */
|
||||
|
||||
/*
|
||||
* R55 (0x37) - EQ 14
|
||||
*/
|
||||
#define WM9081_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */
|
||||
#define WM9081_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */
|
||||
#define WM9081_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */
|
||||
|
||||
/*
|
||||
* R56 (0x38) - EQ 15
|
||||
*/
|
||||
#define WM9081_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */
|
||||
#define WM9081_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */
|
||||
#define WM9081_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */
|
||||
|
||||
/*
|
||||
* R57 (0x39) - EQ 16
|
||||
*/
|
||||
#define WM9081_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */
|
||||
#define WM9081_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */
|
||||
#define WM9081_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */
|
||||
|
||||
/*
|
||||
* R58 (0x3A) - EQ 17
|
||||
*/
|
||||
#define WM9081_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */
|
||||
#define WM9081_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */
|
||||
#define WM9081_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */
|
||||
|
||||
/*
|
||||
* R59 (0x3B) - EQ 18
|
||||
*/
|
||||
#define WM9081_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */
|
||||
#define WM9081_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */
|
||||
#define WM9081_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */
|
||||
|
||||
/*
|
||||
* R60 (0x3C) - EQ 19
|
||||
*/
|
||||
#define WM9081_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */
|
||||
#define WM9081_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */
|
||||
#define WM9081_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */
|
||||
|
||||
/*
|
||||
* R61 (0x3D) - EQ 20
|
||||
*/
|
||||
#define WM9081_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */
|
||||
#define WM9081_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */
|
||||
#define WM9081_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */
|
||||
|
||||
|
||||
#endif
|
@ -282,14 +282,14 @@ struct snd_soc_dai wm9705_dai[] = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM9705_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.formats = SND_SOC_STD_AC97_FMTS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "HiFi Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM9705_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.formats = SND_SOC_STD_AC97_FMTS,
|
||||
},
|
||||
.ops = &wm9705_dai_ops,
|
||||
},
|
||||
|
@ -534,13 +534,13 @@ struct snd_soc_dai wm9712_dai[] = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM9712_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.capture = {
|
||||
.stream_name = "HiFi Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM9712_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.ops = &wm9712_dai_ops_hifi,
|
||||
},
|
||||
{
|
||||
@ -550,7 +550,7 @@ struct snd_soc_dai wm9712_dai[] = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.rates = WM9712_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.ops = &wm9712_dai_ops_aux,
|
||||
}
|
||||
};
|
||||
@ -585,6 +585,8 @@ static int wm9712_reset(struct snd_soc_codec *codec, int try_warm)
|
||||
}
|
||||
|
||||
soc_ac97_ops.reset(codec->ac97);
|
||||
if (soc_ac97_ops.warm_reset)
|
||||
soc_ac97_ops.warm_reset(codec->ac97);
|
||||
if (ac97_read(codec, 0) != wm9712_reg[0])
|
||||
goto err;
|
||||
return 0;
|
||||
|
@ -189,6 +189,26 @@ SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
|
||||
SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
|
||||
};
|
||||
|
||||
static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
u16 status, rate;
|
||||
|
||||
BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
|
||||
|
||||
/* Gracefully shut down the voice interface. */
|
||||
status = ac97_read(codec, AC97_EXTENDED_MID) | 0x1000;
|
||||
rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF;
|
||||
ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200);
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(1));
|
||||
ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00);
|
||||
ac97_write(codec, AC97_EXTENDED_MID, status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* We have to create a fake left and right HP mixers because
|
||||
* the codec only has a single control that is shared by both channels.
|
||||
* This makes it impossible to determine the audio path using the current
|
||||
@ -400,7 +420,8 @@ SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1),
|
||||
SND_SOC_DAPM_DAC_E("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1,
|
||||
wm9713_voice_shutdown, SND_SOC_DAPM_PRE_PMD),
|
||||
SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1),
|
||||
SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0),
|
||||
@ -689,7 +710,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int source)
|
||||
Ndiv = target / source;
|
||||
if ((Ndiv < 5) || (Ndiv > 12))
|
||||
printk(KERN_WARNING
|
||||
"WM9713 PLL N value %d out of recommended range!\n",
|
||||
"WM9713 PLL N value %u out of recommended range!\n",
|
||||
Ndiv);
|
||||
|
||||
pll_div->n = Ndiv;
|
||||
@ -936,21 +957,6 @@ static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm9713_voiceshutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 status, rate;
|
||||
|
||||
/* Gracefully shut down the voice interface. */
|
||||
status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000;
|
||||
rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF;
|
||||
ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200);
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(1));
|
||||
ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00);
|
||||
ac97_write(codec, AC97_EXTENDED_MID, status);
|
||||
}
|
||||
|
||||
static int ac97_hifi_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
@ -1019,7 +1025,6 @@ static struct snd_soc_dai_ops wm9713_dai_ops_aux = {
|
||||
|
||||
static struct snd_soc_dai_ops wm9713_dai_ops_voice = {
|
||||
.hw_params = wm9713_pcm_hw_params,
|
||||
.shutdown = wm9713_voiceshutdown,
|
||||
.set_clkdiv = wm9713_set_dai_clkdiv,
|
||||
.set_pll = wm9713_set_dai_pll,
|
||||
.set_fmt = wm9713_set_dai_fmt,
|
||||
@ -1035,13 +1040,13 @@ struct snd_soc_dai wm9713_dai[] = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM9713_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.capture = {
|
||||
.stream_name = "HiFi Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = WM9713_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.ops = &wm9713_dai_ops_hifi,
|
||||
},
|
||||
{
|
||||
@ -1051,7 +1056,7 @@ struct snd_soc_dai wm9713_dai[] = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.rates = WM9713_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.formats = SND_SOC_STD_AC97_FMTS,},
|
||||
.ops = &wm9713_dai_ops_aux,
|
||||
},
|
||||
{
|
||||
@ -1069,6 +1074,7 @@ struct snd_soc_dai wm9713_dai[] = {
|
||||
.rates = WM9713_PCM_RATES,
|
||||
.formats = WM9713_PCM_FORMATS,},
|
||||
.ops = &wm9713_dai_ops_voice,
|
||||
.symmetric_rates = 1,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm9713_dai);
|
||||
|
@ -1,5 +1,8 @@
|
||||
config SND_SOC_OF_SIMPLE
|
||||
tristate
|
||||
|
||||
config SND_MPC52xx_DMA
|
||||
tristate
|
||||
|
||||
# ASoC platform support for the Freescale MPC8610 SOC. This compiles drivers
|
||||
# for the SSI and the Elo DMA controller. You will still need to select
|
||||
@ -22,7 +25,34 @@ config SND_SOC_MPC8610_HPCD
|
||||
config SND_SOC_MPC5200_I2S
|
||||
tristate "Freescale MPC5200 PSC in I2S mode driver"
|
||||
depends on PPC_MPC52xx && PPC_BESTCOMM
|
||||
select SND_SOC_OF_SIMPLE
|
||||
select SND_MPC52xx_DMA
|
||||
select PPC_BESTCOMM_GEN_BD
|
||||
help
|
||||
Say Y here to support the MPC5200 PSCs in I2S mode.
|
||||
|
||||
config SND_SOC_MPC5200_AC97
|
||||
tristate "Freescale MPC5200 PSC in AC97 mode driver"
|
||||
depends on PPC_MPC52xx && PPC_BESTCOMM
|
||||
select AC97_BUS
|
||||
select SND_MPC52xx_DMA
|
||||
select PPC_BESTCOMM_GEN_BD
|
||||
help
|
||||
Say Y here to support the MPC5200 PSCs in AC97 mode.
|
||||
|
||||
config SND_MPC52xx_SOC_PCM030
|
||||
tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712"
|
||||
depends on PPC_MPC5200_SIMPLE && BROKEN
|
||||
select SND_SOC_MPC5200_AC97
|
||||
select SND_SOC_WM9712
|
||||
help
|
||||
Say Y if you want to add support for sound on the Phytec pcm030
|
||||
baseboard.
|
||||
|
||||
config SND_MPC52xx_SOC_EFIKA
|
||||
tristate "SoC AC97 Audio support for bbplan Efika and STAC9766"
|
||||
depends on PPC_EFIKA && BROKEN
|
||||
select SND_SOC_MPC5200_AC97
|
||||
select SND_SOC_STAC9766
|
||||
help
|
||||
Say Y if you want to add support for sound on the Efika.
|
||||
|
||||
|
@ -10,5 +10,12 @@ snd-soc-fsl-ssi-objs := fsl_ssi.o
|
||||
snd-soc-fsl-dma-objs := fsl_dma.o
|
||||
obj-$(CONFIG_SND_SOC_MPC8610) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o
|
||||
|
||||
# MPC5200 Platform Support
|
||||
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
|
||||
obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
|
||||
obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o
|
||||
|
||||
# MPC5200 Machine Support
|
||||
obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o
|
||||
obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o
|
||||
|
||||
|
90
sound/soc/fsl/efika-audio-fabric.c
Normal file
90
sound/soc/fsl/efika-audio-fabric.c
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Efika driver for the PSC of the Freescale MPC52xx
|
||||
* configured as AC97 interface
|
||||
*
|
||||
* Copyright 2008 Jon Smirl, Digispeaker
|
||||
* Author: Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-of-simple.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
#include "mpc5200_psc_ac97.h"
|
||||
#include "../codecs/stac9766.h"
|
||||
|
||||
static struct snd_soc_device device;
|
||||
static struct snd_soc_card card;
|
||||
|
||||
static struct snd_soc_dai_link efika_fabric_dai[] = {
|
||||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 Analog",
|
||||
.codec_dai = &stac9766_dai[STAC9766_DAI_AC97_ANALOG],
|
||||
.cpu_dai = &psc_ac97_dai[MPC5200_AC97_NORMAL],
|
||||
},
|
||||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 IEC958",
|
||||
.codec_dai = &stac9766_dai[STAC9766_DAI_AC97_DIGITAL],
|
||||
.cpu_dai = &psc_ac97_dai[MPC5200_AC97_SPDIF],
|
||||
},
|
||||
};
|
||||
|
||||
static __init int efika_fabric_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int rc;
|
||||
|
||||
if (!machine_is_compatible("bplan,efika"))
|
||||
return -ENODEV;
|
||||
|
||||
card.platform = &mpc5200_audio_dma_platform;
|
||||
card.name = "Efika";
|
||||
card.dai_link = efika_fabric_dai;
|
||||
card.num_links = ARRAY_SIZE(efika_fabric_dai);
|
||||
|
||||
device.card = &card;
|
||||
device.codec_dev = &soc_codec_dev_stac9766;
|
||||
|
||||
pdev = platform_device_alloc("soc-audio", 1);
|
||||
if (!pdev) {
|
||||
pr_err("efika_fabric_init: platform_device_alloc() failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, &device);
|
||||
device.dev = &pdev->dev;
|
||||
|
||||
rc = platform_device_add(pdev);
|
||||
if (rc) {
|
||||
pr_err("efika_fabric_init: platform_device_add() failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(efika_fabric_init);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
||||
MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -375,18 +375,14 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_runtime *first_runtime =
|
||||
ssi_private->first_stream->runtime;
|
||||
|
||||
if (!first_runtime->rate || !first_runtime->sample_bits) {
|
||||
if (!first_runtime->sample_bits) {
|
||||
dev_err(substream->pcm->card->dev,
|
||||
"set sample rate and size in %s stream first\n",
|
||||
"set sample size in %s stream first\n",
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
|
||||
? "capture" : "playback");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
first_runtime->rate, first_runtime->rate);
|
||||
|
||||
/* If we're in synchronous mode, then we need to constrain
|
||||
* the sample size as well. We don't support independent sample
|
||||
* rates in asynchronous mode.
|
||||
@ -674,7 +670,7 @@ struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
|
||||
ssi_private->dev = ssi_info->dev;
|
||||
ssi_private->asynchronous = ssi_info->asynchronous;
|
||||
|
||||
ssi_private->dev->driver_data = fsl_ssi_dai;
|
||||
dev_set_drvdata(ssi_private->dev, fsl_ssi_dai);
|
||||
|
||||
/* Initialize the the device_attribute structure */
|
||||
dev_attr->attr.name = "ssi-stats";
|
||||
@ -693,6 +689,7 @@ struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
|
||||
fsl_ssi_dai->name = ssi_private->name;
|
||||
fsl_ssi_dai->id = ssi_info->id;
|
||||
fsl_ssi_dai->dev = ssi_info->dev;
|
||||
fsl_ssi_dai->symmetric_rates = 1;
|
||||
|
||||
ret = snd_soc_register_dai(fsl_ssi_dai);
|
||||
if (ret != 0) {
|
||||
|
564
sound/soc/fsl/mpc5200_dma.c
Normal file
564
sound/soc/fsl/mpc5200_dma.c
Normal file
@ -0,0 +1,564 @@
|
||||
/*
|
||||
* Freescale MPC5200 PSC DMA
|
||||
* ALSA SoC Platform driver
|
||||
*
|
||||
* Copyright (C) 2008 Secret Lab Technologies Ltd.
|
||||
* Copyright (C) 2009 Jon Smirl, Digispeaker
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <sysdev/bestcomm/bestcomm.h>
|
||||
#include <sysdev/bestcomm/gen_bd.h>
|
||||
#include <asm/mpc52xx_psc.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
|
||||
/*
|
||||
* Interrupt handlers
|
||||
*/
|
||||
static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma)
|
||||
{
|
||||
struct psc_dma *psc_dma = _psc_dma;
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
u16 isr;
|
||||
|
||||
isr = in_be16(®s->mpc52xx_psc_isr);
|
||||
|
||||
/* Playback underrun error */
|
||||
if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
|
||||
psc_dma->stats.underrun_count++;
|
||||
|
||||
/* Capture overrun error */
|
||||
if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
|
||||
psc_dma->stats.overrun_count++;
|
||||
|
||||
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer
|
||||
* @s: pointer to stream private data structure
|
||||
*
|
||||
* Enqueues another audio period buffer into the bestcomm queue.
|
||||
*
|
||||
* Note: The routine must only be called when there is space available in
|
||||
* the queue. Otherwise the enqueue will fail and the audio ring buffer
|
||||
* will get out of sync
|
||||
*/
|
||||
static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s)
|
||||
{
|
||||
struct bcom_bd *bd;
|
||||
|
||||
/* Prepare and enqueue the next buffer descriptor */
|
||||
bd = bcom_prepare_next_buffer(s->bcom_task);
|
||||
bd->status = s->period_bytes;
|
||||
bd->data[0] = s->period_next_pt;
|
||||
bcom_submit_next_buffer(s->bcom_task, NULL);
|
||||
|
||||
/* Update for next period */
|
||||
s->period_next_pt += s->period_bytes;
|
||||
if (s->period_next_pt >= s->period_end)
|
||||
s->period_next_pt = s->period_start;
|
||||
}
|
||||
|
||||
static void psc_dma_bcom_enqueue_tx(struct psc_dma_stream *s)
|
||||
{
|
||||
while (s->appl_ptr < s->runtime->control->appl_ptr) {
|
||||
|
||||
if (bcom_queue_full(s->bcom_task))
|
||||
return;
|
||||
|
||||
s->appl_ptr += s->period_size;
|
||||
|
||||
psc_dma_bcom_enqueue_next_buffer(s);
|
||||
}
|
||||
}
|
||||
|
||||
/* Bestcomm DMA irq handler */
|
||||
static irqreturn_t psc_dma_bcom_irq_tx(int irq, void *_psc_dma_stream)
|
||||
{
|
||||
struct psc_dma_stream *s = _psc_dma_stream;
|
||||
|
||||
spin_lock(&s->psc_dma->lock);
|
||||
/* For each finished period, dequeue the completed period buffer
|
||||
* and enqueue a new one in it's place. */
|
||||
while (bcom_buffer_done(s->bcom_task)) {
|
||||
bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
|
||||
|
||||
s->period_current_pt += s->period_bytes;
|
||||
if (s->period_current_pt >= s->period_end)
|
||||
s->period_current_pt = s->period_start;
|
||||
}
|
||||
psc_dma_bcom_enqueue_tx(s);
|
||||
spin_unlock(&s->psc_dma->lock);
|
||||
|
||||
/* If the stream is active, then also inform the PCM middle layer
|
||||
* of the period finished event. */
|
||||
if (s->active)
|
||||
snd_pcm_period_elapsed(s->stream);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t psc_dma_bcom_irq_rx(int irq, void *_psc_dma_stream)
|
||||
{
|
||||
struct psc_dma_stream *s = _psc_dma_stream;
|
||||
|
||||
spin_lock(&s->psc_dma->lock);
|
||||
/* For each finished period, dequeue the completed period buffer
|
||||
* and enqueue a new one in it's place. */
|
||||
while (bcom_buffer_done(s->bcom_task)) {
|
||||
bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
|
||||
|
||||
s->period_current_pt += s->period_bytes;
|
||||
if (s->period_current_pt >= s->period_end)
|
||||
s->period_current_pt = s->period_start;
|
||||
|
||||
psc_dma_bcom_enqueue_next_buffer(s);
|
||||
}
|
||||
spin_unlock(&s->psc_dma->lock);
|
||||
|
||||
/* If the stream is active, then also inform the PCM middle layer
|
||||
* of the period finished event. */
|
||||
if (s->active)
|
||||
snd_pcm_period_elapsed(s->stream);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int psc_dma_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_dma_trigger: start and stop the DMA transfer.
|
||||
*
|
||||
* This function is called by ALSA to start, stop, pause, and resume the DMA
|
||||
* transfer of data.
|
||||
*/
|
||||
static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct psc_dma_stream *s;
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
u16 imr;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_dma->capture;
|
||||
else
|
||||
s = &psc_dma->playback;
|
||||
|
||||
dev_dbg(psc_dma->dev, "psc_dma_trigger(substream=%p, cmd=%i)"
|
||||
" stream_id=%i\n",
|
||||
substream, cmd, substream->pstr->stream);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
s->period_bytes = frames_to_bytes(runtime,
|
||||
runtime->period_size);
|
||||
s->period_start = virt_to_phys(runtime->dma_area);
|
||||
s->period_end = s->period_start +
|
||||
(s->period_bytes * runtime->periods);
|
||||
s->period_next_pt = s->period_start;
|
||||
s->period_current_pt = s->period_start;
|
||||
s->period_size = runtime->period_size;
|
||||
s->active = 1;
|
||||
|
||||
/* track appl_ptr so that we have a better chance of detecting
|
||||
* end of stream and not over running it.
|
||||
*/
|
||||
s->runtime = runtime;
|
||||
s->appl_ptr = s->runtime->control->appl_ptr -
|
||||
(runtime->period_size * runtime->periods);
|
||||
|
||||
/* Fill up the bestcomm bd queue and enable DMA.
|
||||
* This will begin filling the PSC's fifo.
|
||||
*/
|
||||
spin_lock_irqsave(&psc_dma->lock, flags);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
bcom_gen_bd_rx_reset(s->bcom_task);
|
||||
for (i = 0; i < runtime->periods; i++)
|
||||
if (!bcom_queue_full(s->bcom_task))
|
||||
psc_dma_bcom_enqueue_next_buffer(s);
|
||||
} else {
|
||||
bcom_gen_bd_tx_reset(s->bcom_task);
|
||||
psc_dma_bcom_enqueue_tx(s);
|
||||
}
|
||||
|
||||
bcom_enable(s->bcom_task);
|
||||
spin_unlock_irqrestore(&psc_dma->lock, flags);
|
||||
|
||||
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
s->active = 0;
|
||||
|
||||
spin_lock_irqsave(&psc_dma->lock, flags);
|
||||
bcom_disable(s->bcom_task);
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
bcom_gen_bd_rx_reset(s->bcom_task);
|
||||
else
|
||||
bcom_gen_bd_tx_reset(s->bcom_task);
|
||||
spin_unlock_irqrestore(&psc_dma->lock, flags);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_dbg(psc_dma->dev, "invalid command\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Update interrupt enable settings */
|
||||
imr = 0;
|
||||
if (psc_dma->playback.active)
|
||||
imr |= MPC52xx_PSC_IMR_TXEMP;
|
||||
if (psc_dma->capture.active)
|
||||
imr |= MPC52xx_PSC_IMR_ORERR;
|
||||
out_be16(®s->isr_imr.imr, psc_dma->imr | imr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* The PSC DMA 'ASoC platform' driver
|
||||
*
|
||||
* Can be referenced by an 'ASoC machine' driver
|
||||
* This driver only deals with the audio bus; it doesn't have any
|
||||
* interaction with the attached codec
|
||||
*/
|
||||
|
||||
static const struct snd_pcm_hardware psc_dma_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_BATCH,
|
||||
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
|
||||
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
|
||||
.rate_min = 8000,
|
||||
.rate_max = 48000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.period_bytes_max = 1024 * 1024,
|
||||
.period_bytes_min = 32,
|
||||
.periods_min = 2,
|
||||
.periods_max = 256,
|
||||
.buffer_bytes_max = 2 * 1024 * 1024,
|
||||
.fifo_size = 512,
|
||||
};
|
||||
|
||||
static int psc_dma_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data;
|
||||
struct psc_dma_stream *s;
|
||||
int rc;
|
||||
|
||||
dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_dma->capture;
|
||||
else
|
||||
s = &psc_dma->playback;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware);
|
||||
|
||||
rc = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (rc < 0) {
|
||||
dev_err(substream->pcm->card->dev, "invalid buffer size\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
s->stream = substream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_dma_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data;
|
||||
struct psc_dma_stream *s;
|
||||
|
||||
dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_dma->capture;
|
||||
else
|
||||
s = &psc_dma->playback;
|
||||
|
||||
if (!psc_dma->playback.active &&
|
||||
!psc_dma->capture.active) {
|
||||
|
||||
/* Disable all interrupts and reset the PSC */
|
||||
out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
|
||||
out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */
|
||||
}
|
||||
s->stream = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
psc_dma_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data;
|
||||
struct psc_dma_stream *s;
|
||||
dma_addr_t count;
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_dma->capture;
|
||||
else
|
||||
s = &psc_dma->playback;
|
||||
|
||||
count = s->period_current_pt - s->period_start;
|
||||
|
||||
return bytes_to_frames(substream->runtime, count);
|
||||
}
|
||||
|
||||
static int
|
||||
psc_dma_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops psc_dma_ops = {
|
||||
.open = psc_dma_open,
|
||||
.close = psc_dma_close,
|
||||
.hw_free = psc_dma_hw_free,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.pointer = psc_dma_pointer,
|
||||
.trigger = psc_dma_trigger,
|
||||
.hw_params = psc_dma_hw_params,
|
||||
};
|
||||
|
||||
static u64 psc_dma_dmamask = 0xffffffff;
|
||||
static int psc_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data;
|
||||
size_t size = psc_dma_hardware.buffer_bytes_max;
|
||||
int rc = 0;
|
||||
|
||||
dev_dbg(rtd->socdev->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n",
|
||||
card, dai, pcm);
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &psc_dma_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = 0xffffffff;
|
||||
|
||||
if (pcm->streams[0].substream) {
|
||||
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
|
||||
size, &pcm->streams[0].substream->dma_buffer);
|
||||
if (rc)
|
||||
goto playback_alloc_err;
|
||||
}
|
||||
|
||||
if (pcm->streams[1].substream) {
|
||||
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
|
||||
size, &pcm->streams[1].substream->dma_buffer);
|
||||
if (rc)
|
||||
goto capture_alloc_err;
|
||||
}
|
||||
|
||||
if (rtd->socdev->card->codec->ac97)
|
||||
rtd->socdev->card->codec->ac97->private_data = psc_dma;
|
||||
|
||||
return 0;
|
||||
|
||||
capture_alloc_err:
|
||||
if (pcm->streams[0].substream)
|
||||
snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
|
||||
|
||||
playback_alloc_err:
|
||||
dev_err(card->dev, "Cannot allocate buffer(s)\n");
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void psc_dma_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
dev_dbg(rtd->socdev->dev, "psc_dma_free(pcm=%p)\n", pcm);
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (substream) {
|
||||
snd_dma_free_pages(&substream->dma_buffer);
|
||||
substream->dma_buffer.area = NULL;
|
||||
substream->dma_buffer.addr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct snd_soc_platform mpc5200_audio_dma_platform = {
|
||||
.name = "mpc5200-psc-audio",
|
||||
.pcm_ops = &psc_dma_ops,
|
||||
.pcm_new = &psc_dma_new,
|
||||
.pcm_free = &psc_dma_free,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(mpc5200_audio_dma_platform);
|
||||
|
||||
int mpc5200_audio_dma_create(struct of_device *op)
|
||||
{
|
||||
phys_addr_t fifo;
|
||||
struct psc_dma *psc_dma;
|
||||
struct resource res;
|
||||
int size, irq, rc;
|
||||
const __be32 *prop;
|
||||
void __iomem *regs;
|
||||
|
||||
/* Fetch the registers and IRQ of the PSC */
|
||||
irq = irq_of_parse_and_map(op->node, 0);
|
||||
if (of_address_to_resource(op->node, 0, &res)) {
|
||||
dev_err(&op->dev, "Missing reg property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
regs = ioremap(res.start, 1 + res.end - res.start);
|
||||
if (!regs) {
|
||||
dev_err(&op->dev, "Could not map registers\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Allocate and initialize the driver private data */
|
||||
psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL);
|
||||
if (!psc_dma) {
|
||||
iounmap(regs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Get the PSC ID */
|
||||
prop = of_get_property(op->node, "cell-index", &size);
|
||||
if (!prop || size < sizeof *prop)
|
||||
return -ENODEV;
|
||||
|
||||
spin_lock_init(&psc_dma->lock);
|
||||
psc_dma->id = be32_to_cpu(*prop);
|
||||
psc_dma->irq = irq;
|
||||
psc_dma->psc_regs = regs;
|
||||
psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs;
|
||||
psc_dma->dev = &op->dev;
|
||||
psc_dma->playback.psc_dma = psc_dma;
|
||||
psc_dma->capture.psc_dma = psc_dma;
|
||||
snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id);
|
||||
|
||||
/* Find the address of the fifo data registers and setup the
|
||||
* DMA tasks */
|
||||
fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
|
||||
psc_dma->capture.bcom_task =
|
||||
bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512);
|
||||
psc_dma->playback.bcom_task =
|
||||
bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo);
|
||||
if (!psc_dma->capture.bcom_task ||
|
||||
!psc_dma->playback.bcom_task) {
|
||||
dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
|
||||
iounmap(regs);
|
||||
kfree(psc_dma);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Disable all interrupts and reset the PSC */
|
||||
out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
|
||||
/* reset receiver */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX);
|
||||
/* reset transmitter */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX);
|
||||
/* reset error */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
/* reset mode */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1);
|
||||
|
||||
/* Set up mode register;
|
||||
* First write: RxRdy (FIFO Alarm) generates rx FIFO irq
|
||||
* Second write: register Normal mode for non loopback
|
||||
*/
|
||||
out_8(&psc_dma->psc_regs->mode, 0);
|
||||
out_8(&psc_dma->psc_regs->mode, 0);
|
||||
|
||||
/* Set the TX and RX fifo alarm thresholds */
|
||||
out_be16(&psc_dma->fifo_regs->rfalarm, 0x100);
|
||||
out_8(&psc_dma->fifo_regs->rfcntl, 0x4);
|
||||
out_be16(&psc_dma->fifo_regs->tfalarm, 0x100);
|
||||
out_8(&psc_dma->fifo_regs->tfcntl, 0x7);
|
||||
|
||||
/* Lookup the IRQ numbers */
|
||||
psc_dma->playback.irq =
|
||||
bcom_get_task_irq(psc_dma->playback.bcom_task);
|
||||
psc_dma->capture.irq =
|
||||
bcom_get_task_irq(psc_dma->capture.bcom_task);
|
||||
|
||||
rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED,
|
||||
"psc-dma-status", psc_dma);
|
||||
rc |= request_irq(psc_dma->capture.irq,
|
||||
&psc_dma_bcom_irq_rx, IRQF_SHARED,
|
||||
"psc-dma-capture", &psc_dma->capture);
|
||||
rc |= request_irq(psc_dma->playback.irq,
|
||||
&psc_dma_bcom_irq_tx, IRQF_SHARED,
|
||||
"psc-dma-playback", &psc_dma->playback);
|
||||
if (rc) {
|
||||
free_irq(psc_dma->irq, psc_dma);
|
||||
free_irq(psc_dma->capture.irq,
|
||||
&psc_dma->capture);
|
||||
free_irq(psc_dma->playback.irq,
|
||||
&psc_dma->playback);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Save what we've done so it can be found again later */
|
||||
dev_set_drvdata(&op->dev, psc_dma);
|
||||
|
||||
/* Tell the ASoC OF helpers about it */
|
||||
return snd_soc_register_platform(&mpc5200_audio_dma_platform);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create);
|
||||
|
||||
int mpc5200_audio_dma_destroy(struct of_device *op)
|
||||
{
|
||||
struct psc_dma *psc_dma = dev_get_drvdata(&op->dev);
|
||||
|
||||
dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n");
|
||||
|
||||
snd_soc_unregister_platform(&mpc5200_audio_dma_platform);
|
||||
|
||||
bcom_gen_bd_rx_release(psc_dma->capture.bcom_task);
|
||||
bcom_gen_bd_tx_release(psc_dma->playback.bcom_task);
|
||||
|
||||
/* Release irqs */
|
||||
free_irq(psc_dma->irq, psc_dma);
|
||||
free_irq(psc_dma->capture.irq, &psc_dma->capture);
|
||||
free_irq(psc_dma->playback.irq, &psc_dma->playback);
|
||||
|
||||
iounmap(psc_dma->psc_regs);
|
||||
kfree(psc_dma);
|
||||
dev_set_drvdata(&op->dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mpc5200_audio_dma_destroy);
|
||||
|
||||
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
|
||||
MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver");
|
||||
MODULE_LICENSE("GPL");
|
80
sound/soc/fsl/mpc5200_dma.h
Normal file
80
sound/soc/fsl/mpc5200_dma.h
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Freescale MPC5200 Audio DMA driver
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__
|
||||
#define __SOUND_SOC_FSL_MPC5200_DMA_H__
|
||||
|
||||
#define PSC_STREAM_NAME_LEN 32
|
||||
|
||||
/**
|
||||
* psc_ac97_stream - Data specific to a single stream (playback or capture)
|
||||
* @active: flag indicating if the stream is active
|
||||
* @psc_dma: pointer back to parent psc_dma data structure
|
||||
* @bcom_task: bestcomm task structure
|
||||
* @irq: irq number for bestcomm task
|
||||
* @period_start: physical address of start of DMA region
|
||||
* @period_end: physical address of end of DMA region
|
||||
* @period_next_pt: physical address of next DMA buffer to enqueue
|
||||
* @period_bytes: size of DMA period in bytes
|
||||
*/
|
||||
struct psc_dma_stream {
|
||||
struct snd_pcm_runtime *runtime;
|
||||
snd_pcm_uframes_t appl_ptr;
|
||||
|
||||
int active;
|
||||
struct psc_dma *psc_dma;
|
||||
struct bcom_task *bcom_task;
|
||||
int irq;
|
||||
struct snd_pcm_substream *stream;
|
||||
dma_addr_t period_start;
|
||||
dma_addr_t period_end;
|
||||
dma_addr_t period_next_pt;
|
||||
dma_addr_t period_current_pt;
|
||||
int period_bytes;
|
||||
int period_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* psc_dma - Private driver data
|
||||
* @name: short name for this device ("PSC0", "PSC1", etc)
|
||||
* @psc_regs: pointer to the PSC's registers
|
||||
* @fifo_regs: pointer to the PSC's FIFO registers
|
||||
* @irq: IRQ of this PSC
|
||||
* @dev: struct device pointer
|
||||
* @dai: the CPU DAI for this device
|
||||
* @sicr: Base value used in serial interface control register; mode is ORed
|
||||
* with this value.
|
||||
* @playback: Playback stream context data
|
||||
* @capture: Capture stream context data
|
||||
*/
|
||||
struct psc_dma {
|
||||
char name[32];
|
||||
struct mpc52xx_psc __iomem *psc_regs;
|
||||
struct mpc52xx_psc_fifo __iomem *fifo_regs;
|
||||
unsigned int irq;
|
||||
struct device *dev;
|
||||
spinlock_t lock;
|
||||
u32 sicr;
|
||||
uint sysclk;
|
||||
int imr;
|
||||
int id;
|
||||
unsigned int slots;
|
||||
|
||||
/* per-stream data */
|
||||
struct psc_dma_stream playback;
|
||||
struct psc_dma_stream capture;
|
||||
|
||||
/* Statistics */
|
||||
struct {
|
||||
unsigned long overrun_count;
|
||||
unsigned long underrun_count;
|
||||
} stats;
|
||||
};
|
||||
|
||||
int mpc5200_audio_dma_create(struct of_device *op);
|
||||
int mpc5200_audio_dma_destroy(struct of_device *op);
|
||||
|
||||
extern struct snd_soc_platform mpc5200_audio_dma_platform;
|
||||
|
||||
#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */
|
329
sound/soc/fsl/mpc5200_psc_ac97.c
Normal file
329
sound/soc/fsl/mpc5200_psc_ac97.c
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip.
|
||||
*
|
||||
* Copyright (C) 2009 Jon Smirl, Digispeaker
|
||||
* Author: Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/time.h>
|
||||
#include <asm/delay.h>
|
||||
#include <asm/mpc52xx_psc.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
#include "mpc5200_psc_ac97.h"
|
||||
|
||||
#define DRV_NAME "mpc5200-psc-ac97"
|
||||
|
||||
/* ALSA only supports a single AC97 device so static is recommend here */
|
||||
static struct psc_dma *psc_dma;
|
||||
|
||||
static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
|
||||
{
|
||||
int status;
|
||||
unsigned int val;
|
||||
|
||||
/* Wait for command send status zero = ready */
|
||||
status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
|
||||
MPC52xx_PSC_SR_CMDSEND), 100, 0);
|
||||
if (status == 0) {
|
||||
pr_err("timeout on ac97 bus (rdy)\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
/* Send the read */
|
||||
out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24));
|
||||
|
||||
/* Wait for the answer */
|
||||
status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) &
|
||||
MPC52xx_PSC_SR_DATA_VAL), 100, 0);
|
||||
if (status == 0) {
|
||||
pr_err("timeout on ac97 read (val) %x\n",
|
||||
in_be16(&psc_dma->psc_regs->sr_csr.status));
|
||||
return -ENODEV;
|
||||
}
|
||||
/* Get the data */
|
||||
val = in_be32(&psc_dma->psc_regs->ac97_data);
|
||||
if (((val >> 24) & 0x7f) != reg) {
|
||||
pr_err("reg echo error on ac97 read\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
val = (val >> 8) & 0xffff;
|
||||
|
||||
return (unsigned short) val;
|
||||
}
|
||||
|
||||
static void psc_ac97_write(struct snd_ac97 *ac97,
|
||||
unsigned short reg, unsigned short val)
|
||||
{
|
||||
int status;
|
||||
|
||||
/* Wait for command status zero = ready */
|
||||
status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
|
||||
MPC52xx_PSC_SR_CMDSEND), 100, 0);
|
||||
if (status == 0) {
|
||||
pr_err("timeout on ac97 bus (write)\n");
|
||||
return;
|
||||
}
|
||||
/* Write data */
|
||||
out_be32(&psc_dma->psc_regs->ac97_cmd,
|
||||
((reg & 0x7f) << 24) | (val << 8));
|
||||
}
|
||||
|
||||
static void psc_ac97_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
|
||||
out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR);
|
||||
udelay(3);
|
||||
out_be32(®s->sicr, psc_dma->sicr);
|
||||
}
|
||||
|
||||
static void psc_ac97_cold_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
|
||||
/* Do a cold reset */
|
||||
out_8(®s->op1, MPC52xx_PSC_OP_RES);
|
||||
udelay(10);
|
||||
out_8(®s->op0, MPC52xx_PSC_OP_RES);
|
||||
udelay(50);
|
||||
psc_ac97_warm_reset(ac97);
|
||||
}
|
||||
|
||||
struct snd_ac97_bus_ops soc_ac97_ops = {
|
||||
.read = psc_ac97_read,
|
||||
.write = psc_ac97_write,
|
||||
.reset = psc_ac97_cold_reset,
|
||||
.warm_reset = psc_ac97_warm_reset,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_ac97_ops);
|
||||
|
||||
static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct psc_dma *psc_dma = cpu_dai->private_data;
|
||||
|
||||
dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
|
||||
" periods=%i buffer_size=%i buffer_bytes=%i channels=%i"
|
||||
" rate=%i format=%i\n",
|
||||
__func__, substream, params_period_size(params),
|
||||
params_period_bytes(params), params_periods(params),
|
||||
params_buffer_size(params), params_buffer_bytes(params),
|
||||
params_channels(params), params_rate(params),
|
||||
params_format(params));
|
||||
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
if (params_channels(params) == 1)
|
||||
psc_dma->slots |= 0x00000100;
|
||||
else
|
||||
psc_dma->slots |= 0x00000300;
|
||||
} else {
|
||||
if (params_channels(params) == 1)
|
||||
psc_dma->slots |= 0x01000000;
|
||||
else
|
||||
psc_dma->slots |= 0x03000000;
|
||||
}
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct psc_dma *psc_dma = cpu_dai->private_data;
|
||||
|
||||
if (params_channels(params) == 1)
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000);
|
||||
else
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
psc_dma->slots &= 0xFFFF0000;
|
||||
else
|
||||
psc_dma->slots &= 0x0000FFFF;
|
||||
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_ac97_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct psc_dma *psc_dma = cpu_dai->private_data;
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
|
||||
/* Go */
|
||||
out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* ALSA SoC Bindings
|
||||
*
|
||||
* - Digital Audio Interface (DAI) template
|
||||
* - create/destroy dai hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* psc_ac97_dai_template: template CPU Digital Audio Interface
|
||||
*/
|
||||
static struct snd_soc_dai_ops psc_ac97_analog_ops = {
|
||||
.hw_params = psc_ac97_hw_analog_params,
|
||||
.trigger = psc_ac97_trigger,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops psc_ac97_digital_ops = {
|
||||
.hw_params = psc_ac97_hw_digital_params,
|
||||
};
|
||||
|
||||
struct snd_soc_dai psc_ac97_dai[] = {
|
||||
{
|
||||
.name = "AC97",
|
||||
.ac97_control = 1,
|
||||
.probe = psc_ac97_probe,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 6,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_BE,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_BE,
|
||||
},
|
||||
.ops = &psc_ac97_analog_ops,
|
||||
},
|
||||
{
|
||||
.name = "SPDIF",
|
||||
.ac97_control = 1,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
|
||||
},
|
||||
.ops = &psc_ac97_digital_ops,
|
||||
} };
|
||||
EXPORT_SYMBOL_GPL(psc_ac97_dai);
|
||||
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* OF platform bus binding code:
|
||||
* - Probe/remove operations
|
||||
* - OF device match table
|
||||
*/
|
||||
static int __devinit psc_ac97_of_probe(struct of_device *op,
|
||||
const struct of_device_id *match)
|
||||
{
|
||||
int rc, i;
|
||||
struct snd_ac97 ac97;
|
||||
struct mpc52xx_psc __iomem *regs;
|
||||
|
||||
rc = mpc5200_audio_dma_create(op);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(psc_ac97_dai); i++)
|
||||
psc_ac97_dai[i].dev = &op->dev;
|
||||
|
||||
rc = snd_soc_register_dais(psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai));
|
||||
if (rc != 0) {
|
||||
dev_err(&op->dev, "Failed to register DAI\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
psc_dma = dev_get_drvdata(&op->dev);
|
||||
regs = psc_dma->psc_regs;
|
||||
ac97.private_data = psc_dma;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(psc_ac97_dai); i++)
|
||||
psc_ac97_dai[i].private_data = psc_dma;
|
||||
|
||||
psc_dma->imr = 0;
|
||||
out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
|
||||
|
||||
/* Configure the serial interface mode to AC97 */
|
||||
psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97;
|
||||
out_be32(®s->sicr, psc_dma->sicr);
|
||||
|
||||
/* No slots active */
|
||||
out_be32(®s->ac97_slots, 0x00000000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit psc_ac97_of_remove(struct of_device *op)
|
||||
{
|
||||
return mpc5200_audio_dma_destroy(op);
|
||||
}
|
||||
|
||||
/* Match table for of_platform binding */
|
||||
static struct of_device_id psc_ac97_match[] __devinitdata = {
|
||||
{ .compatible = "fsl,mpc5200-psc-ac97", },
|
||||
{ .compatible = "fsl,mpc5200b-psc-ac97", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, psc_ac97_match);
|
||||
|
||||
static struct of_platform_driver psc_ac97_driver = {
|
||||
.match_table = psc_ac97_match,
|
||||
.probe = psc_ac97_of_probe,
|
||||
.remove = __devexit_p(psc_ac97_of_remove),
|
||||
.driver = {
|
||||
.name = "mpc5200-psc-ac97",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Module setup and teardown; simply register the of_platform driver
|
||||
* for the PSC in AC97 mode.
|
||||
*/
|
||||
static int __init psc_ac97_init(void)
|
||||
{
|
||||
return of_register_platform_driver(&psc_ac97_driver);
|
||||
}
|
||||
module_init(psc_ac97_init);
|
||||
|
||||
static void __exit psc_ac97_exit(void)
|
||||
{
|
||||
of_unregister_platform_driver(&psc_ac97_driver);
|
||||
}
|
||||
module_exit(psc_ac97_exit);
|
||||
|
||||
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
||||
MODULE_DESCRIPTION("mpc5200 AC97 module");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
15
sound/soc/fsl/mpc5200_psc_ac97.h
Normal file
15
sound/soc/fsl/mpc5200_psc_ac97.h
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Freescale MPC5200 PSC in AC97 mode
|
||||
* ALSA SoC Digital Audio Interface (DAI) driver
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
|
||||
#define __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
|
||||
|
||||
extern struct snd_soc_dai psc_ac97_dai[];
|
||||
|
||||
#define MPC5200_AC97_NORMAL 0
|
||||
#define MPC5200_AC97_SPDIF 1
|
||||
|
||||
#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ */
|
@ -3,31 +3,21 @@
|
||||
* ALSA SoC Digital Audio Interface (DAI) driver
|
||||
*
|
||||
* Copyright (C) 2008 Secret Lab Technologies Ltd.
|
||||
* Copyright (C) 2009 Jon Smirl, Digispeaker
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-of-simple.h>
|
||||
|
||||
#include <sysdev/bestcomm/bestcomm.h>
|
||||
#include <sysdev/bestcomm/gen_bd.h>
|
||||
#include <asm/mpc52xx_psc.h>
|
||||
|
||||
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
|
||||
MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
#include "mpc5200_psc_i2s.h"
|
||||
#include "mpc5200_dma.h"
|
||||
|
||||
/**
|
||||
* PSC_I2S_RATES: sample rates supported by the I2S
|
||||
@ -44,191 +34,17 @@ MODULE_LICENSE("GPL");
|
||||
* PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
|
||||
*/
|
||||
#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
|
||||
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \
|
||||
SNDRV_PCM_FMTBIT_S32_BE)
|
||||
|
||||
/**
|
||||
* psc_i2s_stream - Data specific to a single stream (playback or capture)
|
||||
* @active: flag indicating if the stream is active
|
||||
* @psc_i2s: pointer back to parent psc_i2s data structure
|
||||
* @bcom_task: bestcomm task structure
|
||||
* @irq: irq number for bestcomm task
|
||||
* @period_start: physical address of start of DMA region
|
||||
* @period_end: physical address of end of DMA region
|
||||
* @period_next_pt: physical address of next DMA buffer to enqueue
|
||||
* @period_bytes: size of DMA period in bytes
|
||||
*/
|
||||
struct psc_i2s_stream {
|
||||
int active;
|
||||
struct psc_i2s *psc_i2s;
|
||||
struct bcom_task *bcom_task;
|
||||
int irq;
|
||||
struct snd_pcm_substream *stream;
|
||||
dma_addr_t period_start;
|
||||
dma_addr_t period_end;
|
||||
dma_addr_t period_next_pt;
|
||||
dma_addr_t period_current_pt;
|
||||
int period_bytes;
|
||||
};
|
||||
|
||||
/**
|
||||
* psc_i2s - Private driver data
|
||||
* @name: short name for this device ("PSC0", "PSC1", etc)
|
||||
* @psc_regs: pointer to the PSC's registers
|
||||
* @fifo_regs: pointer to the PSC's FIFO registers
|
||||
* @irq: IRQ of this PSC
|
||||
* @dev: struct device pointer
|
||||
* @dai: the CPU DAI for this device
|
||||
* @sicr: Base value used in serial interface control register; mode is ORed
|
||||
* with this value.
|
||||
* @playback: Playback stream context data
|
||||
* @capture: Capture stream context data
|
||||
*/
|
||||
struct psc_i2s {
|
||||
char name[32];
|
||||
struct mpc52xx_psc __iomem *psc_regs;
|
||||
struct mpc52xx_psc_fifo __iomem *fifo_regs;
|
||||
unsigned int irq;
|
||||
struct device *dev;
|
||||
struct snd_soc_dai dai;
|
||||
spinlock_t lock;
|
||||
u32 sicr;
|
||||
|
||||
/* per-stream data */
|
||||
struct psc_i2s_stream playback;
|
||||
struct psc_i2s_stream capture;
|
||||
|
||||
/* Statistics */
|
||||
struct {
|
||||
int overrun_count;
|
||||
int underrun_count;
|
||||
} stats;
|
||||
};
|
||||
|
||||
/*
|
||||
* Interrupt handlers
|
||||
*/
|
||||
static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s)
|
||||
{
|
||||
struct psc_i2s *psc_i2s = _psc_i2s;
|
||||
struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
|
||||
u16 isr;
|
||||
|
||||
isr = in_be16(®s->mpc52xx_psc_isr);
|
||||
|
||||
/* Playback underrun error */
|
||||
if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
|
||||
psc_i2s->stats.underrun_count++;
|
||||
|
||||
/* Capture overrun error */
|
||||
if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
|
||||
psc_i2s->stats.overrun_count++;
|
||||
|
||||
out_8(®s->command, 4 << 4); /* reset the error status */
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer
|
||||
* @s: pointer to stream private data structure
|
||||
*
|
||||
* Enqueues another audio period buffer into the bestcomm queue.
|
||||
*
|
||||
* Note: The routine must only be called when there is space available in
|
||||
* the queue. Otherwise the enqueue will fail and the audio ring buffer
|
||||
* will get out of sync
|
||||
*/
|
||||
static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s)
|
||||
{
|
||||
struct bcom_bd *bd;
|
||||
|
||||
/* Prepare and enqueue the next buffer descriptor */
|
||||
bd = bcom_prepare_next_buffer(s->bcom_task);
|
||||
bd->status = s->period_bytes;
|
||||
bd->data[0] = s->period_next_pt;
|
||||
bcom_submit_next_buffer(s->bcom_task, NULL);
|
||||
|
||||
/* Update for next period */
|
||||
s->period_next_pt += s->period_bytes;
|
||||
if (s->period_next_pt >= s->period_end)
|
||||
s->period_next_pt = s->period_start;
|
||||
}
|
||||
|
||||
/* Bestcomm DMA irq handler */
|
||||
static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream)
|
||||
{
|
||||
struct psc_i2s_stream *s = _psc_i2s_stream;
|
||||
|
||||
/* For each finished period, dequeue the completed period buffer
|
||||
* and enqueue a new one in it's place. */
|
||||
while (bcom_buffer_done(s->bcom_task)) {
|
||||
bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
|
||||
s->period_current_pt += s->period_bytes;
|
||||
if (s->period_current_pt >= s->period_end)
|
||||
s->period_current_pt = s->period_start;
|
||||
psc_i2s_bcom_enqueue_next_buffer(s);
|
||||
bcom_enable(s->bcom_task);
|
||||
}
|
||||
|
||||
/* If the stream is active, then also inform the PCM middle layer
|
||||
* of the period finished event. */
|
||||
if (s->active)
|
||||
snd_pcm_period_elapsed(s->stream);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_i2s_startup: create a new substream
|
||||
*
|
||||
* This is the first function called when a stream is opened.
|
||||
*
|
||||
* If this is the first stream open, then grab the IRQ and program most of
|
||||
* the PSC registers.
|
||||
*/
|
||||
static int psc_i2s_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
|
||||
int rc;
|
||||
|
||||
dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream);
|
||||
|
||||
if (!psc_i2s->playback.active &&
|
||||
!psc_i2s->capture.active) {
|
||||
/* Setup the IRQs */
|
||||
rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED,
|
||||
"psc-i2s-status", psc_i2s);
|
||||
rc |= request_irq(psc_i2s->capture.irq,
|
||||
&psc_i2s_bcom_irq, IRQF_SHARED,
|
||||
"psc-i2s-capture", &psc_i2s->capture);
|
||||
rc |= request_irq(psc_i2s->playback.irq,
|
||||
&psc_i2s_bcom_irq, IRQF_SHARED,
|
||||
"psc-i2s-playback", &psc_i2s->playback);
|
||||
if (rc) {
|
||||
free_irq(psc_i2s->irq, psc_i2s);
|
||||
free_irq(psc_i2s->capture.irq,
|
||||
&psc_i2s->capture);
|
||||
free_irq(psc_i2s->playback.irq,
|
||||
&psc_i2s->playback);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
|
||||
|
||||
static int psc_i2s_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 psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
|
||||
struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data;
|
||||
u32 mode;
|
||||
|
||||
dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
|
||||
dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
|
||||
" periods=%i buffer_size=%i buffer_bytes=%i\n",
|
||||
__func__, substream, params_period_size(params),
|
||||
params_period_bytes(params), params_periods(params),
|
||||
@ -248,174 +64,14 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
mode = MPC52xx_PSC_SICR_SIM_CODEC_32;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(psc_i2s->dev, "invalid format\n");
|
||||
dev_dbg(psc_dma->dev, "invalid format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
out_be32(&psc_i2s->psc_regs->sicr, psc_i2s->sicr | mode);
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_i2s_hw_free(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_i2s_trigger: start and stop the DMA transfer.
|
||||
*
|
||||
* This function is called by ALSA to start, stop, pause, and resume the DMA
|
||||
* transfer of data.
|
||||
*/
|
||||
static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct psc_i2s_stream *s;
|
||||
struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
|
||||
u16 imr;
|
||||
u8 psc_cmd;
|
||||
unsigned long flags;
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_i2s->capture;
|
||||
else
|
||||
s = &psc_i2s->playback;
|
||||
|
||||
dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)"
|
||||
" stream_id=%i\n",
|
||||
substream, cmd, substream->pstr->stream);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
s->period_bytes = frames_to_bytes(runtime,
|
||||
runtime->period_size);
|
||||
s->period_start = virt_to_phys(runtime->dma_area);
|
||||
s->period_end = s->period_start +
|
||||
(s->period_bytes * runtime->periods);
|
||||
s->period_next_pt = s->period_start;
|
||||
s->period_current_pt = s->period_start;
|
||||
s->active = 1;
|
||||
|
||||
/* First; reset everything */
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
out_8(®s->command, MPC52xx_PSC_RST_RX);
|
||||
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
} else {
|
||||
out_8(®s->command, MPC52xx_PSC_RST_TX);
|
||||
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
}
|
||||
|
||||
/* Next, fill up the bestcomm bd queue and enable DMA.
|
||||
* This will begin filling the PSC's fifo. */
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
bcom_gen_bd_rx_reset(s->bcom_task);
|
||||
else
|
||||
bcom_gen_bd_tx_reset(s->bcom_task);
|
||||
while (!bcom_queue_full(s->bcom_task))
|
||||
psc_i2s_bcom_enqueue_next_buffer(s);
|
||||
bcom_enable(s->bcom_task);
|
||||
|
||||
/* Due to errata in the i2s mode; need to line up enabling
|
||||
* the transmitter with a transition on the frame sync
|
||||
* line */
|
||||
|
||||
spin_lock_irqsave(&psc_i2s->lock, flags);
|
||||
/* first make sure it is low */
|
||||
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0)
|
||||
;
|
||||
/* then wait for the transition to high */
|
||||
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0)
|
||||
;
|
||||
/* Finally, enable the PSC.
|
||||
* Receiver must always be enabled; even when we only want
|
||||
* transmit. (see 15.3.2.3 of MPC5200B User's Guide) */
|
||||
psc_cmd = MPC52xx_PSC_RX_ENABLE;
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
psc_cmd |= MPC52xx_PSC_TX_ENABLE;
|
||||
out_8(®s->command, psc_cmd);
|
||||
spin_unlock_irqrestore(&psc_i2s->lock, flags);
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
/* Turn off the PSC */
|
||||
s->active = 0;
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
if (!psc_i2s->playback.active) {
|
||||
out_8(®s->command, 2 << 4); /* reset rx */
|
||||
out_8(®s->command, 3 << 4); /* reset tx */
|
||||
out_8(®s->command, 4 << 4); /* reset err */
|
||||
}
|
||||
} else {
|
||||
out_8(®s->command, 3 << 4); /* reset tx */
|
||||
out_8(®s->command, 4 << 4); /* reset err */
|
||||
if (!psc_i2s->capture.active)
|
||||
out_8(®s->command, 2 << 4); /* reset rx */
|
||||
}
|
||||
|
||||
bcom_disable(s->bcom_task);
|
||||
while (!bcom_queue_empty(s->bcom_task))
|
||||
bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_dbg(psc_i2s->dev, "invalid command\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Update interrupt enable settings */
|
||||
imr = 0;
|
||||
if (psc_i2s->playback.active)
|
||||
imr |= MPC52xx_PSC_IMR_TXEMP;
|
||||
if (psc_i2s->capture.active)
|
||||
imr |= MPC52xx_PSC_IMR_ORERR;
|
||||
out_be16(®s->isr_imr.imr, imr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_i2s_shutdown: shutdown the data transfer on a stream
|
||||
*
|
||||
* Shutdown the PSC if there are no other substreams open.
|
||||
*/
|
||||
static void psc_i2s_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
|
||||
|
||||
dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream);
|
||||
|
||||
/*
|
||||
* If this is the last active substream, disable the PSC and release
|
||||
* the IRQ.
|
||||
*/
|
||||
if (!psc_i2s->playback.active &&
|
||||
!psc_i2s->capture.active) {
|
||||
|
||||
/* Disable all interrupts and reset the PSC */
|
||||
out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0);
|
||||
out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */
|
||||
out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */
|
||||
out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */
|
||||
out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */
|
||||
|
||||
/* Release irqs */
|
||||
free_irq(psc_i2s->irq, psc_i2s);
|
||||
free_irq(psc_i2s->capture.irq, &psc_i2s->capture);
|
||||
free_irq(psc_i2s->playback.irq, &psc_i2s->playback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_i2s_set_sysclk: set the clock frequency and direction
|
||||
*
|
||||
@ -433,8 +89,8 @@ static void psc_i2s_shutdown(struct snd_pcm_substream *substream,
|
||||
static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct psc_i2s *psc_i2s = cpu_dai->private_data;
|
||||
dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
|
||||
struct psc_dma *psc_dma = cpu_dai->private_data;
|
||||
dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
|
||||
cpu_dai, dir);
|
||||
return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
|
||||
}
|
||||
@ -452,8 +108,8 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
*/
|
||||
static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
|
||||
{
|
||||
struct psc_i2s *psc_i2s = cpu_dai->private_data;
|
||||
dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
|
||||
struct psc_dma *psc_dma = cpu_dai->private_data;
|
||||
dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
|
||||
cpu_dai, format);
|
||||
return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
|
||||
}
|
||||
@ -469,16 +125,13 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
|
||||
* psc_i2s_dai_template: template CPU Digital Audio Interface
|
||||
*/
|
||||
static struct snd_soc_dai_ops psc_i2s_dai_ops = {
|
||||
.startup = psc_i2s_startup,
|
||||
.hw_params = psc_i2s_hw_params,
|
||||
.hw_free = psc_i2s_hw_free,
|
||||
.shutdown = psc_i2s_shutdown,
|
||||
.trigger = psc_i2s_trigger,
|
||||
.set_sysclk = psc_i2s_set_sysclk,
|
||||
.set_fmt = psc_i2s_set_fmt,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai psc_i2s_dai_template = {
|
||||
struct snd_soc_dai psc_i2s_dai[] = {{
|
||||
.name = "I2S",
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
@ -492,223 +145,8 @@ static struct snd_soc_dai psc_i2s_dai_template = {
|
||||
.formats = PSC_I2S_FORMATS,
|
||||
},
|
||||
.ops = &psc_i2s_dai_ops,
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* The PSC I2S 'ASoC platform' driver
|
||||
*
|
||||
* Can be referenced by an 'ASoC machine' driver
|
||||
* This driver only deals with the audio bus; it doesn't have any
|
||||
* interaction with the attached codec
|
||||
*/
|
||||
|
||||
static const struct snd_pcm_hardware psc_i2s_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_BATCH,
|
||||
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
|
||||
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
|
||||
.rate_min = 8000,
|
||||
.rate_max = 48000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.period_bytes_max = 1024 * 1024,
|
||||
.period_bytes_min = 32,
|
||||
.periods_min = 2,
|
||||
.periods_max = 256,
|
||||
.buffer_bytes_max = 2 * 1024 * 1024,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static int psc_i2s_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
|
||||
struct psc_i2s_stream *s;
|
||||
|
||||
dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_i2s->capture;
|
||||
else
|
||||
s = &psc_i2s->playback;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware);
|
||||
|
||||
s->stream = substream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_i2s_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
|
||||
struct psc_i2s_stream *s;
|
||||
|
||||
dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_i2s->capture;
|
||||
else
|
||||
s = &psc_i2s->playback;
|
||||
|
||||
s->stream = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
psc_i2s_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
|
||||
struct psc_i2s_stream *s;
|
||||
dma_addr_t count;
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_i2s->capture;
|
||||
else
|
||||
s = &psc_i2s->playback;
|
||||
|
||||
count = s->period_current_pt - s->period_start;
|
||||
|
||||
return bytes_to_frames(substream->runtime, count);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops psc_i2s_pcm_ops = {
|
||||
.open = psc_i2s_pcm_open,
|
||||
.close = psc_i2s_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.pointer = psc_i2s_pcm_pointer,
|
||||
};
|
||||
|
||||
static u64 psc_i2s_pcm_dmamask = 0xffffffff;
|
||||
static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
size_t size = psc_i2s_pcm_hardware.buffer_bytes_max;
|
||||
int rc = 0;
|
||||
|
||||
dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n",
|
||||
card, dai, pcm);
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &psc_i2s_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = 0xffffffff;
|
||||
|
||||
if (pcm->streams[0].substream) {
|
||||
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
|
||||
&pcm->streams[0].substream->dma_buffer);
|
||||
if (rc)
|
||||
goto playback_alloc_err;
|
||||
}
|
||||
|
||||
if (pcm->streams[1].substream) {
|
||||
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
|
||||
&pcm->streams[1].substream->dma_buffer);
|
||||
if (rc)
|
||||
goto capture_alloc_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
capture_alloc_err:
|
||||
if (pcm->streams[0].substream)
|
||||
snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
|
||||
playback_alloc_err:
|
||||
dev_err(card->dev, "Cannot allocate buffer(s)\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void psc_i2s_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm);
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (substream) {
|
||||
snd_dma_free_pages(&substream->dma_buffer);
|
||||
substream->dma_buffer.area = NULL;
|
||||
substream->dma_buffer.addr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct snd_soc_platform psc_i2s_pcm_soc_platform = {
|
||||
.name = "mpc5200-psc-audio",
|
||||
.pcm_ops = &psc_i2s_pcm_ops,
|
||||
.pcm_new = &psc_i2s_pcm_new,
|
||||
.pcm_free = &psc_i2s_pcm_free,
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Sysfs attributes for debugging
|
||||
*/
|
||||
|
||||
static ssize_t psc_i2s_status_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x "
|
||||
"tfnum=%i tfstat=0x%.4x\n",
|
||||
in_be16(&psc_i2s->psc_regs->sr_csr.status),
|
||||
in_be32(&psc_i2s->psc_regs->sicr),
|
||||
in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff,
|
||||
in_be16(&psc_i2s->fifo_regs->rfstat),
|
||||
in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff,
|
||||
in_be16(&psc_i2s->fifo_regs->tfstat));
|
||||
}
|
||||
|
||||
static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name)
|
||||
{
|
||||
if (strcmp(name, "playback_underrun") == 0)
|
||||
return &psc_i2s->stats.underrun_count;
|
||||
if (strcmp(name, "capture_overrun") == 0)
|
||||
return &psc_i2s->stats.overrun_count;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t psc_i2s_stat_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
|
||||
int *attrib;
|
||||
|
||||
attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
|
||||
if (!attrib)
|
||||
return 0;
|
||||
|
||||
return sprintf(buf, "%i\n", *attrib);
|
||||
}
|
||||
|
||||
static ssize_t psc_i2s_stat_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
|
||||
int *attrib;
|
||||
|
||||
attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
|
||||
if (!attrib)
|
||||
return 0;
|
||||
|
||||
*attrib = simple_strtoul(buf, NULL, 0);
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL);
|
||||
static DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show,
|
||||
psc_i2s_stat_store);
|
||||
static DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show,
|
||||
psc_i2s_stat_store);
|
||||
} };
|
||||
EXPORT_SYMBOL_GPL(psc_i2s_dai);
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* OF platform bus binding code:
|
||||
@ -718,150 +156,65 @@ static DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show,
|
||||
static int __devinit psc_i2s_of_probe(struct of_device *op,
|
||||
const struct of_device_id *match)
|
||||
{
|
||||
phys_addr_t fifo;
|
||||
struct psc_i2s *psc_i2s;
|
||||
struct resource res;
|
||||
int size, psc_id, irq, rc;
|
||||
const __be32 *prop;
|
||||
void __iomem *regs;
|
||||
int rc;
|
||||
struct psc_dma *psc_dma;
|
||||
struct mpc52xx_psc __iomem *regs;
|
||||
|
||||
dev_dbg(&op->dev, "probing psc i2s device\n");
|
||||
rc = mpc5200_audio_dma_create(op);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
/* Get the PSC ID */
|
||||
prop = of_get_property(op->node, "cell-index", &size);
|
||||
if (!prop || size < sizeof *prop)
|
||||
return -ENODEV;
|
||||
psc_id = be32_to_cpu(*prop);
|
||||
|
||||
/* Fetch the registers and IRQ of the PSC */
|
||||
irq = irq_of_parse_and_map(op->node, 0);
|
||||
if (of_address_to_resource(op->node, 0, &res)) {
|
||||
dev_err(&op->dev, "Missing reg property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
regs = ioremap(res.start, 1 + res.end - res.start);
|
||||
if (!regs) {
|
||||
dev_err(&op->dev, "Could not map registers\n");
|
||||
return -ENODEV;
|
||||
rc = snd_soc_register_dais(psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai));
|
||||
if (rc != 0) {
|
||||
pr_err("Failed to register DAI\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocate and initialize the driver private data */
|
||||
psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL);
|
||||
if (!psc_i2s) {
|
||||
iounmap(regs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
spin_lock_init(&psc_i2s->lock);
|
||||
psc_i2s->irq = irq;
|
||||
psc_i2s->psc_regs = regs;
|
||||
psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs;
|
||||
psc_i2s->dev = &op->dev;
|
||||
psc_i2s->playback.psc_i2s = psc_i2s;
|
||||
psc_i2s->capture.psc_i2s = psc_i2s;
|
||||
snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1);
|
||||
|
||||
/* Fill out the CPU DAI structure */
|
||||
memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai);
|
||||
psc_i2s->dai.private_data = psc_i2s;
|
||||
psc_i2s->dai.name = psc_i2s->name;
|
||||
psc_i2s->dai.id = psc_id;
|
||||
|
||||
/* Find the address of the fifo data registers and setup the
|
||||
* DMA tasks */
|
||||
fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
|
||||
psc_i2s->capture.bcom_task =
|
||||
bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512);
|
||||
psc_i2s->playback.bcom_task =
|
||||
bcom_psc_gen_bd_tx_init(psc_id, 10, fifo);
|
||||
if (!psc_i2s->capture.bcom_task ||
|
||||
!psc_i2s->playback.bcom_task) {
|
||||
dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
|
||||
iounmap(regs);
|
||||
kfree(psc_i2s);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Disable all interrupts and reset the PSC */
|
||||
out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0);
|
||||
out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */
|
||||
out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */
|
||||
out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */
|
||||
out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */
|
||||
psc_dma = dev_get_drvdata(&op->dev);
|
||||
regs = psc_dma->psc_regs;
|
||||
|
||||
/* Configure the serial interface mode; defaulting to CODEC8 mode */
|
||||
psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
|
||||
psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
|
||||
MPC52xx_PSC_SICR_CLKPOL;
|
||||
if (of_get_property(op->node, "fsl,cellslave", NULL))
|
||||
psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE |
|
||||
MPC52xx_PSC_SICR_GENCLK;
|
||||
out_be32(&psc_i2s->psc_regs->sicr,
|
||||
psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
|
||||
out_be32(&psc_dma->psc_regs->sicr,
|
||||
psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
|
||||
|
||||
/* Check for the codec handle. If it is not present then we
|
||||
* are done */
|
||||
if (!of_get_property(op->node, "codec-handle", NULL))
|
||||
return 0;
|
||||
|
||||
/* Set up mode register;
|
||||
* First write: RxRdy (FIFO Alarm) generates rx FIFO irq
|
||||
* Second write: register Normal mode for non loopback
|
||||
*/
|
||||
out_8(&psc_i2s->psc_regs->mode, 0);
|
||||
out_8(&psc_i2s->psc_regs->mode, 0);
|
||||
/* Due to errata in the dma mode; need to line up enabling
|
||||
* the transmitter with a transition on the frame sync
|
||||
* line */
|
||||
|
||||
/* Set the TX and RX fifo alarm thresholds */
|
||||
out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100);
|
||||
out_8(&psc_i2s->fifo_regs->rfcntl, 0x4);
|
||||
out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100);
|
||||
out_8(&psc_i2s->fifo_regs->tfcntl, 0x7);
|
||||
/* first make sure it is low */
|
||||
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0)
|
||||
;
|
||||
/* then wait for the transition to high */
|
||||
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0)
|
||||
;
|
||||
/* Finally, enable the PSC.
|
||||
* Receiver must always be enabled; even when we only want
|
||||
* transmit. (see 15.3.2.3 of MPC5200B User's Guide) */
|
||||
|
||||
/* Lookup the IRQ numbers */
|
||||
psc_i2s->playback.irq =
|
||||
bcom_get_task_irq(psc_i2s->playback.bcom_task);
|
||||
psc_i2s->capture.irq =
|
||||
bcom_get_task_irq(psc_i2s->capture.bcom_task);
|
||||
|
||||
/* Save what we've done so it can be found again later */
|
||||
dev_set_drvdata(&op->dev, psc_i2s);
|
||||
|
||||
/* Register the SYSFS files */
|
||||
rc = device_create_file(psc_i2s->dev, &dev_attr_status);
|
||||
rc |= device_create_file(psc_i2s->dev, &dev_attr_capture_overrun);
|
||||
rc |= device_create_file(psc_i2s->dev, &dev_attr_playback_underrun);
|
||||
if (rc)
|
||||
dev_info(psc_i2s->dev, "error creating sysfs files\n");
|
||||
|
||||
snd_soc_register_platform(&psc_i2s_pcm_soc_platform);
|
||||
|
||||
/* Tell the ASoC OF helpers about it */
|
||||
of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node,
|
||||
&psc_i2s->dai);
|
||||
/* Go */
|
||||
out_8(&psc_dma->psc_regs->command,
|
||||
MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int __devexit psc_i2s_of_remove(struct of_device *op)
|
||||
{
|
||||
struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev);
|
||||
|
||||
dev_dbg(&op->dev, "psc_i2s_remove()\n");
|
||||
|
||||
snd_soc_unregister_platform(&psc_i2s_pcm_soc_platform);
|
||||
|
||||
bcom_gen_bd_rx_release(psc_i2s->capture.bcom_task);
|
||||
bcom_gen_bd_tx_release(psc_i2s->playback.bcom_task);
|
||||
|
||||
iounmap(psc_i2s->psc_regs);
|
||||
iounmap(psc_i2s->fifo_regs);
|
||||
kfree(psc_i2s);
|
||||
dev_set_drvdata(&op->dev, NULL);
|
||||
|
||||
return 0;
|
||||
return mpc5200_audio_dma_destroy(op);
|
||||
}
|
||||
|
||||
/* Match table for of_platform binding */
|
||||
static struct of_device_id psc_i2s_match[] __devinitdata = {
|
||||
{ .compatible = "fsl,mpc5200-psc-i2s", },
|
||||
{ .compatible = "fsl,mpc5200b-psc-i2s", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, psc_i2s_match);
|
||||
@ -892,4 +245,7 @@ static void __exit psc_i2s_exit(void)
|
||||
}
|
||||
module_exit(psc_i2s_exit);
|
||||
|
||||
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
|
||||
MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
12
sound/soc/fsl/mpc5200_psc_i2s.h
Normal file
12
sound/soc/fsl/mpc5200_psc_i2s.h
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Freescale MPC5200 PSC in I2S mode
|
||||
* ALSA SoC Digital Audio Interface (DAI) driver
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__
|
||||
#define __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__
|
||||
|
||||
extern struct snd_soc_dai psc_i2s_dai[];
|
||||
|
||||
#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ */
|
90
sound/soc/fsl/pcm030-audio-fabric.c
Normal file
90
sound/soc/fsl/pcm030-audio-fabric.c
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Phytec pcm030 driver for the PSC of the Freescale MPC52xx
|
||||
* configured as AC97 interface
|
||||
*
|
||||
* Copyright 2008 Jon Smirl, Digispeaker
|
||||
* Author: Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-of-simple.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
#include "mpc5200_psc_ac97.h"
|
||||
#include "../codecs/wm9712.h"
|
||||
|
||||
static struct snd_soc_device device;
|
||||
static struct snd_soc_card card;
|
||||
|
||||
static struct snd_soc_dai_link pcm030_fabric_dai[] = {
|
||||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 Analog",
|
||||
.codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI],
|
||||
.cpu_dai = &psc_ac97_dai[MPC5200_AC97_NORMAL],
|
||||
},
|
||||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 IEC958",
|
||||
.codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX],
|
||||
.cpu_dai = &psc_ac97_dai[MPC5200_AC97_SPDIF],
|
||||
},
|
||||
};
|
||||
|
||||
static __init int pcm030_fabric_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int rc;
|
||||
|
||||
if (!machine_is_compatible("phytec,pcm030"))
|
||||
return -ENODEV;
|
||||
|
||||
card.platform = &mpc5200_audio_dma_platform;
|
||||
card.name = "pcm030";
|
||||
card.dai_link = pcm030_fabric_dai;
|
||||
card.num_links = ARRAY_SIZE(pcm030_fabric_dai);
|
||||
|
||||
device.card = &card;
|
||||
device.codec_dev = &soc_codec_dev_wm9712;
|
||||
|
||||
pdev = platform_device_alloc("soc-audio", 1);
|
||||
if (!pdev) {
|
||||
pr_err("pcm030_fabric_init: platform_device_alloc() failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, &device);
|
||||
device.dev = &pdev->dev;
|
||||
|
||||
rc = platform_device_add(pdev);
|
||||
if (rc) {
|
||||
pr_err("pcm030_fabric_init: platform_device_add() failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(pcm030_fabric_init);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
||||
MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -39,6 +39,14 @@ config SND_OMAP_SOC_OMAP2EVM
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the omap2evm board.
|
||||
|
||||
config SND_OMAP_SOC_OMAP3EVM
|
||||
tristate "SoC Audio support for OMAP3EVM board"
|
||||
depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3EVM
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TWL4030
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the omap3evm board.
|
||||
|
||||
config SND_OMAP_SOC_SDP3430
|
||||
tristate "SoC Audio support for Texas Instruments SDP3430"
|
||||
depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_3430SDP
|
||||
|
@ -10,6 +10,7 @@ snd-soc-n810-objs := n810.o
|
||||
snd-soc-osk5912-objs := osk5912.o
|
||||
snd-soc-overo-objs := overo.o
|
||||
snd-soc-omap2evm-objs := omap2evm.o
|
||||
snd-soc-omap3evm-objs := omap3evm.o
|
||||
snd-soc-sdp3430-objs := sdp3430.o
|
||||
snd-soc-omap3pandora-objs := omap3pandora.o
|
||||
snd-soc-omap3beagle-objs := omap3beagle.o
|
||||
@ -18,6 +19,7 @@ obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o
|
||||
obj-$(CONFIG_MACH_OMAP2EVM) += snd-soc-omap2evm.o
|
||||
obj-$(CONFIG_MACH_OMAP3EVM) += snd-soc-omap3evm.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o
|
||||
|
@ -383,10 +383,9 @@ static int __init n810_soc_init(void)
|
||||
clk_set_parent(sys_clkout2_src, func96m_clk);
|
||||
clk_set_rate(sys_clkout2, 12000000);
|
||||
|
||||
if (gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0)
|
||||
BUG();
|
||||
if (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0)
|
||||
BUG();
|
||||
BUG_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) ||
|
||||
(gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0));
|
||||
|
||||
gpio_direction_output(N810_HEADSET_AMP_GPIO, 0);
|
||||
gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0);
|
||||
|
||||
|
@ -215,8 +215,9 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
|
||||
struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
|
||||
int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id;
|
||||
int wlen, channels;
|
||||
int wlen, channels, wpf;
|
||||
unsigned long port;
|
||||
unsigned int format;
|
||||
|
||||
if (cpu_class_is_omap1()) {
|
||||
dma = omap1_dma_reqs[bus_id][substream->stream];
|
||||
@ -244,18 +245,24 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
channels = params_channels(params);
|
||||
format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
||||
wpf = channels = params_channels(params);
|
||||
switch (channels) {
|
||||
case 2:
|
||||
/* Use dual-phase frames */
|
||||
regs->rcr2 |= RPHASE;
|
||||
regs->xcr2 |= XPHASE;
|
||||
if (format == SND_SOC_DAIFMT_I2S) {
|
||||
/* Use dual-phase frames */
|
||||
regs->rcr2 |= RPHASE;
|
||||
regs->xcr2 |= XPHASE;
|
||||
/* Set 1 word per (McBSP) frame for phase1 and phase2 */
|
||||
wpf--;
|
||||
regs->rcr2 |= RFRLEN2(wpf - 1);
|
||||
regs->xcr2 |= XFRLEN2(wpf - 1);
|
||||
}
|
||||
case 1:
|
||||
/* Set 1 word per (McBSP) frame */
|
||||
regs->rcr2 |= RFRLEN2(1 - 1);
|
||||
regs->rcr1 |= RFRLEN1(1 - 1);
|
||||
regs->xcr2 |= XFRLEN2(1 - 1);
|
||||
regs->xcr1 |= XFRLEN1(1 - 1);
|
||||
case 4:
|
||||
/* Set word per (McBSP) frame for phase1 */
|
||||
regs->rcr1 |= RFRLEN1(wpf - 1);
|
||||
regs->xcr1 |= XFRLEN1(wpf - 1);
|
||||
break;
|
||||
default:
|
||||
/* Unsupported number of channels */
|
||||
@ -277,11 +284,12 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
}
|
||||
|
||||
/* Set FS period and length in terms of bit clock periods */
|
||||
switch (mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
switch (format) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
regs->srgr2 |= FPER(wlen * 2 - 1);
|
||||
regs->srgr2 |= FPER(wlen * channels - 1);
|
||||
regs->srgr1 |= FWID(wlen - 1);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
regs->srgr2 |= FPER(wlen * channels - 1);
|
||||
regs->srgr1 |= FWID(0);
|
||||
@ -326,6 +334,13 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
regs->rcr2 |= RDATDLY(1);
|
||||
regs->xcr2 |= XDATDLY(1);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
/* 1-bit data delay */
|
||||
regs->rcr2 |= RDATDLY(1);
|
||||
regs->xcr2 |= XDATDLY(1);
|
||||
/* Invert FS polarity configuration */
|
||||
temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
/* 0-bit data delay */
|
||||
regs->rcr2 |= RDATDLY(0);
|
||||
@ -492,13 +507,13 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = {
|
||||
.id = (link_id), \
|
||||
.playback = { \
|
||||
.channels_min = 1, \
|
||||
.channels_max = 2, \
|
||||
.channels_max = 4, \
|
||||
.rates = OMAP_MCBSP_RATES, \
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, \
|
||||
}, \
|
||||
.capture = { \
|
||||
.channels_min = 1, \
|
||||
.channels_max = 2, \
|
||||
.channels_max = 4, \
|
||||
.rates = OMAP_MCBSP_RATES, \
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, \
|
||||
}, \
|
||||
|
@ -87,8 +87,10 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct omap_pcm_dma_data *dma_data = rtd->dai->cpu_dai->dma_data;
|
||||
int err = 0;
|
||||
|
||||
/* return if this is a bufferless transfer e.g.
|
||||
* codec <--> BT codec or GSM modem -- lg FIXME */
|
||||
if (!dma_data)
|
||||
return -ENODEV;
|
||||
return 0;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = params_buffer_bytes(params);
|
||||
@ -134,6 +136,11 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
struct omap_pcm_dma_data *dma_data = prtd->dma_data;
|
||||
struct omap_dma_channel_params dma_params;
|
||||
|
||||
/* return if this is a bufferless transfer e.g.
|
||||
* codec <--> BT codec or GSM modem -- lg FIXME */
|
||||
if (!prtd->dma_data)
|
||||
return 0;
|
||||
|
||||
memset(&dma_params, 0, sizeof(dma_params));
|
||||
/*
|
||||
* Note: Regardless of interface data formats supported by OMAP McBSP
|
||||
|
@ -86,7 +86,7 @@ static struct snd_soc_dai_link omap2evm_dai = {
|
||||
.name = "TWL4030",
|
||||
.stream_name = "TWL4030",
|
||||
.cpu_dai = &omap_mcbsp_dai[0],
|
||||
.codec_dai = &twl4030_dai,
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
|
||||
.ops = &omap2evm_ops,
|
||||
};
|
||||
|
||||
|
@ -41,23 +41,33 @@ static int omap3beagle_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
unsigned int fmt;
|
||||
int ret;
|
||||
|
||||
switch (params_channels(params)) {
|
||||
case 2: /* Stereo I2S mode */
|
||||
fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
break;
|
||||
case 4: /* Four channel TDM mode */
|
||||
fmt = SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai,
|
||||
SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "can't set codec DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai,
|
||||
SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "can't set cpu DAI configuration\n");
|
||||
return ret;
|
||||
@ -83,7 +93,7 @@ static struct snd_soc_dai_link omap3beagle_dai = {
|
||||
.name = "TWL4030",
|
||||
.stream_name = "TWL4030",
|
||||
.cpu_dai = &omap_mcbsp_dai[0],
|
||||
.codec_dai = &twl4030_dai,
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
|
||||
.ops = &omap3beagle_ops,
|
||||
};
|
||||
|
||||
|
147
sound/soc/omap/omap3evm.c
Normal file
147
sound/soc/omap/omap3evm.c
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* omap3evm.c -- ALSA SoC support for OMAP3 EVM
|
||||
*
|
||||
* Author: Anuj Aggarwal <anuj.aggarwal@ti.com>
|
||||
*
|
||||
* Based on sound/soc/omap/beagle.c by Steve Sakoman
|
||||
*
|
||||
* Copyright (C) 2008 Texas Instruments, Incorporated
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2.
|
||||
*
|
||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
|
||||
* whether express or implied; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <mach/gpio.h>
|
||||
#include <mach/mcbsp.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
#include "omap-pcm.h"
|
||||
#include "../codecs/twl4030.h"
|
||||
|
||||
static int omap3evm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
int ret;
|
||||
|
||||
/* Set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai,
|
||||
SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Can't set codec DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai,
|
||||
SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Can't set cpu DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Can't set codec system clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops omap3evm_ops = {
|
||||
.hw_params = omap3evm_hw_params,
|
||||
};
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link omap3evm_dai = {
|
||||
.name = "TWL4030",
|
||||
.stream_name = "TWL4030",
|
||||
.cpu_dai = &omap_mcbsp_dai[0],
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
|
||||
.ops = &omap3evm_ops,
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card snd_soc_omap3evm = {
|
||||
.name = "omap3evm",
|
||||
.platform = &omap_soc_platform,
|
||||
.dai_link = &omap3evm_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
/* Audio subsystem */
|
||||
static struct snd_soc_device omap3evm_snd_devdata = {
|
||||
.card = &snd_soc_omap3evm,
|
||||
.codec_dev = &soc_codec_dev_twl4030,
|
||||
};
|
||||
|
||||
static struct platform_device *omap3evm_snd_device;
|
||||
|
||||
static int __init omap3evm_soc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_omap3evm()) {
|
||||
pr_err("Not OMAP3 EVM!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
pr_info("OMAP3 EVM SoC init\n");
|
||||
|
||||
omap3evm_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!omap3evm_snd_device) {
|
||||
printk(KERN_ERR "Platform device allocation failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(omap3evm_snd_device, &omap3evm_snd_devdata);
|
||||
omap3evm_snd_devdata.dev = &omap3evm_snd_device->dev;
|
||||
*(unsigned int *)omap3evm_dai.cpu_dai->private_data = 1;
|
||||
|
||||
ret = platform_device_add(omap3evm_snd_device);
|
||||
if (ret)
|
||||
goto err1;
|
||||
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
printk(KERN_ERR "Unable to add platform device\n");
|
||||
platform_device_put(omap3evm_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit omap3evm_soc_exit(void)
|
||||
{
|
||||
platform_device_unregister(omap3evm_snd_device);
|
||||
}
|
||||
|
||||
module_init(omap3evm_soc_init);
|
||||
module_exit(omap3evm_soc_exit);
|
||||
|
||||
MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC OMAP3 EVM");
|
||||
MODULE_LICENSE("GPLv2");
|
@ -228,14 +228,14 @@ static struct snd_soc_dai_link omap3pandora_dai[] = {
|
||||
.name = "PCM1773",
|
||||
.stream_name = "HiFi Out",
|
||||
.cpu_dai = &omap_mcbsp_dai[0],
|
||||
.codec_dai = &twl4030_dai,
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
|
||||
.ops = &omap3pandora_out_ops,
|
||||
.init = omap3pandora_out_init,
|
||||
}, {
|
||||
.name = "TWL4030",
|
||||
.stream_name = "Line/Mic In",
|
||||
.cpu_dai = &omap_mcbsp_dai[1],
|
||||
.codec_dai = &twl4030_dai,
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
|
||||
.ops = &omap3pandora_in_ops,
|
||||
.init = omap3pandora_in_init,
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ static struct snd_soc_dai_link overo_dai = {
|
||||
.name = "TWL4030",
|
||||
.stream_name = "TWL4030",
|
||||
.cpu_dai = &omap_mcbsp_dai[0],
|
||||
.codec_dai = &twl4030_dai,
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
|
||||
.ops = &overo_ops,
|
||||
};
|
||||
|
||||
|
@ -84,6 +84,49 @@ static struct snd_soc_ops sdp3430_ops = {
|
||||
.hw_params = sdp3430_hw_params,
|
||||
};
|
||||
|
||||
static int sdp3430_hw_voice_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
int ret;
|
||||
|
||||
/* Set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai,
|
||||
SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFM);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "can't set codec DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai,
|
||||
SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "can't set cpu DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "can't set codec system clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops sdp3430_voice_ops = {
|
||||
.hw_params = sdp3430_hw_voice_params,
|
||||
};
|
||||
|
||||
/* Headset jack */
|
||||
static struct snd_soc_jack hs_jack;
|
||||
|
||||
@ -192,28 +235,58 @@ static int sdp3430_twl4030_init(struct snd_soc_codec *codec)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdp3430_twl4030_voice_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
unsigned short reg;
|
||||
|
||||
/* Enable voice interface */
|
||||
reg = codec->read(codec, TWL4030_REG_VOICE_IF);
|
||||
reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN;
|
||||
codec->write(codec, TWL4030_REG_VOICE_IF, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link sdp3430_dai = {
|
||||
.name = "TWL4030",
|
||||
.stream_name = "TWL4030",
|
||||
.cpu_dai = &omap_mcbsp_dai[0],
|
||||
.codec_dai = &twl4030_dai,
|
||||
.init = sdp3430_twl4030_init,
|
||||
.ops = &sdp3430_ops,
|
||||
static struct snd_soc_dai_link sdp3430_dai[] = {
|
||||
{
|
||||
.name = "TWL4030 I2S",
|
||||
.stream_name = "TWL4030 Audio",
|
||||
.cpu_dai = &omap_mcbsp_dai[0],
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
|
||||
.init = sdp3430_twl4030_init,
|
||||
.ops = &sdp3430_ops,
|
||||
},
|
||||
{
|
||||
.name = "TWL4030 PCM",
|
||||
.stream_name = "TWL4030 Voice",
|
||||
.cpu_dai = &omap_mcbsp_dai[1],
|
||||
.codec_dai = &twl4030_dai[TWL4030_DAI_VOICE],
|
||||
.init = sdp3430_twl4030_voice_init,
|
||||
.ops = &sdp3430_voice_ops,
|
||||
},
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card snd_soc_sdp3430 = {
|
||||
.name = "SDP3430",
|
||||
.platform = &omap_soc_platform,
|
||||
.dai_link = &sdp3430_dai,
|
||||
.num_links = 1,
|
||||
.dai_link = sdp3430_dai,
|
||||
.num_links = ARRAY_SIZE(sdp3430_dai),
|
||||
};
|
||||
|
||||
/* twl4030 setup */
|
||||
static struct twl4030_setup_data twl4030_setup = {
|
||||
.ramp_delay_value = 3,
|
||||
.sysclk = 26000,
|
||||
};
|
||||
|
||||
/* Audio subsystem */
|
||||
static struct snd_soc_device sdp3430_snd_devdata = {
|
||||
.card = &snd_soc_sdp3430,
|
||||
.codec_dev = &soc_codec_dev_twl4030,
|
||||
.codec_data = &twl4030_setup,
|
||||
};
|
||||
|
||||
static struct platform_device *sdp3430_snd_device;
|
||||
@ -236,7 +309,8 @@ static int __init sdp3430_soc_init(void)
|
||||
|
||||
platform_set_drvdata(sdp3430_snd_device, &sdp3430_snd_devdata);
|
||||
sdp3430_snd_devdata.dev = &sdp3430_snd_device->dev;
|
||||
*(unsigned int *)sdp3430_dai.cpu_dai->private_data = 1; /* McBSP2 */
|
||||
*(unsigned int *)sdp3430_dai[0].cpu_dai->private_data = 1; /* McBSP2 */
|
||||
*(unsigned int *)sdp3430_dai[1].cpu_dai->private_data = 2; /* McBSP3 */
|
||||
|
||||
ret = platform_device_add(sdp3430_snd_device);
|
||||
if (ret)
|
||||
|
@ -89,13 +89,13 @@ config SND_PXA2XX_SOC_E800
|
||||
Toshiba e800 PDA
|
||||
|
||||
config SND_PXA2XX_SOC_EM_X270
|
||||
tristate "SoC Audio support for CompuLab EM-x270"
|
||||
tristate "SoC Audio support for CompuLab EM-x270, eXeda and CM-X300"
|
||||
depends on SND_PXA2XX_SOC && MACH_EM_X270
|
||||
select SND_PXA2XX_SOC_AC97
|
||||
select SND_SOC_WM9712
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on
|
||||
CompuLab EM-x270.
|
||||
CompuLab EM-x270, eXeda and CM-X300 machines.
|
||||
|
||||
config SND_PXA2XX_SOC_PALM27X
|
||||
bool "SoC Audio support for Palm T|X, T5 and LifeDrive"
|
||||
@ -134,3 +134,12 @@ config SND_PXA2XX_SOC_MIOA701
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the
|
||||
MIO A701.
|
||||
|
||||
config SND_PXA2XX_SOC_IMOTE2
|
||||
tristate "SoC Audio support for IMote 2"
|
||||
depends on SND_PXA2XX_SOC && MACH_INTELMOTE2
|
||||
select SND_PXA2XX_SOC_I2S
|
||||
select SND_SOC_WM8940
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the
|
||||
IMote 2.
|
||||
|
@ -22,6 +22,7 @@ snd-soc-palm27x-objs := palm27x.o
|
||||
snd-soc-zylonite-objs := zylonite.o
|
||||
snd-soc-magician-objs := magician.o
|
||||
snd-soc-mioa701-objs := mioa701_wm9713.o
|
||||
snd-soc-imote2-objs := imote2.o
|
||||
|
||||
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o
|
||||
obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o
|
||||
@ -35,3 +36,4 @@ obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o
|
||||
obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o
|
||||
obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o
|
||||
obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o
|
||||
obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* em-x270.c -- SoC audio for EM-X270
|
||||
* SoC audio driver for EM-X270, eXeda and CM-X300
|
||||
*
|
||||
* Copyright 2007 CompuLab, Ltd.
|
||||
* Copyright 2007, 2009 CompuLab, Ltd.
|
||||
*
|
||||
* Author: Mike Rapoport <mike@compulab.co.il>
|
||||
*
|
||||
@ -68,7 +68,8 @@ static int __init em_x270_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_em_x270())
|
||||
if (!(machine_is_em_x270() || machine_is_exeda()
|
||||
|| machine_is_cm_x300()))
|
||||
return -ENODEV;
|
||||
|
||||
em_x270_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
@ -95,5 +96,5 @@ module_exit(em_x270_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Mike Rapoport");
|
||||
MODULE_DESCRIPTION("ALSA SoC EM-X270");
|
||||
MODULE_DESCRIPTION("ALSA SoC EM-X270, eXeda and CM-X300");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
114
sound/soc/pxa/imote2.c
Normal file
114
sound/soc/pxa/imote2.c
Normal file
@ -0,0 +1,114 @@
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "../codecs/wm8940.h"
|
||||
#include "pxa2xx-i2s.h"
|
||||
#include "pxa2xx-pcm.h"
|
||||
|
||||
static int imote2_asoc_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
unsigned int clk = 0;
|
||||
int ret;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 8000:
|
||||
case 16000:
|
||||
case 48000:
|
||||
case 96000:
|
||||
clk = 12288000;
|
||||
break;
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
clk = 11289600;
|
||||
break;
|
||||
}
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* CPU should be clock master */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set the I2S system clock as input (unused) */
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, clk,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imote2_asoc_ops = {
|
||||
.hw_params = imote2_asoc_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link imote2_dai = {
|
||||
.name = "WM8940",
|
||||
.stream_name = "WM8940",
|
||||
.cpu_dai = &pxa_i2s_dai,
|
||||
.codec_dai = &wm8940_dai,
|
||||
.ops = &imote2_asoc_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_imote2 = {
|
||||
.name = "Imote2",
|
||||
.platform = &pxa2xx_soc_platform,
|
||||
.dai_link = &imote2_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct snd_soc_device imote2_snd_devdata = {
|
||||
.card = &snd_soc_imote2,
|
||||
.codec_dev = &soc_codec_dev_wm8940,
|
||||
};
|
||||
|
||||
static struct platform_device *imote2_snd_device;
|
||||
|
||||
static int __init imote2_asoc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_intelmote2())
|
||||
return -ENODEV;
|
||||
imote2_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!imote2_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(imote2_snd_device, &imote2_snd_devdata);
|
||||
imote2_snd_devdata.dev = &imote2_snd_device->dev;
|
||||
ret = platform_device_add(imote2_snd_device);
|
||||
if (ret)
|
||||
platform_device_put(imote2_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(imote2_asoc_init);
|
||||
|
||||
static void __exit imote2_asoc_exit(void)
|
||||
{
|
||||
platform_device_unregister(imote2_snd_device);
|
||||
}
|
||||
module_exit(imote2_asoc_exit);
|
||||
|
||||
MODULE_AUTHOR("Jonathan Cameron");
|
||||
MODULE_DESCRIPTION("ALSA SoC Imote 2");
|
||||
MODULE_LICENSE("GPL");
|
@ -106,7 +106,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
/* 513156 Hz ~= _2_ * 8000 Hz * 32 (+0.23%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_16;
|
||||
break;
|
||||
case 32:
|
||||
default: /* 32 */
|
||||
/* 1026312 Hz ~= _2_ * 8000 Hz * 64 (+0.23%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_8;
|
||||
}
|
||||
@ -118,7 +118,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
/* 351375 Hz ~= 11025 Hz * 32 (-0.41%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_4;
|
||||
break;
|
||||
case 32:
|
||||
default: /* 32 */
|
||||
/* 702750 Hz ~= 11025 Hz * 64 (-0.41%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_2;
|
||||
}
|
||||
@ -130,7 +130,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
/* 702750 Hz ~= 22050 Hz * 32 (-0.41%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_2;
|
||||
break;
|
||||
case 32:
|
||||
default: /* 32 */
|
||||
/* 1405500 Hz ~= 22050 Hz * 64 (-0.41%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_1;
|
||||
}
|
||||
@ -142,7 +142,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
/* 1405500 Hz ~= 44100 Hz * 32 (-0.41%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_2;
|
||||
break;
|
||||
case 32:
|
||||
default: /* 32 */
|
||||
/* 2811000 Hz ~= 44100 Hz * 64 (-0.41%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_1;
|
||||
}
|
||||
@ -154,19 +154,20 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
/* 1529375 Hz ~= 48000 Hz * 32 (-0.44%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_2;
|
||||
break;
|
||||
case 32:
|
||||
default: /* 32 */
|
||||
/* 3058750 Hz ~= 48000 Hz * 64 (-0.44%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_1;
|
||||
}
|
||||
break;
|
||||
case 96000:
|
||||
default:
|
||||
acps = 12235000;
|
||||
switch (width) {
|
||||
case 16:
|
||||
/* 3058750 Hz ~= 96000 Hz * 32 (-0.44%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_1;
|
||||
break;
|
||||
case 32:
|
||||
default: /* 32 */
|
||||
/* 6117500 Hz ~= 96000 Hz * 64 (-0.44%) */
|
||||
acds = PXA_SSP_CLK_AUDIO_DIV_2;
|
||||
div4 = PXA_SSP_CLK_SCDB_1;
|
||||
|
@ -50,139 +50,6 @@ struct ssp_priv {
|
||||
#endif
|
||||
};
|
||||
|
||||
#define PXA2xx_SSP1_BASE 0x41000000
|
||||
#define PXA27x_SSP2_BASE 0x41700000
|
||||
#define PXA27x_SSP3_BASE 0x41900000
|
||||
#define PXA3xx_SSP4_BASE 0x41a00000
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_mono_out = {
|
||||
.name = "SSP1 PCM Mono out",
|
||||
.dev_addr = PXA2xx_SSP1_BASE + SSDR,
|
||||
.drcmr = &DRCMR(14),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_mono_in = {
|
||||
.name = "SSP1 PCM Mono in",
|
||||
.dev_addr = PXA2xx_SSP1_BASE + SSDR,
|
||||
.drcmr = &DRCMR(13),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_stereo_out = {
|
||||
.name = "SSP1 PCM Stereo out",
|
||||
.dev_addr = PXA2xx_SSP1_BASE + SSDR,
|
||||
.drcmr = &DRCMR(14),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_stereo_in = {
|
||||
.name = "SSP1 PCM Stereo in",
|
||||
.dev_addr = PXA2xx_SSP1_BASE + SSDR,
|
||||
.drcmr = &DRCMR(13),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_mono_out = {
|
||||
.name = "SSP2 PCM Mono out",
|
||||
.dev_addr = PXA27x_SSP2_BASE + SSDR,
|
||||
.drcmr = &DRCMR(16),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_mono_in = {
|
||||
.name = "SSP2 PCM Mono in",
|
||||
.dev_addr = PXA27x_SSP2_BASE + SSDR,
|
||||
.drcmr = &DRCMR(15),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_stereo_out = {
|
||||
.name = "SSP2 PCM Stereo out",
|
||||
.dev_addr = PXA27x_SSP2_BASE + SSDR,
|
||||
.drcmr = &DRCMR(16),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_stereo_in = {
|
||||
.name = "SSP2 PCM Stereo in",
|
||||
.dev_addr = PXA27x_SSP2_BASE + SSDR,
|
||||
.drcmr = &DRCMR(15),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_mono_out = {
|
||||
.name = "SSP3 PCM Mono out",
|
||||
.dev_addr = PXA27x_SSP3_BASE + SSDR,
|
||||
.drcmr = &DRCMR(67),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_mono_in = {
|
||||
.name = "SSP3 PCM Mono in",
|
||||
.dev_addr = PXA27x_SSP3_BASE + SSDR,
|
||||
.drcmr = &DRCMR(66),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_stereo_out = {
|
||||
.name = "SSP3 PCM Stereo out",
|
||||
.dev_addr = PXA27x_SSP3_BASE + SSDR,
|
||||
.drcmr = &DRCMR(67),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_stereo_in = {
|
||||
.name = "SSP3 PCM Stereo in",
|
||||
.dev_addr = PXA27x_SSP3_BASE + SSDR,
|
||||
.drcmr = &DRCMR(66),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_mono_out = {
|
||||
.name = "SSP4 PCM Mono out",
|
||||
.dev_addr = PXA3xx_SSP4_BASE + SSDR,
|
||||
.drcmr = &DRCMR(67),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_mono_in = {
|
||||
.name = "SSP4 PCM Mono in",
|
||||
.dev_addr = PXA3xx_SSP4_BASE + SSDR,
|
||||
.drcmr = &DRCMR(66),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH2,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_stereo_out = {
|
||||
.name = "SSP4 PCM Stereo out",
|
||||
.dev_addr = PXA3xx_SSP4_BASE + SSDR,
|
||||
.drcmr = &DRCMR(67),
|
||||
.dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_stereo_in = {
|
||||
.name = "SSP4 PCM Stereo in",
|
||||
.dev_addr = PXA3xx_SSP4_BASE + SSDR,
|
||||
.drcmr = &DRCMR(66),
|
||||
.dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
|
||||
DCMD_BURST16 | DCMD_WIDTH4,
|
||||
};
|
||||
|
||||
static void dump_registers(struct ssp_device *ssp)
|
||||
{
|
||||
dev_dbg(&ssp->pdev->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n",
|
||||
@ -194,25 +61,33 @@ static void dump_registers(struct ssp_device *ssp)
|
||||
ssp_read_reg(ssp, SSACD));
|
||||
}
|
||||
|
||||
static struct pxa2xx_pcm_dma_params *ssp_dma_params[4][4] = {
|
||||
{
|
||||
&pxa_ssp1_pcm_mono_out, &pxa_ssp1_pcm_mono_in,
|
||||
&pxa_ssp1_pcm_stereo_out, &pxa_ssp1_pcm_stereo_in,
|
||||
},
|
||||
{
|
||||
&pxa_ssp2_pcm_mono_out, &pxa_ssp2_pcm_mono_in,
|
||||
&pxa_ssp2_pcm_stereo_out, &pxa_ssp2_pcm_stereo_in,
|
||||
},
|
||||
{
|
||||
&pxa_ssp3_pcm_mono_out, &pxa_ssp3_pcm_mono_in,
|
||||
&pxa_ssp3_pcm_stereo_out, &pxa_ssp3_pcm_stereo_in,
|
||||
},
|
||||
{
|
||||
&pxa_ssp4_pcm_mono_out, &pxa_ssp4_pcm_mono_in,
|
||||
&pxa_ssp4_pcm_stereo_out, &pxa_ssp4_pcm_stereo_in,
|
||||
},
|
||||
struct pxa2xx_pcm_dma_data {
|
||||
struct pxa2xx_pcm_dma_params params;
|
||||
char name[20];
|
||||
};
|
||||
|
||||
static struct pxa2xx_pcm_dma_params *
|
||||
ssp_get_dma_params(struct ssp_device *ssp, int width4, int out)
|
||||
{
|
||||
struct pxa2xx_pcm_dma_data *dma;
|
||||
|
||||
dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL);
|
||||
if (dma == NULL)
|
||||
return NULL;
|
||||
|
||||
snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id,
|
||||
width4 ? "32-bit" : "16-bit", out ? "out" : "in");
|
||||
|
||||
dma->params.name = dma->name;
|
||||
dma->params.drcmr = &DRCMR(out ? ssp->drcmr_tx : ssp->drcmr_rx);
|
||||
dma->params.dcmd = (out ? (DCMD_INCSRCADDR | DCMD_FLOWTRG) :
|
||||
(DCMD_INCTRGADDR | DCMD_FLOWSRC)) |
|
||||
(width4 ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16;
|
||||
dma->params.dev_addr = ssp->phys_base + SSDR;
|
||||
|
||||
return &dma->params;
|
||||
}
|
||||
|
||||
static int pxa_ssp_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
@ -227,6 +102,11 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream,
|
||||
clk_enable(priv->dev.ssp->clk);
|
||||
ssp_disable(&priv->dev);
|
||||
}
|
||||
|
||||
if (cpu_dai->dma_data) {
|
||||
kfree(cpu_dai->dma_data);
|
||||
cpu_dai->dma_data = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -241,6 +121,11 @@ static void pxa_ssp_shutdown(struct snd_pcm_substream *substream,
|
||||
ssp_disable(&priv->dev);
|
||||
clk_disable(priv->dev.ssp->clk);
|
||||
}
|
||||
|
||||
if (cpu_dai->dma_data) {
|
||||
kfree(cpu_dai->dma_data);
|
||||
cpu_dai->dma_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
@ -323,7 +208,7 @@ static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS);
|
||||
|
||||
dev_dbg(&ssp->pdev->dev,
|
||||
"pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %d\n",
|
||||
"pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n",
|
||||
cpu_dai->id, clk_id, freq);
|
||||
|
||||
switch (clk_id) {
|
||||
@ -472,7 +357,7 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai,
|
||||
ssacd |= (0x6 << 4);
|
||||
|
||||
dev_dbg(&ssp->pdev->dev,
|
||||
"Using SSACDD %x to supply %dHz\n",
|
||||
"Using SSACDD %x to supply %uHz\n",
|
||||
val, freq_out);
|
||||
break;
|
||||
}
|
||||
@ -589,7 +474,10 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
sspsp |= SSPSP_SCMODE(3);
|
||||
sspsp |= SSPSP_SCMODE(2);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
@ -606,7 +494,13 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
sspsp |= SSPSP_SFRMP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
sspsp |= SSPSP_SCMODE(2);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
@ -644,25 +538,23 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct ssp_priv *priv = cpu_dai->private_data;
|
||||
struct ssp_device *ssp = priv->dev.ssp;
|
||||
int dma = 0, chn = params_channels(params);
|
||||
int chn = params_channels(params);
|
||||
u32 sscr0;
|
||||
u32 sspsp;
|
||||
int width = snd_pcm_format_physical_width(params_format(params));
|
||||
int ttsa = ssp_read_reg(ssp, SSTSA) & 0xf;
|
||||
|
||||
/* select correct DMA params */
|
||||
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dma = 1; /* capture DMA offset is 1,3 */
|
||||
/* generate correct DMA params */
|
||||
if (cpu_dai->dma_data)
|
||||
kfree(cpu_dai->dma_data);
|
||||
|
||||
/* Network mode with one active slot (ttsa == 1) can be used
|
||||
* to force 16-bit frame width on the wire (for S16_LE), even
|
||||
* with two channels. Use 16-bit DMA transfers for this case.
|
||||
*/
|
||||
if (((chn == 2) && (ttsa != 1)) || (width == 32))
|
||||
dma += 2; /* 32-bit DMA offset is 2, 16-bit is 0 */
|
||||
|
||||
cpu_dai->dma_data = ssp_dma_params[cpu_dai->id][dma];
|
||||
|
||||
dev_dbg(&ssp->pdev->dev, "pxa_ssp_hw_params: dma %d\n", dma);
|
||||
cpu_dai->dma_data = ssp_get_dma_params(ssp,
|
||||
((chn == 2) && (ttsa != 1)) || (width == 32),
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
||||
|
||||
/* we can only change the settings if the port is not in use */
|
||||
if (ssp_read_reg(ssp, SSCR0) & SSCR0_SSE)
|
||||
|
@ -106,10 +106,8 @@ static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream,
|
||||
if (IS_ERR(clk_i2s))
|
||||
return PTR_ERR(clk_i2s);
|
||||
|
||||
if (!cpu_dai->active) {
|
||||
SACR0 |= SACR0_RST;
|
||||
if (!cpu_dai->active)
|
||||
SACR0 = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -178,9 +176,7 @@ static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
|
||||
/* is port used by another stream */
|
||||
if (!(SACR0 & SACR0_ENB)) {
|
||||
|
||||
SACR0 = 0;
|
||||
SACR1 = 0;
|
||||
if (pxa_i2s.master)
|
||||
SACR0 |= SACR0_BCKD;
|
||||
|
||||
@ -226,6 +222,10 @@ static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
SACR1 &= ~SACR1_DRPL;
|
||||
else
|
||||
SACR1 &= ~SACR1_DREC;
|
||||
SACR0 |= SACR0_ENB;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
@ -252,21 +252,16 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream,
|
||||
SAIMR &= ~SAIMR_RFS;
|
||||
}
|
||||
|
||||
if (SACR1 & (SACR1_DREC | SACR1_DRPL)) {
|
||||
if ((SACR1 & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) {
|
||||
SACR0 &= ~SACR0_ENB;
|
||||
pxa_i2s_wait();
|
||||
clk_disable(clk_i2s);
|
||||
}
|
||||
|
||||
clk_put(clk_i2s);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai)
|
||||
{
|
||||
if (!dai->active)
|
||||
return 0;
|
||||
|
||||
/* store registers */
|
||||
pxa_i2s.sacr0 = SACR0;
|
||||
pxa_i2s.sacr1 = SACR1;
|
||||
@ -281,16 +276,14 @@ static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai)
|
||||
|
||||
static int pxa2xx_i2s_resume(struct snd_soc_dai *dai)
|
||||
{
|
||||
if (!dai->active)
|
||||
return 0;
|
||||
|
||||
pxa_i2s_wait();
|
||||
|
||||
SACR0 = pxa_i2s.sacr0 &= ~SACR0_ENB;
|
||||
SACR0 = pxa_i2s.sacr0 & ~SACR0_ENB;
|
||||
SACR1 = pxa_i2s.sacr1;
|
||||
SAIMR = pxa_i2s.saimr;
|
||||
SADIV = pxa_i2s.sadiv;
|
||||
SACR0 |= SACR0_ENB;
|
||||
|
||||
SACR0 = pxa_i2s.sacr0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -329,6 +322,7 @@ struct snd_soc_dai pxa_i2s_dai = {
|
||||
.rates = PXA2XX_I2S_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.ops = &pxa_i2s_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
|
||||
EXPORT_SYMBOL_GPL(pxa_i2s_dai);
|
||||
@ -346,6 +340,19 @@ static int pxa2xx_i2s_probe(struct platform_device *dev)
|
||||
if (ret != 0)
|
||||
clk_put(clk_i2s);
|
||||
|
||||
/*
|
||||
* PXA Developer's Manual:
|
||||
* If SACR0[ENB] is toggled in the middle of a normal operation,
|
||||
* the SACR0[RST] bit must also be set and cleared to reset all
|
||||
* I2S controller registers.
|
||||
*/
|
||||
SACR0 = SACR0_RST;
|
||||
SACR0 = 0;
|
||||
/* Make sure RPL and REC are disabled */
|
||||
SACR1 = SACR1_DRPL | SACR1_DREC;
|
||||
/* Along with FIFO servicing */
|
||||
SAIMR &= ~(SAIMR_RFS | SAIMR_TFS);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,20 @@
|
||||
|
||||
#include "s3c-i2s-v2.h"
|
||||
|
||||
#undef S3C_IIS_V2_SUPPORTED
|
||||
|
||||
#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413)
|
||||
#define S3C_IIS_V2_SUPPORTED
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PLAT_S3C64XX
|
||||
#define S3C_IIS_V2_SUPPORTED
|
||||
#endif
|
||||
|
||||
#ifndef S3C_IIS_V2_SUPPORTED
|
||||
#error Unsupported CPU model
|
||||
#endif
|
||||
|
||||
#define S3C2412_I2S_DEBUG_CON 0
|
||||
|
||||
static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
|
||||
@ -75,7 +89,7 @@ static inline void dbg_showcon(const char *fn, u32 con)
|
||||
|
||||
|
||||
/* Turn on or off the transmission path. */
|
||||
void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
{
|
||||
void __iomem *regs = i2s->regs;
|
||||
u32 fic, con, mod;
|
||||
@ -105,7 +119,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n");
|
||||
dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n",
|
||||
mod & S3C2412_IISMOD_MODE_MASK);
|
||||
break;
|
||||
}
|
||||
|
||||
writel(con, regs + S3C2412_IISCON);
|
||||
@ -132,7 +148,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n");
|
||||
dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n",
|
||||
mod & S3C2412_IISMOD_MODE_MASK);
|
||||
break;
|
||||
}
|
||||
|
||||
writel(mod, regs + S3C2412_IISMOD);
|
||||
@ -143,9 +161,8 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
dbg_showcon(__func__, con);
|
||||
pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s3c2412_snd_txctrl);
|
||||
|
||||
void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
{
|
||||
void __iomem *regs = i2s->regs;
|
||||
u32 fic, con, mod;
|
||||
@ -175,7 +192,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n");
|
||||
dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n",
|
||||
mod & S3C2412_IISMOD_MODE_MASK);
|
||||
}
|
||||
|
||||
writel(mod, regs + S3C2412_IISMOD);
|
||||
@ -199,7 +217,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n");
|
||||
dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n",
|
||||
mod & S3C2412_IISMOD_MODE_MASK);
|
||||
}
|
||||
|
||||
writel(con, regs + S3C2412_IISCON);
|
||||
@ -209,7 +228,6 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
|
||||
fic = readl(regs + S3C2412_IISFIC);
|
||||
pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s3c2412_snd_rxctrl);
|
||||
|
||||
/*
|
||||
* Wait for the LR signal to allow synchronisation to the L/R clock
|
||||
@ -266,7 +284,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
|
||||
*/
|
||||
#define IISMOD_MASTER_MASK (1 << 11)
|
||||
#define IISMOD_SLAVE (1 << 11)
|
||||
#define IISMOD_MASTER (0x0)
|
||||
#define IISMOD_MASTER (0 << 11)
|
||||
#endif
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
@ -281,7 +299,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
|
||||
iismod |= IISMOD_MASTER;
|
||||
break;
|
||||
default:
|
||||
pr_debug("unknwon master/slave format\n");
|
||||
pr_err("unknwon master/slave format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -298,7 +316,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
|
||||
iismod |= S3C2412_IISMOD_SDF_IIS;
|
||||
break;
|
||||
default:
|
||||
pr_debug("Unknown data format\n");
|
||||
pr_err("Unknown data format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -327,6 +345,7 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
iismod = readl(i2s->regs + S3C2412_IISMOD);
|
||||
pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
|
||||
|
||||
#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413)
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S8:
|
||||
iismod |= S3C2412_IISMOD_8BIT;
|
||||
@ -335,6 +354,25 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
iismod &= ~S3C2412_IISMOD_8BIT;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PLAT_S3C64XX
|
||||
iismod &= ~0x606;
|
||||
/* Sample size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S8:
|
||||
/* 8 bit sample, 16fs BCLK */
|
||||
iismod |= 0x2004;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
/* 16 bit sample, 32fs BCLK */
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
/* 24 bit sample, 48fs BCLK */
|
||||
iismod |= 0x4002;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
writel(iismod, i2s->regs + S3C2412_IISMOD);
|
||||
pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
|
||||
@ -489,6 +527,8 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
|
||||
unsigned int best_rate = 0;
|
||||
unsigned int best_deviation = INT_MAX;
|
||||
|
||||
pr_debug("Input clock rate %ldHz\n", clkrate);
|
||||
|
||||
if (fstab == NULL)
|
||||
fstab = iis_fs_tab;
|
||||
|
||||
@ -507,7 +547,7 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
|
||||
actual = clkrate / (fsdiv * div);
|
||||
deviation = actual - rate;
|
||||
|
||||
printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n",
|
||||
printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n",
|
||||
fsdiv, div, actual, deviation);
|
||||
|
||||
deviation = abs(deviation);
|
||||
@ -523,7 +563,7 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
|
||||
break;
|
||||
}
|
||||
|
||||
printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n",
|
||||
printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n",
|
||||
best_fs, best_div, best_rate);
|
||||
|
||||
info->fs_div = best_fs;
|
||||
@ -539,12 +579,31 @@ int s3c_i2sv2_probe(struct platform_device *pdev,
|
||||
unsigned long base)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
unsigned int iismod;
|
||||
|
||||
i2s->dev = dev;
|
||||
|
||||
/* record our i2s structure for later use in the callbacks */
|
||||
dai->private_data = i2s;
|
||||
|
||||
if (!base) {
|
||||
struct resource *res = platform_get_resource(pdev,
|
||||
IORESOURCE_MEM,
|
||||
0);
|
||||
if (!res) {
|
||||
dev_err(dev, "Unable to get register resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (!request_mem_region(res->start, resource_size(res),
|
||||
"s3c64xx-i2s-v4")) {
|
||||
dev_err(dev, "Unable to request register region\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
base = res->start;
|
||||
}
|
||||
|
||||
i2s->regs = ioremap(base, 0x100);
|
||||
if (i2s->regs == NULL) {
|
||||
dev_err(dev, "cannot ioremap registers\n");
|
||||
@ -560,12 +619,16 @@ int s3c_i2sv2_probe(struct platform_device *pdev,
|
||||
|
||||
clk_enable(i2s->iis_pclk);
|
||||
|
||||
/* Mark ourselves as in TXRX mode so we can run through our cleanup
|
||||
* process without warnings. */
|
||||
iismod = readl(i2s->regs + S3C2412_IISMOD);
|
||||
iismod |= S3C2412_IISMOD_MODE_TXRX;
|
||||
writel(iismod, i2s->regs + S3C2412_IISMOD);
|
||||
s3c2412_snd_txctrl(i2s, 0);
|
||||
s3c2412_snd_rxctrl(i2s, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(s3c_i2sv2_probe);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
@ -120,7 +120,7 @@ static int s3c2412_i2s_probe(struct platform_device *pdev,
|
||||
|
||||
s3c2412_i2s.iis_cclk = clk_get(&pdev->dev, "i2sclk");
|
||||
if (s3c2412_i2s.iis_cclk == NULL) {
|
||||
pr_debug("failed to get i2sclk clock\n");
|
||||
pr_err("failed to get i2sclk clock\n");
|
||||
iounmap(s3c2412_i2s.regs);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
@ -108,48 +108,19 @@ static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *dai)
|
||||
struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct s3c_i2sv2_info *i2s = to_info(dai);
|
||||
|
||||
return clk_get_rate(i2s->iis_cclk);
|
||||
return i2s->iis_cclk;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clockrate);
|
||||
EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock);
|
||||
|
||||
static int s3c64xx_i2s_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct s3c_i2sv2_info *i2s;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "%s: probing dai %d\n", __func__, pdev->id);
|
||||
|
||||
if (pdev->id < 0 || pdev->id > ARRAY_SIZE(s3c64xx_i2s)) {
|
||||
dev_err(dev, "id %d out of range\n", pdev->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
i2s = &s3c64xx_i2s[pdev->id];
|
||||
|
||||
ret = s3c_i2sv2_probe(pdev, dai, i2s,
|
||||
pdev->id ? S3C64XX_PA_IIS1 : S3C64XX_PA_IIS0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id];
|
||||
i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id];
|
||||
|
||||
i2s->iis_cclk = clk_get(dev, "audio-bus");
|
||||
if (IS_ERR(i2s->iis_cclk)) {
|
||||
dev_err(dev, "failed to get audio-bus");
|
||||
iounmap(i2s->regs);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* configure GPIO for i2s port */
|
||||
switch (pdev->id) {
|
||||
switch (dai->id) {
|
||||
case 0:
|
||||
s3c_gpio_cfgpin(S3C64XX_GPD(0), S3C64XX_GPD0_I2S0_CLK);
|
||||
s3c_gpio_cfgpin(S3C64XX_GPD(1), S3C64XX_GPD1_I2S0_CDCLK);
|
||||
@ -175,41 +146,122 @@ static int s3c64xx_i2s_probe(struct platform_device *pdev,
|
||||
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
|
||||
|
||||
#define S3C64XX_I2S_FMTS \
|
||||
(SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE)
|
||||
(SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops = {
|
||||
.set_sysclk = s3c64xx_i2s_set_sysclk,
|
||||
};
|
||||
|
||||
struct snd_soc_dai s3c64xx_i2s_dai = {
|
||||
.name = "s3c64xx-i2s",
|
||||
.id = 0,
|
||||
.probe = s3c64xx_i2s_probe,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = S3C64XX_I2S_RATES,
|
||||
.formats = S3C64XX_I2S_FMTS,
|
||||
struct snd_soc_dai s3c64xx_i2s_dai[] = {
|
||||
{
|
||||
.name = "s3c64xx-i2s",
|
||||
.id = 0,
|
||||
.probe = s3c64xx_i2s_probe,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = S3C64XX_I2S_RATES,
|
||||
.formats = S3C64XX_I2S_FMTS,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = S3C64XX_I2S_RATES,
|
||||
.formats = S3C64XX_I2S_FMTS,
|
||||
},
|
||||
.ops = &s3c64xx_i2s_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = S3C64XX_I2S_RATES,
|
||||
.formats = S3C64XX_I2S_FMTS,
|
||||
{
|
||||
.name = "s3c64xx-i2s",
|
||||
.id = 1,
|
||||
.probe = s3c64xx_i2s_probe,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = S3C64XX_I2S_RATES,
|
||||
.formats = S3C64XX_I2S_FMTS,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = S3C64XX_I2S_RATES,
|
||||
.formats = S3C64XX_I2S_FMTS,
|
||||
},
|
||||
.ops = &s3c64xx_i2s_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
},
|
||||
.ops = &s3c64xx_i2s_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai);
|
||||
|
||||
static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct s3c_i2sv2_info *i2s;
|
||||
struct snd_soc_dai *dai;
|
||||
int ret;
|
||||
|
||||
if (pdev->id >= ARRAY_SIZE(s3c64xx_i2s)) {
|
||||
dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
i2s = &s3c64xx_i2s[pdev->id];
|
||||
dai = &s3c64xx_i2s_dai[pdev->id];
|
||||
dai->dev = &pdev->dev;
|
||||
|
||||
i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id];
|
||||
i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id];
|
||||
|
||||
i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus");
|
||||
if (IS_ERR(i2s->iis_cclk)) {
|
||||
dev_err(&pdev->dev, "failed to get audio-bus\n");
|
||||
ret = PTR_ERR(i2s->iis_cclk);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = s3c_i2sv2_probe(pdev, dai, i2s, 0);
|
||||
if (ret)
|
||||
goto err_clk;
|
||||
|
||||
ret = s3c_i2sv2_register_dai(dai);
|
||||
if (ret != 0)
|
||||
goto err_i2sv2;
|
||||
|
||||
return 0;
|
||||
|
||||
err_i2sv2:
|
||||
/* Not implemented for I2Sv2 core yet */
|
||||
err_clk:
|
||||
clk_put(i2s->iis_cclk);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __devexit int s3c64xx_iis_dev_remove(struct platform_device *pdev)
|
||||
{
|
||||
dev_err(&pdev->dev, "Device removal not yet supported\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver s3c64xx_iis_driver = {
|
||||
.probe = s3c64xx_iis_dev_probe,
|
||||
.remove = s3c64xx_iis_dev_remove,
|
||||
.driver = {
|
||||
.name = "s3c64xx-iis",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s3c64xx_i2s_init(void)
|
||||
{
|
||||
return s3c_i2sv2_register_dai(&s3c64xx_i2s_dai);
|
||||
return platform_driver_register(&s3c64xx_iis_driver);
|
||||
}
|
||||
module_init(s3c64xx_i2s_init);
|
||||
|
||||
static void __exit s3c64xx_i2s_exit(void)
|
||||
{
|
||||
snd_soc_unregister_dai(&s3c64xx_i2s_dai);
|
||||
platform_driver_unregister(&s3c64xx_iis_driver);
|
||||
}
|
||||
module_exit(s3c64xx_i2s_exit);
|
||||
|
||||
@ -217,6 +269,3 @@ module_exit(s3c64xx_i2s_exit);
|
||||
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
|
||||
MODULE_DESCRIPTION("S3C64XX I2S SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#ifndef __SND_SOC_S3C24XX_S3C64XX_I2S_H
|
||||
#define __SND_SOC_S3C24XX_S3C64XX_I2S_H __FILE__
|
||||
|
||||
struct clk;
|
||||
|
||||
#include "s3c-i2s-v2.h"
|
||||
|
||||
#define S3C64XX_DIV_BCLK S3C_I2SV2_DIV_BCLK
|
||||
@ -24,8 +26,8 @@
|
||||
#define S3C64XX_CLKSRC_PCLK (0)
|
||||
#define S3C64XX_CLKSRC_MUX (1)
|
||||
|
||||
extern struct snd_soc_dai s3c64xx_i2s_dai;
|
||||
extern struct snd_soc_dai s3c64xx_i2s_dai[];
|
||||
|
||||
extern unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *cpu_dai);
|
||||
extern struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai);
|
||||
|
||||
#endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */
|
||||
|
19
sound/soc/s6000/Kconfig
Normal file
19
sound/soc/s6000/Kconfig
Normal file
@ -0,0 +1,19 @@
|
||||
config SND_S6000_SOC
|
||||
tristate "SoC Audio for the Stretch s6000 family"
|
||||
depends on XTENSA_VARIANT_S6000
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
s6000 family chips. You will also need to select the platform
|
||||
to support below.
|
||||
|
||||
config SND_S6000_SOC_I2S
|
||||
tristate
|
||||
|
||||
config SND_S6000_SOC_S6IPCAM
|
||||
tristate "SoC Audio support for Stretch 6105 IP Camera"
|
||||
depends on SND_S6000_SOC && XTENSA_PLATFORM_S6105
|
||||
select SND_S6000_SOC_I2S
|
||||
select SND_SOC_TLV320AIC3X
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the
|
||||
Stretch s6105 IP Camera Reference Design.
|
11
sound/soc/s6000/Makefile
Normal file
11
sound/soc/s6000/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
# s6000 Platform Support
|
||||
snd-soc-s6000-objs := s6000-pcm.o
|
||||
snd-soc-s6000-i2s-objs := s6000-i2s.o
|
||||
|
||||
obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o
|
||||
obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o
|
||||
|
||||
# s6105 Machine Support
|
||||
snd-soc-s6ipcam-objs := s6105-ipcam.o
|
||||
|
||||
obj-$(CONFIG_SND_S6000_SOC_S6IPCAM) += snd-soc-s6ipcam.o
|
629
sound/soc/s6000/s6000-i2s.c
Normal file
629
sound/soc/s6000/s6000-i2s.c
Normal file
@ -0,0 +1,629 @@
|
||||
/*
|
||||
* ALSA SoC I2S Audio Layer for the Stretch S6000 family
|
||||
*
|
||||
* Author: Daniel Gloeckner, <dg@emlix.com>
|
||||
* Copyright: (C) 2009 emlix GmbH <info@emlix.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "s6000-i2s.h"
|
||||
#include "s6000-pcm.h"
|
||||
|
||||
struct s6000_i2s_dev {
|
||||
dma_addr_t sifbase;
|
||||
u8 __iomem *scbbase;
|
||||
unsigned int wide;
|
||||
unsigned int channel_in;
|
||||
unsigned int channel_out;
|
||||
unsigned int lines_in;
|
||||
unsigned int lines_out;
|
||||
struct s6000_pcm_dma_params dma_params;
|
||||
};
|
||||
|
||||
#define S6_I2S_INTERRUPT_STATUS 0x00
|
||||
#define S6_I2S_INT_OVERRUN 1
|
||||
#define S6_I2S_INT_UNDERRUN 2
|
||||
#define S6_I2S_INT_ALIGNMENT 4
|
||||
#define S6_I2S_INTERRUPT_ENABLE 0x04
|
||||
#define S6_I2S_INTERRUPT_RAW 0x08
|
||||
#define S6_I2S_INTERRUPT_CLEAR 0x0C
|
||||
#define S6_I2S_INTERRUPT_SET 0x10
|
||||
#define S6_I2S_MODE 0x20
|
||||
#define S6_I2S_DUAL 0
|
||||
#define S6_I2S_WIDE 1
|
||||
#define S6_I2S_TX_DEFAULT 0x24
|
||||
#define S6_I2S_DATA_CFG(c) (0x40 + 0x10 * (c))
|
||||
#define S6_I2S_IN 0
|
||||
#define S6_I2S_OUT 1
|
||||
#define S6_I2S_UNUSED 2
|
||||
#define S6_I2S_INTERFACE_CFG(c) (0x44 + 0x10 * (c))
|
||||
#define S6_I2S_DIV_MASK 0x001fff
|
||||
#define S6_I2S_16BIT 0x000000
|
||||
#define S6_I2S_20BIT 0x002000
|
||||
#define S6_I2S_24BIT 0x004000
|
||||
#define S6_I2S_32BIT 0x006000
|
||||
#define S6_I2S_BITS_MASK 0x006000
|
||||
#define S6_I2S_MEM_16BIT 0x000000
|
||||
#define S6_I2S_MEM_32BIT 0x008000
|
||||
#define S6_I2S_MEM_MASK 0x008000
|
||||
#define S6_I2S_CHANNELS_SHIFT 16
|
||||
#define S6_I2S_CHANNELS_MASK 0x030000
|
||||
#define S6_I2S_SCK_IN 0x000000
|
||||
#define S6_I2S_SCK_OUT 0x040000
|
||||
#define S6_I2S_SCK_DIR 0x040000
|
||||
#define S6_I2S_WS_IN 0x000000
|
||||
#define S6_I2S_WS_OUT 0x080000
|
||||
#define S6_I2S_WS_DIR 0x080000
|
||||
#define S6_I2S_LEFT_FIRST 0x000000
|
||||
#define S6_I2S_RIGHT_FIRST 0x100000
|
||||
#define S6_I2S_FIRST 0x100000
|
||||
#define S6_I2S_CUR_SCK 0x200000
|
||||
#define S6_I2S_CUR_WS 0x400000
|
||||
#define S6_I2S_ENABLE(c) (0x48 + 0x10 * (c))
|
||||
#define S6_I2S_DISABLE_IF 0x02
|
||||
#define S6_I2S_ENABLE_IF 0x03
|
||||
#define S6_I2S_IS_BUSY 0x04
|
||||
#define S6_I2S_DMA_ACTIVE 0x08
|
||||
#define S6_I2S_IS_ENABLED 0x10
|
||||
|
||||
#define S6_I2S_NUM_LINES 4
|
||||
|
||||
#define S6_I2S_SIF_PORT0 0x0000000
|
||||
#define S6_I2S_SIF_PORT1 0x0000080 /* docs say 0x0000010 */
|
||||
|
||||
static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val)
|
||||
{
|
||||
writel(val, dev->scbbase + reg);
|
||||
}
|
||||
|
||||
static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg)
|
||||
{
|
||||
return readl(dev->scbbase + reg);
|
||||
}
|
||||
|
||||
static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg,
|
||||
u32 mask, u32 val)
|
||||
{
|
||||
val ^= s6_i2s_read_reg(dev, reg) & ~mask;
|
||||
s6_i2s_write_reg(dev, reg, val);
|
||||
}
|
||||
|
||||
static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel)
|
||||
{
|
||||
int i, j, cur, prev;
|
||||
|
||||
/*
|
||||
* Wait for WCLK to toggle 5 times before enabling the channel
|
||||
* s6000 Family Datasheet 3.6.4:
|
||||
* "At least two cycles of WS must occur between commands
|
||||
* to disable or enable the interface"
|
||||
*/
|
||||
j = 0;
|
||||
prev = ~S6_I2S_CUR_WS;
|
||||
for (i = 1000000; --i && j < 6; ) {
|
||||
cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel))
|
||||
& S6_I2S_CUR_WS;
|
||||
if (prev != cur) {
|
||||
prev = cur;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
if (j < 6)
|
||||
printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n");
|
||||
|
||||
s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF);
|
||||
}
|
||||
|
||||
static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel)
|
||||
{
|
||||
s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF);
|
||||
}
|
||||
|
||||
static void s6000_i2s_start(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data;
|
||||
int channel;
|
||||
|
||||
channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
dev->channel_out : dev->channel_in;
|
||||
|
||||
s6000_i2s_start_channel(dev, channel);
|
||||
}
|
||||
|
||||
static void s6000_i2s_stop(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data;
|
||||
int channel;
|
||||
|
||||
channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
dev->channel_out : dev->channel_in;
|
||||
|
||||
s6000_i2s_stop_channel(dev, channel);
|
||||
}
|
||||
|
||||
static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
int after)
|
||||
{
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after)
|
||||
s6000_i2s_start(substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (!after)
|
||||
s6000_i2s_stop(substream);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev)
|
||||
{
|
||||
unsigned int pending;
|
||||
pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW);
|
||||
pending &= S6_I2S_INT_ALIGNMENT |
|
||||
S6_I2S_INT_UNDERRUN |
|
||||
S6_I2S_INT_OVERRUN;
|
||||
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending);
|
||||
|
||||
return pending;
|
||||
}
|
||||
|
||||
static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct s6000_i2s_dev *dev = cpu_dai->private_data;
|
||||
unsigned int errors;
|
||||
unsigned int ret;
|
||||
|
||||
errors = s6000_i2s_int_sources(dev);
|
||||
if (likely(!errors))
|
||||
return 0;
|
||||
|
||||
ret = 0;
|
||||
if (errors & S6_I2S_INT_ALIGNMENT)
|
||||
printk(KERN_ERR "s6000-i2s: WCLK misaligned\n");
|
||||
if (errors & S6_I2S_INT_UNDERRUN)
|
||||
ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK;
|
||||
if (errors & S6_I2S_INT_OVERRUN)
|
||||
ret |= 1 << SNDRV_PCM_STREAM_CAPTURE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev)
|
||||
{
|
||||
int channel;
|
||||
int n = 50;
|
||||
for (channel = 0; channel < 2; channel++) {
|
||||
while (--n >= 0) {
|
||||
int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel));
|
||||
if ((v & S6_I2S_IS_ENABLED)
|
||||
|| !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY)))
|
||||
break;
|
||||
udelay(20);
|
||||
}
|
||||
}
|
||||
if (n < 0)
|
||||
printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces");
|
||||
}
|
||||
|
||||
static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct s6000_i2s_dev *dev = cpu_dai->private_data;
|
||||
u32 w;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
w = S6_I2S_SCK_IN | S6_I2S_WS_IN;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFM:
|
||||
w = S6_I2S_SCK_OUT | S6_I2S_WS_IN;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
w = S6_I2S_SCK_IN | S6_I2S_WS_OUT;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
w |= S6_I2S_LEFT_FIRST;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
w |= S6_I2S_RIGHT_FIRST;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0),
|
||||
S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
|
||||
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1),
|
||||
S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
|
||||
{
|
||||
struct s6000_i2s_dev *dev = dai->private_data;
|
||||
|
||||
if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2)
|
||||
return -EINVAL;
|
||||
|
||||
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id),
|
||||
S6_I2S_DIV_MASK, div / 2 - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s6000_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct s6000_i2s_dev *dev = dai->private_data;
|
||||
int interf;
|
||||
u32 w = 0;
|
||||
|
||||
if (dev->wide)
|
||||
interf = 0;
|
||||
else {
|
||||
w |= (((params_channels(params) - 2) / 2)
|
||||
<< S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK;
|
||||
interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
? dev->channel_out : dev->channel_in;
|
||||
}
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT;
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n",
|
||||
params_format(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf))
|
||||
& S6_I2S_IS_ENABLED) {
|
||||
printk(KERN_ERR "s6000-i2s: interface already enabled\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf),
|
||||
S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK,
|
||||
w);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s6000_i2s_dai_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct s6000_i2s_dev *dev = dai->private_data;
|
||||
struct s6000_snd_platform_data *pdata = pdev->dev.platform_data;
|
||||
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
|
||||
dev->wide = pdata->wide;
|
||||
dev->channel_in = pdata->channel_in;
|
||||
dev->channel_out = pdata->channel_out;
|
||||
dev->lines_in = pdata->lines_in;
|
||||
dev->lines_out = pdata->lines_out;
|
||||
|
||||
s6_i2s_write_reg(dev, S6_I2S_MODE,
|
||||
dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL);
|
||||
|
||||
if (dev->wide) {
|
||||
int i;
|
||||
|
||||
if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES)
|
||||
return -EINVAL;
|
||||
|
||||
dev->channel_in = 0;
|
||||
dev->channel_out = 1;
|
||||
dai->capture.channels_min = 2 * dev->lines_in;
|
||||
dai->capture.channels_max = dai->capture.channels_min;
|
||||
dai->playback.channels_min = 2 * dev->lines_out;
|
||||
dai->playback.channels_max = dai->playback.channels_min;
|
||||
|
||||
for (i = 0; i < dev->lines_out; i++)
|
||||
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT);
|
||||
|
||||
for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++)
|
||||
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i),
|
||||
S6_I2S_UNUSED);
|
||||
|
||||
for (; i < S6_I2S_NUM_LINES; i++)
|
||||
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN);
|
||||
} else {
|
||||
unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED};
|
||||
|
||||
if (dev->lines_in > 1 || dev->lines_out > 1)
|
||||
return -EINVAL;
|
||||
|
||||
dai->capture.channels_min = 2 * dev->lines_in;
|
||||
dai->capture.channels_max = 8 * dev->lines_in;
|
||||
dai->playback.channels_min = 2 * dev->lines_out;
|
||||
dai->playback.channels_max = 8 * dev->lines_out;
|
||||
|
||||
if (dev->lines_in)
|
||||
cfg[dev->channel_in] = S6_I2S_IN;
|
||||
if (dev->lines_out)
|
||||
cfg[dev->channel_out] = S6_I2S_OUT;
|
||||
|
||||
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]);
|
||||
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]);
|
||||
}
|
||||
|
||||
if (dev->lines_out) {
|
||||
if (dev->lines_in) {
|
||||
if (!dev->dma_params.dma_out)
|
||||
return -ENODEV;
|
||||
} else {
|
||||
dev->dma_params.dma_out = dev->dma_params.dma_in;
|
||||
dev->dma_params.dma_in = 0;
|
||||
}
|
||||
}
|
||||
dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ?
|
||||
S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
|
||||
dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ?
|
||||
S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
|
||||
dev->dma_params.same_rate = pdata->same_rate | pdata->wide;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
|
||||
SNDRV_PCM_RATE_8000_192000)
|
||||
#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
static struct snd_soc_dai_ops s6000_i2s_dai_ops = {
|
||||
.set_fmt = s6000_i2s_set_dai_fmt,
|
||||
.set_clkdiv = s6000_i2s_set_clkdiv,
|
||||
.hw_params = s6000_i2s_hw_params,
|
||||
};
|
||||
|
||||
struct snd_soc_dai s6000_i2s_dai = {
|
||||
.name = "s6000-i2s",
|
||||
.id = 0,
|
||||
.probe = s6000_i2s_dai_probe,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.formats = S6000_I2S_FORMATS,
|
||||
.rates = S6000_I2S_RATES,
|
||||
.rate_min = 0,
|
||||
.rate_max = 1562500,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.formats = S6000_I2S_FORMATS,
|
||||
.rates = S6000_I2S_RATES,
|
||||
.rate_min = 0,
|
||||
.rate_max = 1562500,
|
||||
},
|
||||
.ops = &s6000_i2s_dai_ops,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s6000_i2s_dai);
|
||||
|
||||
static int __devinit s6000_i2s_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct s6000_i2s_dev *dev;
|
||||
struct resource *scbmem, *sifmem, *region, *dma1, *dma2;
|
||||
u8 __iomem *mmio;
|
||||
int ret;
|
||||
|
||||
scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!scbmem) {
|
||||
dev_err(&pdev->dev, "no mem resource?\n");
|
||||
ret = -ENODEV;
|
||||
goto err_release_none;
|
||||
}
|
||||
|
||||
region = request_mem_region(scbmem->start,
|
||||
scbmem->end - scbmem->start + 1,
|
||||
pdev->name);
|
||||
if (!region) {
|
||||
dev_err(&pdev->dev, "I2S SCB region already claimed\n");
|
||||
ret = -EBUSY;
|
||||
goto err_release_none;
|
||||
}
|
||||
|
||||
mmio = ioremap(scbmem->start, scbmem->end - scbmem->start + 1);
|
||||
if (!mmio) {
|
||||
dev_err(&pdev->dev, "can't ioremap SCB region\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_release_scb;
|
||||
}
|
||||
|
||||
sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
if (!sifmem) {
|
||||
dev_err(&pdev->dev, "no second mem resource?\n");
|
||||
ret = -ENODEV;
|
||||
goto err_release_map;
|
||||
}
|
||||
|
||||
region = request_mem_region(sifmem->start,
|
||||
sifmem->end - sifmem->start + 1,
|
||||
pdev->name);
|
||||
if (!region) {
|
||||
dev_err(&pdev->dev, "I2S SIF region already claimed\n");
|
||||
ret = -EBUSY;
|
||||
goto err_release_map;
|
||||
}
|
||||
|
||||
dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!dma1) {
|
||||
dev_err(&pdev->dev, "no dma resource?\n");
|
||||
ret = -ENODEV;
|
||||
goto err_release_sif;
|
||||
}
|
||||
|
||||
region = request_mem_region(dma1->start, dma1->end - dma1->start + 1,
|
||||
pdev->name);
|
||||
if (!region) {
|
||||
dev_err(&pdev->dev, "I2S DMA region already claimed\n");
|
||||
ret = -EBUSY;
|
||||
goto err_release_sif;
|
||||
}
|
||||
|
||||
dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1);
|
||||
if (dma2) {
|
||||
region = request_mem_region(dma2->start,
|
||||
dma2->end - dma2->start + 1,
|
||||
pdev->name);
|
||||
if (!region) {
|
||||
dev_err(&pdev->dev,
|
||||
"I2S DMA region already claimed\n");
|
||||
ret = -EBUSY;
|
||||
goto err_release_dma1;
|
||||
}
|
||||
}
|
||||
|
||||
dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL);
|
||||
if (!dev) {
|
||||
ret = -ENOMEM;
|
||||
goto err_release_dma2;
|
||||
}
|
||||
|
||||
s6000_i2s_dai.dev = &pdev->dev;
|
||||
s6000_i2s_dai.private_data = dev;
|
||||
s6000_i2s_dai.dma_data = &dev->dma_params;
|
||||
|
||||
dev->sifbase = sifmem->start;
|
||||
dev->scbbase = mmio;
|
||||
|
||||
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
|
||||
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR,
|
||||
S6_I2S_INT_ALIGNMENT |
|
||||
S6_I2S_INT_UNDERRUN |
|
||||
S6_I2S_INT_OVERRUN);
|
||||
|
||||
s6000_i2s_stop_channel(dev, 0);
|
||||
s6000_i2s_stop_channel(dev, 1);
|
||||
s6000_i2s_wait_disabled(dev);
|
||||
|
||||
dev->dma_params.check_xrun = s6000_i2s_check_xrun;
|
||||
dev->dma_params.trigger = s6000_i2s_trigger;
|
||||
dev->dma_params.dma_in = dma1->start;
|
||||
dev->dma_params.dma_out = dma2 ? dma2->start : 0;
|
||||
dev->dma_params.irq = platform_get_irq(pdev, 0);
|
||||
if (dev->dma_params.irq < 0) {
|
||||
dev_err(&pdev->dev, "no irq resource?\n");
|
||||
ret = -ENODEV;
|
||||
goto err_release_dev;
|
||||
}
|
||||
|
||||
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE,
|
||||
S6_I2S_INT_ALIGNMENT |
|
||||
S6_I2S_INT_UNDERRUN |
|
||||
S6_I2S_INT_OVERRUN);
|
||||
|
||||
ret = snd_soc_register_dai(&s6000_i2s_dai);
|
||||
if (ret)
|
||||
goto err_release_dev;
|
||||
|
||||
return 0;
|
||||
|
||||
err_release_dev:
|
||||
kfree(dev);
|
||||
err_release_dma2:
|
||||
if (dma2)
|
||||
release_mem_region(dma2->start, dma2->end - dma2->start + 1);
|
||||
err_release_dma1:
|
||||
release_mem_region(dma1->start, dma1->end - dma1->start + 1);
|
||||
err_release_sif:
|
||||
release_mem_region(sifmem->start, (sifmem->end - sifmem->start) + 1);
|
||||
err_release_map:
|
||||
iounmap(mmio);
|
||||
err_release_scb:
|
||||
release_mem_region(scbmem->start, (scbmem->end - scbmem->start) + 1);
|
||||
err_release_none:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __devexit s6000_i2s_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct s6000_i2s_dev *dev = s6000_i2s_dai.private_data;
|
||||
struct resource *region;
|
||||
void __iomem *mmio = dev->scbbase;
|
||||
|
||||
snd_soc_unregister_dai(&s6000_i2s_dai);
|
||||
|
||||
s6000_i2s_stop_channel(dev, 0);
|
||||
s6000_i2s_stop_channel(dev, 1);
|
||||
|
||||
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
|
||||
s6000_i2s_dai.private_data = 0;
|
||||
kfree(dev);
|
||||
|
||||
region = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
release_mem_region(region->start, region->end - region->start + 1);
|
||||
|
||||
region = platform_get_resource(pdev, IORESOURCE_DMA, 1);
|
||||
if (region)
|
||||
release_mem_region(region->start,
|
||||
region->end - region->start + 1);
|
||||
|
||||
region = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
release_mem_region(region->start, (region->end - region->start) + 1);
|
||||
|
||||
iounmap(mmio);
|
||||
region = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_mem_region(region->start, (region->end - region->start) + 1);
|
||||
}
|
||||
|
||||
static struct platform_driver s6000_i2s_driver = {
|
||||
.probe = s6000_i2s_probe,
|
||||
.remove = __devexit_p(s6000_i2s_remove),
|
||||
.driver = {
|
||||
.name = "s6000-i2s",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s6000_i2s_init(void)
|
||||
{
|
||||
return platform_driver_register(&s6000_i2s_driver);
|
||||
}
|
||||
module_init(s6000_i2s_init);
|
||||
|
||||
static void __exit s6000_i2s_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s6000_i2s_driver);
|
||||
}
|
||||
module_exit(s6000_i2s_exit);
|
||||
|
||||
MODULE_AUTHOR("Daniel Gloeckner");
|
||||
MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
25
sound/soc/s6000/s6000-i2s.h
Normal file
25
sound/soc/s6000/s6000-i2s.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* ALSA SoC I2S Audio Layer for the Stretch s6000 family
|
||||
*
|
||||
* Author: Daniel Gloeckner, <dg@emlix.com>
|
||||
* Copyright: (C) 2009 emlix GmbH <info@emlix.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _S6000_I2S_H
|
||||
#define _S6000_I2S_H
|
||||
|
||||
extern struct snd_soc_dai s6000_i2s_dai;
|
||||
|
||||
struct s6000_snd_platform_data {
|
||||
int lines_in;
|
||||
int lines_out;
|
||||
int channel_in;
|
||||
int channel_out;
|
||||
int wide;
|
||||
int same_rate;
|
||||
};
|
||||
#endif
|
497
sound/soc/s6000/s6000-pcm.c
Normal file
497
sound/soc/s6000/s6000-pcm.c
Normal file
@ -0,0 +1,497 @@
|
||||
/*
|
||||
* ALSA PCM interface for the Stetch s6000 family
|
||||
*
|
||||
* Author: Daniel Gloeckner, <dg@emlix.com>
|
||||
* Copyright: (C) 2009 emlix GmbH <info@emlix.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/dma.h>
|
||||
#include <variant/dmac.h>
|
||||
|
||||
#include "s6000-pcm.h"
|
||||
|
||||
#define S6_PCM_PREALLOCATE_SIZE (96 * 1024)
|
||||
#define S6_PCM_PREALLOCATE_MAX (2048 * 1024)
|
||||
|
||||
static struct snd_pcm_hardware s6000_pcm_hardware = {
|
||||
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX),
|
||||
.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
|
||||
.rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
|
||||
SNDRV_PCM_RATE_8000_192000),
|
||||
.rate_min = 0,
|
||||
.rate_max = 1562500,
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.buffer_bytes_max = 0x7ffffff0,
|
||||
.period_bytes_min = 16,
|
||||
.period_bytes_max = 0xfffff0,
|
||||
.periods_min = 2,
|
||||
.periods_max = 1024, /* no limit */
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
struct s6000_runtime_data {
|
||||
spinlock_t lock;
|
||||
int period; /* current DMA period */
|
||||
};
|
||||
|
||||
static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct s6000_runtime_data *prtd = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
int channel;
|
||||
unsigned int period_size;
|
||||
unsigned int dma_offset;
|
||||
dma_addr_t dma_pos;
|
||||
dma_addr_t src, dst;
|
||||
|
||||
period_size = snd_pcm_lib_period_bytes(substream);
|
||||
dma_offset = prtd->period * period_size;
|
||||
dma_pos = runtime->dma_addr + dma_offset;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
src = dma_pos;
|
||||
dst = par->sif_out;
|
||||
channel = par->dma_out;
|
||||
} else {
|
||||
src = par->sif_in;
|
||||
dst = dma_pos;
|
||||
channel = par->dma_in;
|
||||
}
|
||||
|
||||
if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel),
|
||||
DMA_INDEX_CHNL(channel)))
|
||||
return;
|
||||
|
||||
if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) {
|
||||
printk(KERN_ERR "s6000-pcm: fifo full\n");
|
||||
return;
|
||||
}
|
||||
|
||||
BUG_ON(period_size & 15);
|
||||
s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel),
|
||||
src, dst, period_size);
|
||||
|
||||
prtd->period++;
|
||||
if (unlikely(prtd->period >= runtime->periods))
|
||||
prtd->period = 0;
|
||||
}
|
||||
|
||||
static irqreturn_t s6000_pcm_irq(int irq, void *data)
|
||||
{
|
||||
struct snd_pcm *pcm = data;
|
||||
struct snd_soc_pcm_runtime *runtime = pcm->private_data;
|
||||
struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
|
||||
struct s6000_runtime_data *prtd;
|
||||
unsigned int has_xrun;
|
||||
int i, ret = IRQ_NONE;
|
||||
u32 channel[2] = {
|
||||
[SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out,
|
||||
[SNDRV_PCM_STREAM_CAPTURE] = params->dma_in
|
||||
};
|
||||
|
||||
has_xrun = params->check_xrun(runtime->dai->cpu_dai);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(channel); ++i) {
|
||||
struct snd_pcm_substream *substream = pcm->streams[i].substream;
|
||||
unsigned int pending;
|
||||
|
||||
if (!channel[i])
|
||||
continue;
|
||||
|
||||
if (unlikely(has_xrun & (1 << i)) &&
|
||||
substream->runtime &&
|
||||
snd_pcm_running(substream)) {
|
||||
dev_dbg(pcm->dev, "xrun\n");
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
pending = s6dmac_int_sources(DMA_MASK_DMAC(channel[i]),
|
||||
DMA_INDEX_CHNL(channel[i]));
|
||||
|
||||
if (pending & 1) {
|
||||
ret = IRQ_HANDLED;
|
||||
if (likely(substream->runtime &&
|
||||
snd_pcm_running(substream))) {
|
||||
snd_pcm_period_elapsed(substream);
|
||||
dev_dbg(pcm->dev, "period elapsed %x %x\n",
|
||||
s6dmac_cur_src(DMA_MASK_DMAC(channel[i]),
|
||||
DMA_INDEX_CHNL(channel[i])),
|
||||
s6dmac_cur_dst(DMA_MASK_DMAC(channel[i]),
|
||||
DMA_INDEX_CHNL(channel[i])));
|
||||
prtd = substream->runtime->private_data;
|
||||
spin_lock(&prtd->lock);
|
||||
s6000_pcm_enqueue_dma(substream);
|
||||
spin_unlock(&prtd->lock);
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(pending & ~7)) {
|
||||
if (pending & (1 << 3))
|
||||
printk(KERN_WARNING
|
||||
"s6000-pcm: DMA %x Underflow\n",
|
||||
channel[i]);
|
||||
if (pending & (1 << 4))
|
||||
printk(KERN_WARNING
|
||||
"s6000-pcm: DMA %x Overflow\n",
|
||||
channel[i]);
|
||||
if (pending & 0x1e0)
|
||||
printk(KERN_WARNING
|
||||
"s6000-pcm: DMA %x Master Error "
|
||||
"(mask %x)\n",
|
||||
channel[i], pending >> 5);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s6000_pcm_start(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct s6000_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
unsigned long flags;
|
||||
int srcinc;
|
||||
u32 dma;
|
||||
|
||||
spin_lock_irqsave(&prtd->lock, flags);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
srcinc = 1;
|
||||
dma = par->dma_out;
|
||||
} else {
|
||||
srcinc = 0;
|
||||
dma = par->dma_in;
|
||||
}
|
||||
s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma),
|
||||
1 /* priority 1 (0 is max) */,
|
||||
0 /* peripheral requests w/o xfer length mode */,
|
||||
srcinc /* source address increment */,
|
||||
srcinc^1 /* destination address increment */,
|
||||
0 /* chunksize 0 (skip impossible on this dma) */,
|
||||
0 /* source skip after chunk (impossible) */,
|
||||
0 /* destination skip after chunk (impossible) */,
|
||||
4 /* 16 byte burst size */,
|
||||
-1 /* don't conserve bandwidth */,
|
||||
0 /* low watermark irq descriptor theshold */,
|
||||
0 /* disable hardware timestamps */,
|
||||
1 /* enable channel */);
|
||||
|
||||
s6000_pcm_enqueue_dma(substream);
|
||||
s6000_pcm_enqueue_dma(substream);
|
||||
|
||||
spin_unlock_irqrestore(&prtd->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s6000_pcm_stop(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct s6000_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
unsigned long flags;
|
||||
u32 channel;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
channel = par->dma_out;
|
||||
else
|
||||
channel = par->dma_in;
|
||||
|
||||
s6dmac_set_terminal_count(DMA_MASK_DMAC(channel),
|
||||
DMA_INDEX_CHNL(channel), 0);
|
||||
|
||||
spin_lock_irqsave(&prtd->lock, flags);
|
||||
|
||||
s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel));
|
||||
|
||||
spin_unlock_irqrestore(&prtd->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
int ret;
|
||||
|
||||
ret = par->trigger(substream, cmd, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ret = s6000_pcm_start(substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
ret = s6000_pcm_stop(substream);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return par->trigger(substream, cmd, 1);
|
||||
}
|
||||
|
||||
static int s6000_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct s6000_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
prtd->period = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct s6000_runtime_data *prtd = runtime->private_data;
|
||||
unsigned long flags;
|
||||
unsigned int offset;
|
||||
dma_addr_t count;
|
||||
|
||||
spin_lock_irqsave(&prtd->lock, flags);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out),
|
||||
DMA_INDEX_CHNL(par->dma_out));
|
||||
else
|
||||
count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in),
|
||||
DMA_INDEX_CHNL(par->dma_in));
|
||||
|
||||
count -= runtime->dma_addr;
|
||||
|
||||
spin_unlock_irqrestore(&prtd->lock, flags);
|
||||
|
||||
offset = bytes_to_frames(runtime, count);
|
||||
if (unlikely(offset >= runtime->buffer_size))
|
||||
offset = 0;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int s6000_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct s6000_runtime_data *prtd;
|
||||
int ret;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware);
|
||||
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (par->same_rate) {
|
||||
int rate;
|
||||
spin_lock(&par->lock); /* needed? */
|
||||
rate = par->rate;
|
||||
spin_unlock(&par->lock);
|
||||
if (rate != -1) {
|
||||
ret = snd_pcm_hw_constraint_minmax(runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
rate, rate);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL);
|
||||
if (prtd == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&prtd->lock);
|
||||
|
||||
runtime->private_data = prtd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s6000_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct s6000_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
kfree(prtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s6000_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
int ret;
|
||||
ret = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (ret < 0) {
|
||||
printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (par->same_rate) {
|
||||
spin_lock(&par->lock);
|
||||
if (par->rate == -1 ||
|
||||
!(par->in_use & ~(1 << substream->stream))) {
|
||||
par->rate = params_rate(hw_params);
|
||||
par->in_use |= 1 << substream->stream;
|
||||
} else if (params_rate(hw_params) != par->rate) {
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
par->in_use &= ~(1 << substream->stream);
|
||||
ret = -EBUSY;
|
||||
}
|
||||
spin_unlock(&par->lock);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s6000_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
|
||||
|
||||
spin_lock(&par->lock);
|
||||
par->in_use &= ~(1 << substream->stream);
|
||||
if (!par->in_use)
|
||||
par->rate = -1;
|
||||
spin_unlock(&par->lock);
|
||||
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops s6000_pcm_ops = {
|
||||
.open = s6000_pcm_open,
|
||||
.close = s6000_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = s6000_pcm_hw_params,
|
||||
.hw_free = s6000_pcm_hw_free,
|
||||
.trigger = s6000_pcm_trigger,
|
||||
.prepare = s6000_pcm_prepare,
|
||||
.pointer = s6000_pcm_pointer,
|
||||
};
|
||||
|
||||
static void s6000_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *runtime = pcm->private_data;
|
||||
struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
|
||||
|
||||
free_irq(params->irq, pcm);
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
}
|
||||
|
||||
static u64 s6000_pcm_dmamask = DMA_32BIT_MASK;
|
||||
|
||||
static int s6000_pcm_new(struct snd_card *card,
|
||||
struct snd_soc_dai *dai, struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *runtime = pcm->private_data;
|
||||
struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
|
||||
int res;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &s6000_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = DMA_32BIT_MASK;
|
||||
|
||||
if (params->dma_in) {
|
||||
s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in),
|
||||
DMA_INDEX_CHNL(params->dma_in));
|
||||
s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in),
|
||||
DMA_INDEX_CHNL(params->dma_in));
|
||||
}
|
||||
|
||||
if (params->dma_out) {
|
||||
s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out),
|
||||
DMA_INDEX_CHNL(params->dma_out));
|
||||
s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out),
|
||||
DMA_INDEX_CHNL(params->dma_out));
|
||||
}
|
||||
|
||||
res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED,
|
||||
s6000_soc_platform.name, pcm);
|
||||
if (res) {
|
||||
printk(KERN_ERR "s6000-pcm couldn't get IRQ\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_DEV,
|
||||
card->dev,
|
||||
S6_PCM_PREALLOCATE_SIZE,
|
||||
S6_PCM_PREALLOCATE_MAX);
|
||||
if (res)
|
||||
printk(KERN_WARNING "s6000-pcm: preallocation failed\n");
|
||||
|
||||
spin_lock_init(¶ms->lock);
|
||||
params->in_use = 0;
|
||||
params->rate = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_platform s6000_soc_platform = {
|
||||
.name = "s6000-audio",
|
||||
.pcm_ops = &s6000_pcm_ops,
|
||||
.pcm_new = s6000_pcm_new,
|
||||
.pcm_free = s6000_pcm_free,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(s6000_soc_platform);
|
||||
|
||||
static int __init s6000_pcm_init(void)
|
||||
{
|
||||
return snd_soc_register_platform(&s6000_soc_platform);
|
||||
}
|
||||
module_init(s6000_pcm_init);
|
||||
|
||||
static void __exit s6000_pcm_exit(void)
|
||||
{
|
||||
snd_soc_unregister_platform(&s6000_soc_platform);
|
||||
}
|
||||
module_exit(s6000_pcm_exit);
|
||||
|
||||
MODULE_AUTHOR("Daniel Gloeckner");
|
||||
MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");
|
||||
MODULE_LICENSE("GPL");
|
35
sound/soc/s6000/s6000-pcm.h
Normal file
35
sound/soc/s6000/s6000-pcm.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* ALSA PCM interface for the Stretch s6000 family
|
||||
*
|
||||
* Author: Daniel Gloeckner, <dg@emlix.com>
|
||||
* Copyright: (C) 2009 emlix GmbH <info@emlix.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _S6000_PCM_H
|
||||
#define _S6000_PCM_H
|
||||
|
||||
struct snd_soc_dai;
|
||||
struct snd_pcm_substream;
|
||||
|
||||
struct s6000_pcm_dma_params {
|
||||
unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai);
|
||||
int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after);
|
||||
dma_addr_t sif_in;
|
||||
dma_addr_t sif_out;
|
||||
u32 dma_in;
|
||||
u32 dma_out;
|
||||
int irq;
|
||||
int same_rate;
|
||||
|
||||
spinlock_t lock;
|
||||
int in_use;
|
||||
int rate;
|
||||
};
|
||||
|
||||
extern struct snd_soc_platform s6000_soc_platform;
|
||||
|
||||
#endif
|
244
sound/soc/s6000/s6105-ipcam.c
Normal file
244
sound/soc/s6000/s6105-ipcam.c
Normal file
@ -0,0 +1,244 @@
|
||||
/*
|
||||
* ASoC driver for Stretch s6105 IP camera platform
|
||||
*
|
||||
* Author: Daniel Gloeckner, <dg@emlix.com>
|
||||
* Copyright: (C) 2009 emlix GmbH <info@emlix.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include <variant/dmac.h>
|
||||
|
||||
#include "../codecs/tlv320aic3x.h"
|
||||
#include "s6000-pcm.h"
|
||||
#include "s6000-i2s.h"
|
||||
|
||||
#define S6105_CAM_CODEC_CLOCK 12288000
|
||||
|
||||
static int s6105_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
int ret = 0;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM |
|
||||
SND_SOC_DAIFMT_NB_NF);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set the codec system clock */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CAM_CODEC_CLOCK,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops s6105_ops = {
|
||||
.hw_params = s6105_hw_params,
|
||||
};
|
||||
|
||||
/* s6105 machine dapm widgets */
|
||||
static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Audio Out Differential", NULL),
|
||||
SND_SOC_DAPM_LINE("Audio Out Stereo", NULL),
|
||||
SND_SOC_DAPM_LINE("Audio In", NULL),
|
||||
};
|
||||
|
||||
/* s6105 machine audio_mapnections to the codec pins */
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Audio Out connected to HPLOUT, HPLCOM, HPROUT */
|
||||
{"Audio Out Differential", NULL, "HPLOUT"},
|
||||
{"Audio Out Differential", NULL, "HPLCOM"},
|
||||
{"Audio Out Stereo", NULL, "HPLOUT"},
|
||||
{"Audio Out Stereo", NULL, "HPROUT"},
|
||||
|
||||
/* Audio In connected to LINE1L, LINE1R */
|
||||
{"LINE1L", NULL, "Audio In"},
|
||||
{"LINE1R", NULL, "Audio In"},
|
||||
};
|
||||
|
||||
static int output_type_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 2;
|
||||
if (uinfo->value.enumerated.item) {
|
||||
uinfo->value.enumerated.item = 1;
|
||||
strcpy(uinfo->value.enumerated.name, "HPLOUT/HPROUT");
|
||||
} else {
|
||||
strcpy(uinfo->value.enumerated.name, "HPLOUT/HPLCOM");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int output_type_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.enumerated.item[0] = kcontrol->private_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int output_type_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = kcontrol->private_data;
|
||||
unsigned int val = (ucontrol->value.enumerated.item[0] != 0);
|
||||
char *differential = "Audio Out Differential";
|
||||
char *stereo = "Audio Out Stereo";
|
||||
|
||||
if (kcontrol->private_value == val)
|
||||
return 0;
|
||||
kcontrol->private_value = val;
|
||||
snd_soc_dapm_disable_pin(codec, val ? differential : stereo);
|
||||
snd_soc_dapm_sync(codec);
|
||||
snd_soc_dapm_enable_pin(codec, val ? stereo : differential);
|
||||
snd_soc_dapm_sync(codec);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct snd_kcontrol_new audio_out_mux = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Master Output Mux",
|
||||
.index = 0,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = output_type_info,
|
||||
.get = output_type_get,
|
||||
.put = output_type_put,
|
||||
.private_value = 1 /* default to stereo */
|
||||
};
|
||||
|
||||
/* Logic for a aic3x as connected on the s6105 ip camera ref design */
|
||||
static int s6105_aic3x_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
/* Add s6105 specific widgets */
|
||||
snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
|
||||
ARRAY_SIZE(aic3x_dapm_widgets));
|
||||
|
||||
/* Set up s6105 specific audio path audio_map */
|
||||
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
|
||||
|
||||
/* not present */
|
||||
snd_soc_dapm_nc_pin(codec, "MONO_LOUT");
|
||||
snd_soc_dapm_nc_pin(codec, "LINE2L");
|
||||
snd_soc_dapm_nc_pin(codec, "LINE2R");
|
||||
|
||||
/* not connected */
|
||||
snd_soc_dapm_nc_pin(codec, "MIC3L"); /* LINE2L on this chip */
|
||||
snd_soc_dapm_nc_pin(codec, "MIC3R"); /* LINE2R on this chip */
|
||||
snd_soc_dapm_nc_pin(codec, "LLOUT");
|
||||
snd_soc_dapm_nc_pin(codec, "RLOUT");
|
||||
snd_soc_dapm_nc_pin(codec, "HPRCOM");
|
||||
|
||||
/* always connected */
|
||||
snd_soc_dapm_enable_pin(codec, "Audio In");
|
||||
|
||||
/* must correspond to audio_out_mux.private_value initializer */
|
||||
snd_soc_dapm_disable_pin(codec, "Audio Out Differential");
|
||||
snd_soc_dapm_sync(codec);
|
||||
snd_soc_dapm_enable_pin(codec, "Audio Out Stereo");
|
||||
|
||||
snd_soc_dapm_sync(codec);
|
||||
|
||||
snd_ctl_add(codec->card, snd_ctl_new1(&audio_out_mux, codec));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* s6105 digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link s6105_dai = {
|
||||
.name = "TLV320AIC31",
|
||||
.stream_name = "AIC31",
|
||||
.cpu_dai = &s6000_i2s_dai,
|
||||
.codec_dai = &aic3x_dai,
|
||||
.init = s6105_aic3x_init,
|
||||
.ops = &s6105_ops,
|
||||
};
|
||||
|
||||
/* s6105 audio machine driver */
|
||||
static struct snd_soc_card snd_soc_card_s6105 = {
|
||||
.name = "Stretch IP Camera",
|
||||
.platform = &s6000_soc_platform,
|
||||
.dai_link = &s6105_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
/* s6105 audio private data */
|
||||
static struct aic3x_setup_data s6105_aic3x_setup = {
|
||||
.i2c_bus = 0,
|
||||
.i2c_address = 0x18,
|
||||
};
|
||||
|
||||
/* s6105 audio subsystem */
|
||||
static struct snd_soc_device s6105_snd_devdata = {
|
||||
.card = &snd_soc_card_s6105,
|
||||
.codec_dev = &soc_codec_dev_aic3x,
|
||||
.codec_data = &s6105_aic3x_setup,
|
||||
};
|
||||
|
||||
static struct s6000_snd_platform_data __initdata s6105_snd_data = {
|
||||
.wide = 0,
|
||||
.channel_in = 0,
|
||||
.channel_out = 1,
|
||||
.lines_in = 1,
|
||||
.lines_out = 1,
|
||||
.same_rate = 1,
|
||||
};
|
||||
|
||||
static struct platform_device *s6105_snd_device;
|
||||
|
||||
static int __init s6105_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
s6105_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!s6105_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(s6105_snd_device, &s6105_snd_devdata);
|
||||
s6105_snd_devdata.dev = &s6105_snd_device->dev;
|
||||
platform_device_add_data(s6105_snd_device, &s6105_snd_data,
|
||||
sizeof(s6105_snd_data));
|
||||
|
||||
ret = platform_device_add(s6105_snd_device);
|
||||
if (ret)
|
||||
platform_device_put(s6105_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit s6105_exit(void)
|
||||
{
|
||||
platform_device_unregister(s6105_snd_device);
|
||||
}
|
||||
|
||||
module_init(s6105_init);
|
||||
module_exit(s6105_exit);
|
||||
|
||||
MODULE_AUTHOR("Daniel Gloeckner");
|
||||
MODULE_DESCRIPTION("Stretch s6105 IP camera ASoC driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -145,7 +145,7 @@ static int ssi_hw_params(struct snd_pcm_substream *substream,
|
||||
recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1;
|
||||
|
||||
pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr);
|
||||
pr_debug("bits: %d channels: %d\n", bits, channels);
|
||||
pr_debug("bits: %u channels: %u\n", bits, channels);
|
||||
|
||||
ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA |
|
||||
CR_SWL_MASK);
|
||||
|
@ -113,6 +113,35 @@ 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_device *socdev = rtd->socdev;
|
||||
struct snd_soc_card *card = socdev->card;
|
||||
struct snd_soc_dai_link *machine = rtd->dai;
|
||||
struct snd_soc_dai *cpu_dai = machine->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = machine->codec_dai;
|
||||
int ret;
|
||||
|
||||
if (codec_dai->symmetric_rates || cpu_dai->symmetric_rates ||
|
||||
machine->symmetric_rates) {
|
||||
dev_dbg(card->dev, "Symmetry forces %dHz rate\n",
|
||||
machine->rate);
|
||||
|
||||
ret = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
machine->rate,
|
||||
machine->rate);
|
||||
if (ret < 0) {
|
||||
dev_err(card->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
|
||||
@ -221,6 +250,13 @@ static int soc_pcm_open(struct snd_pcm_substream *substream)
|
||||
goto machine_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 machine_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,
|
||||
@ -263,7 +299,6 @@ static void close_delayed_work(struct work_struct *work)
|
||||
{
|
||||
struct snd_soc_card *card = container_of(work, struct snd_soc_card,
|
||||
delayed_work.work);
|
||||
struct snd_soc_device *socdev = card->socdev;
|
||||
struct snd_soc_codec *codec = card->codec;
|
||||
struct snd_soc_dai *codec_dai;
|
||||
int i;
|
||||
@ -279,27 +314,10 @@ static void close_delayed_work(struct work_struct *work)
|
||||
|
||||
/* are we waiting on this codec DAI stream */
|
||||
if (codec_dai->pop_wait == 1) {
|
||||
|
||||
/* Reduce power if no longer active */
|
||||
if (codec->active == 0) {
|
||||
pr_debug("pop wq D1 %s %s\n", codec->name,
|
||||
codec_dai->playback.stream_name);
|
||||
snd_soc_dapm_set_bias_level(socdev,
|
||||
SND_SOC_BIAS_PREPARE);
|
||||
}
|
||||
|
||||
codec_dai->pop_wait = 0;
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_STOP);
|
||||
|
||||
/* Fall into standby if no longer active */
|
||||
if (codec->active == 0) {
|
||||
pr_debug("pop wq D3 %s %s\n", codec->name,
|
||||
codec_dai->playback.stream_name);
|
||||
snd_soc_dapm_set_bias_level(socdev,
|
||||
SND_SOC_BIAS_STANDBY);
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pcm_mutex);
|
||||
@ -363,10 +381,6 @@ static int soc_codec_close(struct snd_pcm_substream *substream)
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_STOP);
|
||||
|
||||
if (codec->active == 0 && codec_dai->pop_wait == 0)
|
||||
snd_soc_dapm_set_bias_level(socdev,
|
||||
SND_SOC_BIAS_STANDBY);
|
||||
}
|
||||
|
||||
mutex_unlock(&pcm_mutex);
|
||||
@ -431,36 +445,16 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
cancel_delayed_work(&card->delayed_work);
|
||||
}
|
||||
|
||||
/* do we need to power up codec */
|
||||
if (codec->bias_level != SND_SOC_BIAS_ON) {
|
||||
snd_soc_dapm_set_bias_level(socdev,
|
||||
SND_SOC_BIAS_PREPARE);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
else
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
else
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
|
||||
snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_ON);
|
||||
snd_soc_dai_digital_mute(codec_dai, 0);
|
||||
|
||||
} else {
|
||||
/* codec already powered - power on widgets */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->playback.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
else
|
||||
snd_soc_dapm_stream_event(codec,
|
||||
codec_dai->capture.stream_name,
|
||||
SND_SOC_DAPM_STREAM_START);
|
||||
|
||||
snd_soc_dai_digital_mute(codec_dai, 0);
|
||||
}
|
||||
snd_soc_dai_digital_mute(codec_dai, 0);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pcm_mutex);
|
||||
@ -521,6 +515,8 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
}
|
||||
}
|
||||
|
||||
machine->rate = params_rate(params);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pcm_mutex);
|
||||
return ret;
|
||||
@ -632,6 +628,12 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
struct snd_soc_codec *codec = card->codec;
|
||||
int i;
|
||||
|
||||
/* If the initialization of this soc device failed, there is no codec
|
||||
* associated with it. Just bail out in this case.
|
||||
*/
|
||||
if (!codec)
|
||||
return 0;
|
||||
|
||||
/* Due to the resume being scheduled into a workqueue we could
|
||||
* suspend before that's finished - wait for it to complete.
|
||||
*/
|
||||
@ -1334,6 +1336,7 @@ int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
|
||||
return ret;
|
||||
}
|
||||
|
||||
codec->socdev = socdev;
|
||||
codec->card->dev = socdev->dev;
|
||||
codec->card->private_data = codec;
|
||||
strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver));
|
||||
@ -1744,7 +1747,7 @@ int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol,
|
||||
{
|
||||
int max = kcontrol->private_value;
|
||||
|
||||
if (max == 1)
|
||||
if (max == 1 && !strstr(kcontrol->id.name, " Volume"))
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
||||
else
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
@ -1774,7 +1777,7 @@ int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
|
||||
unsigned int shift = mc->shift;
|
||||
unsigned int rshift = mc->rshift;
|
||||
|
||||
if (max == 1)
|
||||
if (max == 1 && !strstr(kcontrol->id.name, " Volume"))
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
||||
else
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
@ -1881,7 +1884,7 @@ int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol,
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
int max = mc->max;
|
||||
|
||||
if (max == 1)
|
||||
if (max == 1 && !strstr(kcontrol->id.name, " Volume"))
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
||||
else
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
@ -2065,7 +2068,7 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8);
|
||||
int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
|
||||
unsigned int freq, int dir)
|
||||
{
|
||||
if (dai->ops->set_sysclk)
|
||||
if (dai->ops && dai->ops->set_sysclk)
|
||||
return dai->ops->set_sysclk(dai, clk_id, freq, dir);
|
||||
else
|
||||
return -EINVAL;
|
||||
@ -2085,7 +2088,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
|
||||
int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
if (dai->ops->set_clkdiv)
|
||||
if (dai->ops && dai->ops->set_clkdiv)
|
||||
return dai->ops->set_clkdiv(dai, div_id, div);
|
||||
else
|
||||
return -EINVAL;
|
||||
@ -2104,7 +2107,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv);
|
||||
int snd_soc_dai_set_pll(struct snd_soc_dai *dai,
|
||||
int pll_id, unsigned int freq_in, unsigned int freq_out)
|
||||
{
|
||||
if (dai->ops->set_pll)
|
||||
if (dai->ops && dai->ops->set_pll)
|
||||
return dai->ops->set_pll(dai, pll_id, freq_in, freq_out);
|
||||
else
|
||||
return -EINVAL;
|
||||
@ -2120,7 +2123,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
|
||||
*/
|
||||
int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
if (dai->ops->set_fmt)
|
||||
if (dai->ops && dai->ops->set_fmt)
|
||||
return dai->ops->set_fmt(dai, fmt);
|
||||
else
|
||||
return -EINVAL;
|
||||
@ -2139,7 +2142,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt);
|
||||
int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
|
||||
unsigned int mask, int slots)
|
||||
{
|
||||
if (dai->ops->set_sysclk)
|
||||
if (dai->ops && dai->ops->set_tdm_slot)
|
||||
return dai->ops->set_tdm_slot(dai, mask, slots);
|
||||
else
|
||||
return -EINVAL;
|
||||
@ -2155,7 +2158,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot);
|
||||
*/
|
||||
int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate)
|
||||
{
|
||||
if (dai->ops->set_sysclk)
|
||||
if (dai->ops && dai->ops->set_tristate)
|
||||
return dai->ops->set_tristate(dai, tristate);
|
||||
else
|
||||
return -EINVAL;
|
||||
@ -2171,7 +2174,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate);
|
||||
*/
|
||||
int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
if (dai->ops->digital_mute)
|
||||
if (dai->ops && dai->ops->digital_mute)
|
||||
return dai->ops->digital_mute(dai, mute);
|
||||
else
|
||||
return -EINVAL;
|
||||
@ -2352,6 +2355,39 @@ void snd_soc_unregister_platform(struct snd_soc_platform *platform)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_unregister_platform);
|
||||
|
||||
static u64 codec_format_map[] = {
|
||||
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,
|
||||
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3BE,
|
||||
SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_U24_3BE,
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE,
|
||||
SNDRV_PCM_FMTBIT_U20_3LE | SNDRV_PCM_FMTBIT_U20_3BE,
|
||||
SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE,
|
||||
SNDRV_PCM_FMTBIT_U18_3LE | SNDRV_PCM_FMTBIT_U18_3BE,
|
||||
SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE,
|
||||
SNDRV_PCM_FMTBIT_FLOAT64_LE | SNDRV_PCM_FMTBIT_FLOAT64_BE,
|
||||
SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE
|
||||
| SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
|
||||
};
|
||||
|
||||
/* Fix up the DAI formats for endianness: codecs don't actually see
|
||||
* the endianness of the data but we're using the CPU format
|
||||
* definitions which do need to include endianness so we ensure that
|
||||
* codec DAIs always have both big and little endian variants set.
|
||||
*/
|
||||
static void fixup_codec_formats(struct snd_soc_pcm_stream *stream)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(codec_format_map); i++)
|
||||
if (stream->formats & codec_format_map[i])
|
||||
stream->formats |= codec_format_map[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_register_codec - Register a codec with the ASoC core
|
||||
*
|
||||
@ -2359,6 +2395,8 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_platform);
|
||||
*/
|
||||
int snd_soc_register_codec(struct snd_soc_codec *codec)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!codec->name)
|
||||
return -EINVAL;
|
||||
|
||||
@ -2368,6 +2406,11 @@ int snd_soc_register_codec(struct snd_soc_codec *codec)
|
||||
|
||||
INIT_LIST_HEAD(&codec->list);
|
||||
|
||||
for (i = 0; i < codec->num_dai; i++) {
|
||||
fixup_codec_formats(&codec->dai[i].playback);
|
||||
fixup_codec_formats(&codec->dai[i].capture);
|
||||
}
|
||||
|
||||
mutex_lock(&client_mutex);
|
||||
list_add(&codec->list, &codec_list);
|
||||
snd_soc_instantiate_cards();
|
||||
|
@ -12,7 +12,7 @@
|
||||
* Features:
|
||||
* o Changes power status of internal codec blocks depending on the
|
||||
* dynamic configuration of codec internal audio paths and active
|
||||
* DAC's/ADC's.
|
||||
* DACs/ADCs.
|
||||
* o Platform power domain - can support external components i.e. amps and
|
||||
* mic/meadphone insertion events.
|
||||
* o Automatic Mic Bias support
|
||||
@ -52,23 +52,21 @@
|
||||
|
||||
/* dapm power sequences - make this per codec in the future */
|
||||
static int dapm_up_seq[] = {
|
||||
snd_soc_dapm_pre, snd_soc_dapm_micbias, snd_soc_dapm_mic,
|
||||
snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_dac,
|
||||
snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_pga,
|
||||
snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_post
|
||||
snd_soc_dapm_pre, snd_soc_dapm_supply, snd_soc_dapm_micbias,
|
||||
snd_soc_dapm_mic, snd_soc_dapm_mux, snd_soc_dapm_value_mux,
|
||||
snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl,
|
||||
snd_soc_dapm_pga, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk,
|
||||
snd_soc_dapm_post
|
||||
};
|
||||
|
||||
static int dapm_down_seq[] = {
|
||||
snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk,
|
||||
snd_soc_dapm_pga, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_mixer,
|
||||
snd_soc_dapm_dac, snd_soc_dapm_mic, snd_soc_dapm_micbias,
|
||||
snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_post
|
||||
snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_supply,
|
||||
snd_soc_dapm_post
|
||||
};
|
||||
|
||||
static int dapm_status = 1;
|
||||
module_param(dapm_status, int, 0);
|
||||
MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries");
|
||||
|
||||
static void pop_wait(u32 pop_time)
|
||||
{
|
||||
if (pop_time)
|
||||
@ -96,6 +94,48 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
|
||||
return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_set_bias_level - set the bias level for the system
|
||||
* @socdev: audio device
|
||||
* @level: level to configure
|
||||
*
|
||||
* Configure the bias (power) levels for the SoC audio device.
|
||||
*
|
||||
* Returns 0 for success else error.
|
||||
*/
|
||||
static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_card *card = socdev->card;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int ret = 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
dev_dbg(socdev->dev, "Setting full bias\n");
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
dev_dbg(socdev->dev, "Setting bias prepare\n");
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
dev_dbg(socdev->dev, "Setting standby bias\n");
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
dev_dbg(socdev->dev, "Setting bias off\n");
|
||||
break;
|
||||
default:
|
||||
dev_err(socdev->dev, "Setting invalid bias %d\n", level);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (card->set_bias_level)
|
||||
ret = card->set_bias_level(card, level);
|
||||
if (ret == 0 && codec->set_bias_level)
|
||||
ret = codec->set_bias_level(codec, level);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set up initial codec paths */
|
||||
static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
|
||||
struct snd_soc_dapm_path *p, int i)
|
||||
@ -165,6 +205,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
|
||||
case snd_soc_dapm_dac:
|
||||
case snd_soc_dapm_micbias:
|
||||
case snd_soc_dapm_vmid:
|
||||
case snd_soc_dapm_supply:
|
||||
p->connect = 1;
|
||||
break;
|
||||
/* does effect routing - dynamically connected */
|
||||
@ -179,7 +220,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
|
||||
}
|
||||
}
|
||||
|
||||
/* connect mux widget to it's interconnecting audio paths */
|
||||
/* connect mux widget to its interconnecting audio paths */
|
||||
static int dapm_connect_mux(struct snd_soc_codec *codec,
|
||||
struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
|
||||
struct snd_soc_dapm_path *path, const char *control_name,
|
||||
@ -202,7 +243,7 @@ static int dapm_connect_mux(struct snd_soc_codec *codec,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* connect mixer widget to it's interconnecting audio paths */
|
||||
/* connect mixer widget to its interconnecting audio paths */
|
||||
static int dapm_connect_mixer(struct snd_soc_codec *codec,
|
||||
struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
|
||||
struct snd_soc_dapm_path *path, const char *control_name)
|
||||
@ -357,8 +398,9 @@ static int dapm_new_mixer(struct snd_soc_codec *codec,
|
||||
path->long_name);
|
||||
ret = snd_ctl_add(codec->card, path->kcontrol);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "asoc: failed to add dapm kcontrol %s\n",
|
||||
path->long_name);
|
||||
printk(KERN_ERR "asoc: failed to add dapm kcontrol %s: %d\n",
|
||||
path->long_name,
|
||||
ret);
|
||||
kfree(path->long_name);
|
||||
path->long_name = NULL;
|
||||
return ret;
|
||||
@ -434,6 +476,9 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
|
||||
struct snd_soc_dapm_path *path;
|
||||
int con = 0;
|
||||
|
||||
if (widget->id == snd_soc_dapm_supply)
|
||||
return 0;
|
||||
|
||||
if (widget->id == snd_soc_dapm_adc && widget->active)
|
||||
return 1;
|
||||
|
||||
@ -470,6 +515,9 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
|
||||
struct snd_soc_dapm_path *path;
|
||||
int con = 0;
|
||||
|
||||
if (widget->id == snd_soc_dapm_supply)
|
||||
return 0;
|
||||
|
||||
/* active stream ? */
|
||||
if (widget->id == snd_soc_dapm_dac && widget->active)
|
||||
return 1;
|
||||
@ -521,6 +569,126 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dapm_reg_event);
|
||||
|
||||
/* Standard power change method, used to apply power changes to most
|
||||
* widgets.
|
||||
*/
|
||||
static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* call any power change event handlers */
|
||||
if (w->event)
|
||||
pr_debug("power %s event for %s flags %x\n",
|
||||
w->power ? "on" : "off",
|
||||
w->name, w->event_flags);
|
||||
|
||||
/* power up pre event */
|
||||
if (w->power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
|
||||
ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down pre event */
|
||||
if (!w->power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
|
||||
ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Lower PGA volume to reduce pops */
|
||||
if (w->id == snd_soc_dapm_pga && !w->power)
|
||||
dapm_set_pga(w, w->power);
|
||||
|
||||
dapm_update_bits(w);
|
||||
|
||||
/* Raise PGA volume to reduce pops */
|
||||
if (w->id == snd_soc_dapm_pga && w->power)
|
||||
dapm_set_pga(w, w->power);
|
||||
|
||||
/* power up post event */
|
||||
if (w->power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_POST_PMU)) {
|
||||
ret = w->event(w,
|
||||
NULL, SND_SOC_DAPM_POST_PMU);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down post event */
|
||||
if (!w->power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_POST_PMD)) {
|
||||
ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generic check to see if a widget should be powered.
|
||||
*/
|
||||
static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
int in, out;
|
||||
|
||||
in = is_connected_input_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
out = is_connected_output_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
return out != 0 && in != 0;
|
||||
}
|
||||
|
||||
/* Check to see if an ADC has power */
|
||||
static int dapm_adc_check_power(struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
int in;
|
||||
|
||||
if (w->active) {
|
||||
in = is_connected_input_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
return in != 0;
|
||||
} else {
|
||||
return dapm_generic_check_power(w);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check to see if a DAC has power */
|
||||
static int dapm_dac_check_power(struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
int out;
|
||||
|
||||
if (w->active) {
|
||||
out = is_connected_output_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
return out != 0;
|
||||
} else {
|
||||
return dapm_generic_check_power(w);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check to see if a power supply is needed */
|
||||
static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
struct snd_soc_dapm_path *path;
|
||||
int power = 0;
|
||||
|
||||
/* Check if one of our outputs is connected */
|
||||
list_for_each_entry(path, &w->sinks, list_source) {
|
||||
if (path->sink && path->sink->power_check &&
|
||||
path->sink->power_check(path->sink)) {
|
||||
power = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dapm_clear_walk(w->codec);
|
||||
|
||||
return power;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan a single DAPM widget for a complete audio path and update the
|
||||
* power status appropriately.
|
||||
@ -528,32 +696,10 @@ EXPORT_SYMBOL_GPL(dapm_reg_event);
|
||||
static int dapm_power_widget(struct snd_soc_codec *codec, int event,
|
||||
struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
int in, out, power_change, power, ret;
|
||||
int ret;
|
||||
|
||||
/* vmid - no action */
|
||||
if (w->id == snd_soc_dapm_vmid)
|
||||
return 0;
|
||||
|
||||
/* active ADC */
|
||||
if (w->id == snd_soc_dapm_adc && w->active) {
|
||||
in = is_connected_input_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
w->power = (in != 0) ? 1 : 0;
|
||||
dapm_update_bits(w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* active DAC */
|
||||
if (w->id == snd_soc_dapm_dac && w->active) {
|
||||
out = is_connected_output_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
w->power = (out != 0) ? 1 : 0;
|
||||
dapm_update_bits(w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* pre and post event widgets */
|
||||
if (w->id == snd_soc_dapm_pre) {
|
||||
switch (w->id) {
|
||||
case snd_soc_dapm_pre:
|
||||
if (!w->event)
|
||||
return 0;
|
||||
|
||||
@ -569,8 +715,8 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event,
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (w->id == snd_soc_dapm_post) {
|
||||
|
||||
case snd_soc_dapm_post:
|
||||
if (!w->event)
|
||||
return 0;
|
||||
|
||||
@ -586,70 +732,10 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event,
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return dapm_generic_apply_power(w);
|
||||
}
|
||||
|
||||
/* all other widgets */
|
||||
in = is_connected_input_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
out = is_connected_output_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
power = (out != 0 && in != 0) ? 1 : 0;
|
||||
power_change = (w->power == power) ? 0 : 1;
|
||||
w->power = power;
|
||||
|
||||
if (!power_change)
|
||||
return 0;
|
||||
|
||||
/* call any power change event handlers */
|
||||
if (w->event)
|
||||
pr_debug("power %s event for %s flags %x\n",
|
||||
w->power ? "on" : "off",
|
||||
w->name, w->event_flags);
|
||||
|
||||
/* power up pre event */
|
||||
if (power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
|
||||
ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down pre event */
|
||||
if (!power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
|
||||
ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Lower PGA volume to reduce pops */
|
||||
if (w->id == snd_soc_dapm_pga && !power)
|
||||
dapm_set_pga(w, power);
|
||||
|
||||
dapm_update_bits(w);
|
||||
|
||||
/* Raise PGA volume to reduce pops */
|
||||
if (w->id == snd_soc_dapm_pga && power)
|
||||
dapm_set_pga(w, power);
|
||||
|
||||
/* power up post event */
|
||||
if (power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_POST_PMU)) {
|
||||
ret = w->event(w,
|
||||
NULL, SND_SOC_DAPM_POST_PMU);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down post event */
|
||||
if (!power && w->event &&
|
||||
(w->event_flags & SND_SOC_DAPM_POST_PMD)) {
|
||||
ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -663,31 +749,102 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event,
|
||||
*/
|
||||
static int dapm_power_widgets(struct snd_soc_codec *codec, int event)
|
||||
{
|
||||
struct snd_soc_device *socdev = codec->socdev;
|
||||
struct snd_soc_dapm_widget *w;
|
||||
int i, c = 1, *seq = NULL, ret = 0;
|
||||
int ret = 0;
|
||||
int i, power;
|
||||
int sys_power = 0;
|
||||
|
||||
/* do we have a sequenced stream event */
|
||||
if (event == SND_SOC_DAPM_STREAM_START) {
|
||||
c = ARRAY_SIZE(dapm_up_seq);
|
||||
seq = dapm_up_seq;
|
||||
} else if (event == SND_SOC_DAPM_STREAM_STOP) {
|
||||
c = ARRAY_SIZE(dapm_down_seq);
|
||||
seq = dapm_down_seq;
|
||||
INIT_LIST_HEAD(&codec->up_list);
|
||||
INIT_LIST_HEAD(&codec->down_list);
|
||||
|
||||
/* Check which widgets we need to power and store them in
|
||||
* lists indicating if they should be powered up or down.
|
||||
*/
|
||||
list_for_each_entry(w, &codec->dapm_widgets, list) {
|
||||
switch (w->id) {
|
||||
case snd_soc_dapm_pre:
|
||||
list_add_tail(&codec->down_list, &w->power_list);
|
||||
break;
|
||||
case snd_soc_dapm_post:
|
||||
list_add_tail(&codec->up_list, &w->power_list);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!w->power_check)
|
||||
continue;
|
||||
|
||||
power = w->power_check(w);
|
||||
if (power)
|
||||
sys_power = 1;
|
||||
|
||||
if (w->power == power)
|
||||
continue;
|
||||
|
||||
if (power)
|
||||
list_add_tail(&w->power_list, &codec->up_list);
|
||||
else
|
||||
list_add_tail(&w->power_list,
|
||||
&codec->down_list);
|
||||
|
||||
w->power = power;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < c; i++) {
|
||||
list_for_each_entry(w, &codec->dapm_widgets, list) {
|
||||
/* If we're changing to all on or all off then prepare */
|
||||
if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) ||
|
||||
(!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) {
|
||||
ret = snd_soc_dapm_set_bias_level(socdev,
|
||||
SND_SOC_BIAS_PREPARE);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to prepare bias: %d\n", ret);
|
||||
}
|
||||
|
||||
/* Power down widgets first; try to avoid amplifying pops. */
|
||||
for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) {
|
||||
list_for_each_entry(w, &codec->down_list, power_list) {
|
||||
/* is widget in stream order */
|
||||
if (seq && seq[i] && w->id != seq[i])
|
||||
if (w->id != dapm_down_seq[i])
|
||||
continue;
|
||||
|
||||
ret = dapm_power_widget(codec, event, w);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
pr_err("Failed to power down %s: %d\n",
|
||||
w->name, ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now power up. */
|
||||
for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) {
|
||||
list_for_each_entry(w, &codec->up_list, power_list) {
|
||||
/* is widget in stream order */
|
||||
if (w->id != dapm_up_seq[i])
|
||||
continue;
|
||||
|
||||
ret = dapm_power_widget(codec, event, w);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to power up %s: %d\n",
|
||||
w->name, ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we just powered the last thing off drop to standby bias */
|
||||
if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) {
|
||||
ret = snd_soc_dapm_set_bias_level(socdev,
|
||||
SND_SOC_BIAS_STANDBY);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to apply standby bias: %d\n", ret);
|
||||
}
|
||||
|
||||
/* If we just powered up then move to active bias */
|
||||
if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) {
|
||||
ret = snd_soc_dapm_set_bias_level(socdev,
|
||||
SND_SOC_BIAS_ON);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to apply active bias: %d\n", ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -723,6 +880,7 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action)
|
||||
case snd_soc_dapm_pga:
|
||||
case snd_soc_dapm_mixer:
|
||||
case snd_soc_dapm_mixer_named_ctl:
|
||||
case snd_soc_dapm_supply:
|
||||
if (w->name) {
|
||||
in = is_connected_input_ep(w);
|
||||
dapm_clear_walk(w->codec);
|
||||
@ -851,6 +1009,7 @@ static ssize_t dapm_widget_show(struct device *dev,
|
||||
case snd_soc_dapm_pga:
|
||||
case snd_soc_dapm_mixer:
|
||||
case snd_soc_dapm_mixer_named_ctl:
|
||||
case snd_soc_dapm_supply:
|
||||
if (w->name)
|
||||
count += sprintf(buf + count, "%s: %s\n",
|
||||
w->name, w->power ? "On":"Off");
|
||||
@ -883,16 +1042,12 @@ static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL);
|
||||
|
||||
int snd_soc_dapm_sys_add(struct device *dev)
|
||||
{
|
||||
if (!dapm_status)
|
||||
return 0;
|
||||
return device_create_file(dev, &dev_attr_dapm_widget);
|
||||
}
|
||||
|
||||
static void snd_soc_dapm_sys_remove(struct device *dev)
|
||||
{
|
||||
if (dapm_status) {
|
||||
device_remove_file(dev, &dev_attr_dapm_widget);
|
||||
}
|
||||
device_remove_file(dev, &dev_attr_dapm_widget);
|
||||
}
|
||||
|
||||
/* free all dapm widgets and resources */
|
||||
@ -1015,6 +1170,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_codec *codec,
|
||||
case snd_soc_dapm_vmid:
|
||||
case snd_soc_dapm_pre:
|
||||
case snd_soc_dapm_post:
|
||||
case snd_soc_dapm_supply:
|
||||
list_add(&path->list, &codec->dapm_paths);
|
||||
list_add(&path->list_sink, &wsink->sources);
|
||||
list_add(&path->list_source, &wsource->sinks);
|
||||
@ -1108,15 +1264,22 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec)
|
||||
case snd_soc_dapm_switch:
|
||||
case snd_soc_dapm_mixer:
|
||||
case snd_soc_dapm_mixer_named_ctl:
|
||||
w->power_check = dapm_generic_check_power;
|
||||
dapm_new_mixer(codec, w);
|
||||
break;
|
||||
case snd_soc_dapm_mux:
|
||||
case snd_soc_dapm_value_mux:
|
||||
w->power_check = dapm_generic_check_power;
|
||||
dapm_new_mux(codec, w);
|
||||
break;
|
||||
case snd_soc_dapm_adc:
|
||||
w->power_check = dapm_adc_check_power;
|
||||
break;
|
||||
case snd_soc_dapm_dac:
|
||||
w->power_check = dapm_dac_check_power;
|
||||
break;
|
||||
case snd_soc_dapm_pga:
|
||||
w->power_check = dapm_generic_check_power;
|
||||
dapm_new_pga(codec, w);
|
||||
break;
|
||||
case snd_soc_dapm_input:
|
||||
@ -1126,6 +1289,10 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec)
|
||||
case snd_soc_dapm_hp:
|
||||
case snd_soc_dapm_mic:
|
||||
case snd_soc_dapm_line:
|
||||
w->power_check = dapm_generic_check_power;
|
||||
break;
|
||||
case snd_soc_dapm_supply:
|
||||
w->power_check = dapm_supply_check_power;
|
||||
case snd_soc_dapm_vmid:
|
||||
case snd_soc_dapm_pre:
|
||||
case snd_soc_dapm_post:
|
||||
@ -1625,36 +1792,12 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_set_bias_level - set the bias level for the system
|
||||
* @socdev: audio device
|
||||
* @level: level to configure
|
||||
*
|
||||
* Configure the bias (power) levels for the SoC audio device.
|
||||
*
|
||||
* Returns 0 for success else error.
|
||||
*/
|
||||
int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_card *card = socdev->card;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int ret = 0;
|
||||
|
||||
if (card->set_bias_level)
|
||||
ret = card->set_bias_level(card, level);
|
||||
if (ret == 0 && codec->set_bias_level)
|
||||
ret = codec->set_bias_level(codec, level);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_enable_pin - enable pin.
|
||||
* @codec: SoC codec
|
||||
* @pin: pin name
|
||||
*
|
||||
* Enables input/output pin and it's parents or children widgets iff there is
|
||||
* Enables input/output pin and its parents or children widgets iff there is
|
||||
* a valid audio route and active audio stream.
|
||||
* NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
|
||||
* do any widget power switching.
|
||||
@ -1670,7 +1813,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin);
|
||||
* @codec: SoC codec
|
||||
* @pin: pin name
|
||||
*
|
||||
* Disables input/output pin and it's parents or children widgets.
|
||||
* Disables input/output pin and its parents or children widgets.
|
||||
* NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
|
||||
* do any widget power switching.
|
||||
*/
|
||||
|
29
sound/soc/txx9/Kconfig
Normal file
29
sound/soc/txx9/Kconfig
Normal file
@ -0,0 +1,29 @@
|
||||
##
|
||||
## TXx9 ACLC
|
||||
##
|
||||
config SND_SOC_TXX9ACLC
|
||||
tristate "SoC Audio for TXx9"
|
||||
depends on HAS_TXX9_ACLC && TXX9_DMAC
|
||||
help
|
||||
This option enables support for the AC Link Controllers in TXx9 SoC.
|
||||
|
||||
config HAS_TXX9_ACLC
|
||||
bool
|
||||
|
||||
config SND_SOC_TXX9ACLC_AC97
|
||||
tristate
|
||||
select AC97_BUS
|
||||
select SND_AC97_CODEC
|
||||
select SND_SOC_AC97_BUS
|
||||
|
||||
|
||||
##
|
||||
## Boards
|
||||
##
|
||||
config SND_SOC_TXX9ACLC_GENERIC
|
||||
tristate "Generic TXx9 ACLC sound machine"
|
||||
depends on SND_SOC_TXX9ACLC
|
||||
select SND_SOC_TXX9ACLC_AC97
|
||||
select SND_SOC_AC97_CODEC
|
||||
help
|
||||
This is a generic AC97 sound machine for use in TXx9 based systems.
|
11
sound/soc/txx9/Makefile
Normal file
11
sound/soc/txx9/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
# Platform
|
||||
snd-soc-txx9aclc-objs := txx9aclc.o
|
||||
snd-soc-txx9aclc-ac97-objs := txx9aclc-ac97.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_TXX9ACLC) += snd-soc-txx9aclc.o
|
||||
obj-$(CONFIG_SND_SOC_TXX9ACLC_AC97) += snd-soc-txx9aclc-ac97.o
|
||||
|
||||
# Machine
|
||||
snd-soc-txx9aclc-generic-objs := txx9aclc-generic.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_TXX9ACLC_GENERIC) += snd-soc-txx9aclc-generic.o
|
255
sound/soc/txx9/txx9aclc-ac97.c
Normal file
255
sound/soc/txx9/txx9aclc-ac97.c
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* TXx9 ACLC AC97 driver
|
||||
*
|
||||
* Copyright (C) 2009 Atsushi Nemoto
|
||||
*
|
||||
* Based on RBTX49xx patch from CELF patch archive.
|
||||
* (C) Copyright TOSHIBA CORPORATION 2004-2006
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include "txx9aclc.h"
|
||||
|
||||
#define AC97_DIR \
|
||||
(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
|
||||
|
||||
#define AC97_RATES \
|
||||
SNDRV_PCM_RATE_8000_48000
|
||||
|
||||
#ifdef __BIG_ENDIAN
|
||||
#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_BE
|
||||
#else
|
||||
#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_LE
|
||||
#endif
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq);
|
||||
|
||||
/* REVISIT: How to find txx9aclc_soc_device from snd_ac97? */
|
||||
static struct txx9aclc_soc_device *txx9aclc_soc_dev;
|
||||
|
||||
static int txx9aclc_regready(struct txx9aclc_soc_device *dev)
|
||||
{
|
||||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev);
|
||||
|
||||
return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY;
|
||||
}
|
||||
|
||||
/* AC97 controller reads codec register */
|
||||
static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97,
|
||||
unsigned short reg)
|
||||
{
|
||||
struct txx9aclc_soc_device *dev = txx9aclc_soc_dev;
|
||||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev);
|
||||
void __iomem *base = drvdata->base;
|
||||
u32 dat;
|
||||
|
||||
if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num)))
|
||||
return 0xffff;
|
||||
reg |= ac97->num << 7;
|
||||
dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ;
|
||||
__raw_writel(dat, base + ACREGACC);
|
||||
__raw_writel(ACINT_REGACCRDY, base + ACINTEN);
|
||||
if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) {
|
||||
__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
|
||||
dev_err(dev->soc_dev.dev, "ac97 read timeout (reg %#x)\n", reg);
|
||||
dat = 0xffff;
|
||||
goto done;
|
||||
}
|
||||
dat = __raw_readl(base + ACREGACC);
|
||||
if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) {
|
||||
dev_err(dev->soc_dev.dev, "reg mismatch %x with %x\n",
|
||||
dat, reg);
|
||||
dat = 0xffff;
|
||||
goto done;
|
||||
}
|
||||
dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff;
|
||||
done:
|
||||
__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
|
||||
return dat;
|
||||
}
|
||||
|
||||
/* AC97 controller writes to codec register */
|
||||
static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
struct txx9aclc_soc_device *dev = txx9aclc_soc_dev;
|
||||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev);
|
||||
void __iomem *base = drvdata->base;
|
||||
|
||||
__raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) |
|
||||
(val << ACREGACC_DAT_SHIFT),
|
||||
base + ACREGACC);
|
||||
__raw_writel(ACINT_REGACCRDY, base + ACINTEN);
|
||||
if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) {
|
||||
dev_err(dev->soc_dev.dev,
|
||||
"ac97 write timeout (reg %#x)\n", reg);
|
||||
}
|
||||
__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
|
||||
}
|
||||
|
||||
static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct txx9aclc_soc_device *dev = txx9aclc_soc_dev;
|
||||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev);
|
||||
void __iomem *base = drvdata->base;
|
||||
u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY;
|
||||
|
||||
__raw_writel(ACCTL_ENLINK, base + ACCTLDIS);
|
||||
mmiowb();
|
||||
udelay(1);
|
||||
__raw_writel(ACCTL_ENLINK, base + ACCTLEN);
|
||||
/* wait for primary codec ready status */
|
||||
__raw_writel(ready, base + ACINTEN);
|
||||
if (!wait_event_timeout(ac97_waitq,
|
||||
(__raw_readl(base + ACINTSTS) & ready) == ready,
|
||||
HZ)) {
|
||||
dev_err(&ac97->dev, "primary codec is not ready "
|
||||
"(status %#x)\n",
|
||||
__raw_readl(base + ACINTSTS));
|
||||
}
|
||||
__raw_writel(ACINT_REGACCRDY, base + ACINTSTS);
|
||||
__raw_writel(ready, base + ACINTDIS);
|
||||
}
|
||||
|
||||
/* AC97 controller operations */
|
||||
struct snd_ac97_bus_ops soc_ac97_ops = {
|
||||
.read = txx9aclc_ac97_read,
|
||||
.write = txx9aclc_ac97_write,
|
||||
.reset = txx9aclc_ac97_cold_reset,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_ac97_ops);
|
||||
|
||||
static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct txx9aclc_plat_drvdata *drvdata = dev_id;
|
||||
void __iomem *base = drvdata->base;
|
||||
|
||||
__raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS);
|
||||
wake_up(&ac97_waitq);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int txx9aclc_ac97_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct txx9aclc_soc_device *dev =
|
||||
container_of(socdev, struct txx9aclc_soc_device, soc_dev);
|
||||
|
||||
dev->aclc_pdev = to_platform_device(dai->dev);
|
||||
txx9aclc_soc_dev = dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void txx9aclc_ac97_remove(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct platform_device *aclc_pdev = to_platform_device(dai->dev);
|
||||
struct txx9aclc_plat_drvdata *drvdata = platform_get_drvdata(aclc_pdev);
|
||||
|
||||
/* disable AC-link */
|
||||
__raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS);
|
||||
txx9aclc_soc_dev = NULL;
|
||||
}
|
||||
|
||||
struct snd_soc_dai txx9aclc_ac97_dai = {
|
||||
.name = "txx9aclc_ac97",
|
||||
.ac97_control = 1,
|
||||
.probe = txx9aclc_ac97_probe,
|
||||
.remove = txx9aclc_ac97_remove,
|
||||
.playback = {
|
||||
.rates = AC97_RATES,
|
||||
.formats = AC97_FMTS,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
},
|
||||
.capture = {
|
||||
.rates = AC97_RATES,
|
||||
.formats = AC97_FMTS,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(txx9aclc_ac97_dai);
|
||||
|
||||
static int __devinit txx9aclc_ac97_dev_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct txx9aclc_plat_drvdata *drvdata;
|
||||
struct resource *r;
|
||||
int err;
|
||||
int irq;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r)
|
||||
return -EBUSY;
|
||||
|
||||
if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r),
|
||||
dev_name(&pdev->dev)))
|
||||
return -EBUSY;
|
||||
|
||||
drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, drvdata);
|
||||
drvdata->physbase = r->start;
|
||||
if (sizeof(drvdata->physbase) > sizeof(r->start) &&
|
||||
r->start >= TXX9_DIRECTMAP_BASE &&
|
||||
r->start < TXX9_DIRECTMAP_BASE + 0x400000)
|
||||
drvdata->physbase |= 0xf00000000ull;
|
||||
drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
|
||||
if (!drvdata->base)
|
||||
return -EBUSY;
|
||||
err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq,
|
||||
IRQF_DISABLED, dev_name(&pdev->dev), drvdata);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
txx9aclc_ac97_dai.dev = &pdev->dev;
|
||||
return snd_soc_register_dai(&txx9aclc_ac97_dai);
|
||||
}
|
||||
|
||||
static int __devexit txx9aclc_ac97_dev_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_dai(&txx9aclc_ac97_dai);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver txx9aclc_ac97_driver = {
|
||||
.probe = txx9aclc_ac97_dev_probe,
|
||||
.remove = __devexit_p(txx9aclc_ac97_dev_remove),
|
||||
.driver = {
|
||||
.name = "txx9aclc-ac97",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init txx9aclc_ac97_init(void)
|
||||
{
|
||||
return platform_driver_register(&txx9aclc_ac97_driver);
|
||||
}
|
||||
|
||||
static void __exit txx9aclc_ac97_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&txx9aclc_ac97_driver);
|
||||
}
|
||||
|
||||
module_init(txx9aclc_ac97_init);
|
||||
module_exit(txx9aclc_ac97_exit);
|
||||
|
||||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
|
||||
MODULE_DESCRIPTION("TXx9 ACLC AC97 driver");
|
||||
MODULE_LICENSE("GPL");
|
98
sound/soc/txx9/txx9aclc-generic.c
Normal file
98
sound/soc/txx9/txx9aclc-generic.c
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Generic TXx9 ACLC machine driver
|
||||
*
|
||||
* Copyright (C) 2009 Atsushi Nemoto
|
||||
*
|
||||
* Based on RBTX49xx patch from CELF patch archive.
|
||||
* (C) Copyright TOSHIBA CORPORATION 2004-2006
|
||||
*
|
||||
* 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 is a very generic AC97 sound machine driver for boards which
|
||||
* have (AC97) audio at ACLC (e.g. RBTX49XX boards).
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include "../codecs/ac97.h"
|
||||
#include "txx9aclc.h"
|
||||
|
||||
static struct snd_soc_dai_link txx9aclc_generic_dai = {
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 HiFi",
|
||||
.cpu_dai = &txx9aclc_ac97_dai,
|
||||
.codec_dai = &ac97_dai,
|
||||
};
|
||||
|
||||
static struct snd_soc_card txx9aclc_generic_card = {
|
||||
.name = "Generic TXx9 ACLC Audio",
|
||||
.platform = &txx9aclc_soc_platform,
|
||||
.dai_link = &txx9aclc_generic_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct txx9aclc_soc_device txx9aclc_generic_soc_device = {
|
||||
.soc_dev = {
|
||||
.card = &txx9aclc_generic_card,
|
||||
.codec_dev = &soc_codec_dev_ac97,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init txx9aclc_generic_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct txx9aclc_soc_device *dev = &txx9aclc_generic_soc_device;
|
||||
struct platform_device *soc_pdev;
|
||||
int ret;
|
||||
|
||||
soc_pdev = platform_device_alloc("soc-audio", -1);
|
||||
if (!soc_pdev)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(soc_pdev, &dev->soc_dev);
|
||||
dev->soc_dev.dev = &soc_pdev->dev;
|
||||
ret = platform_device_add(soc_pdev);
|
||||
if (ret) {
|
||||
platform_device_put(soc_pdev);
|
||||
return ret;
|
||||
}
|
||||
platform_set_drvdata(pdev, soc_pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __exit txx9aclc_generic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_device *soc_pdev = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(soc_pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver txx9aclc_generic_driver = {
|
||||
.remove = txx9aclc_generic_remove,
|
||||
.driver = {
|
||||
.name = "txx9aclc-generic",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init txx9aclc_generic_init(void)
|
||||
{
|
||||
return platform_driver_probe(&txx9aclc_generic_driver,
|
||||
txx9aclc_generic_probe);
|
||||
}
|
||||
|
||||
static void __exit txx9aclc_generic_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&txx9aclc_generic_driver);
|
||||
}
|
||||
|
||||
module_init(txx9aclc_generic_init);
|
||||
module_exit(txx9aclc_generic_exit);
|
||||
|
||||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
|
||||
MODULE_DESCRIPTION("Generic TXx9 ACLC ALSA SoC audio driver");
|
||||
MODULE_LICENSE("GPL");
|
430
sound/soc/txx9/txx9aclc.c
Normal file
430
sound/soc/txx9/txx9aclc.c
Normal file
@ -0,0 +1,430 @@
|
||||
/*
|
||||
* Generic TXx9 ACLC platform driver
|
||||
*
|
||||
* Copyright (C) 2009 Atsushi Nemoto
|
||||
*
|
||||
* Based on RBTX49xx patch from CELF patch archive.
|
||||
* (C) Copyright TOSHIBA CORPORATION 2004-2006
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include "txx9aclc.h"
|
||||
|
||||
static const struct snd_pcm_hardware txx9aclc_pcm_hardware = {
|
||||
/*
|
||||
* REVISIT: SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
|
||||
* needs more works for noncoherent MIPS.
|
||||
*/
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BATCH |
|
||||
SNDRV_PCM_INFO_PAUSE,
|
||||
#ifdef __BIG_ENDIAN
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_BE,
|
||||
#else
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
#endif
|
||||
.period_bytes_min = 1024,
|
||||
.period_bytes_max = 8 * 1024,
|
||||
.periods_min = 2,
|
||||
.periods_max = 4096,
|
||||
.buffer_bytes_max = 32 * 1024,
|
||||
};
|
||||
|
||||
static int txx9aclc_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct txx9aclc_dmadata *dmadata = runtime->private_data;
|
||||
int ret;
|
||||
|
||||
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_dbg(socdev->dev,
|
||||
"runtime->dma_area = %#lx dma_addr = %#lx dma_bytes = %zd "
|
||||
"runtime->min_align %ld\n",
|
||||
(unsigned long)runtime->dma_area,
|
||||
(unsigned long)runtime->dma_addr, runtime->dma_bytes,
|
||||
runtime->min_align);
|
||||
dev_dbg(socdev->dev,
|
||||
"periods %d period_bytes %d stream %d\n",
|
||||
params_periods(params), params_period_bytes(params),
|
||||
substream->stream);
|
||||
|
||||
dmadata->substream = substream;
|
||||
dmadata->pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct txx9aclc_dmadata *dmadata = runtime->private_data;
|
||||
|
||||
dmadata->dma_addr = runtime->dma_addr;
|
||||
dmadata->buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
|
||||
dmadata->period_bytes = snd_pcm_lib_period_bytes(substream);
|
||||
|
||||
if (dmadata->buffer_bytes == dmadata->period_bytes) {
|
||||
dmadata->frag_bytes = dmadata->period_bytes >> 1;
|
||||
dmadata->frags = 2;
|
||||
} else {
|
||||
dmadata->frag_bytes = dmadata->period_bytes;
|
||||
dmadata->frags = dmadata->buffer_bytes / dmadata->period_bytes;
|
||||
}
|
||||
dmadata->frag_count = 0;
|
||||
dmadata->pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void txx9aclc_dma_complete(void *arg)
|
||||
{
|
||||
struct txx9aclc_dmadata *dmadata = arg;
|
||||
unsigned long flags;
|
||||
|
||||
/* dma completion handler cannot submit new operations */
|
||||
spin_lock_irqsave(&dmadata->dma_lock, flags);
|
||||
if (dmadata->frag_count >= 0) {
|
||||
dmadata->dmacount--;
|
||||
BUG_ON(dmadata->dmacount < 0);
|
||||
tasklet_schedule(&dmadata->tasklet);
|
||||
}
|
||||
spin_unlock_irqrestore(&dmadata->dma_lock, flags);
|
||||
}
|
||||
|
||||
static struct dma_async_tx_descriptor *
|
||||
txx9aclc_dma_submit(struct txx9aclc_dmadata *dmadata, dma_addr_t buf_dma_addr)
|
||||
{
|
||||
struct dma_chan *chan = dmadata->dma_chan;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct scatterlist sg;
|
||||
|
||||
sg_init_table(&sg, 1);
|
||||
sg_set_page(&sg, pfn_to_page(PFN_DOWN(buf_dma_addr)),
|
||||
dmadata->frag_bytes, buf_dma_addr & (PAGE_SIZE - 1));
|
||||
sg_dma_address(&sg) = buf_dma_addr;
|
||||
desc = chan->device->device_prep_slave_sg(chan, &sg, 1,
|
||||
dmadata->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
||||
DMA_TO_DEVICE : DMA_FROM_DEVICE,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc) {
|
||||
dev_err(&chan->dev->device, "cannot prepare slave dma\n");
|
||||
return NULL;
|
||||
}
|
||||
desc->callback = txx9aclc_dma_complete;
|
||||
desc->callback_param = dmadata;
|
||||
desc->tx_submit(desc);
|
||||
return desc;
|
||||
}
|
||||
|
||||
#define NR_DMA_CHAIN 2
|
||||
|
||||
static void txx9aclc_dma_tasklet(unsigned long data)
|
||||
{
|
||||
struct txx9aclc_dmadata *dmadata = (struct txx9aclc_dmadata *)data;
|
||||
struct dma_chan *chan = dmadata->dma_chan;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct snd_pcm_substream *substream = dmadata->substream;
|
||||
u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
||||
ACCTL_AUDODMA : ACCTL_AUDIDMA;
|
||||
int i;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dmadata->dma_lock, flags);
|
||||
if (dmadata->frag_count < 0) {
|
||||
struct txx9aclc_soc_device *dev =
|
||||
container_of(dmadata, struct txx9aclc_soc_device,
|
||||
dmadata[substream->stream]);
|
||||
struct txx9aclc_plat_drvdata *drvdata =
|
||||
txx9aclc_get_plat_drvdata(dev);
|
||||
void __iomem *base = drvdata->base;
|
||||
|
||||
spin_unlock_irqrestore(&dmadata->dma_lock, flags);
|
||||
chan->device->device_terminate_all(chan);
|
||||
/* first time */
|
||||
for (i = 0; i < NR_DMA_CHAIN; i++) {
|
||||
desc = txx9aclc_dma_submit(dmadata,
|
||||
dmadata->dma_addr + i * dmadata->frag_bytes);
|
||||
if (!desc)
|
||||
return;
|
||||
}
|
||||
dmadata->dmacount = NR_DMA_CHAIN;
|
||||
chan->device->device_issue_pending(chan);
|
||||
spin_lock_irqsave(&dmadata->dma_lock, flags);
|
||||
__raw_writel(ctlbit, base + ACCTLEN);
|
||||
dmadata->frag_count = NR_DMA_CHAIN % dmadata->frags;
|
||||
spin_unlock_irqrestore(&dmadata->dma_lock, flags);
|
||||
return;
|
||||
}
|
||||
BUG_ON(dmadata->dmacount >= NR_DMA_CHAIN);
|
||||
while (dmadata->dmacount < NR_DMA_CHAIN) {
|
||||
dmadata->dmacount++;
|
||||
spin_unlock_irqrestore(&dmadata->dma_lock, flags);
|
||||
desc = txx9aclc_dma_submit(dmadata,
|
||||
dmadata->dma_addr +
|
||||
dmadata->frag_count * dmadata->frag_bytes);
|
||||
if (!desc)
|
||||
return;
|
||||
chan->device->device_issue_pending(chan);
|
||||
|
||||
spin_lock_irqsave(&dmadata->dma_lock, flags);
|
||||
dmadata->frag_count++;
|
||||
dmadata->frag_count %= dmadata->frags;
|
||||
dmadata->pos += dmadata->frag_bytes;
|
||||
dmadata->pos %= dmadata->buffer_bytes;
|
||||
if ((dmadata->frag_count * dmadata->frag_bytes) %
|
||||
dmadata->period_bytes == 0)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
spin_unlock_irqrestore(&dmadata->dma_lock, flags);
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct txx9aclc_soc_device *dev =
|
||||
container_of(rtd->socdev, struct txx9aclc_soc_device, soc_dev);
|
||||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev);
|
||||
void __iomem *base = drvdata->base;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
||||
ACCTL_AUDODMA : ACCTL_AUDIDMA;
|
||||
|
||||
spin_lock_irqsave(&dmadata->dma_lock, flags);
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
dmadata->frag_count = -1;
|
||||
tasklet_schedule(&dmadata->tasklet);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
__raw_writel(ctlbit, base + ACCTLDIS);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
__raw_writel(ctlbit, base + ACCTLEN);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
spin_unlock_irqrestore(&dmadata->dma_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
txx9aclc_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
|
||||
|
||||
return bytes_to_frames(substream->runtime, dmadata->pos);
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct txx9aclc_soc_device *dev =
|
||||
container_of(rtd->socdev, struct txx9aclc_soc_device, soc_dev);
|
||||
struct txx9aclc_dmadata *dmadata = &dev->dmadata[substream->stream];
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_set_runtime_hwparams(substream, &txx9aclc_pcm_hardware);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* ensure that buffer size is a multiple of period size */
|
||||
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
substream->runtime->private_data = dmadata;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
|
||||
struct dma_chan *chan = dmadata->dma_chan;
|
||||
|
||||
dmadata->frag_count = -1;
|
||||
chan->device->device_terminate_all(chan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops txx9aclc_pcm_ops = {
|
||||
.open = txx9aclc_pcm_open,
|
||||
.close = txx9aclc_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = txx9aclc_pcm_hw_params,
|
||||
.hw_free = txx9aclc_pcm_hw_free,
|
||||
.prepare = txx9aclc_pcm_prepare,
|
||||
.trigger = txx9aclc_pcm_trigger,
|
||||
.pointer = txx9aclc_pcm_pointer,
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
card->dev, 64 * 1024, 4 * 1024 * 1024);
|
||||
}
|
||||
|
||||
static bool filter(struct dma_chan *chan, void *param)
|
||||
{
|
||||
struct txx9aclc_dmadata *dmadata = param;
|
||||
char devname[BUS_ID_SIZE + 2];
|
||||
|
||||
sprintf(devname, "%s.%d", dmadata->dma_res->name,
|
||||
(int)dmadata->dma_res->start);
|
||||
if (strcmp(dev_name(chan->device->dev), devname) == 0) {
|
||||
chan->private = &dmadata->dma_slave;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev,
|
||||
struct txx9aclc_dmadata *dmadata)
|
||||
{
|
||||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev);
|
||||
struct txx9dmac_slave *ds = &dmadata->dma_slave;
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
spin_lock_init(&dmadata->dma_lock);
|
||||
|
||||
ds->reg_width = sizeof(u32);
|
||||
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
ds->tx_reg = drvdata->physbase + ACAUDODAT;
|
||||
ds->rx_reg = 0;
|
||||
} else {
|
||||
ds->tx_reg = 0;
|
||||
ds->rx_reg = drvdata->physbase + ACAUDIDAT;
|
||||
}
|
||||
|
||||
/* Try to grab a DMA channel */
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
dmadata->dma_chan = dma_request_channel(mask, filter, dmadata);
|
||||
if (!dmadata->dma_chan) {
|
||||
dev_err(dev->soc_dev.dev,
|
||||
"DMA channel for %s is not available\n",
|
||||
dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
||||
"playback" : "capture");
|
||||
return -EBUSY;
|
||||
}
|
||||
tasklet_init(&dmadata->tasklet, txx9aclc_dma_tasklet,
|
||||
(unsigned long)dmadata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct txx9aclc_soc_device *dev =
|
||||
container_of(socdev, struct txx9aclc_soc_device, soc_dev);
|
||||
struct resource *r;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
dev->dmadata[0].stream = SNDRV_PCM_STREAM_PLAYBACK;
|
||||
dev->dmadata[1].stream = SNDRV_PCM_STREAM_CAPTURE;
|
||||
for (i = 0; i < 2; i++) {
|
||||
r = platform_get_resource(dev->aclc_pdev, IORESOURCE_DMA, i);
|
||||
if (!r) {
|
||||
ret = -EBUSY;
|
||||
goto exit;
|
||||
}
|
||||
dev->dmadata[i].dma_res = r;
|
||||
ret = txx9aclc_dma_init(dev, &dev->dmadata[i]);
|
||||
if (ret)
|
||||
goto exit;
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit:
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (dev->dmadata[i].dma_chan)
|
||||
dma_release_channel(dev->dmadata[i].dma_chan);
|
||||
dev->dmadata[i].dma_chan = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int txx9aclc_pcm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct txx9aclc_soc_device *dev =
|
||||
container_of(socdev, struct txx9aclc_soc_device, soc_dev);
|
||||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev);
|
||||
void __iomem *base = drvdata->base;
|
||||
int i;
|
||||
|
||||
/* disable all FIFO DMAs */
|
||||
__raw_writel(ACCTL_AUDODMA | ACCTL_AUDIDMA, base + ACCTLDIS);
|
||||
/* dummy R/W to clear pending DMAREQ if any */
|
||||
__raw_writel(__raw_readl(base + ACAUDIDAT), base + ACAUDODAT);
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
struct txx9aclc_dmadata *dmadata = &dev->dmadata[i];
|
||||
struct dma_chan *chan = dmadata->dma_chan;
|
||||
if (chan) {
|
||||
dmadata->frag_count = -1;
|
||||
chan->device->device_terminate_all(chan);
|
||||
dma_release_channel(chan);
|
||||
}
|
||||
dev->dmadata[i].dma_chan = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_platform txx9aclc_soc_platform = {
|
||||
.name = "txx9aclc-audio",
|
||||
.probe = txx9aclc_pcm_probe,
|
||||
.remove = txx9aclc_pcm_remove,
|
||||
.pcm_ops = &txx9aclc_pcm_ops,
|
||||
.pcm_new = txx9aclc_pcm_new,
|
||||
.pcm_free = txx9aclc_pcm_free_dma_buffers,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(txx9aclc_soc_platform);
|
||||
|
||||
static int __init txx9aclc_soc_platform_init(void)
|
||||
{
|
||||
return snd_soc_register_platform(&txx9aclc_soc_platform);
|
||||
}
|
||||
|
||||
static void __exit txx9aclc_soc_platform_exit(void)
|
||||
{
|
||||
snd_soc_unregister_platform(&txx9aclc_soc_platform);
|
||||
}
|
||||
|
||||
module_init(txx9aclc_soc_platform_init);
|
||||
module_exit(txx9aclc_soc_platform_exit);
|
||||
|
||||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
|
||||
MODULE_DESCRIPTION("TXx9 ACLC Audio DMA driver");
|
||||
MODULE_LICENSE("GPL");
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user