dect
/
linux-2.6
Archived
13
0
Fork 0

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:
Takashi Iwai 2009-06-10 07:26:18 +02:00
commit ba252af8d6
101 changed files with 12473 additions and 1653 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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;
};

View File

@ -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

View File

@ -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
View 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

View File

@ -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);

View File

@ -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;

View File

@ -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"

View File

@ -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/

View File

@ -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.

View File

@ -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

View File

@ -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);

View 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");

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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,
};
/*

View 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);

View 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 */

View File

@ -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
View 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");

View 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

View File

@ -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

View File

@ -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__ */

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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]);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

60
sound/soc/codecs/wm8988.h Normal file
View 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

View File

@ -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

File diff suppressed because it is too large Load Diff

787
sound/soc/codecs/wm9081.h Normal file
View 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

View File

@ -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,
},

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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

View 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");

View File

@ -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
View 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(&regs->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(&regs->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(&regs->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(&regs->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");

View 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__ */

View 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(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR);
udelay(3);
out_be32(&regs->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(&regs->op1, MPC52xx_PSC_OP_RES);
udelay(10);
out_8(&regs->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(&regs->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(&regs->sicr, psc_dma->sicr);
/* No slots active */
out_be32(&regs->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");

View 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__ */

View File

@ -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(&regs->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(&regs->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(&regs->command, MPC52xx_PSC_RST_RX);
out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
} else {
out_8(&regs->command, MPC52xx_PSC_RST_TX);
out_8(&regs->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(&regs->ipcr_acr.ipcr) & 0x80) != 0)
;
/* then wait for the transition to high */
while ((in_8(&regs->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(&regs->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(&regs->command, 2 << 4); /* reset rx */
out_8(&regs->command, 3 << 4); /* reset tx */
out_8(&regs->command, 4 << 4); /* reset err */
}
} else {
out_8(&regs->command, 3 << 4); /* reset tx */
out_8(&regs->command, 4 << 4); /* reset err */
if (!psc_i2s->capture.active)
out_8(&regs->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(&regs->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(&regs->ipcr_acr.ipcr) & 0x80) != 0)
;
/* then wait for the transition to high */
while ((in_8(&regs->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");

View 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__ */

View 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");

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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, \
}, \

View File

@ -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

View File

@ -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,
};

View File

@ -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
View 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");

View File

@ -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,
}

View File

@ -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,
};

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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
View 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");

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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");

View File

@ -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
View 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
View 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
View 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");

View 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
View 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(&params->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");

View 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

View 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");

View File

@ -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);

View File

@ -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();

View File

@ -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
View 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
View 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

View 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");

View 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
View 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