Merge branch 'topic/asoc' into for-linus
This commit is contained in:
commit
6679ee1870
|
@ -13,3 +13,7 @@ obj-$(CONFIG_USB_EHCI_MXC) += ehci.o
|
|||
obj-$(CONFIG_MXC_ULPI) += ulpi.o
|
||||
obj-$(CONFIG_ARCH_MXC_AUDMUX_V1) += audmux-v1.o
|
||||
obj-$(CONFIG_ARCH_MXC_AUDMUX_V2) += audmux-v2.o
|
||||
ifdef CONFIG_SND_IMX_SOC
|
||||
obj-y += ssi-fiq.o
|
||||
obj-y += ssi-fiq-ksym.o
|
||||
endif
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Exported ksyms for the SSI FIQ handler
|
||||
*
|
||||
* Copyright (C) 2009, Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* 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 <mach/ssi.h>
|
||||
|
||||
EXPORT_SYMBOL(imx_ssi_fiq_tx_buffer);
|
||||
EXPORT_SYMBOL(imx_ssi_fiq_rx_buffer);
|
||||
EXPORT_SYMBOL(imx_ssi_fiq_start);
|
||||
EXPORT_SYMBOL(imx_ssi_fiq_end);
|
||||
EXPORT_SYMBOL(imx_ssi_fiq_base);
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (C) 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* 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/linkage.h>
|
||||
#include <asm/assembler.h>
|
||||
|
||||
/*
|
||||
* r8 = bit 0-15: tx offset, bit 16-31: tx buffer size
|
||||
* r9 = bit 0-15: rx offset, bit 16-31: rx buffer size
|
||||
*/
|
||||
|
||||
#define SSI_STX0 0x00
|
||||
#define SSI_SRX0 0x08
|
||||
#define SSI_SISR 0x14
|
||||
#define SSI_SIER 0x18
|
||||
#define SSI_SACNT 0x38
|
||||
|
||||
#define SSI_SACNT_AC97EN (1 << 0)
|
||||
|
||||
#define SSI_SIER_TFE0_EN (1 << 0)
|
||||
#define SSI_SISR_TFE0 (1 << 0)
|
||||
#define SSI_SISR_RFF0 (1 << 2)
|
||||
#define SSI_SIER_RFF0_EN (1 << 2)
|
||||
|
||||
.text
|
||||
.global imx_ssi_fiq_start
|
||||
.global imx_ssi_fiq_end
|
||||
.global imx_ssi_fiq_base
|
||||
.global imx_ssi_fiq_rx_buffer
|
||||
.global imx_ssi_fiq_tx_buffer
|
||||
|
||||
imx_ssi_fiq_start:
|
||||
ldr r12, imx_ssi_fiq_base
|
||||
|
||||
/* TX */
|
||||
ldr r11, imx_ssi_fiq_tx_buffer
|
||||
|
||||
/* shall we send? */
|
||||
ldr r13, [r12, #SSI_SIER]
|
||||
tst r13, #SSI_SIER_TFE0_EN
|
||||
beq 1f
|
||||
|
||||
/* TX FIFO empty? */
|
||||
ldr r13, [r12, #SSI_SISR]
|
||||
tst r13, #SSI_SISR_TFE0
|
||||
beq 1f
|
||||
|
||||
mov r10, #0x10000
|
||||
sub r10, #1
|
||||
and r10, r10, r8 /* r10: current buffer offset */
|
||||
|
||||
add r11, r11, r10
|
||||
|
||||
ldrh r13, [r11]
|
||||
strh r13, [r12, #SSI_STX0]
|
||||
|
||||
ldrh r13, [r11, #2]
|
||||
strh r13, [r12, #SSI_STX0]
|
||||
|
||||
ldrh r13, [r11, #4]
|
||||
strh r13, [r12, #SSI_STX0]
|
||||
|
||||
ldrh r13, [r11, #6]
|
||||
strh r13, [r12, #SSI_STX0]
|
||||
|
||||
add r10, #8
|
||||
lsr r13, r8, #16 /* r13: buffer size */
|
||||
cmp r10, r13
|
||||
lslgt r8, r13, #16
|
||||
addle r8, #8
|
||||
1:
|
||||
/* RX */
|
||||
|
||||
/* shall we receive? */
|
||||
ldr r13, [r12, #SSI_SIER]
|
||||
tst r13, #SSI_SIER_RFF0_EN
|
||||
beq 1f
|
||||
|
||||
/* RX FIFO full? */
|
||||
ldr r13, [r12, #SSI_SISR]
|
||||
tst r13, #SSI_SISR_RFF0
|
||||
beq 1f
|
||||
|
||||
ldr r11, imx_ssi_fiq_rx_buffer
|
||||
|
||||
mov r10, #0x10000
|
||||
sub r10, #1
|
||||
and r10, r10, r9 /* r10: current buffer offset */
|
||||
|
||||
add r11, r11, r10
|
||||
|
||||
ldr r13, [r12, #SSI_SACNT]
|
||||
tst r13, #SSI_SACNT_AC97EN
|
||||
|
||||
ldr r13, [r12, #SSI_SRX0]
|
||||
strh r13, [r11]
|
||||
|
||||
ldr r13, [r12, #SSI_SRX0]
|
||||
strh r13, [r11, #2]
|
||||
|
||||
/* dummy read to skip slot 12 */
|
||||
ldrne r13, [r12, #SSI_SRX0]
|
||||
|
||||
ldr r13, [r12, #SSI_SRX0]
|
||||
strh r13, [r11, #4]
|
||||
|
||||
ldr r13, [r12, #SSI_SRX0]
|
||||
strh r13, [r11, #6]
|
||||
|
||||
/* dummy read to skip slot 12 */
|
||||
ldrne r13, [r12, #SSI_SRX0]
|
||||
|
||||
add r10, #8
|
||||
lsr r13, r9, #16 /* r13: buffer size */
|
||||
cmp r10, r13
|
||||
lslgt r9, r13, #16
|
||||
addle r9, #8
|
||||
|
||||
1:
|
||||
@ return from FIQ
|
||||
subs pc, lr, #4
|
||||
imx_ssi_fiq_base:
|
||||
.word 0x0
|
||||
imx_ssi_fiq_rx_buffer:
|
||||
.word 0x0
|
||||
imx_ssi_fiq_tx_buffer:
|
||||
.word 0x0
|
||||
imx_ssi_fiq_end:
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* platform header for the SIU ASoC driver
|
||||
*
|
||||
* Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
*
|
||||
* 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 ASM_SIU_H
|
||||
#define ASM_SIU_H
|
||||
|
||||
#include <asm/dma-sh.h>
|
||||
|
||||
struct device;
|
||||
|
||||
struct siu_platform {
|
||||
struct device *dma_dev;
|
||||
enum sh_dmae_slave_chan_id dma_slave_tx_a;
|
||||
enum sh_dmae_slave_chan_id dma_slave_rx_a;
|
||||
enum sh_dmae_slave_chan_id dma_slave_tx_b;
|
||||
enum sh_dmae_slave_chan_id dma_slave_rx_b;
|
||||
};
|
||||
|
||||
#endif /* ASM_SIU_H */
|
|
@ -115,7 +115,8 @@
|
|||
#define twl_has_watchdog() false
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE)
|
||||
#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\
|
||||
defined(CONFIG_SND_SOC_TWL6030) || defined(CONFIG_SND_SOC_TWL6030_MODULE)
|
||||
#define twl_has_codec() true
|
||||
#else
|
||||
#define twl_has_codec() false
|
||||
|
@ -711,8 +712,19 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
|
|||
return PTR_ERR(child);
|
||||
}
|
||||
|
||||
if (twl_has_codec() && pdata->codec) {
|
||||
child = add_child(1, "twl4030_codec",
|
||||
if (twl_has_codec() && pdata->codec && twl_class_is_4030()) {
|
||||
sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;
|
||||
child = add_child(sub_chip_id, "twl4030_codec",
|
||||
pdata->codec, sizeof(*pdata->codec),
|
||||
false, 0, 0);
|
||||
if (IS_ERR(child))
|
||||
return PTR_ERR(child);
|
||||
}
|
||||
|
||||
/* Phoenix*/
|
||||
if (twl_has_codec() && pdata->codec && twl_class_is_6030()) {
|
||||
sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;
|
||||
child = add_child(sub_chip_id, "twl6030_codec",
|
||||
pdata->codec, sizeof(*pdata->codec),
|
||||
false, 0, 0);
|
||||
if (IS_ERR(child))
|
||||
|
|
|
@ -547,6 +547,10 @@ struct twl4030_codec_data {
|
|||
unsigned int audio_mclk;
|
||||
struct twl4030_codec_audio_data *audio;
|
||||
struct twl4030_codec_vibra_data *vibra;
|
||||
|
||||
/* twl6030 */
|
||||
int audpwron_gpio; /* audio power-on gpio */
|
||||
int naudint_irq; /* audio interrupt */
|
||||
};
|
||||
|
||||
struct twl4030_platform_data {
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#include <linux/list.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
struct snd_pcm_substream;
|
||||
|
||||
/*
|
||||
|
|
|
@ -95,6 +95,21 @@
|
|||
.shift = wshift, .invert = winvert, .kcontrols = wcontrols, \
|
||||
.num_kcontrols = 1}
|
||||
|
||||
/* Simplified versions of above macros, assuming wncontrols = ARRAY_SIZE(wcontrols) */
|
||||
#define SOC_PGA_ARRAY(wname, wreg, wshift, winvert,\
|
||||
wcontrols) \
|
||||
{ .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \
|
||||
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)}
|
||||
#define SOC_MIXER_ARRAY(wname, wreg, wshift, winvert, \
|
||||
wcontrols)\
|
||||
{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
|
||||
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)}
|
||||
#define SOC_MIXER_NAMED_CTL_ARRAY(wname, wreg, wshift, winvert, \
|
||||
wcontrols)\
|
||||
{ .id = snd_soc_dapm_mixer_named_ctl, .name = wname, .reg = wreg, \
|
||||
.shift = wshift, .invert = winvert, .kcontrols = wcontrols, \
|
||||
.num_kcontrols = ARRAY_SIZE(wcontrols)}
|
||||
|
||||
/* path domain with event - event handler must return 0 for success */
|
||||
#define SND_SOC_DAPM_PGA_E(wname, wreg, wshift, winvert, wcontrols, \
|
||||
wncontrols, wevent, wflags) \
|
||||
|
@ -126,6 +141,23 @@
|
|||
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1, \
|
||||
.event = wevent, .event_flags = wflags}
|
||||
|
||||
/* Simplified versions of above macros, assuming wncontrols = ARRAY_SIZE(wcontrols) */
|
||||
#define SOC_PGA_E_ARRAY(wname, wreg, wshift, winvert, wcontrols, \
|
||||
wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \
|
||||
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \
|
||||
.event = wevent, .event_flags = wflags}
|
||||
#define SOC_MIXER_E_ARRAY(wname, wreg, wshift, winvert, wcontrols, \
|
||||
wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
|
||||
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \
|
||||
.event = wevent, .event_flags = wflags}
|
||||
#define SOC_MIXER_NAMED_CTL_E_ARRAY(wname, wreg, wshift, winvert, \
|
||||
wcontrols, wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
|
||||
.invert = winvert, .kcontrols = wcontrols, \
|
||||
.num_kcontrols = ARRAY_SIZE(wcontrols), .event = wevent, .event_flags = wflags}
|
||||
|
||||
/* events that are pre and post DAPM */
|
||||
#define SND_SOC_DAPM_PRE(wname, wevent) \
|
||||
{ .id = snd_soc_dapm_pre, .name = wname, .kcontrols = NULL, \
|
||||
|
|
|
@ -168,6 +168,23 @@
|
|||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = (unsigned long)&xenum }
|
||||
|
||||
/*
|
||||
* Simplified versions of above macros, declaring a struct and calculating
|
||||
* ARRAY_SIZE internally
|
||||
*/
|
||||
#define SOC_ENUM_DOUBLE_DECL(name, xreg, xshift_l, xshift_r, xtexts) \
|
||||
struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \
|
||||
ARRAY_SIZE(xtexts), xtexts)
|
||||
#define SOC_ENUM_SINGLE_DECL(name, xreg, xshift, xtexts) \
|
||||
SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts)
|
||||
#define SOC_ENUM_SINGLE_EXT_DECL(name, xtexts) \
|
||||
struct soc_enum name = SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(xtexts), xtexts)
|
||||
#define SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift_l, xshift_r, xmask, xtexts, xvalues) \
|
||||
struct soc_enum name = SOC_VALUE_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, \
|
||||
ARRAY_SIZE(xtexts), xtexts, xvalues)
|
||||
#define SOC_VALUE_ENUM_SINGLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \
|
||||
SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues)
|
||||
|
||||
/*
|
||||
* Bias levels
|
||||
*
|
||||
|
@ -253,6 +270,9 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
|
|||
/* codec register bit access */
|
||||
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
|
||||
unsigned int mask, unsigned int value);
|
||||
int snd_soc_update_bits_locked(struct snd_soc_codec *codec,
|
||||
unsigned short reg, unsigned int mask,
|
||||
unsigned int value);
|
||||
int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg,
|
||||
unsigned int mask, unsigned int value);
|
||||
|
||||
|
@ -402,6 +422,10 @@ struct snd_soc_codec {
|
|||
short reg_cache_size;
|
||||
short reg_cache_step;
|
||||
|
||||
unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
|
||||
unsigned int cache_only:1; /* Suppress writes to hardware */
|
||||
unsigned int cache_sync:1; /* Cache needs to be synced to hardware */
|
||||
|
||||
/* dapm */
|
||||
u32 pop_time;
|
||||
struct list_head dapm_widgets;
|
||||
|
@ -497,6 +521,8 @@ struct snd_soc_card {
|
|||
int (*set_bias_level)(struct snd_soc_card *,
|
||||
enum snd_soc_bias_level level);
|
||||
|
||||
long pmdown_time;
|
||||
|
||||
/* CPU <--> Codec DAI links */
|
||||
struct snd_soc_dai_link *dai_link;
|
||||
int num_links;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
struct tlv320dac33_platform_data {
|
||||
int power_gpio;
|
||||
u8 burst_bclkdiv;
|
||||
};
|
||||
|
||||
#endif /* __TLV320DAC33_PLAT_H */
|
||||
|
|
|
@ -23,7 +23,13 @@
|
|||
#ifndef TPA6130A2_PLAT_H
|
||||
#define TPA6130A2_PLAT_H
|
||||
|
||||
enum tpa_model {
|
||||
TPA6130A2,
|
||||
TPA6140A2,
|
||||
};
|
||||
|
||||
struct tpa6130a2_platform_data {
|
||||
enum tpa_model id;
|
||||
int power_gpio;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* linux/sound/wm2000.h -- Platform data for WM2000
|
||||
*
|
||||
* Copyright 2010 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_WM2000_H
|
||||
#define __LINUX_SND_WM2000_H
|
||||
|
||||
struct wm2000_platform_data {
|
||||
/** Filename for system-specific image to download to device. */
|
||||
const char *download_file;
|
||||
|
||||
/** Divide MCLK by 2 for system clock? */
|
||||
unsigned int mclkdiv2:1;
|
||||
|
||||
/** Disable speech clarity enhancement, for use when an
|
||||
* external algorithm is used. */
|
||||
unsigned int speech_enh_disable:1;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Platform data for WM8904
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics PLC.
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MFD_WM8994_PDATA_H__
|
||||
#define __MFD_WM8994_PDATA_H__
|
||||
|
||||
#define WM8904_DRC_REGS 4
|
||||
#define WM8904_EQ_REGS 25
|
||||
|
||||
/**
|
||||
* DRC configurations are specified with a label and a set of register
|
||||
* values to write (the enable bits will be ignored). At runtime an
|
||||
* enumerated control will be presented for each DRC block allowing
|
||||
* the user to choose the configration to use.
|
||||
*
|
||||
* Configurations may be generated by hand or by using the DRC control
|
||||
* panel provided by the WISCE - see http://www.wolfsonmicro.com/wisce/
|
||||
* for details.
|
||||
*/
|
||||
struct wm8904_drc_cfg {
|
||||
const char *name;
|
||||
u16 regs[WM8904_DRC_REGS];
|
||||
};
|
||||
|
||||
/**
|
||||
* ReTune Mobile configurations are specified with a label, sample
|
||||
* rate and set of values to write (the enable bits will be ignored).
|
||||
*
|
||||
* Configurations are expected to be generated using the ReTune Mobile
|
||||
* control panel in WISCE - see http://www.wolfsonmicro.com/wisce/
|
||||
*/
|
||||
struct wm8904_retune_mobile_cfg {
|
||||
const char *name;
|
||||
unsigned int rate;
|
||||
u16 regs[WM8904_EQ_REGS];
|
||||
};
|
||||
|
||||
struct wm8904_pdata {
|
||||
int num_drc_cfgs;
|
||||
struct wm8904_drc_cfg *drc_cfgs;
|
||||
|
||||
int num_retune_mobile_cfgs;
|
||||
struct wm8904_retune_mobile_cfg *retune_mobile_cfgs;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Platform data for WM8955
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics PLC.
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __WM8955_PDATA_H__
|
||||
#define __WM8955_PDATA_H__
|
||||
|
||||
struct wm8955_pdata {
|
||||
/* Configure LOUT2/ROUT2 to drive a speaker */
|
||||
unsigned int out2_speaker:1;
|
||||
|
||||
/* Configure MONOIN+/- in differential mode */
|
||||
unsigned int monoin_diff:1;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -349,9 +349,7 @@ static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
|||
sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \
|
||||
size, &sport_handle->tx_dma_phy, GFP_KERNEL);
|
||||
if (!sport_handle->tx_dma_buf) {
|
||||
pr_err("Failed to allocate memory for tx dma \
|
||||
buf - Please increase uncached DMA \
|
||||
memory region\n");
|
||||
pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n");
|
||||
return -ENOMEM;
|
||||
} else
|
||||
memset(sport_handle->tx_dma_buf, 0, size);
|
||||
|
@ -362,9 +360,7 @@ static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
|||
sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \
|
||||
size, &sport_handle->rx_dma_phy, GFP_KERNEL);
|
||||
if (!sport_handle->rx_dma_buf) {
|
||||
pr_err("Failed to allocate memory for rx dma \
|
||||
buf - Please increase uncached DMA \
|
||||
memory region\n");
|
||||
pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n");
|
||||
return -ENOMEM;
|
||||
} else
|
||||
memset(sport_handle->rx_dma_buf, 0, size);
|
||||
|
|
|
@ -207,8 +207,7 @@ static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
|||
buf->area = dma_alloc_coherent(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area) {
|
||||
pr_err("Failed to allocate dma memory \
|
||||
Please increase uncached DMA memory region\n");
|
||||
pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
buf->bytes = size;
|
||||
|
|
|
@ -244,8 +244,7 @@ static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
|||
buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area) {
|
||||
pr_err("Failed to allocate dma memory \
|
||||
Please increase uncached DMA memory region\n");
|
||||
pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
buf->bytes = size;
|
||||
|
|
|
@ -23,6 +23,7 @@ config SND_SOC_ALL_CODECS
|
|||
select SND_SOC_AK4671 if I2C
|
||||
select SND_SOC_CS4270 if I2C
|
||||
select SND_SOC_MAX9877 if I2C
|
||||
select SND_SOC_DA7210 if I2C
|
||||
select SND_SOC_PCM3008
|
||||
select SND_SOC_SPDIF
|
||||
select SND_SOC_SSM2602 if I2C
|
||||
|
@ -35,6 +36,7 @@ config SND_SOC_ALL_CODECS
|
|||
select SND_SOC_TWL4030 if TWL4030_CORE
|
||||
select SND_SOC_UDA134X
|
||||
select SND_SOC_UDA1380 if I2C
|
||||
select SND_SOC_WM2000 if I2C
|
||||
select SND_SOC_WM8350 if MFD_WM8350
|
||||
select SND_SOC_WM8400 if MFD_WM8400
|
||||
select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
|
||||
|
@ -49,14 +51,18 @@ config SND_SOC_ALL_CODECS
|
|||
select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8900 if I2C
|
||||
select SND_SOC_WM8903 if I2C
|
||||
select SND_SOC_WM8904 if I2C
|
||||
select SND_SOC_WM8940 if I2C
|
||||
select SND_SOC_WM8955 if I2C
|
||||
select SND_SOC_WM8960 if I2C
|
||||
select SND_SOC_WM8961 if I2C
|
||||
select SND_SOC_WM8971 if I2C
|
||||
select SND_SOC_WM8974 if I2C
|
||||
select SND_SOC_WM8978 if I2C
|
||||
select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8990 if I2C
|
||||
select SND_SOC_WM8993 if I2C
|
||||
select SND_SOC_WM8994 if MFD_WM8994
|
||||
select SND_SOC_WM9081 if I2C
|
||||
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
|
||||
|
@ -112,6 +118,9 @@ config SND_SOC_AK4671
|
|||
config SND_SOC_CS4270
|
||||
tristate
|
||||
|
||||
config SND_SOC_DA7210
|
||||
tristate
|
||||
|
||||
# Cirrus Logic CS4270 Codec VD = 3.3V Errata
|
||||
# Select if you are affected by the errata where the part will not function
|
||||
# if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will
|
||||
|
@ -203,9 +212,15 @@ config SND_SOC_WM8900
|
|||
config SND_SOC_WM8903
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8904
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8940
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8955
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8960
|
||||
tristate
|
||||
|
||||
|
@ -218,6 +233,9 @@ config SND_SOC_WM8971
|
|||
config SND_SOC_WM8974
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8978
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8988
|
||||
tristate
|
||||
|
||||
|
@ -227,6 +245,9 @@ config SND_SOC_WM8990
|
|||
config SND_SOC_WM8993
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8994
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM9081
|
||||
tristate
|
||||
|
||||
|
@ -245,3 +266,6 @@ config SND_SOC_MAX9877
|
|||
|
||||
config SND_SOC_TPA6130A2
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM2000
|
||||
tristate
|
||||
|
|
|
@ -10,6 +10,7 @@ snd-soc-ak4642-objs := ak4642.o
|
|||
snd-soc-ak4671-objs := ak4671.o
|
||||
snd-soc-cs4270-objs := cs4270.o
|
||||
snd-soc-cx20442-objs := cx20442.o
|
||||
snd-soc-da7210-objs := da7210.o
|
||||
snd-soc-l3-objs := l3.o
|
||||
snd-soc-pcm3008-objs := pcm3008.o
|
||||
snd-soc-spdif-objs := spdif_transciever.o
|
||||
|
@ -36,14 +37,18 @@ snd-soc-wm8753-objs := wm8753.o
|
|||
snd-soc-wm8776-objs := wm8776.o
|
||||
snd-soc-wm8900-objs := wm8900.o
|
||||
snd-soc-wm8903-objs := wm8903.o
|
||||
snd-soc-wm8904-objs := wm8904.o
|
||||
snd-soc-wm8940-objs := wm8940.o
|
||||
snd-soc-wm8955-objs := wm8955.o
|
||||
snd-soc-wm8960-objs := wm8960.o
|
||||
snd-soc-wm8961-objs := wm8961.o
|
||||
snd-soc-wm8971-objs := wm8971.o
|
||||
snd-soc-wm8974-objs := wm8974.o
|
||||
snd-soc-wm8978-objs := wm8978.o
|
||||
snd-soc-wm8988-objs := wm8988.o
|
||||
snd-soc-wm8990-objs := wm8990.o
|
||||
snd-soc-wm8993-objs := wm8993.o
|
||||
snd-soc-wm8994-objs := wm8994.o
|
||||
snd-soc-wm9081-objs := wm9081.o
|
||||
snd-soc-wm9705-objs := wm9705.o
|
||||
snd-soc-wm9712-objs := wm9712.o
|
||||
|
@ -53,6 +58,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o
|
|||
# Amp
|
||||
snd-soc-max9877-objs := max9877.o
|
||||
snd-soc-tpa6130a2-objs := tpa6130a2.o
|
||||
snd-soc-wm2000-objs := wm2000.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
|
||||
obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
|
||||
|
@ -66,6 +72,7 @@ obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
|
|||
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
|
||||
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
|
||||
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
|
||||
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
|
||||
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
|
||||
|
@ -92,14 +99,18 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
|
|||
obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.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_WM8974) += snd-soc-wm8974.o
|
||||
obj-$(CONFIG_SND_SOC_WM8904) += snd-soc-wm8904.o
|
||||
obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o
|
||||
obj-$(CONFIG_SND_SOC_WM8955) += snd-soc-wm8955.o
|
||||
obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o
|
||||
obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o
|
||||
obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
|
||||
obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o
|
||||
obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o
|
||||
obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
|
||||
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
|
||||
obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o
|
||||
obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.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
|
||||
|
@ -109,3 +120,4 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
|
|||
# Amp
|
||||
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
|
||||
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
|
||||
obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
|
||||
|
|
|
@ -171,57 +171,35 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream,
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* interface to read/write ad1836 register
|
||||
*/
|
||||
#define AD1836_SPI_REG_SHFT 12
|
||||
#define AD1836_SPI_READ (1 << 11)
|
||||
#define AD1836_SPI_VAL_MSK 0x3FF
|
||||
|
||||
/*
|
||||
* write to the ad1836 register space
|
||||
*/
|
||||
|
||||
static int ad1836_write_reg(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
#ifdef CONFIG_PM
|
||||
static int ad1836_soc_suspend(struct platform_device *pdev,
|
||||
pm_message_t state)
|
||||
{
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
int ret = 0;
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
if (value != reg_cache[reg]) {
|
||||
unsigned short buf;
|
||||
struct spi_transfer t = {
|
||||
.tx_buf = &buf,
|
||||
.len = 2,
|
||||
};
|
||||
struct spi_message m;
|
||||
/* reset clock control mode */
|
||||
u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
|
||||
adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
|
||||
|
||||
buf = (reg << AD1836_SPI_REG_SHFT) |
|
||||
(value & AD1836_SPI_VAL_MSK);
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t, &m);
|
||||
ret = spi_sync(codec->control_data, &m);
|
||||
if (ret == 0)
|
||||
reg_cache[reg] = value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
|
||||
}
|
||||
|
||||
/*
|
||||
* read from the ad1836 register space cache
|
||||
*/
|
||||
static unsigned int ad1836_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
static int ad1836_soc_resume(struct platform_device *pdev)
|
||||
{
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
if (reg >= codec->reg_cache_size)
|
||||
return -EINVAL;
|
||||
/* restore clock control mode */
|
||||
u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
|
||||
adc_ctrl2 |= AD1836_ADC_AUX;
|
||||
|
||||
return reg_cache[reg];
|
||||
return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
|
||||
}
|
||||
#else
|
||||
#define ad1836_soc_suspend NULL
|
||||
#define ad1836_soc_resume NULL
|
||||
#endif
|
||||
|
||||
static int __devinit ad1836_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
|
@ -306,32 +284,38 @@ static int ad1836_register(struct ad1836_priv *ad1836)
|
|||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &ad1836_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->write = ad1836_write_reg;
|
||||
codec->read = ad1836_read_reg_cache;
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
ad1836_dai.dev = codec->dev;
|
||||
ad1836_codec = codec;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 4, 12, SND_SOC_SPI);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to set cache I/O: %d\n",
|
||||
ret);
|
||||
kfree(ad1836);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* default setting for ad1836 */
|
||||
/* de-emphasis: 48kHz, power-on dac */
|
||||
codec->write(codec, AD1836_DAC_CTRL1, 0x300);
|
||||
snd_soc_write(codec, AD1836_DAC_CTRL1, 0x300);
|
||||
/* unmute dac channels */
|
||||
codec->write(codec, AD1836_DAC_CTRL2, 0x0);
|
||||
snd_soc_write(codec, AD1836_DAC_CTRL2, 0x0);
|
||||
/* high-pass filter enable, power-on adc */
|
||||
codec->write(codec, AD1836_ADC_CTRL1, 0x100);
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL1, 0x100);
|
||||
/* unmute adc channles, adc aux mode */
|
||||
codec->write(codec, AD1836_ADC_CTRL2, 0x180);
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL2, 0x180);
|
||||
/* left/right diff:PGA/MUX */
|
||||
codec->write(codec, AD1836_ADC_CTRL3, 0x3A);
|
||||
snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
|
||||
/* volume */
|
||||
codec->write(codec, AD1836_DAC_L1_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_R1_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_L2_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_R2_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_L3_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_R3_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_L1_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_R1_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_L2_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_R2_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_L3_VOL, 0x3FF);
|
||||
snd_soc_write(codec, AD1836_DAC_R3_VOL, 0x3FF);
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
|
@ -404,6 +388,8 @@ static int ad1836_remove(struct platform_device *pdev)
|
|||
struct snd_soc_codec_device soc_codec_dev_ad1836 = {
|
||||
.probe = ad1836_probe,
|
||||
.remove = ad1836_remove,
|
||||
.suspend = ad1836_soc_suspend,
|
||||
.resume = ad1836_soc_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_ad1836);
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
#define AD1836_ADC_SERFMT_MASK (7 << 6)
|
||||
#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6)
|
||||
#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6)
|
||||
#define AD1836_ADC_AUX (0x6 << 6)
|
||||
|
||||
#define AD1836_ADC_CTRL3 14
|
||||
|
||||
|
|
|
@ -46,6 +46,11 @@ struct ad1938_priv {
|
|||
u8 reg_cache[AD1938_NUM_REGS];
|
||||
};
|
||||
|
||||
/* ad1938 register cache & default register settings */
|
||||
static const u8 ad1938_reg[AD1938_NUM_REGS] = {
|
||||
0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0,
|
||||
};
|
||||
|
||||
static struct snd_soc_codec *ad1938_codec;
|
||||
struct snd_soc_codec_device soc_codec_dev_ad1938;
|
||||
static int ad1938_register(struct ad1938_priv *ad1938);
|
||||
|
@ -97,6 +102,7 @@ static const struct snd_kcontrol_new ad1938_snd_controls[] = {
|
|||
static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1),
|
||||
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_SUPPLY("PLL_PWR", AD1938_PLL_CLK_CTRL0, 0, 1, NULL, 0),
|
||||
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0),
|
||||
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
|
||||
|
@ -107,6 +113,8 @@ static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = {
|
|||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_paths[] = {
|
||||
{ "DAC", NULL, "PLL_PWR" },
|
||||
{ "ADC", NULL, "PLL_PWR" },
|
||||
{ "DAC", NULL, "ADC_PWR" },
|
||||
{ "ADC", NULL, "ADC_PWR" },
|
||||
{ "DAC1OUT", "DAC1 Switch", "DAC" },
|
||||
|
@ -126,30 +134,20 @@ static int ad1938_mute(struct snd_soc_dai *dai, int mute)
|
|||
struct snd_soc_codec *codec = dai->codec;
|
||||
int reg;
|
||||
|
||||
reg = codec->read(codec, AD1938_DAC_CTRL2);
|
||||
reg = snd_soc_read(codec, AD1938_DAC_CTRL2);
|
||||
reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg &
|
||||
(~AD1938_DAC_MASTER_MUTE);
|
||||
codec->write(codec, AD1938_DAC_CTRL2, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int ad1938_pll_powerctrl(struct snd_soc_codec *codec, int cmd)
|
||||
{
|
||||
int reg = codec->read(codec, AD1938_PLL_CLK_CTRL0);
|
||||
reg = (cmd > 0) ? reg & (~AD1938_PLL_POWERDOWN) : reg |
|
||||
AD1938_PLL_POWERDOWN;
|
||||
codec->write(codec, AD1938_PLL_CLK_CTRL0, reg);
|
||||
snd_soc_write(codec, AD1938_DAC_CTRL2, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
|
||||
unsigned int mask, int slots, int width)
|
||||
unsigned int rx_mask, int slots, int width)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
int dac_reg = codec->read(codec, AD1938_DAC_CTRL1);
|
||||
int adc_reg = codec->read(codec, AD1938_ADC_CTRL2);
|
||||
int dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1);
|
||||
int adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2);
|
||||
|
||||
dac_reg &= ~AD1938_DAC_CHAN_MASK;
|
||||
adc_reg &= ~AD1938_ADC_CHAN_MASK;
|
||||
|
@ -175,8 +173,8 @@ static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec->write(codec, AD1938_DAC_CTRL1, dac_reg);
|
||||
codec->write(codec, AD1938_ADC_CTRL2, adc_reg);
|
||||
snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg);
|
||||
snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -187,8 +185,8 @@ static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
int adc_reg, dac_reg;
|
||||
|
||||
adc_reg = codec->read(codec, AD1938_ADC_CTRL2);
|
||||
dac_reg = codec->read(codec, AD1938_DAC_CTRL1);
|
||||
adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2);
|
||||
dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1);
|
||||
|
||||
/* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S
|
||||
* with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A)
|
||||
|
@ -265,8 +263,8 @@ static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec->write(codec, AD1938_ADC_CTRL2, adc_reg);
|
||||
codec->write(codec, AD1938_DAC_CTRL1, dac_reg);
|
||||
snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg);
|
||||
snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -295,134 +293,13 @@ static int ad1938_hw_params(struct snd_pcm_substream *substream,
|
|||
break;
|
||||
}
|
||||
|
||||
reg = codec->read(codec, AD1938_DAC_CTRL2);
|
||||
reg = snd_soc_read(codec, AD1938_DAC_CTRL2);
|
||||
reg = (reg & (~AD1938_DAC_WORD_LEN_MASK)) | word_len;
|
||||
codec->write(codec, AD1938_DAC_CTRL2, reg);
|
||||
snd_soc_write(codec, AD1938_DAC_CTRL2, reg);
|
||||
|
||||
reg = codec->read(codec, AD1938_ADC_CTRL1);
|
||||
reg = snd_soc_read(codec, AD1938_ADC_CTRL1);
|
||||
reg = (reg & (~AD1938_ADC_WORD_LEN_MASK)) | word_len;
|
||||
codec->write(codec, AD1938_ADC_CTRL1, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
ad1938_pll_powerctrl(codec, 1);
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
case SND_SOC_BIAS_OFF:
|
||||
ad1938_pll_powerctrl(codec, 0);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* interface to read/write ad1938 register
|
||||
*/
|
||||
|
||||
#define AD1938_SPI_ADDR 0x4
|
||||
#define AD1938_SPI_READ 0x1
|
||||
#define AD1938_SPI_BUFLEN 3
|
||||
|
||||
/*
|
||||
* write to the ad1938 register space
|
||||
*/
|
||||
|
||||
static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
int ret = 0;
|
||||
|
||||
if (value != reg_cache[reg]) {
|
||||
uint8_t buf[AD1938_SPI_BUFLEN];
|
||||
struct spi_transfer t = {
|
||||
.tx_buf = buf,
|
||||
.len = AD1938_SPI_BUFLEN,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
||||
buf[0] = AD1938_SPI_ADDR << 1;
|
||||
buf[1] = reg;
|
||||
buf[2] = value;
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t, &m);
|
||||
ret = spi_sync(codec->control_data, &m);
|
||||
if (ret == 0)
|
||||
reg_cache[reg] = value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* read from the ad1938 register space cache
|
||||
*/
|
||||
|
||||
static unsigned int ad1938_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
|
||||
if (reg >= codec->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
return reg_cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* read from the ad1938 register space
|
||||
*/
|
||||
|
||||
static unsigned int ad1938_read_reg(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
char w_buf[AD1938_SPI_BUFLEN];
|
||||
char r_buf[AD1938_SPI_BUFLEN];
|
||||
int ret;
|
||||
|
||||
struct spi_transfer t = {
|
||||
.tx_buf = w_buf,
|
||||
.rx_buf = r_buf,
|
||||
.len = AD1938_SPI_BUFLEN,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
||||
w_buf[0] = (AD1938_SPI_ADDR << 1) | AD1938_SPI_READ;
|
||||
w_buf[1] = reg;
|
||||
w_buf[2] = 0;
|
||||
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t, &m);
|
||||
ret = spi_sync(codec->control_data, &m);
|
||||
if (ret == 0)
|
||||
return r_buf[2];
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int ad1938_fill_cache(struct snd_soc_codec *codec)
|
||||
{
|
||||
int i;
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
struct spi_device *spi = codec->control_data;
|
||||
|
||||
for (i = 0; i < codec->reg_cache_size; i++) {
|
||||
int ret = ad1938_read_reg(codec, i);
|
||||
if (ret == -EIO) {
|
||||
dev_err(&spi->dev, "AD1938 SPI read failure\n");
|
||||
return ret;
|
||||
}
|
||||
reg_cache[i] = ret;
|
||||
}
|
||||
snd_soc_write(codec, AD1938_ADC_CTRL1, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -512,32 +389,37 @@ static int ad1938_register(struct ad1938_priv *ad1938)
|
|||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &ad1938_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->write = ad1938_write_reg;
|
||||
codec->read = ad1938_read_reg_cache;
|
||||
codec->set_bias_level = ad1938_set_bias_level;
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
ad1938_dai.dev = codec->dev;
|
||||
ad1938_codec = codec;
|
||||
|
||||
memcpy(codec->reg_cache, ad1938_reg, AD1938_NUM_REGS);
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 16, 8, SND_SOC_SPI);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to set cache I/O: %d\n",
|
||||
ret);
|
||||
kfree(ad1938);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* default setting for ad1938 */
|
||||
|
||||
/* unmute dac channels */
|
||||
codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0);
|
||||
snd_soc_write(codec, AD1938_DAC_CHNL_MUTE, 0x0);
|
||||
/* de-emphasis: 48kHz, powedown dac */
|
||||
codec->write(codec, AD1938_DAC_CTRL2, 0x1A);
|
||||
snd_soc_write(codec, AD1938_DAC_CTRL2, 0x1A);
|
||||
/* powerdown dac, dac in tdm mode */
|
||||
codec->write(codec, AD1938_DAC_CTRL0, 0x41);
|
||||
snd_soc_write(codec, AD1938_DAC_CTRL0, 0x41);
|
||||
/* high-pass filter enable */
|
||||
codec->write(codec, AD1938_ADC_CTRL0, 0x3);
|
||||
snd_soc_write(codec, AD1938_ADC_CTRL0, 0x3);
|
||||
/* sata delay=1, adc aux mode */
|
||||
codec->write(codec, AD1938_ADC_CTRL1, 0x43);
|
||||
snd_soc_write(codec, AD1938_ADC_CTRL1, 0x43);
|
||||
/* pll input: mclki/xi */
|
||||
codec->write(codec, AD1938_PLL_CLK_CTRL0, 0x9D);
|
||||
codec->write(codec, AD1938_PLL_CLK_CTRL1, 0x04);
|
||||
|
||||
ad1938_fill_cache(codec);
|
||||
snd_soc_write(codec, AD1938_PLL_CLK_CTRL0, 0x9D);
|
||||
snd_soc_write(codec, AD1938_PLL_CLK_CTRL1, 0x04);
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
|
@ -559,7 +441,6 @@ static int ad1938_register(struct ad1938_priv *ad1938)
|
|||
|
||||
static void ad1938_unregister(struct ad1938_priv *ad1938)
|
||||
{
|
||||
ad1938_set_bias_level(&ad1938->codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_unregister_dai(&ad1938_dai);
|
||||
snd_soc_unregister_codec(&ad1938->codec);
|
||||
kfree(ad1938);
|
||||
|
@ -593,7 +474,6 @@ static int ad1938_probe(struct platform_device *pdev)
|
|||
ARRAY_SIZE(ad1938_dapm_widgets));
|
||||
snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
|
||||
|
||||
ad1938_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
pcm_err:
|
||||
return ret;
|
||||
|
@ -610,37 +490,9 @@ static int ad1938_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ad1938_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;
|
||||
|
||||
ad1938_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
|
||||
ad1938_set_bias_level(codec, SND_SOC_BIAS_ON);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define ad1938_suspend NULL
|
||||
#define ad1938_resume NULL
|
||||
#endif
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_ad1938 = {
|
||||
.probe = ad1938_probe,
|
||||
.remove = ad1938_remove,
|
||||
.suspend = ad1938_suspend,
|
||||
.resume = ad1938_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_ad1938);
|
||||
|
||||
|
|
|
@ -185,9 +185,7 @@ struct snd_soc_dai ak4104_dai = {
|
|||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_44100 |
|
||||
SNDRV_PCM_RATE_48000 |
|
||||
SNDRV_PCM_RATE_32000,
|
||||
.rates = SNDRV_PCM_RATE_8000_192000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S24_3LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <sound/initval.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include "cs4270.h"
|
||||
|
||||
|
@ -106,6 +107,10 @@
|
|||
#define CS4270_MUTE_DAC_A 0x01
|
||||
#define CS4270_MUTE_DAC_B 0x02
|
||||
|
||||
static const char *supply_names[] = {
|
||||
"va", "vd", "vlc"
|
||||
};
|
||||
|
||||
/* Private data for the CS4270 */
|
||||
struct cs4270_private {
|
||||
struct snd_soc_codec codec;
|
||||
|
@ -114,6 +119,9 @@ struct cs4270_private {
|
|||
unsigned int mode; /* The mode (I2S or left-justified) */
|
||||
unsigned int slave_mode;
|
||||
unsigned int manual_mute;
|
||||
|
||||
/* power domain regulators */
|
||||
struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -192,6 +200,11 @@ static struct cs4270_mode_ratios cs4270_mode_ratios[] = {
|
|||
* This function must be called by the machine driver's 'startup' function,
|
||||
* otherwise the list of supported sample rates will not be available in
|
||||
* time for ALSA.
|
||||
*
|
||||
* For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause
|
||||
* theoretically possible sample rates to be enabled. Call it again with a
|
||||
* proper value set one the external clock is set (most probably you would do
|
||||
* that from a machine's driver 'hw_param' hook.
|
||||
*/
|
||||
static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
|
@ -205,20 +218,27 @@ static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
|||
|
||||
cs4270->mclk = freq;
|
||||
|
||||
for (i = 0; i < NUM_MCLK_RATIOS; i++) {
|
||||
unsigned int rate = freq / cs4270_mode_ratios[i].ratio;
|
||||
rates |= snd_pcm_rate_to_rate_bit(rate);
|
||||
if (rate < rate_min)
|
||||
rate_min = rate;
|
||||
if (rate > rate_max)
|
||||
rate_max = rate;
|
||||
}
|
||||
/* FIXME: soc should support a rate list */
|
||||
rates &= ~SNDRV_PCM_RATE_KNOT;
|
||||
if (cs4270->mclk) {
|
||||
for (i = 0; i < NUM_MCLK_RATIOS; i++) {
|
||||
unsigned int rate = freq / cs4270_mode_ratios[i].ratio;
|
||||
rates |= snd_pcm_rate_to_rate_bit(rate);
|
||||
if (rate < rate_min)
|
||||
rate_min = rate;
|
||||
if (rate > rate_max)
|
||||
rate_max = rate;
|
||||
}
|
||||
/* FIXME: soc should support a rate list */
|
||||
rates &= ~SNDRV_PCM_RATE_KNOT;
|
||||
|
||||
if (!rates) {
|
||||
dev_err(codec->dev, "could not find a valid sample rate\n");
|
||||
return -EINVAL;
|
||||
if (!rates) {
|
||||
dev_err(codec->dev, "could not find a valid sample rate\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
/* enable all possible rates */
|
||||
rates = SNDRV_PCM_RATE_8000_192000;
|
||||
rate_min = 8000;
|
||||
rate_max = 192000;
|
||||
}
|
||||
|
||||
codec_dai->playback.rates = rates;
|
||||
|
@ -579,7 +599,8 @@ static int cs4270_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = cs4270_codec;
|
||||
int ret;
|
||||
struct cs4270_private *cs4270 = codec->private_data;
|
||||
int i, ret;
|
||||
|
||||
/* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */
|
||||
socdev->card->codec = codec;
|
||||
|
@ -599,8 +620,26 @@ static int cs4270_probe(struct platform_device *pdev)
|
|||
goto error_free_pcms;
|
||||
}
|
||||
|
||||
/* get the power supply regulators */
|
||||
for (i = 0; i < ARRAY_SIZE(supply_names); i++)
|
||||
cs4270->supplies[i].supply = supply_names[i];
|
||||
|
||||
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(cs4270->supplies),
|
||||
cs4270->supplies);
|
||||
if (ret < 0)
|
||||
goto error_free_pcms;
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies),
|
||||
cs4270->supplies);
|
||||
if (ret < 0)
|
||||
goto error_free_regulators;
|
||||
|
||||
return 0;
|
||||
|
||||
error_free_regulators:
|
||||
regulator_bulk_free(ARRAY_SIZE(cs4270->supplies),
|
||||
cs4270->supplies);
|
||||
|
||||
error_free_pcms:
|
||||
snd_soc_free_pcms(socdev);
|
||||
|
||||
|
@ -616,8 +655,12 @@ error_free_pcms:
|
|||
static int cs4270_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = cs4270_codec;
|
||||
struct cs4270_private *cs4270 = codec->private_data;
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies), cs4270->supplies);
|
||||
regulator_bulk_free(ARRAY_SIZE(cs4270->supplies), cs4270->supplies);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
@ -799,17 +842,33 @@ MODULE_DEVICE_TABLE(i2c, cs4270_id);
|
|||
static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg)
|
||||
{
|
||||
struct snd_soc_codec *codec = cs4270_codec;
|
||||
int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
|
||||
struct cs4270_private *cs4270 = codec->private_data;
|
||||
int reg, ret;
|
||||
|
||||
return snd_soc_write(codec, CS4270_PWRCTL, reg);
|
||||
reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
ret = snd_soc_write(codec, CS4270_PWRCTL, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies),
|
||||
cs4270->supplies);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cs4270_soc_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_codec *codec = cs4270_codec;
|
||||
struct cs4270_private *cs4270 = codec->private_data;
|
||||
struct i2c_client *i2c_client = codec->control_data;
|
||||
int reg;
|
||||
|
||||
regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies),
|
||||
cs4270->supplies);
|
||||
|
||||
/* In case the device was put to hard reset during sleep, we need to
|
||||
* wait 500ns here before any I2C communication. */
|
||||
ndelay(500);
|
||||
|
|
|
@ -0,0 +1,589 @@
|
|||
/*
|
||||
* DA7210 ALSA Soc codec driver
|
||||
*
|
||||
* Copyright (c) 2009 Dialog Semiconductor
|
||||
* Written by David Chen <Dajun.chen@diasemi.com>
|
||||
*
|
||||
* Copyright (C) 2009 Renesas Solutions Corp.
|
||||
* Cleanups by Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* Tested on SuperH Ecovec24 board with S16/S24 LE in 48KHz using I2S
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/kernel.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/tlv.h>
|
||||
#include <sound/initval.h>
|
||||
#include <asm/div64.h>
|
||||
|
||||
#include "da7210.h"
|
||||
|
||||
/* DA7210 register space */
|
||||
#define DA7210_STATUS 0x02
|
||||
#define DA7210_STARTUP1 0x03
|
||||
#define DA7210_MIC_L 0x07
|
||||
#define DA7210_MIC_R 0x08
|
||||
#define DA7210_INMIX_L 0x0D
|
||||
#define DA7210_INMIX_R 0x0E
|
||||
#define DA7210_ADC_HPF 0x0F
|
||||
#define DA7210_ADC 0x10
|
||||
#define DA7210_DAC_HPF 0x14
|
||||
#define DA7210_DAC_L 0x15
|
||||
#define DA7210_DAC_R 0x16
|
||||
#define DA7210_DAC_SEL 0x17
|
||||
#define DA7210_OUTMIX_L 0x1C
|
||||
#define DA7210_OUTMIX_R 0x1D
|
||||
#define DA7210_HP_L_VOL 0x21
|
||||
#define DA7210_HP_R_VOL 0x22
|
||||
#define DA7210_HP_CFG 0x23
|
||||
#define DA7210_DAI_SRC_SEL 0x25
|
||||
#define DA7210_DAI_CFG1 0x26
|
||||
#define DA7210_DAI_CFG3 0x28
|
||||
#define DA7210_PLL_DIV3 0x2B
|
||||
#define DA7210_PLL 0x2C
|
||||
|
||||
/* STARTUP1 bit fields */
|
||||
#define DA7210_SC_MST_EN (1 << 0)
|
||||
|
||||
/* MIC_L bit fields */
|
||||
#define DA7210_MICBIAS_EN (1 << 6)
|
||||
#define DA7210_MIC_L_EN (1 << 7)
|
||||
|
||||
/* MIC_R bit fields */
|
||||
#define DA7210_MIC_R_EN (1 << 7)
|
||||
|
||||
/* INMIX_L bit fields */
|
||||
#define DA7210_IN_L_EN (1 << 7)
|
||||
|
||||
/* INMIX_R bit fields */
|
||||
#define DA7210_IN_R_EN (1 << 7)
|
||||
|
||||
/* ADC_HPF bit fields */
|
||||
#define DA7210_ADC_VOICE_EN (1 << 7)
|
||||
|
||||
/* ADC bit fields */
|
||||
#define DA7210_ADC_L_EN (1 << 3)
|
||||
#define DA7210_ADC_R_EN (1 << 7)
|
||||
|
||||
/* DAC_HPF fields */
|
||||
#define DA7210_DAC_VOICE_EN (1 << 7)
|
||||
|
||||
/* DAC_SEL bit fields */
|
||||
#define DA7210_DAC_L_SRC_DAI_L (4 << 0)
|
||||
#define DA7210_DAC_L_EN (1 << 3)
|
||||
#define DA7210_DAC_R_SRC_DAI_R (5 << 4)
|
||||
#define DA7210_DAC_R_EN (1 << 7)
|
||||
|
||||
/* OUTMIX_L bit fields */
|
||||
#define DA7210_OUT_L_EN (1 << 7)
|
||||
|
||||
/* OUTMIX_R bit fields */
|
||||
#define DA7210_OUT_R_EN (1 << 7)
|
||||
|
||||
/* HP_CFG bit fields */
|
||||
#define DA7210_HP_2CAP_MODE (1 << 1)
|
||||
#define DA7210_HP_SENSE_EN (1 << 2)
|
||||
#define DA7210_HP_L_EN (1 << 3)
|
||||
#define DA7210_HP_MODE (1 << 6)
|
||||
#define DA7210_HP_R_EN (1 << 7)
|
||||
|
||||
/* DAI_SRC_SEL bit fields */
|
||||
#define DA7210_DAI_OUT_L_SRC (6 << 0)
|
||||
#define DA7210_DAI_OUT_R_SRC (7 << 4)
|
||||
|
||||
/* DAI_CFG1 bit fields */
|
||||
#define DA7210_DAI_WORD_S16_LE (0 << 0)
|
||||
#define DA7210_DAI_WORD_S24_LE (2 << 0)
|
||||
#define DA7210_DAI_FLEN_64BIT (1 << 2)
|
||||
#define DA7210_DAI_MODE_MASTER (1 << 7)
|
||||
|
||||
/* DAI_CFG3 bit fields */
|
||||
#define DA7210_DAI_FORMAT_I2SMODE (0 << 0)
|
||||
#define DA7210_DAI_OE (1 << 3)
|
||||
#define DA7210_DAI_EN (1 << 7)
|
||||
|
||||
/*PLL_DIV3 bit fields */
|
||||
#define DA7210_MCLK_RANGE_10_20_MHZ (1 << 4)
|
||||
#define DA7210_PLL_BYP (1 << 6)
|
||||
|
||||
/* PLL bit fields */
|
||||
#define DA7210_PLL_FS_48000 (11 << 0)
|
||||
|
||||
#define DA7210_VERSION "0.0.1"
|
||||
|
||||
/* Codec private data */
|
||||
struct da7210_priv {
|
||||
struct snd_soc_codec codec;
|
||||
};
|
||||
|
||||
static struct snd_soc_codec *da7210_codec;
|
||||
|
||||
/*
|
||||
* Register cache
|
||||
*/
|
||||
static const u8 da7210_reg[] = {
|
||||
0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R0 - R7 */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, /* R8 - RF */
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x54, /* R10 - R17 */
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R18 - R1F */
|
||||
0x00, 0x00, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, /* R20 - R27 */
|
||||
0x04, 0x00, 0x00, 0x30, 0x2A, 0x00, 0x40, 0x00, /* R28 - R2F */
|
||||
0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, /* R30 - R37 */
|
||||
0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, /* R38 - R3F */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R40 - R4F */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R48 - R4F */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R50 - R57 */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R58 - R5F */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R60 - R67 */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R68 - R6F */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R70 - R77 */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x00, /* R78 - R7F */
|
||||
0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, /* R80 - R87 */
|
||||
0x00, /* R88 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Read da7210 register cache
|
||||
*/
|
||||
static inline u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg)
|
||||
{
|
||||
u8 *cache = codec->reg_cache;
|
||||
BUG_ON(reg > ARRAY_SIZE(da7210_reg));
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to the da7210 register space
|
||||
*/
|
||||
static int da7210_write(struct snd_soc_codec *codec, u32 reg, u32 value)
|
||||
{
|
||||
u8 *cache = codec->reg_cache;
|
||||
u8 data[2];
|
||||
|
||||
BUG_ON(codec->volatile_register);
|
||||
|
||||
data[0] = reg & 0xff;
|
||||
data[1] = value & 0xff;
|
||||
|
||||
if (reg >= codec->reg_cache_size)
|
||||
return -EIO;
|
||||
|
||||
if (2 != codec->hw_write(codec->control_data, data, 2))
|
||||
return -EIO;
|
||||
|
||||
cache[reg] = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the da7210 register space.
|
||||
*/
|
||||
static inline u32 da7210_read(struct snd_soc_codec *codec, u32 reg)
|
||||
{
|
||||
if (DA7210_STATUS == reg)
|
||||
return i2c_smbus_read_byte_data(codec->control_data, reg);
|
||||
|
||||
return da7210_read_reg_cache(codec, reg);
|
||||
}
|
||||
|
||||
static int da7210_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
|
||||
if (is_play) {
|
||||
/* PlayBack Volume 40 */
|
||||
snd_soc_update_bits(codec, DA7210_HP_L_VOL, 0x3F, 40);
|
||||
snd_soc_update_bits(codec, DA7210_HP_R_VOL, 0x3F, 40);
|
||||
|
||||
/* Enable Out */
|
||||
snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10);
|
||||
snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10);
|
||||
|
||||
} else {
|
||||
/* Volume 7 */
|
||||
snd_soc_update_bits(codec, DA7210_MIC_L, 0x7, 0x7);
|
||||
snd_soc_update_bits(codec, DA7210_MIC_R, 0x7, 0x7);
|
||||
|
||||
/* Enable Mic */
|
||||
snd_soc_update_bits(codec, DA7210_INMIX_L, 0x1F, 0x1);
|
||||
snd_soc_update_bits(codec, DA7210_INMIX_R, 0x1F, 0x1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set PCM DAI word length.
|
||||
*/
|
||||
static int da7210_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;
|
||||
u32 dai_cfg1;
|
||||
u32 reg, mask;
|
||||
|
||||
/* set DAI source to Left and Right ADC */
|
||||
da7210_write(codec, DA7210_DAI_SRC_SEL,
|
||||
DA7210_DAI_OUT_R_SRC | DA7210_DAI_OUT_L_SRC);
|
||||
|
||||
/* Enable DAI */
|
||||
da7210_write(codec, DA7210_DAI_CFG3, DA7210_DAI_OE | DA7210_DAI_EN);
|
||||
|
||||
dai_cfg1 = 0xFC & da7210_read(codec, DA7210_DAI_CFG1);
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
dai_cfg1 |= DA7210_DAI_WORD_S16_LE;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
dai_cfg1 |= DA7210_DAI_WORD_S24_LE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
|
||||
|
||||
/* FIXME
|
||||
*
|
||||
* It support 48K only now
|
||||
*/
|
||||
switch (params_rate(params)) {
|
||||
case 48000:
|
||||
if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
|
||||
reg = DA7210_DAC_HPF;
|
||||
mask = DA7210_DAC_VOICE_EN;
|
||||
} else {
|
||||
reg = DA7210_ADC_HPF;
|
||||
mask = DA7210_ADC_VOICE_EN;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, reg, mask, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set DAI mode and Format
|
||||
*/
|
||||
static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u32 dai_cfg1;
|
||||
u32 dai_cfg3;
|
||||
|
||||
dai_cfg1 = 0x7f & da7210_read(codec, DA7210_DAI_CFG1);
|
||||
dai_cfg3 = 0xfc & da7210_read(codec, DA7210_DAI_CFG3);
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
dai_cfg1 |= DA7210_DAI_MODE_MASTER;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* FIXME
|
||||
*
|
||||
* It support I2S only now
|
||||
*/
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
dai_cfg3 |= DA7210_DAI_FORMAT_I2SMODE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* FIXME
|
||||
*
|
||||
* It support 64bit data transmission only now
|
||||
*/
|
||||
dai_cfg1 |= DA7210_DAI_FLEN_64BIT;
|
||||
|
||||
da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
|
||||
da7210_write(codec, DA7210_DAI_CFG3, dai_cfg3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
/* DAI operations */
|
||||
static struct snd_soc_dai_ops da7210_dai_ops = {
|
||||
.startup = da7210_startup,
|
||||
.hw_params = da7210_hw_params,
|
||||
.set_fmt = da7210_set_dai_fmt,
|
||||
};
|
||||
|
||||
struct snd_soc_dai da7210_dai = {
|
||||
.name = "DA7210 IIS",
|
||||
.id = 0,
|
||||
/* playback capabilities */
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = DA7210_FORMATS,
|
||||
},
|
||||
/* capture capabilities */
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = DA7210_FORMATS,
|
||||
},
|
||||
.ops = &da7210_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(da7210_dai);
|
||||
|
||||
/*
|
||||
* Initialize the DA7210 driver
|
||||
* register the mixer and dsp interfaces with the kernel
|
||||
*/
|
||||
static int da7210_init(struct da7210_priv *da7210)
|
||||
{
|
||||
struct snd_soc_codec *codec = &da7210->codec;
|
||||
int ret = 0;
|
||||
|
||||
if (da7210_codec) {
|
||||
dev_err(codec->dev, "Another da7210 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = da7210;
|
||||
codec->name = "DA7210";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = da7210_read;
|
||||
codec->write = da7210_write;
|
||||
codec->dai = &da7210_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
codec->reg_cache_size = ARRAY_SIZE(da7210_reg);
|
||||
codec->reg_cache = kmemdup(da7210_reg,
|
||||
sizeof(da7210_reg), GFP_KERNEL);
|
||||
|
||||
if (!codec->reg_cache)
|
||||
return -ENOMEM;
|
||||
|
||||
da7210_dai.dev = codec->dev;
|
||||
da7210_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register CODEC: %d\n", ret);
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&da7210_dai);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
/* FIXME
|
||||
*
|
||||
* This driver use fixed value here
|
||||
*/
|
||||
|
||||
/*
|
||||
* ADC settings
|
||||
*/
|
||||
|
||||
/* Enable Left & Right MIC PGA and Mic Bias */
|
||||
da7210_write(codec, DA7210_MIC_L, DA7210_MIC_L_EN | DA7210_MICBIAS_EN);
|
||||
da7210_write(codec, DA7210_MIC_R, DA7210_MIC_R_EN);
|
||||
|
||||
/* Enable Left and Right input PGA */
|
||||
da7210_write(codec, DA7210_INMIX_L, DA7210_IN_L_EN);
|
||||
da7210_write(codec, DA7210_INMIX_R, DA7210_IN_R_EN);
|
||||
|
||||
/* Enable Left and Right ADC */
|
||||
da7210_write(codec, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN);
|
||||
|
||||
/*
|
||||
* DAC settings
|
||||
*/
|
||||
|
||||
/* Enable Left and Right DAC */
|
||||
da7210_write(codec, DA7210_DAC_SEL,
|
||||
DA7210_DAC_L_SRC_DAI_L | DA7210_DAC_L_EN |
|
||||
DA7210_DAC_R_SRC_DAI_R | DA7210_DAC_R_EN);
|
||||
|
||||
/* Enable Left and Right out PGA */
|
||||
da7210_write(codec, DA7210_OUTMIX_L, DA7210_OUT_L_EN);
|
||||
da7210_write(codec, DA7210_OUTMIX_R, DA7210_OUT_R_EN);
|
||||
|
||||
/* Enable Left and Right HeadPhone PGA */
|
||||
da7210_write(codec, DA7210_HP_CFG,
|
||||
DA7210_HP_2CAP_MODE | DA7210_HP_SENSE_EN |
|
||||
DA7210_HP_L_EN | DA7210_HP_MODE | DA7210_HP_R_EN);
|
||||
|
||||
/* Diable PLL and bypass it */
|
||||
da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000);
|
||||
|
||||
/* Bypass PLL and set MCLK freq rang to 10-20MHz */
|
||||
da7210_write(codec, DA7210_PLL_DIV3,
|
||||
DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP);
|
||||
|
||||
/* Activate all enabled subsystem */
|
||||
da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN);
|
||||
|
||||
return ret;
|
||||
|
||||
init_err:
|
||||
kfree(codec->reg_cache);
|
||||
codec->reg_cache = NULL;
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
static int __devinit da7210_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct da7210_priv *da7210;
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
da7210 = kzalloc(sizeof(struct da7210_priv), GFP_KERNEL);
|
||||
if (!da7210)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &da7210->codec;
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
i2c_set_clientdata(i2c, da7210);
|
||||
codec->control_data = i2c;
|
||||
|
||||
ret = da7210_init(da7210);
|
||||
if (ret < 0)
|
||||
pr_err("Failed to initialise da7210 audio codec\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit da7210_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct da7210_priv *da7210 = i2c_get_clientdata(client);
|
||||
|
||||
snd_soc_unregister_dai(&da7210_dai);
|
||||
kfree(da7210->codec.reg_cache);
|
||||
kfree(da7210);
|
||||
da7210_codec = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id da7210_i2c_id[] = {
|
||||
{ "da7210", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, da7210_i2c_id);
|
||||
|
||||
/* I2C codec control layer */
|
||||
static struct i2c_driver da7210_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "DA7210 I2C Codec",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = da7210_i2c_probe,
|
||||
.remove = __devexit_p(da7210_i2c_remove),
|
||||
.id_table = da7210_i2c_id,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int da7210_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
if (!da7210_codec) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = da7210_codec;
|
||||
codec = da7210_codec;
|
||||
|
||||
/* Register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0)
|
||||
goto pcm_err;
|
||||
|
||||
dev_info(&pdev->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION);
|
||||
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da7210_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_da7210 = {
|
||||
.probe = da7210_probe,
|
||||
.remove = da7210_remove,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_da7210);
|
||||
|
||||
static int __init da7210_modinit(void)
|
||||
{
|
||||
int ret = 0;
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
ret = i2c_add_driver(&da7210_i2c_driver);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
module_init(da7210_modinit);
|
||||
|
||||
static void __exit da7210_exit(void)
|
||||
{
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_del_driver(&da7210_i2c_driver);
|
||||
#endif
|
||||
}
|
||||
module_exit(da7210_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC DA7210 driver");
|
||||
MODULE_AUTHOR("David Chen, Kuninori Morimoto");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* da7210.h -- audio driver for da7210
|
||||
*
|
||||
* Copyright (c) 2009 Dialog Semiconductor
|
||||
* Written by David Chen <Dajun.chen@diasemi.com>
|
||||
*
|
||||
* Copyright (C) 2009 Renesas Solutions Corp.
|
||||
* Cleanups by Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DA7210_H
|
||||
#define _DA7210_H
|
||||
|
||||
extern struct snd_soc_dai da7210_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_da7210;
|
||||
|
||||
#endif
|
||||
|
|
@ -765,9 +765,10 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct aic3x_priv *aic3x = codec->private_data;
|
||||
int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0;
|
||||
u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
|
||||
u16 pll_d = 1;
|
||||
u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
|
||||
u16 d, pll_d = 1;
|
||||
u8 reg;
|
||||
int clk;
|
||||
|
||||
/* select data word length */
|
||||
data =
|
||||
|
@ -833,48 +834,70 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream,
|
|||
if (bypass_pll)
|
||||
return 0;
|
||||
|
||||
/* Use PLL
|
||||
* find an apropriate setup for j, d, r and p by iterating over
|
||||
* p and r - j and d are calculated for each fraction.
|
||||
* Up to 128 values are probed, the closest one wins the game.
|
||||
/* Use PLL, compute apropriate setup for j, d, r and p, the closest
|
||||
* one wins the game. Try with d==0 first, next with d!=0.
|
||||
* Constraints for j are according to the datasheet.
|
||||
* The sysclk is divided by 1000 to prevent integer overflows.
|
||||
*/
|
||||
|
||||
codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000);
|
||||
|
||||
for (r = 1; r <= 16; r++)
|
||||
for (p = 1; p <= 8; p++) {
|
||||
int clk, tmp = (codec_clk * pll_r * 10) / pll_p;
|
||||
u8 j = tmp / 10000;
|
||||
u16 d = tmp % 10000;
|
||||
for (j = 4; j <= 55; j++) {
|
||||
/* This is actually 1000*((j+(d/10000))*r)/p
|
||||
* The term had to be converted to get
|
||||
* rid of the division by 10000; d = 0 here
|
||||
*/
|
||||
int tmp_clk = (1000 * j * r) / p;
|
||||
|
||||
if (j > 63)
|
||||
continue;
|
||||
/* Check whether this values get closer than
|
||||
* the best ones we had before
|
||||
*/
|
||||
if (abs(codec_clk - tmp_clk) <
|
||||
abs(codec_clk - last_clk)) {
|
||||
pll_j = j; pll_d = 0;
|
||||
pll_r = r; pll_p = p;
|
||||
last_clk = tmp_clk;
|
||||
}
|
||||
|
||||
if (d != 0 && aic3x->sysclk < 10000000)
|
||||
continue;
|
||||
|
||||
/* This is actually 1000 * ((j + (d/10000)) * r) / p
|
||||
* The term had to be converted to get rid of the
|
||||
* division by 10000 */
|
||||
clk = ((10000 * j * r) + (d * r)) / (10 * p);
|
||||
|
||||
/* check whether this values get closer than the best
|
||||
* ones we had before */
|
||||
if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) {
|
||||
pll_j = j; pll_d = d; pll_r = r; pll_p = p;
|
||||
last_clk = clk;
|
||||
/* Early exit for exact matches */
|
||||
if (tmp_clk == codec_clk)
|
||||
goto found;
|
||||
}
|
||||
|
||||
/* Early exit for exact matches */
|
||||
if (clk == codec_clk)
|
||||
break;
|
||||
}
|
||||
|
||||
/* try with d != 0 */
|
||||
for (p = 1; p <= 8; p++) {
|
||||
j = codec_clk * p / 1000;
|
||||
|
||||
if (j < 4 || j > 11)
|
||||
continue;
|
||||
|
||||
/* do not use codec_clk here since we'd loose precision */
|
||||
d = ((2048 * p * fsref) - j * aic3x->sysclk)
|
||||
* 100 / (aic3x->sysclk/100);
|
||||
|
||||
clk = (10000 * j + d) / (10 * p);
|
||||
|
||||
/* check whether this values get closer than the best
|
||||
* ones we had before */
|
||||
if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) {
|
||||
pll_j = j; pll_d = d; pll_r = 1; pll_p = p;
|
||||
last_clk = clk;
|
||||
}
|
||||
|
||||
/* Early exit for exact matches */
|
||||
if (clk == codec_clk)
|
||||
goto found;
|
||||
}
|
||||
|
||||
if (last_clk == 0) {
|
||||
printk(KERN_ERR "%s(): unable to setup PLL\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
found:
|
||||
data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
|
||||
aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT));
|
||||
aic3x_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG, pll_r << PLLR_SHIFT);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
@ -58,11 +59,26 @@ enum dac33_state {
|
|||
DAC33_FLUSH,
|
||||
};
|
||||
|
||||
enum dac33_fifo_modes {
|
||||
DAC33_FIFO_BYPASS = 0,
|
||||
DAC33_FIFO_MODE1,
|
||||
DAC33_FIFO_MODE7,
|
||||
DAC33_FIFO_LAST_MODE,
|
||||
};
|
||||
|
||||
#define DAC33_NUM_SUPPLIES 3
|
||||
static const char *dac33_supply_names[DAC33_NUM_SUPPLIES] = {
|
||||
"AVDD",
|
||||
"DVDD",
|
||||
"IOVDD",
|
||||
};
|
||||
|
||||
struct tlv320dac33_priv {
|
||||
struct mutex mutex;
|
||||
struct workqueue_struct *dac33_wq;
|
||||
struct work_struct work;
|
||||
struct snd_soc_codec codec;
|
||||
struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES];
|
||||
int power_gpio;
|
||||
int chip_power;
|
||||
int irq;
|
||||
|
@ -73,8 +89,9 @@ struct tlv320dac33_priv {
|
|||
* this */
|
||||
unsigned int nsample_max; /* nsample should not be higher than
|
||||
* this */
|
||||
unsigned int nsample_switch; /* Use FIFO or bypass FIFO switch */
|
||||
enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */
|
||||
unsigned int nsample; /* burst read amount from host */
|
||||
u8 burst_bclkdiv; /* BCLK divider value in burst mode */
|
||||
|
||||
enum dac33_state state;
|
||||
};
|
||||
|
@ -297,28 +314,49 @@ static inline void dac33_soft_power(struct snd_soc_codec *codec, int power)
|
|||
dac33_write(codec, DAC33_PWR_CTRL, reg);
|
||||
}
|
||||
|
||||
static void dac33_hard_power(struct snd_soc_codec *codec, int power)
|
||||
static int dac33_hard_power(struct snd_soc_codec *codec, int power)
|
||||
{
|
||||
struct tlv320dac33_priv *dac33 = codec->private_data;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dac33->mutex);
|
||||
if (power) {
|
||||
if (dac33->power_gpio >= 0) {
|
||||
gpio_set_value(dac33->power_gpio, 1);
|
||||
dac33->chip_power = 1;
|
||||
/* Restore registers */
|
||||
dac33_restore_regs(codec);
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies),
|
||||
dac33->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev,
|
||||
"Failed to enable supplies: %d\n", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (dac33->power_gpio >= 0)
|
||||
gpio_set_value(dac33->power_gpio, 1);
|
||||
|
||||
dac33->chip_power = 1;
|
||||
|
||||
/* Restore registers */
|
||||
dac33_restore_regs(codec);
|
||||
|
||||
dac33_soft_power(codec, 1);
|
||||
} else {
|
||||
dac33_soft_power(codec, 0);
|
||||
if (dac33->power_gpio >= 0) {
|
||||
if (dac33->power_gpio >= 0)
|
||||
gpio_set_value(dac33->power_gpio, 0);
|
||||
dac33->chip_power = 0;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&dac33->mutex);
|
||||
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(dac33->supplies),
|
||||
dac33->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev,
|
||||
"Failed to disable supplies: %d\n", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
dac33->chip_power = 0;
|
||||
}
|
||||
|
||||
exit:
|
||||
mutex_unlock(&dac33->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dac33_get_nsample(struct snd_kcontrol *kcontrol,
|
||||
|
@ -351,39 +389,48 @@ static int dac33_set_nsample(struct snd_kcontrol *kcontrol,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int dac33_get_nsample_switch(struct snd_kcontrol *kcontrol,
|
||||
static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct tlv320dac33_priv *dac33 = codec->private_data;
|
||||
|
||||
ucontrol->value.integer.value[0] = dac33->nsample_switch;
|
||||
ucontrol->value.integer.value[0] = dac33->fifo_mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dac33_set_nsample_switch(struct snd_kcontrol *kcontrol,
|
||||
static int dac33_set_fifo_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct tlv320dac33_priv *dac33 = codec->private_data;
|
||||
int ret = 0;
|
||||
|
||||
if (dac33->nsample_switch == ucontrol->value.integer.value[0])
|
||||
if (dac33->fifo_mode == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
/* Do not allow changes while stream is running*/
|
||||
if (codec->active)
|
||||
return -EPERM;
|
||||
|
||||
if (ucontrol->value.integer.value[0] < 0 ||
|
||||
ucontrol->value.integer.value[0] > 1)
|
||||
ucontrol->value.integer.value[0] >= DAC33_FIFO_LAST_MODE)
|
||||
ret = -EINVAL;
|
||||
else
|
||||
dac33->nsample_switch = ucontrol->value.integer.value[0];
|
||||
dac33->fifo_mode = ucontrol->value.integer.value[0];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Codec operation modes */
|
||||
static const char *dac33_fifo_mode_texts[] = {
|
||||
"Bypass", "Mode 1", "Mode 7"
|
||||
};
|
||||
|
||||
static const struct soc_enum dac33_fifo_mode_enum =
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(dac33_fifo_mode_texts),
|
||||
dac33_fifo_mode_texts);
|
||||
|
||||
/*
|
||||
* DACL/R digital volume control:
|
||||
* from 0 dB to -63.5 in 0.5 dB steps
|
||||
|
@ -406,8 +453,8 @@ static const struct snd_kcontrol_new dac33_snd_controls[] = {
|
|||
static const struct snd_kcontrol_new dac33_nsample_snd_controls[] = {
|
||||
SOC_SINGLE_EXT("nSample", 0, 0, 5900, 0,
|
||||
dac33_get_nsample, dac33_set_nsample),
|
||||
SOC_SINGLE_EXT("nSample Switch", 0, 0, 1, 0,
|
||||
dac33_get_nsample_switch, dac33_set_nsample_switch),
|
||||
SOC_ENUM_EXT("FIFO Mode", dac33_fifo_mode_enum,
|
||||
dac33_get_fifo_mode, dac33_set_fifo_mode),
|
||||
};
|
||||
|
||||
/* Analog bypass */
|
||||
|
@ -469,6 +516,8 @@ static int dac33_add_widgets(struct snd_soc_codec *codec)
|
|||
static int dac33_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
dac33_soft_power(codec, 1);
|
||||
|
@ -476,12 +525,19 @@ static int dac33_set_bias_level(struct snd_soc_codec *codec,
|
|||
case SND_SOC_BIAS_PREPARE:
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF)
|
||||
dac33_hard_power(codec, 1);
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
ret = dac33_hard_power(codec, 1);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
dac33_soft_power(codec, 0);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
dac33_hard_power(codec, 0);
|
||||
ret = dac33_hard_power(codec, 0);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
|
@ -489,6 +545,51 @@ static int dac33_set_bias_level(struct snd_soc_codec *codec,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
codec = &dac33->codec;
|
||||
|
||||
switch (dac33->fifo_mode) {
|
||||
case DAC33_FIFO_MODE1:
|
||||
dac33_write16(codec, DAC33_NSAMPLE_MSB,
|
||||
DAC33_THRREG(dac33->nsample));
|
||||
dac33_write16(codec, DAC33_PREFILL_MSB,
|
||||
DAC33_THRREG(dac33->alarm_threshold));
|
||||
break;
|
||||
case DAC33_FIFO_MODE7:
|
||||
dac33_write16(codec, DAC33_PREFILL_MSB,
|
||||
DAC33_THRREG(10));
|
||||
break;
|
||||
default:
|
||||
dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
|
||||
dac33->fifo_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
codec = &dac33->codec;
|
||||
|
||||
switch (dac33->fifo_mode) {
|
||||
case DAC33_FIFO_MODE1:
|
||||
dac33_write16(codec, DAC33_NSAMPLE_MSB,
|
||||
DAC33_THRREG(dac33->nsample));
|
||||
break;
|
||||
case DAC33_FIFO_MODE7:
|
||||
/* At the moment we are not using interrupts in mode7 */
|
||||
break;
|
||||
default:
|
||||
dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
|
||||
dac33->fifo_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void dac33_work(struct work_struct *work)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
|
@ -502,14 +603,10 @@ static void dac33_work(struct work_struct *work)
|
|||
switch (dac33->state) {
|
||||
case DAC33_PREFILL:
|
||||
dac33->state = DAC33_PLAYBACK;
|
||||
dac33_write16(codec, DAC33_NSAMPLE_MSB,
|
||||
DAC33_THRREG(dac33->nsample));
|
||||
dac33_write16(codec, DAC33_PREFILL_MSB,
|
||||
DAC33_THRREG(dac33->alarm_threshold));
|
||||
dac33_prefill_handler(dac33);
|
||||
break;
|
||||
case DAC33_PLAYBACK:
|
||||
dac33_write16(codec, DAC33_NSAMPLE_MSB,
|
||||
DAC33_THRREG(dac33->nsample));
|
||||
dac33_playback_handler(dac33);
|
||||
break;
|
||||
case DAC33_IDLE:
|
||||
break;
|
||||
|
@ -547,7 +644,7 @@ static void dac33_shutdown(struct snd_pcm_substream *substream,
|
|||
unsigned int pwr_ctrl;
|
||||
|
||||
/* Stop pending workqueue */
|
||||
if (dac33->nsample_switch)
|
||||
if (dac33->fifo_mode)
|
||||
cancel_work_sync(&dac33->work);
|
||||
|
||||
mutex_lock(&dac33->mutex);
|
||||
|
@ -603,7 +700,7 @@ static int dac33_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
#define CALC_OSCSET(rate, refclk) ( \
|
||||
((((rate * 10000) / refclk) * 4096) + 5000) / 10000)
|
||||
((((rate * 10000) / refclk) * 4096) + 7000) / 10000)
|
||||
#define CALC_RATIOSET(rate, refclk) ( \
|
||||
((((refclk * 100000) / rate) * 16384) + 50000) / 100000)
|
||||
|
||||
|
@ -619,7 +716,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream)
|
|||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct tlv320dac33_priv *dac33 = codec->private_data;
|
||||
unsigned int oscset, ratioset, pwr_ctrl, reg_tmp;
|
||||
u8 aictrl_a, fifoctrl_a;
|
||||
u8 aictrl_a, aictrl_b, fifoctrl_a;
|
||||
|
||||
switch (substream->runtime->rate) {
|
||||
case 44100:
|
||||
|
@ -637,7 +734,10 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream)
|
|||
|
||||
aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A);
|
||||
aictrl_a &= ~(DAC33_NCYCL_MASK | DAC33_WLEN_MASK);
|
||||
/* Read FIFO control A, and clear FIFO flush bit */
|
||||
fifoctrl_a = dac33_read_reg_cache(codec, DAC33_FIFO_CTRL_A);
|
||||
fifoctrl_a &= ~DAC33_FIFOFLUSH;
|
||||
|
||||
fifoctrl_a &= ~DAC33_WIDTH;
|
||||
switch (substream->runtime->format) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
|
@ -675,7 +775,8 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream)
|
|||
|
||||
dac33_oscwait(codec);
|
||||
|
||||
if (dac33->nsample_switch) {
|
||||
if (dac33->fifo_mode) {
|
||||
/* Generic for all FIFO modes */
|
||||
/* 50-51 : ASRC Control registers */
|
||||
dac33_write(codec, DAC33_ASRC_CTRL_A, (1 << 4)); /* div=2 */
|
||||
dac33_write(codec, DAC33_ASRC_CTRL_B, 1); /* ??? */
|
||||
|
@ -685,38 +786,101 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream)
|
|||
|
||||
/* Set interrupts to high active */
|
||||
dac33_write(codec, DAC33_INTP_CTRL_A, DAC33_INTPM_AHIGH);
|
||||
|
||||
dac33_write(codec, DAC33_FIFO_IRQ_MODE_B,
|
||||
DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL));
|
||||
dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT);
|
||||
} else {
|
||||
/* FIFO bypass mode */
|
||||
/* 50-51 : ASRC Control registers */
|
||||
dac33_write(codec, DAC33_ASRC_CTRL_A, DAC33_SRCBYP);
|
||||
dac33_write(codec, DAC33_ASRC_CTRL_B, 0); /* ??? */
|
||||
}
|
||||
|
||||
if (dac33->nsample_switch)
|
||||
/* Interrupt behaviour configuration */
|
||||
switch (dac33->fifo_mode) {
|
||||
case DAC33_FIFO_MODE1:
|
||||
dac33_write(codec, DAC33_FIFO_IRQ_MODE_B,
|
||||
DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL));
|
||||
dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT);
|
||||
break;
|
||||
case DAC33_FIFO_MODE7:
|
||||
/* Disable all interrupts */
|
||||
dac33_write(codec, DAC33_FIFO_IRQ_MASK, 0);
|
||||
break;
|
||||
default:
|
||||
/* in FIFO bypass mode, the interrupts are not used */
|
||||
break;
|
||||
}
|
||||
|
||||
aictrl_b = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
|
||||
|
||||
switch (dac33->fifo_mode) {
|
||||
case DAC33_FIFO_MODE1:
|
||||
/*
|
||||
* For mode1:
|
||||
* Disable the FIFO bypass (Enable the use of FIFO)
|
||||
* Select nSample mode
|
||||
* BCLK is only running when data is needed by DAC33
|
||||
*/
|
||||
fifoctrl_a &= ~DAC33_FBYPAS;
|
||||
else
|
||||
fifoctrl_a &= ~DAC33_FAUTO;
|
||||
aictrl_b &= ~DAC33_BCLKON;
|
||||
break;
|
||||
case DAC33_FIFO_MODE7:
|
||||
/*
|
||||
* For mode1:
|
||||
* Disable the FIFO bypass (Enable the use of FIFO)
|
||||
* Select Threshold mode
|
||||
* BCLK is only running when data is needed by DAC33
|
||||
*/
|
||||
fifoctrl_a &= ~DAC33_FBYPAS;
|
||||
fifoctrl_a |= DAC33_FAUTO;
|
||||
aictrl_b &= ~DAC33_BCLKON;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* For FIFO bypass mode:
|
||||
* Enable the FIFO bypass (Disable the FIFO use)
|
||||
* Set the BCLK as continous
|
||||
*/
|
||||
fifoctrl_a |= DAC33_FBYPAS;
|
||||
aictrl_b |= DAC33_BCLKON;
|
||||
break;
|
||||
}
|
||||
|
||||
dac33_write(codec, DAC33_FIFO_CTRL_A, fifoctrl_a);
|
||||
|
||||
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a);
|
||||
reg_tmp = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
|
||||
if (dac33->nsample_switch)
|
||||
reg_tmp &= ~DAC33_BCLKON;
|
||||
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b);
|
||||
|
||||
/*
|
||||
* BCLK divide ratio
|
||||
* 0: 1.5
|
||||
* 1: 1
|
||||
* 2: 2
|
||||
* ...
|
||||
* 254: 254
|
||||
* 255: 255
|
||||
*/
|
||||
if (dac33->fifo_mode)
|
||||
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C,
|
||||
dac33->burst_bclkdiv);
|
||||
else
|
||||
reg_tmp |= DAC33_BCLKON;
|
||||
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, reg_tmp);
|
||||
|
||||
if (dac33->nsample_switch) {
|
||||
/* 20: BCLK divide ratio */
|
||||
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 3);
|
||||
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 32);
|
||||
|
||||
switch (dac33->fifo_mode) {
|
||||
case DAC33_FIFO_MODE1:
|
||||
dac33_write16(codec, DAC33_ATHR_MSB,
|
||||
DAC33_THRREG(dac33->alarm_threshold));
|
||||
} else {
|
||||
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 32);
|
||||
break;
|
||||
case DAC33_FIFO_MODE7:
|
||||
/*
|
||||
* Configure the threshold levels, and leave 10 sample space
|
||||
* at the bottom, and also at the top of the FIFO
|
||||
*/
|
||||
dac33_write16(codec, DAC33_UTHR_MSB,
|
||||
DAC33_THRREG(DAC33_BUFFER_SIZE_SAMPLES - 10));
|
||||
dac33_write16(codec, DAC33_LTHR_MSB,
|
||||
DAC33_THRREG(10));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&dac33->mutex);
|
||||
|
@ -789,7 +953,7 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
|
|||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (dac33->nsample_switch) {
|
||||
if (dac33->fifo_mode) {
|
||||
dac33->state = DAC33_PREFILL;
|
||||
queue_work(dac33->dac33_wq, &dac33->work);
|
||||
}
|
||||
|
@ -797,7 +961,7 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
|
|||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (dac33->nsample_switch) {
|
||||
if (dac33->fifo_mode) {
|
||||
dac33->state = DAC33_FLUSH;
|
||||
queue_work(dac33->dac33_wq, &dac33->work);
|
||||
}
|
||||
|
@ -843,6 +1007,7 @@ static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct tlv320dac33_priv *dac33 = codec->private_data;
|
||||
u8 aictrl_a, aictrl_b;
|
||||
|
||||
aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A);
|
||||
|
@ -855,7 +1020,11 @@ static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
/* Codec Slave */
|
||||
aictrl_a &= ~(DAC33_MSBCLK | DAC33_MSWCLK);
|
||||
if (dac33->fifo_mode) {
|
||||
dev_err(codec->dev, "FIFO mode requires master mode\n");
|
||||
return -EINVAL;
|
||||
} else
|
||||
aictrl_a &= ~(DAC33_MSBCLK | DAC33_MSWCLK);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
@ -959,6 +1128,9 @@ static int dac33_soc_probe(struct platform_device *pdev)
|
|||
/* power on device */
|
||||
dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Bias level configuration has enabled regulator an extra time */
|
||||
regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies);
|
||||
|
||||
return 0;
|
||||
|
||||
pcm_err:
|
||||
|
@ -1033,13 +1205,13 @@ struct snd_soc_dai dac33_dai = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(dac33_dai);
|
||||
|
||||
static int dac33_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
static int __devinit dac33_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct tlv320dac33_platform_data *pdata;
|
||||
struct tlv320dac33_priv *dac33;
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
int ret, i;
|
||||
|
||||
if (client->dev.platform_data == NULL) {
|
||||
dev_err(&client->dev, "Platform data not set\n");
|
||||
|
@ -1080,10 +1252,11 @@ static int dac33_i2c_probe(struct i2c_client *client,
|
|||
i2c_set_clientdata(client, dac33);
|
||||
|
||||
dac33->power_gpio = pdata->power_gpio;
|
||||
dac33->burst_bclkdiv = pdata->burst_bclkdiv;
|
||||
dac33->irq = client->irq;
|
||||
dac33->nsample = NSAMPLE_MAX;
|
||||
/* Disable FIFO use by default */
|
||||
dac33->nsample_switch = 0;
|
||||
dac33->fifo_mode = DAC33_FIFO_BYPASS;
|
||||
|
||||
tlv320dac33_codec = codec;
|
||||
|
||||
|
@ -1130,6 +1303,24 @@ static int dac33_i2c_probe(struct i2c_client *client,
|
|||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dac33->supplies); i++)
|
||||
dac33->supplies[i].supply = dac33_supply_names[i];
|
||||
|
||||
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(dac33->supplies),
|
||||
dac33->supplies);
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
|
||||
goto err_get;
|
||||
}
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies),
|
||||
dac33->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
|
@ -1149,6 +1340,10 @@ static int dac33_i2c_probe(struct i2c_client *client,
|
|||
return ret;
|
||||
|
||||
error_codec:
|
||||
regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies);
|
||||
err_enable:
|
||||
regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
|
||||
err_get:
|
||||
if (dac33->irq >= 0) {
|
||||
free_irq(dac33->irq, &dac33->codec);
|
||||
destroy_workqueue(dac33->dac33_wq);
|
||||
|
@ -1165,7 +1360,7 @@ error_reg:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int dac33_i2c_remove(struct i2c_client *client)
|
||||
static int __devexit dac33_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tlv320dac33_priv *dac33;
|
||||
|
||||
|
@ -1177,6 +1372,8 @@ static int dac33_i2c_remove(struct i2c_client *client)
|
|||
if (dac33->irq >= 0)
|
||||
free_irq(dac33->irq, &dac33->codec);
|
||||
|
||||
regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
|
||||
|
||||
destroy_workqueue(dac33->dac33_wq);
|
||||
snd_soc_unregister_dai(&dac33_dai);
|
||||
snd_soc_unregister_codec(&dac33->codec);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <sound/tpa6130a2-plat.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
@ -34,10 +35,22 @@
|
|||
|
||||
static struct i2c_client *tpa6130a2_client;
|
||||
|
||||
#define TPA6130A2_NUM_SUPPLIES 2
|
||||
static const char *tpa6130a2_supply_names[TPA6130A2_NUM_SUPPLIES] = {
|
||||
"CPVSS",
|
||||
"Vdd",
|
||||
};
|
||||
|
||||
static const char *tpa6140a2_supply_names[TPA6130A2_NUM_SUPPLIES] = {
|
||||
"HPVdd",
|
||||
"AVdd",
|
||||
};
|
||||
|
||||
/* This struct is used to save the context */
|
||||
struct tpa6130a2_data {
|
||||
struct mutex mutex;
|
||||
unsigned char regs[TPA6130A2_CACHEREGNUM];
|
||||
struct regulator_bulk_data supplies[TPA6130A2_NUM_SUPPLIES];
|
||||
int power_gpio;
|
||||
unsigned char power_state;
|
||||
};
|
||||
|
@ -106,10 +119,11 @@ static void tpa6130a2_initialize(void)
|
|||
tpa6130a2_i2c_write(i, data->regs[i]);
|
||||
}
|
||||
|
||||
static void tpa6130a2_power(int power)
|
||||
static int tpa6130a2_power(int power)
|
||||
{
|
||||
struct tpa6130a2_data *data;
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
BUG_ON(tpa6130a2_client == NULL);
|
||||
data = i2c_get_clientdata(tpa6130a2_client);
|
||||
|
@ -117,11 +131,20 @@ static void tpa6130a2_power(int power)
|
|||
mutex_lock(&data->mutex);
|
||||
if (power) {
|
||||
/* Power on */
|
||||
if (data->power_gpio >= 0) {
|
||||
if (data->power_gpio >= 0)
|
||||
gpio_set_value(data->power_gpio, 1);
|
||||
data->power_state = 1;
|
||||
tpa6130a2_initialize();
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies),
|
||||
data->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(&tpa6130a2_client->dev,
|
||||
"Failed to enable supplies: %d\n", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data->power_state = 1;
|
||||
tpa6130a2_initialize();
|
||||
|
||||
/* Clear SWS */
|
||||
val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
|
||||
val &= ~TPA6130A2_SWS;
|
||||
|
@ -131,13 +154,25 @@ static void tpa6130a2_power(int power)
|
|||
val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
|
||||
val |= TPA6130A2_SWS;
|
||||
tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
|
||||
|
||||
/* Power off */
|
||||
if (data->power_gpio >= 0) {
|
||||
if (data->power_gpio >= 0)
|
||||
gpio_set_value(data->power_gpio, 0);
|
||||
data->power_state = 0;
|
||||
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(data->supplies),
|
||||
data->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(&tpa6130a2_client->dev,
|
||||
"Failed to disable supplies: %d\n", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data->power_state = 0;
|
||||
}
|
||||
|
||||
exit:
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol,
|
||||
|
@ -237,12 +272,8 @@ static const struct snd_kcontrol_new tpa6130a2_controls[] = {
|
|||
*/
|
||||
static void tpa6130a2_channel_enable(u8 channel, int enable)
|
||||
{
|
||||
struct tpa6130a2_data *data;
|
||||
u8 val;
|
||||
|
||||
BUG_ON(tpa6130a2_client == NULL);
|
||||
data = i2c_get_clientdata(tpa6130a2_client);
|
||||
|
||||
if (enable) {
|
||||
/* Enable channel */
|
||||
/* Enable amplifier */
|
||||
|
@ -299,15 +330,17 @@ static int tpa6130a2_right_event(struct snd_soc_dapm_widget *w,
|
|||
static int tpa6130a2_supply_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_POST_PMU:
|
||||
tpa6130a2_power(1);
|
||||
ret = tpa6130a2_power(1);
|
||||
break;
|
||||
case SND_SOC_DAPM_POST_PMD:
|
||||
tpa6130a2_power(0);
|
||||
ret = tpa6130a2_power(0);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = {
|
||||
|
@ -346,13 +379,13 @@ int tpa6130a2_add_controls(struct snd_soc_codec *codec)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(tpa6130a2_add_controls);
|
||||
|
||||
static int tpa6130a2_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
static int __devinit tpa6130a2_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev;
|
||||
struct tpa6130a2_data *data;
|
||||
struct tpa6130a2_platform_data *pdata;
|
||||
int ret;
|
||||
int i, ret;
|
||||
|
||||
dev = &client->dev;
|
||||
|
||||
|
@ -387,15 +420,38 @@ static int tpa6130a2_probe(struct i2c_client *client,
|
|||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to request power GPIO (%d)\n",
|
||||
data->power_gpio);
|
||||
goto fail;
|
||||
goto err_gpio;
|
||||
}
|
||||
gpio_direction_output(data->power_gpio, 0);
|
||||
} else {
|
||||
data->power_state = 1;
|
||||
tpa6130a2_initialize();
|
||||
}
|
||||
|
||||
tpa6130a2_power(1);
|
||||
switch (pdata->id) {
|
||||
case TPA6130A2:
|
||||
for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
|
||||
data->supplies[i].supply = tpa6130a2_supply_names[i];
|
||||
break;
|
||||
case TPA6140A2:
|
||||
for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
|
||||
data->supplies[i].supply = tpa6140a2_supply_names[i];;
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n",
|
||||
pdata->id);
|
||||
for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
|
||||
data->supplies[i].supply = tpa6130a2_supply_names[i];
|
||||
}
|
||||
|
||||
ret = regulator_bulk_get(dev, ARRAY_SIZE(data->supplies),
|
||||
data->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to request supplies: %d\n", ret);
|
||||
goto err_regulator;
|
||||
}
|
||||
|
||||
ret = tpa6130a2_power(1);
|
||||
if (ret != 0)
|
||||
goto err_power;
|
||||
|
||||
|
||||
/* Read version */
|
||||
ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
|
||||
|
@ -404,10 +460,18 @@ static int tpa6130a2_probe(struct i2c_client *client,
|
|||
dev_warn(dev, "UNTESTED version detected (%d)\n", ret);
|
||||
|
||||
/* Disable the chip */
|
||||
tpa6130a2_power(0);
|
||||
ret = tpa6130a2_power(0);
|
||||
if (ret != 0)
|
||||
goto err_power;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
|
||||
err_power:
|
||||
regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies);
|
||||
err_regulator:
|
||||
if (data->power_gpio >= 0)
|
||||
gpio_free(data->power_gpio);
|
||||
err_gpio:
|
||||
kfree(data);
|
||||
i2c_set_clientdata(tpa6130a2_client, NULL);
|
||||
tpa6130a2_client = NULL;
|
||||
|
@ -415,7 +479,7 @@ fail:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int tpa6130a2_remove(struct i2c_client *client)
|
||||
static int __devexit tpa6130a2_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tpa6130a2_data *data = i2c_get_clientdata(client);
|
||||
|
||||
|
@ -423,6 +487,9 @@ static int tpa6130a2_remove(struct i2c_client *client)
|
|||
|
||||
if (data->power_gpio >= 0)
|
||||
gpio_free(data->power_gpio);
|
||||
|
||||
regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies);
|
||||
|
||||
kfree(data);
|
||||
tpa6130a2_client = NULL;
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
|
|||
0x0c, /* REG_ATXR1PGA (0xB) */
|
||||
0x00, /* REG_AVTXL2PGA (0xC) */
|
||||
0x00, /* REG_AVTXR2PGA (0xD) */
|
||||
0x01, /* REG_AUDIO_IF (0xE) */
|
||||
0x00, /* REG_AUDIO_IF (0xE) */
|
||||
0x00, /* REG_VOICE_IF (0xF) */
|
||||
0x00, /* REG_ARXR1PGA (0x10) */
|
||||
0x00, /* REG_ARXL1PGA (0x11) */
|
||||
|
@ -64,19 +64,19 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
|
|||
0x00, /* REG_VRXPGA (0x14) */
|
||||
0x00, /* REG_VSTPGA (0x15) */
|
||||
0x00, /* REG_VRX2ARXPGA (0x16) */
|
||||
0x0c, /* REG_AVDAC_CTL (0x17) */
|
||||
0x00, /* REG_AVDAC_CTL (0x17) */
|
||||
0x00, /* REG_ARX2VTXPGA (0x18) */
|
||||
0x00, /* REG_ARXL1_APGA_CTL (0x19) */
|
||||
0x00, /* REG_ARXR1_APGA_CTL (0x1A) */
|
||||
0x4b, /* REG_ARXL2_APGA_CTL (0x1B) */
|
||||
0x4b, /* REG_ARXR2_APGA_CTL (0x1C) */
|
||||
0x4a, /* REG_ARXL2_APGA_CTL (0x1B) */
|
||||
0x4a, /* REG_ARXR2_APGA_CTL (0x1C) */
|
||||
0x00, /* REG_ATX2ARXPGA (0x1D) */
|
||||
0x00, /* REG_BT_IF (0x1E) */
|
||||
0x00, /* REG_BTPGA (0x1F) */
|
||||
0x00, /* REG_BTSTPGA (0x20) */
|
||||
0x00, /* REG_EAR_CTL (0x21) */
|
||||
0x24, /* REG_HS_SEL (0x22) */
|
||||
0x0a, /* REG_HS_GAIN_SET (0x23) */
|
||||
0x00, /* REG_HS_SEL (0x22) */
|
||||
0x00, /* REG_HS_GAIN_SET (0x23) */
|
||||
0x00, /* REG_HS_POPN_SET (0x24) */
|
||||
0x00, /* REG_PREDL_CTL (0x25) */
|
||||
0x00, /* REG_PREDR_CTL (0x26) */
|
||||
|
@ -99,7 +99,7 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
|
|||
0x00, /* REG_I2S_RX_SCRAMBLE_H (0x37) */
|
||||
0x00, /* REG_I2S_RX_SCRAMBLE_M (0x38) */
|
||||
0x00, /* REG_I2S_RX_SCRAMBLE_L (0x39) */
|
||||
0x16, /* REG_APLL_CTL (0x3A) */
|
||||
0x06, /* REG_APLL_CTL (0x3A) */
|
||||
0x00, /* REG_DTMF_CTL (0x3B) */
|
||||
0x00, /* REG_DTMF_PGA_CTL2 (0x3C) */
|
||||
0x00, /* REG_DTMF_PGA_CTL1 (0x3D) */
|
||||
|
@ -1203,6 +1203,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
|
|||
SND_SOC_DAPM_SUPPLY("APLL Enable", SND_SOC_NOPM, 0, 0, apll_event,
|
||||
SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD),
|
||||
|
||||
SND_SOC_DAPM_SUPPLY("AIF Enable", TWL4030_REG_AUDIO_IF, 0, 0, NULL, 0),
|
||||
|
||||
/* Output MIXER controls */
|
||||
/* Earpiece */
|
||||
SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
|
||||
|
@ -1337,6 +1339,11 @@ static const struct snd_soc_dapm_route intercon[] = {
|
|||
{"Digital L2 Playback Mixer", NULL, "APLL Enable"},
|
||||
{"Digital Voice Playback Mixer", NULL, "APLL Enable"},
|
||||
|
||||
{"Digital R1 Playback Mixer", NULL, "AIF Enable"},
|
||||
{"Digital L1 Playback Mixer", NULL, "AIF Enable"},
|
||||
{"Digital R2 Playback Mixer", NULL, "AIF Enable"},
|
||||
{"Digital L2 Playback Mixer", NULL, "AIF Enable"},
|
||||
|
||||
{"Analog L1 Playback Mixer", NULL, "Digital L1 Playback Mixer"},
|
||||
{"Analog R1 Playback Mixer", NULL, "Digital R1 Playback Mixer"},
|
||||
{"Analog L2 Playback Mixer", NULL, "Digital L2 Playback Mixer"},
|
||||
|
@ -1455,6 +1462,11 @@ static const struct snd_soc_dapm_route intercon[] = {
|
|||
{"ADC Virtual Left2", NULL, "APLL Enable"},
|
||||
{"ADC Virtual Right2", NULL, "APLL Enable"},
|
||||
|
||||
{"ADC Virtual Left1", NULL, "AIF Enable"},
|
||||
{"ADC Virtual Right1", NULL, "AIF Enable"},
|
||||
{"ADC Virtual Left2", NULL, "AIF Enable"},
|
||||
{"ADC Virtual Right2", NULL, "AIF Enable"},
|
||||
|
||||
/* Analog bypass routes */
|
||||
{"Right1 Analog Loopback", "Switch", "Analog Right"},
|
||||
{"Left1 Analog Loopback", "Switch", "Analog Left"},
|
||||
|
@ -2152,8 +2164,6 @@ static int twl4030_soc_remove(struct platform_device *pdev)
|
|||
twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
kfree(codec->private_data);
|
||||
kfree(codec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2192,7 +2202,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev)
|
|||
codec->write = twl4030_write;
|
||||
codec->set_bias_level = twl4030_set_bias_level;
|
||||
codec->dai = twl4030_dai;
|
||||
codec->num_dai = ARRAY_SIZE(twl4030_dai),
|
||||
codec->num_dai = ARRAY_SIZE(twl4030_dai);
|
||||
codec->reg_cache_size = sizeof(twl4030_reg);
|
||||
codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg),
|
||||
GFP_KERNEL);
|
||||
|
@ -2237,6 +2247,9 @@ static int __devexit twl4030_codec_remove(struct platform_device *pdev)
|
|||
{
|
||||
struct twl4030_priv *twl4030 = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_unregister_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai));
|
||||
snd_soc_unregister_codec(&twl4030->codec);
|
||||
kfree(twl4030->codec.reg_cache);
|
||||
kfree(twl4030);
|
||||
|
||||
twl4030_codec = NULL;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
/* Register descriptions are here */
|
||||
#include <linux/mfd/twl4030-codec.h>
|
||||
|
||||
/* Sgadow register used by the audio driver */
|
||||
/* Shadow register used by the audio driver */
|
||||
#define TWL4030_REG_SW_SHADOW 0x4A
|
||||
#define TWL4030_CACHEREGNUM (TWL4030_REG_SW_SHADOW + 1)
|
||||
|
||||
|
|
|
@ -0,0 +1,888 @@
|
|||
/*
|
||||
* wm2000.c -- WM2000 ALSA Soc Audio driver
|
||||
*
|
||||
* Copyright 2008-2010 Wolfson Microelectronics PLC.
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The download image for the WM2000 will be requested as
|
||||
* 'wm2000_anc.bin' by default (overridable via platform data) at
|
||||
* runtime and is expected to be in flat binary format. This is
|
||||
* generated by Wolfson configuration tools and includes
|
||||
* system-specific callibration information. If supplied as a
|
||||
* sequence of ASCII-encoded hexidecimal bytes this can be converted
|
||||
* into a flat binary with a command such as this on the command line:
|
||||
*
|
||||
* perl -e 'while (<>) { s/[\r\n]+// ; printf("%c", hex($_)); }'
|
||||
* < file > wm2000_anc.bin
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/debugfs.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 <sound/wm2000.h>
|
||||
|
||||
#include "wm2000.h"
|
||||
|
||||
enum wm2000_anc_mode {
|
||||
ANC_ACTIVE = 0,
|
||||
ANC_BYPASS = 1,
|
||||
ANC_STANDBY = 2,
|
||||
ANC_OFF = 3,
|
||||
};
|
||||
|
||||
struct wm2000_priv {
|
||||
struct i2c_client *i2c;
|
||||
|
||||
enum wm2000_anc_mode anc_mode;
|
||||
|
||||
unsigned int anc_active:1;
|
||||
unsigned int anc_eng_ena:1;
|
||||
unsigned int spk_ena:1;
|
||||
|
||||
unsigned int mclk_div:1;
|
||||
unsigned int speech_clarity:1;
|
||||
|
||||
int anc_download_size;
|
||||
char *anc_download;
|
||||
};
|
||||
|
||||
static struct i2c_client *wm2000_i2c;
|
||||
|
||||
static int wm2000_write(struct i2c_client *i2c, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
int ret;
|
||||
|
||||
data[0] = (reg >> 8) & 0xff;
|
||||
data[1] = reg & 0xff;
|
||||
data[2] = value & 0xff;
|
||||
|
||||
dev_vdbg(&i2c->dev, "write %x = %x\n", reg, value);
|
||||
|
||||
ret = i2c_master_send(i2c, data, 3);
|
||||
if (ret == 3)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static unsigned int wm2000_read(struct i2c_client *i2c, unsigned int r)
|
||||
{
|
||||
struct i2c_msg xfer[2];
|
||||
u8 reg[2];
|
||||
u8 data;
|
||||
int ret;
|
||||
|
||||
/* Write register */
|
||||
reg[0] = (r >> 8) & 0xff;
|
||||
reg[1] = r & 0xff;
|
||||
xfer[0].addr = i2c->addr;
|
||||
xfer[0].flags = 0;
|
||||
xfer[0].len = sizeof(reg);
|
||||
xfer[0].buf = ®[0];
|
||||
|
||||
/* Read data */
|
||||
xfer[1].addr = i2c->addr;
|
||||
xfer[1].flags = I2C_M_RD;
|
||||
xfer[1].len = 1;
|
||||
xfer[1].buf = &data;
|
||||
|
||||
ret = i2c_transfer(i2c->adapter, xfer, 2);
|
||||
if (ret != 2) {
|
||||
dev_err(&i2c->dev, "i2c_transfer() returned %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_vdbg(&i2c->dev, "read %x from %x\n", data, r);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void wm2000_reset(struct wm2000_priv *wm2000)
|
||||
{
|
||||
struct i2c_client *i2c = wm2000->i2c;
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
|
||||
wm2000_write(i2c, WM2000_REG_ID1, 0);
|
||||
|
||||
wm2000->anc_mode = ANC_OFF;
|
||||
}
|
||||
|
||||
static int wm2000_poll_bit(struct i2c_client *i2c,
|
||||
unsigned int reg, u8 mask, int timeout)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = wm2000_read(i2c, reg);
|
||||
|
||||
while (!(val & mask) && --timeout) {
|
||||
msleep(1);
|
||||
val = wm2000_read(i2c, reg);
|
||||
}
|
||||
|
||||
if (timeout == 0)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int wm2000_power_up(struct i2c_client *i2c, int analogue)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
int ret, timeout;
|
||||
|
||||
BUG_ON(wm2000->anc_mode != ANC_OFF);
|
||||
|
||||
dev_dbg(&i2c->dev, "Beginning power up\n");
|
||||
|
||||
if (!wm2000->mclk_div) {
|
||||
dev_dbg(&i2c->dev, "Disabling MCLK divider\n");
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2,
|
||||
WM2000_MCLK_DIV2_ENA_CLR);
|
||||
} else {
|
||||
dev_dbg(&i2c->dev, "Enabling MCLK divider\n");
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2,
|
||||
WM2000_MCLK_DIV2_ENA_SET);
|
||||
}
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_SET);
|
||||
|
||||
/* Wait for ANC engine to become ready */
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
|
||||
WM2000_ANC_ENG_IDLE, 1)) {
|
||||
dev_err(&i2c->dev, "ANC engine failed to reset\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
|
||||
WM2000_STATUS_BOOT_COMPLETE, 1)) {
|
||||
dev_err(&i2c->dev, "ANC engine failed to initialise\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
|
||||
|
||||
/* Open code download of the data since it is the only bulk
|
||||
* write we do. */
|
||||
dev_dbg(&i2c->dev, "Downloading %d bytes\n",
|
||||
wm2000->anc_download_size - 2);
|
||||
|
||||
ret = i2c_master_send(i2c, wm2000->anc_download,
|
||||
wm2000->anc_download_size);
|
||||
if (ret < 0) {
|
||||
dev_err(&i2c->dev, "i2c_transfer() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (ret != wm2000->anc_download_size) {
|
||||
dev_err(&i2c->dev, "i2c_transfer() failed, %d != %d\n",
|
||||
ret, wm2000->anc_download_size);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(&i2c->dev, "Download complete\n");
|
||||
|
||||
if (analogue) {
|
||||
timeout = 248;
|
||||
wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4);
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_ANA_SEQ_INCLUDE |
|
||||
WM2000_MODE_MOUSE_ENABLE |
|
||||
WM2000_MODE_THERMAL_ENABLE);
|
||||
} else {
|
||||
timeout = 10;
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_MOUSE_ENABLE |
|
||||
WM2000_MODE_THERMAL_ENABLE);
|
||||
}
|
||||
|
||||
ret = wm2000_read(i2c, WM2000_REG_SPEECH_CLARITY);
|
||||
if (wm2000->speech_clarity)
|
||||
ret &= ~WM2000_SPEECH_CLARITY;
|
||||
else
|
||||
ret |= WM2000_SPEECH_CLARITY;
|
||||
wm2000_write(i2c, WM2000_REG_SPEECH_CLARITY, ret);
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_START0, 0x33);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_START1, 0x02);
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
|
||||
WM2000_STATUS_MOUSE_ACTIVE, timeout)) {
|
||||
dev_err(&i2c->dev, "Timed out waiting for device after %dms\n",
|
||||
timeout * 10);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
dev_dbg(&i2c->dev, "ANC active\n");
|
||||
if (analogue)
|
||||
dev_dbg(&i2c->dev, "Analogue active\n");
|
||||
wm2000->anc_mode = ANC_ACTIVE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_power_down(struct i2c_client *i2c, int analogue)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
int timeout;
|
||||
|
||||
if (analogue) {
|
||||
timeout = 248;
|
||||
wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_ANA_SEQ_INCLUDE |
|
||||
WM2000_MODE_POWER_DOWN);
|
||||
} else {
|
||||
timeout = 10;
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_POWER_DOWN);
|
||||
}
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
|
||||
WM2000_STATUS_POWER_DOWN_COMPLETE, timeout)) {
|
||||
dev_err(&i2c->dev, "Timeout waiting for ANC power down\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
|
||||
WM2000_ANC_ENG_IDLE, 1)) {
|
||||
dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
dev_dbg(&i2c->dev, "powered off\n");
|
||||
wm2000->anc_mode = ANC_OFF;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_enter_bypass(struct i2c_client *i2c, int analogue)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
|
||||
BUG_ON(wm2000->anc_mode != ANC_ACTIVE);
|
||||
|
||||
if (analogue) {
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_ANA_SEQ_INCLUDE |
|
||||
WM2000_MODE_THERMAL_ENABLE |
|
||||
WM2000_MODE_BYPASS_ENTRY);
|
||||
} else {
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_THERMAL_ENABLE |
|
||||
WM2000_MODE_BYPASS_ENTRY);
|
||||
}
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
|
||||
WM2000_STATUS_ANC_DISABLED, 10)) {
|
||||
dev_err(&i2c->dev, "Timeout waiting for ANC disable\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
|
||||
WM2000_ANC_ENG_IDLE, 1)) {
|
||||
dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
|
||||
|
||||
wm2000->anc_mode = ANC_BYPASS;
|
||||
dev_dbg(&i2c->dev, "bypass enabled\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_exit_bypass(struct i2c_client *i2c, int analogue)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
|
||||
BUG_ON(wm2000->anc_mode != ANC_BYPASS);
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0);
|
||||
|
||||
if (analogue) {
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_ANA_SEQ_INCLUDE |
|
||||
WM2000_MODE_MOUSE_ENABLE |
|
||||
WM2000_MODE_THERMAL_ENABLE);
|
||||
} else {
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_MOUSE_ENABLE |
|
||||
WM2000_MODE_THERMAL_ENABLE);
|
||||
}
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
|
||||
WM2000_STATUS_MOUSE_ACTIVE, 10)) {
|
||||
dev_err(&i2c->dev, "Timed out waiting for MOUSE\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
wm2000->anc_mode = ANC_ACTIVE;
|
||||
dev_dbg(&i2c->dev, "MOUSE active\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_enter_standby(struct i2c_client *i2c, int analogue)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
int timeout;
|
||||
|
||||
BUG_ON(wm2000->anc_mode != ANC_ACTIVE);
|
||||
|
||||
if (analogue) {
|
||||
timeout = 248;
|
||||
wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4);
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_ANA_SEQ_INCLUDE |
|
||||
WM2000_MODE_THERMAL_ENABLE |
|
||||
WM2000_MODE_STANDBY_ENTRY);
|
||||
} else {
|
||||
timeout = 10;
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_THERMAL_ENABLE |
|
||||
WM2000_MODE_STANDBY_ENTRY);
|
||||
}
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
|
||||
WM2000_STATUS_ANC_DISABLED, timeout)) {
|
||||
dev_err(&i2c->dev,
|
||||
"Timed out waiting for ANC disable after 1ms\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, WM2000_ANC_ENG_IDLE,
|
||||
1)) {
|
||||
dev_err(&i2c->dev,
|
||||
"Timed out waiting for standby after %dms\n",
|
||||
timeout * 10);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
|
||||
|
||||
wm2000->anc_mode = ANC_STANDBY;
|
||||
dev_dbg(&i2c->dev, "standby\n");
|
||||
if (analogue)
|
||||
dev_dbg(&i2c->dev, "Analogue disabled\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_exit_standby(struct i2c_client *i2c, int analogue)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
int timeout;
|
||||
|
||||
BUG_ON(wm2000->anc_mode != ANC_STANDBY);
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0);
|
||||
|
||||
if (analogue) {
|
||||
timeout = 248;
|
||||
wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4);
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_ANA_SEQ_INCLUDE |
|
||||
WM2000_MODE_THERMAL_ENABLE |
|
||||
WM2000_MODE_MOUSE_ENABLE);
|
||||
} else {
|
||||
timeout = 10;
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
|
||||
WM2000_MODE_THERMAL_ENABLE |
|
||||
WM2000_MODE_MOUSE_ENABLE);
|
||||
}
|
||||
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
|
||||
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
|
||||
|
||||
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
|
||||
WM2000_STATUS_MOUSE_ACTIVE, timeout)) {
|
||||
dev_err(&i2c->dev, "Timed out waiting for MOUSE after %dms\n",
|
||||
timeout * 10);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
wm2000->anc_mode = ANC_ACTIVE;
|
||||
dev_dbg(&i2c->dev, "MOUSE active\n");
|
||||
if (analogue)
|
||||
dev_dbg(&i2c->dev, "Analogue enabled\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef int (*wm2000_mode_fn)(struct i2c_client *i2c, int analogue);
|
||||
|
||||
static struct {
|
||||
enum wm2000_anc_mode source;
|
||||
enum wm2000_anc_mode dest;
|
||||
int analogue;
|
||||
wm2000_mode_fn step[2];
|
||||
} anc_transitions[] = {
|
||||
{
|
||||
.source = ANC_OFF,
|
||||
.dest = ANC_ACTIVE,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_power_up,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_OFF,
|
||||
.dest = ANC_STANDBY,
|
||||
.step = {
|
||||
wm2000_power_up,
|
||||
wm2000_enter_standby,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_OFF,
|
||||
.dest = ANC_BYPASS,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_power_up,
|
||||
wm2000_enter_bypass,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_ACTIVE,
|
||||
.dest = ANC_BYPASS,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_enter_bypass,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_ACTIVE,
|
||||
.dest = ANC_STANDBY,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_enter_standby,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_ACTIVE,
|
||||
.dest = ANC_OFF,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_power_down,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_BYPASS,
|
||||
.dest = ANC_ACTIVE,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_exit_bypass,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_BYPASS,
|
||||
.dest = ANC_STANDBY,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_exit_bypass,
|
||||
wm2000_enter_standby,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_BYPASS,
|
||||
.dest = ANC_OFF,
|
||||
.step = {
|
||||
wm2000_exit_bypass,
|
||||
wm2000_power_down,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_STANDBY,
|
||||
.dest = ANC_ACTIVE,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_exit_standby,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_STANDBY,
|
||||
.dest = ANC_BYPASS,
|
||||
.analogue = 1,
|
||||
.step = {
|
||||
wm2000_exit_standby,
|
||||
wm2000_enter_bypass,
|
||||
},
|
||||
},
|
||||
{
|
||||
.source = ANC_STANDBY,
|
||||
.dest = ANC_OFF,
|
||||
.step = {
|
||||
wm2000_exit_standby,
|
||||
wm2000_power_down,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static int wm2000_anc_transition(struct wm2000_priv *wm2000,
|
||||
enum wm2000_anc_mode mode)
|
||||
{
|
||||
struct i2c_client *i2c = wm2000->i2c;
|
||||
int i, j;
|
||||
int ret;
|
||||
|
||||
if (wm2000->anc_mode == mode)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(anc_transitions); i++)
|
||||
if (anc_transitions[i].source == wm2000->anc_mode &&
|
||||
anc_transitions[i].dest == mode)
|
||||
break;
|
||||
if (i == ARRAY_SIZE(anc_transitions)) {
|
||||
dev_err(&i2c->dev, "No transition for %d->%d\n",
|
||||
wm2000->anc_mode, mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(anc_transitions[j].step); j++) {
|
||||
if (!anc_transitions[i].step[j])
|
||||
break;
|
||||
ret = anc_transitions[i].step[j](i2c,
|
||||
anc_transitions[i].analogue);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_anc_set_mode(struct wm2000_priv *wm2000)
|
||||
{
|
||||
struct i2c_client *i2c = wm2000->i2c;
|
||||
enum wm2000_anc_mode mode;
|
||||
|
||||
if (wm2000->anc_eng_ena && wm2000->spk_ena)
|
||||
if (wm2000->anc_active)
|
||||
mode = ANC_ACTIVE;
|
||||
else
|
||||
mode = ANC_BYPASS;
|
||||
else
|
||||
mode = ANC_STANDBY;
|
||||
|
||||
dev_dbg(&i2c->dev, "Set mode %d (enabled %d, mute %d, active %d)\n",
|
||||
mode, wm2000->anc_eng_ena, !wm2000->spk_ena,
|
||||
wm2000->anc_active);
|
||||
|
||||
return wm2000_anc_transition(wm2000, mode);
|
||||
}
|
||||
|
||||
static int wm2000_anc_mode_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
|
||||
|
||||
ucontrol->value.enumerated.item[0] = wm2000->anc_active;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
|
||||
int anc_active = ucontrol->value.enumerated.item[0];
|
||||
|
||||
if (anc_active > 1)
|
||||
return -EINVAL;
|
||||
|
||||
wm2000->anc_active = anc_active;
|
||||
|
||||
return wm2000_anc_set_mode(wm2000);
|
||||
}
|
||||
|
||||
static int wm2000_speaker_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
|
||||
|
||||
ucontrol->value.enumerated.item[0] = wm2000->spk_ena;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2000_speaker_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
|
||||
int val = ucontrol->value.enumerated.item[0];
|
||||
|
||||
if (val > 1)
|
||||
return -EINVAL;
|
||||
|
||||
wm2000->spk_ena = val;
|
||||
|
||||
return wm2000_anc_set_mode(wm2000);
|
||||
}
|
||||
|
||||
static const struct snd_kcontrol_new wm2000_controls[] = {
|
||||
SOC_SINGLE_BOOL_EXT("WM2000 ANC Switch", 0,
|
||||
wm2000_anc_mode_get,
|
||||
wm2000_anc_mode_put),
|
||||
SOC_SINGLE_BOOL_EXT("WM2000 Switch", 0,
|
||||
wm2000_speaker_get,
|
||||
wm2000_speaker_put),
|
||||
};
|
||||
|
||||
static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
|
||||
|
||||
if (SND_SOC_DAPM_EVENT_ON(event))
|
||||
wm2000->anc_eng_ena = 1;
|
||||
|
||||
if (SND_SOC_DAPM_EVENT_OFF(event))
|
||||
wm2000->anc_eng_ena = 0;
|
||||
|
||||
return wm2000_anc_set_mode(wm2000);
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = {
|
||||
/* Externally visible pins */
|
||||
SND_SOC_DAPM_OUTPUT("WM2000 SPKN"),
|
||||
SND_SOC_DAPM_OUTPUT("WM2000 SPKP"),
|
||||
|
||||
SND_SOC_DAPM_INPUT("WM2000 LINN"),
|
||||
SND_SOC_DAPM_INPUT("WM2000 LINP"),
|
||||
|
||||
SND_SOC_DAPM_PGA_E("ANC Engine", SND_SOC_NOPM, 0, 0, NULL, 0,
|
||||
wm2000_anc_power_event,
|
||||
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
};
|
||||
|
||||
/* Target, Path, Source */
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{ "WM2000 SPKN", NULL, "ANC Engine" },
|
||||
{ "WM2000 SPKP", NULL, "ANC Engine" },
|
||||
{ "ANC Engine", NULL, "WM2000 LINN" },
|
||||
{ "ANC Engine", NULL, "WM2000 LINP" },
|
||||
};
|
||||
|
||||
/* Called from the machine driver */
|
||||
int wm2000_add_controls(struct snd_soc_codec *codec)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!wm2000_i2c) {
|
||||
pr_err("WM2000 not yet probed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = snd_soc_dapm_new_controls(codec, wm2000_dapm_widgets,
|
||||
ARRAY_SIZE(wm2000_dapm_widgets));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return snd_soc_add_controls(codec, wm2000_controls,
|
||||
ARRAY_SIZE(wm2000_controls));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm2000_add_controls);
|
||||
|
||||
static int __devinit wm2000_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *i2c_id)
|
||||
{
|
||||
struct wm2000_priv *wm2000;
|
||||
struct wm2000_platform_data *pdata;
|
||||
const char *filename;
|
||||
const struct firmware *fw;
|
||||
int reg, ret;
|
||||
u16 id;
|
||||
|
||||
if (wm2000_i2c) {
|
||||
dev_err(&i2c->dev, "Another WM2000 is already registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm2000 = kzalloc(sizeof(struct wm2000_priv), GFP_KERNEL);
|
||||
if (wm2000 == NULL) {
|
||||
dev_err(&i2c->dev, "Unable to allocate private data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Verify that this is a WM2000 */
|
||||
reg = wm2000_read(i2c, WM2000_REG_ID1);
|
||||
id = reg << 8;
|
||||
reg = wm2000_read(i2c, WM2000_REG_ID2);
|
||||
id |= reg & 0xff;
|
||||
|
||||
if (id != 0x2000) {
|
||||
dev_err(&i2c->dev, "Device is not a WM2000 - ID %x\n", id);
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
reg = wm2000_read(i2c, WM2000_REG_REVISON);
|
||||
dev_info(&i2c->dev, "revision %c\n", reg + 'A');
|
||||
|
||||
filename = "wm2000_anc.bin";
|
||||
pdata = dev_get_platdata(&i2c->dev);
|
||||
if (pdata) {
|
||||
wm2000->mclk_div = pdata->mclkdiv2;
|
||||
wm2000->speech_clarity = !pdata->speech_enh_disable;
|
||||
|
||||
if (pdata->download_file)
|
||||
filename = pdata->download_file;
|
||||
}
|
||||
|
||||
ret = request_firmware(&fw, filename, &i2c->dev);
|
||||
if (ret != 0) {
|
||||
dev_err(&i2c->dev, "Failed to acquire ANC data: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Pre-cook the concatenation of the register address onto the image */
|
||||
wm2000->anc_download_size = fw->size + 2;
|
||||
wm2000->anc_download = kmalloc(wm2000->anc_download_size, GFP_KERNEL);
|
||||
if (wm2000->anc_download == NULL) {
|
||||
dev_err(&i2c->dev, "Out of memory\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_fw;
|
||||
}
|
||||
|
||||
wm2000->anc_download[0] = 0x80;
|
||||
wm2000->anc_download[1] = 0x00;
|
||||
memcpy(wm2000->anc_download + 2, fw->data, fw->size);
|
||||
|
||||
release_firmware(fw);
|
||||
|
||||
dev_set_drvdata(&i2c->dev, wm2000);
|
||||
wm2000->anc_eng_ena = 1;
|
||||
wm2000->i2c = i2c;
|
||||
|
||||
wm2000_reset(wm2000);
|
||||
|
||||
/* This will trigger a transition to standby mode by default */
|
||||
wm2000_anc_set_mode(wm2000);
|
||||
|
||||
wm2000_i2c = i2c;
|
||||
|
||||
return 0;
|
||||
|
||||
err_fw:
|
||||
release_firmware(fw);
|
||||
err:
|
||||
kfree(wm2000);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __devexit int wm2000_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
|
||||
wm2000_anc_transition(wm2000, ANC_OFF);
|
||||
|
||||
wm2000_i2c = NULL;
|
||||
kfree(wm2000->anc_download);
|
||||
kfree(wm2000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm2000_i2c_shutdown(struct i2c_client *i2c)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
|
||||
wm2000_anc_transition(wm2000, ANC_OFF);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm2000_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
|
||||
return wm2000_anc_transition(wm2000, ANC_OFF);
|
||||
}
|
||||
|
||||
static int wm2000_i2c_resume(struct i2c_client *i2c)
|
||||
{
|
||||
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
|
||||
|
||||
return wm2000_anc_set_mode(wm2000);
|
||||
}
|
||||
#else
|
||||
#define wm2000_i2c_suspend NULL
|
||||
#define wm2000_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm2000_i2c_id[] = {
|
||||
{ "wm2000", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm2000_i2c_id);
|
||||
|
||||
static struct i2c_driver wm2000_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "wm2000",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm2000_i2c_probe,
|
||||
.remove = __devexit_p(wm2000_i2c_remove),
|
||||
.suspend = wm2000_i2c_suspend,
|
||||
.resume = wm2000_i2c_resume,
|
||||
.shutdown = wm2000_i2c_shutdown,
|
||||
.id_table = wm2000_i2c_id,
|
||||
};
|
||||
|
||||
static int __init wm2000_init(void)
|
||||
{
|
||||
return i2c_add_driver(&wm2000_i2c_driver);
|
||||
}
|
||||
module_init(wm2000_init);
|
||||
|
||||
static void __exit wm2000_exit(void)
|
||||
{
|
||||
i2c_del_driver(&wm2000_i2c_driver);
|
||||
}
|
||||
module_exit(wm2000_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC WM2000 driver");
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* wm2000.h -- WM2000 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 _WM2000_H
|
||||
#define _WM2000_H
|
||||
|
||||
struct wm2000_setup_data {
|
||||
unsigned short i2c_address;
|
||||
int mclk_div; /* Set to a non-zero value if MCLK_DIV_2 required */
|
||||
};
|
||||
|
||||
extern int wm2000_add_controls(struct snd_soc_codec *codec);
|
||||
|
||||
extern struct snd_soc_dai wm2000_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm2000;
|
||||
|
||||
#define WM2000_REG_SYS_START 0x8000
|
||||
#define WM2000_REG_SPEECH_CLARITY 0x8fef
|
||||
#define WM2000_REG_SYS_WATCHDOG 0x8ff6
|
||||
#define WM2000_REG_ANA_VMID_PD_TIME 0x8ff7
|
||||
#define WM2000_REG_ANA_VMID_PU_TIME 0x8ff8
|
||||
#define WM2000_REG_CAT_FLTR_INDX 0x8ff9
|
||||
#define WM2000_REG_CAT_GAIN_0 0x8ffa
|
||||
#define WM2000_REG_SYS_STATUS 0x8ffc
|
||||
#define WM2000_REG_SYS_MODE_CNTRL 0x8ffd
|
||||
#define WM2000_REG_SYS_START0 0x8ffe
|
||||
#define WM2000_REG_SYS_START1 0x8fff
|
||||
#define WM2000_REG_ID1 0xf000
|
||||
#define WM2000_REG_ID2 0xf001
|
||||
#define WM2000_REG_REVISON 0xf002
|
||||
#define WM2000_REG_SYS_CTL1 0xf003
|
||||
#define WM2000_REG_SYS_CTL2 0xf004
|
||||
#define WM2000_REG_ANC_STAT 0xf005
|
||||
#define WM2000_REG_IF_CTL 0xf006
|
||||
|
||||
/* SPEECH_CLARITY */
|
||||
#define WM2000_SPEECH_CLARITY 0x01
|
||||
|
||||
/* SYS_STATUS */
|
||||
#define WM2000_STATUS_MOUSE_ACTIVE 0x40
|
||||
#define WM2000_STATUS_CAT_FREQ_COMPLETE 0x20
|
||||
#define WM2000_STATUS_CAT_GAIN_COMPLETE 0x10
|
||||
#define WM2000_STATUS_THERMAL_SHUTDOWN_COMPLETE 0x08
|
||||
#define WM2000_STATUS_ANC_DISABLED 0x04
|
||||
#define WM2000_STATUS_POWER_DOWN_COMPLETE 0x02
|
||||
#define WM2000_STATUS_BOOT_COMPLETE 0x01
|
||||
|
||||
/* SYS_MODE_CNTRL */
|
||||
#define WM2000_MODE_ANA_SEQ_INCLUDE 0x80
|
||||
#define WM2000_MODE_MOUSE_ENABLE 0x40
|
||||
#define WM2000_MODE_CAT_FREQ_ENABLE 0x20
|
||||
#define WM2000_MODE_CAT_GAIN_ENABLE 0x10
|
||||
#define WM2000_MODE_BYPASS_ENTRY 0x08
|
||||
#define WM2000_MODE_STANDBY_ENTRY 0x04
|
||||
#define WM2000_MODE_THERMAL_ENABLE 0x02
|
||||
#define WM2000_MODE_POWER_DOWN 0x01
|
||||
|
||||
/* SYS_CTL1 */
|
||||
#define WM2000_SYS_STBY 0x01
|
||||
|
||||
/* SYS_CTL2 */
|
||||
#define WM2000_MCLK_DIV2_ENA_CLR 0x80
|
||||
#define WM2000_MCLK_DIV2_ENA_SET 0x40
|
||||
#define WM2000_ANC_ENG_CLR 0x20
|
||||
#define WM2000_ANC_ENG_SET 0x10
|
||||
#define WM2000_ANC_INT_N_CLR 0x08
|
||||
#define WM2000_ANC_INT_N_SET 0x04
|
||||
#define WM2000_RAM_CLR 0x02
|
||||
#define WM2000_RAM_SET 0x01
|
||||
|
||||
/* ANC_STAT */
|
||||
#define WM2000_ANC_ENG_IDLE 0x01
|
||||
|
||||
#endif
|
|
@ -44,23 +44,16 @@ struct snd_soc_dai wm8727_dai = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(wm8727_dai);
|
||||
|
||||
static struct snd_soc_codec *wm8727_codec;
|
||||
|
||||
static int wm8727_soc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
||||
if (codec == NULL)
|
||||
return -ENOMEM;
|
||||
mutex_init(&codec->mutex);
|
||||
codec->name = "WM8727";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &wm8727_dai;
|
||||
codec->num_dai = 1;
|
||||
socdev->card->codec = codec;
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
BUG_ON(!wm8727_codec);
|
||||
|
||||
socdev->card->codec = wm8727_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
|
@ -80,12 +73,9 @@ pcm_err:
|
|||
static int wm8727_soc_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);
|
||||
kfree(codec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -98,13 +88,55 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_wm8727);
|
|||
|
||||
static __devinit int wm8727_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
if (wm8727_codec) {
|
||||
dev_err(&pdev->dev, "Another WM8727 is registered\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
||||
if (codec == NULL)
|
||||
return -ENOMEM;
|
||||
wm8727_codec = codec;
|
||||
|
||||
platform_set_drvdata(pdev, codec);
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
codec->dev = &pdev->dev;
|
||||
codec->name = "WM8727";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &wm8727_dai;
|
||||
codec->num_dai = 1;
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
wm8727_dai.dev = &pdev->dev;
|
||||
return snd_soc_register_dai(&wm8727_dai);
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "Failed to register CODEC: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8727_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "Failed to register DAI: %d\n", ret);
|
||||
goto err_codec;
|
||||
}
|
||||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err:
|
||||
kfree(codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit wm8727_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_dai(&wm8727_dai);
|
||||
snd_soc_unregister_codec(platform_get_drvdata(pdev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -456,6 +456,9 @@ static int wm8731_resume(struct platform_device *pdev)
|
|||
|
||||
/* Sync reg_cache with the hardware */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) {
|
||||
if (cache[i] == wm8731_reg[i])
|
||||
continue;
|
||||
|
||||
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
|
||||
data[1] = cache[i] & 0x00ff;
|
||||
codec->hw_write(codec->control_data, data, 2);
|
||||
|
|
|
@ -1507,10 +1507,6 @@ static int wm8753_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;
|
||||
|
||||
/* we only need to suspend if we are a valid card */
|
||||
if (!codec->card)
|
||||
return 0;
|
||||
|
||||
wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1523,10 +1519,6 @@ static int wm8753_resume(struct platform_device *pdev)
|
|||
u8 data[2];
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
/* we only need to resume if we are a valid card */
|
||||
if (!codec->card)
|
||||
return 0;
|
||||
|
||||
/* Sync reg_cache with the hardware */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8753_reg); i++) {
|
||||
if (i + 1 == WM8753_RESET)
|
||||
|
|
|
@ -406,6 +406,8 @@ static int wm8776_resume(struct platform_device *pdev)
|
|||
|
||||
/* Sync reg_cache with the hardware */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8776_reg); i++) {
|
||||
if (cache[i] == wm8776_reg[i])
|
||||
continue;
|
||||
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
|
||||
data[1] = cache[i] & 0x00ff;
|
||||
codec->hw_write(codec->control_data, data, 2);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,489 @@
|
|||
/*
|
||||
* wm8955.h -- WM8904 ASoC driver
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics, plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _WM8955_H
|
||||
#define _WM8955_H
|
||||
|
||||
#define WM8955_CLK_MCLK 1
|
||||
|
||||
extern struct snd_soc_dai wm8955_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8955;
|
||||
|
||||
/*
|
||||
* Register values.
|
||||
*/
|
||||
#define WM8955_LOUT1_VOLUME 0x02
|
||||
#define WM8955_ROUT1_VOLUME 0x03
|
||||
#define WM8955_DAC_CONTROL 0x05
|
||||
#define WM8955_AUDIO_INTERFACE 0x07
|
||||
#define WM8955_SAMPLE_RATE 0x08
|
||||
#define WM8955_LEFT_DAC_VOLUME 0x0A
|
||||
#define WM8955_RIGHT_DAC_VOLUME 0x0B
|
||||
#define WM8955_BASS_CONTROL 0x0C
|
||||
#define WM8955_TREBLE_CONTROL 0x0D
|
||||
#define WM8955_RESET 0x0F
|
||||
#define WM8955_ADDITIONAL_CONTROL_1 0x17
|
||||
#define WM8955_ADDITIONAL_CONTROL_2 0x18
|
||||
#define WM8955_POWER_MANAGEMENT_1 0x19
|
||||
#define WM8955_POWER_MANAGEMENT_2 0x1A
|
||||
#define WM8955_ADDITIONAL_CONTROL_3 0x1B
|
||||
#define WM8955_LEFT_OUT_MIX_1 0x22
|
||||
#define WM8955_LEFT_OUT_MIX_2 0x23
|
||||
#define WM8955_RIGHT_OUT_MIX_1 0x24
|
||||
#define WM8955_RIGHT_OUT_MIX_2 0x25
|
||||
#define WM8955_MONO_OUT_MIX_1 0x26
|
||||
#define WM8955_MONO_OUT_MIX_2 0x27
|
||||
#define WM8955_LOUT2_VOLUME 0x28
|
||||
#define WM8955_ROUT2_VOLUME 0x29
|
||||
#define WM8955_MONOOUT_VOLUME 0x2A
|
||||
#define WM8955_CLOCKING_PLL 0x2B
|
||||
#define WM8955_PLL_CONTROL_1 0x2C
|
||||
#define WM8955_PLL_CONTROL_2 0x2D
|
||||
#define WM8955_PLL_CONTROL_3 0x2E
|
||||
#define WM8955_PLL_CONTROL_4 0x3B
|
||||
|
||||
#define WM8955_REGISTER_COUNT 29
|
||||
#define WM8955_MAX_REGISTER 0x3B
|
||||
|
||||
/*
|
||||
* Field Definitions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* R2 (0x02) - LOUT1 volume
|
||||
*/
|
||||
#define WM8955_LO1VU 0x0100 /* LO1VU */
|
||||
#define WM8955_LO1VU_MASK 0x0100 /* LO1VU */
|
||||
#define WM8955_LO1VU_SHIFT 8 /* LO1VU */
|
||||
#define WM8955_LO1VU_WIDTH 1 /* LO1VU */
|
||||
#define WM8955_LO1ZC 0x0080 /* LO1ZC */
|
||||
#define WM8955_LO1ZC_MASK 0x0080 /* LO1ZC */
|
||||
#define WM8955_LO1ZC_SHIFT 7 /* LO1ZC */
|
||||
#define WM8955_LO1ZC_WIDTH 1 /* LO1ZC */
|
||||
#define WM8955_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
|
||||
#define WM8955_LOUTVOL_SHIFT 0 /* LOUTVOL - [6:0] */
|
||||
#define WM8955_LOUTVOL_WIDTH 7 /* LOUTVOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R3 (0x03) - ROUT1 volume
|
||||
*/
|
||||
#define WM8955_RO1VU 0x0100 /* RO1VU */
|
||||
#define WM8955_RO1VU_MASK 0x0100 /* RO1VU */
|
||||
#define WM8955_RO1VU_SHIFT 8 /* RO1VU */
|
||||
#define WM8955_RO1VU_WIDTH 1 /* RO1VU */
|
||||
#define WM8955_RO1ZC 0x0080 /* RO1ZC */
|
||||
#define WM8955_RO1ZC_MASK 0x0080 /* RO1ZC */
|
||||
#define WM8955_RO1ZC_SHIFT 7 /* RO1ZC */
|
||||
#define WM8955_RO1ZC_WIDTH 1 /* RO1ZC */
|
||||
#define WM8955_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
|
||||
#define WM8955_ROUTVOL_SHIFT 0 /* ROUTVOL - [6:0] */
|
||||
#define WM8955_ROUTVOL_WIDTH 7 /* ROUTVOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R5 (0x05) - DAC Control
|
||||
*/
|
||||
#define WM8955_DAT 0x0080 /* DAT */
|
||||
#define WM8955_DAT_MASK 0x0080 /* DAT */
|
||||
#define WM8955_DAT_SHIFT 7 /* DAT */
|
||||
#define WM8955_DAT_WIDTH 1 /* DAT */
|
||||
#define WM8955_DACMU 0x0008 /* DACMU */
|
||||
#define WM8955_DACMU_MASK 0x0008 /* DACMU */
|
||||
#define WM8955_DACMU_SHIFT 3 /* DACMU */
|
||||
#define WM8955_DACMU_WIDTH 1 /* DACMU */
|
||||
#define WM8955_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
|
||||
#define WM8955_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
|
||||
#define WM8955_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
|
||||
|
||||
/*
|
||||
* R7 (0x07) - Audio Interface
|
||||
*/
|
||||
#define WM8955_BCLKINV 0x0080 /* BCLKINV */
|
||||
#define WM8955_BCLKINV_MASK 0x0080 /* BCLKINV */
|
||||
#define WM8955_BCLKINV_SHIFT 7 /* BCLKINV */
|
||||
#define WM8955_BCLKINV_WIDTH 1 /* BCLKINV */
|
||||
#define WM8955_MS 0x0040 /* MS */
|
||||
#define WM8955_MS_MASK 0x0040 /* MS */
|
||||
#define WM8955_MS_SHIFT 6 /* MS */
|
||||
#define WM8955_MS_WIDTH 1 /* MS */
|
||||
#define WM8955_LRSWAP 0x0020 /* LRSWAP */
|
||||
#define WM8955_LRSWAP_MASK 0x0020 /* LRSWAP */
|
||||
#define WM8955_LRSWAP_SHIFT 5 /* LRSWAP */
|
||||
#define WM8955_LRSWAP_WIDTH 1 /* LRSWAP */
|
||||
#define WM8955_LRP 0x0010 /* LRP */
|
||||
#define WM8955_LRP_MASK 0x0010 /* LRP */
|
||||
#define WM8955_LRP_SHIFT 4 /* LRP */
|
||||
#define WM8955_LRP_WIDTH 1 /* LRP */
|
||||
#define WM8955_WL_MASK 0x000C /* WL - [3:2] */
|
||||
#define WM8955_WL_SHIFT 2 /* WL - [3:2] */
|
||||
#define WM8955_WL_WIDTH 2 /* WL - [3:2] */
|
||||
#define WM8955_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */
|
||||
#define WM8955_FORMAT_SHIFT 0 /* FORMAT - [1:0] */
|
||||
#define WM8955_FORMAT_WIDTH 2 /* FORMAT - [1:0] */
|
||||
|
||||
/*
|
||||
* R8 (0x08) - Sample Rate
|
||||
*/
|
||||
#define WM8955_BCLKDIV2 0x0080 /* BCLKDIV2 */
|
||||
#define WM8955_BCLKDIV2_MASK 0x0080 /* BCLKDIV2 */
|
||||
#define WM8955_BCLKDIV2_SHIFT 7 /* BCLKDIV2 */
|
||||
#define WM8955_BCLKDIV2_WIDTH 1 /* BCLKDIV2 */
|
||||
#define WM8955_MCLKDIV2 0x0040 /* MCLKDIV2 */
|
||||
#define WM8955_MCLKDIV2_MASK 0x0040 /* MCLKDIV2 */
|
||||
#define WM8955_MCLKDIV2_SHIFT 6 /* MCLKDIV2 */
|
||||
#define WM8955_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
|
||||
#define WM8955_SR_MASK 0x003E /* SR - [5:1] */
|
||||
#define WM8955_SR_SHIFT 1 /* SR - [5:1] */
|
||||
#define WM8955_SR_WIDTH 5 /* SR - [5:1] */
|
||||
#define WM8955_USB 0x0001 /* USB */
|
||||
#define WM8955_USB_MASK 0x0001 /* USB */
|
||||
#define WM8955_USB_SHIFT 0 /* USB */
|
||||
#define WM8955_USB_WIDTH 1 /* USB */
|
||||
|
||||
/*
|
||||
* R10 (0x0A) - Left DAC volume
|
||||
*/
|
||||
#define WM8955_LDVU 0x0100 /* LDVU */
|
||||
#define WM8955_LDVU_MASK 0x0100 /* LDVU */
|
||||
#define WM8955_LDVU_SHIFT 8 /* LDVU */
|
||||
#define WM8955_LDVU_WIDTH 1 /* LDVU */
|
||||
#define WM8955_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */
|
||||
#define WM8955_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */
|
||||
#define WM8955_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */
|
||||
|
||||
/*
|
||||
* R11 (0x0B) - Right DAC volume
|
||||
*/
|
||||
#define WM8955_RDVU 0x0100 /* RDVU */
|
||||
#define WM8955_RDVU_MASK 0x0100 /* RDVU */
|
||||
#define WM8955_RDVU_SHIFT 8 /* RDVU */
|
||||
#define WM8955_RDVU_WIDTH 1 /* RDVU */
|
||||
#define WM8955_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */
|
||||
#define WM8955_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */
|
||||
#define WM8955_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */
|
||||
|
||||
/*
|
||||
* R12 (0x0C) - Bass control
|
||||
*/
|
||||
#define WM8955_BB 0x0080 /* BB */
|
||||
#define WM8955_BB_MASK 0x0080 /* BB */
|
||||
#define WM8955_BB_SHIFT 7 /* BB */
|
||||
#define WM8955_BB_WIDTH 1 /* BB */
|
||||
#define WM8955_BC 0x0040 /* BC */
|
||||
#define WM8955_BC_MASK 0x0040 /* BC */
|
||||
#define WM8955_BC_SHIFT 6 /* BC */
|
||||
#define WM8955_BC_WIDTH 1 /* BC */
|
||||
#define WM8955_BASS_MASK 0x000F /* BASS - [3:0] */
|
||||
#define WM8955_BASS_SHIFT 0 /* BASS - [3:0] */
|
||||
#define WM8955_BASS_WIDTH 4 /* BASS - [3:0] */
|
||||
|
||||
/*
|
||||
* R13 (0x0D) - Treble control
|
||||
*/
|
||||
#define WM8955_TC 0x0040 /* TC */
|
||||
#define WM8955_TC_MASK 0x0040 /* TC */
|
||||
#define WM8955_TC_SHIFT 6 /* TC */
|
||||
#define WM8955_TC_WIDTH 1 /* TC */
|
||||
#define WM8955_TRBL_MASK 0x000F /* TRBL - [3:0] */
|
||||
#define WM8955_TRBL_SHIFT 0 /* TRBL - [3:0] */
|
||||
#define WM8955_TRBL_WIDTH 4 /* TRBL - [3:0] */
|
||||
|
||||
/*
|
||||
* R15 (0x0F) - Reset
|
||||
*/
|
||||
#define WM8955_RESET_MASK 0x01FF /* RESET - [8:0] */
|
||||
#define WM8955_RESET_SHIFT 0 /* RESET - [8:0] */
|
||||
#define WM8955_RESET_WIDTH 9 /* RESET - [8:0] */
|
||||
|
||||
/*
|
||||
* R23 (0x17) - Additional control (1)
|
||||
*/
|
||||
#define WM8955_TSDEN 0x0100 /* TSDEN */
|
||||
#define WM8955_TSDEN_MASK 0x0100 /* TSDEN */
|
||||
#define WM8955_TSDEN_SHIFT 8 /* TSDEN */
|
||||
#define WM8955_TSDEN_WIDTH 1 /* TSDEN */
|
||||
#define WM8955_VSEL_MASK 0x00C0 /* VSEL - [7:6] */
|
||||
#define WM8955_VSEL_SHIFT 6 /* VSEL - [7:6] */
|
||||
#define WM8955_VSEL_WIDTH 2 /* VSEL - [7:6] */
|
||||
#define WM8955_DMONOMIX_MASK 0x0030 /* DMONOMIX - [5:4] */
|
||||
#define WM8955_DMONOMIX_SHIFT 4 /* DMONOMIX - [5:4] */
|
||||
#define WM8955_DMONOMIX_WIDTH 2 /* DMONOMIX - [5:4] */
|
||||
#define WM8955_DACINV 0x0002 /* DACINV */
|
||||
#define WM8955_DACINV_MASK 0x0002 /* DACINV */
|
||||
#define WM8955_DACINV_SHIFT 1 /* DACINV */
|
||||
#define WM8955_DACINV_WIDTH 1 /* DACINV */
|
||||
#define WM8955_TOEN 0x0001 /* TOEN */
|
||||
#define WM8955_TOEN_MASK 0x0001 /* TOEN */
|
||||
#define WM8955_TOEN_SHIFT 0 /* TOEN */
|
||||
#define WM8955_TOEN_WIDTH 1 /* TOEN */
|
||||
|
||||
/*
|
||||
* R24 (0x18) - Additional control (2)
|
||||
*/
|
||||
#define WM8955_OUT3SW_MASK 0x0180 /* OUT3SW - [8:7] */
|
||||
#define WM8955_OUT3SW_SHIFT 7 /* OUT3SW - [8:7] */
|
||||
#define WM8955_OUT3SW_WIDTH 2 /* OUT3SW - [8:7] */
|
||||
#define WM8955_ROUT2INV 0x0010 /* ROUT2INV */
|
||||
#define WM8955_ROUT2INV_MASK 0x0010 /* ROUT2INV */
|
||||
#define WM8955_ROUT2INV_SHIFT 4 /* ROUT2INV */
|
||||
#define WM8955_ROUT2INV_WIDTH 1 /* ROUT2INV */
|
||||
#define WM8955_DACOSR 0x0001 /* DACOSR */
|
||||
#define WM8955_DACOSR_MASK 0x0001 /* DACOSR */
|
||||
#define WM8955_DACOSR_SHIFT 0 /* DACOSR */
|
||||
#define WM8955_DACOSR_WIDTH 1 /* DACOSR */
|
||||
|
||||
/*
|
||||
* R25 (0x19) - Power Management (1)
|
||||
*/
|
||||
#define WM8955_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */
|
||||
#define WM8955_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */
|
||||
#define WM8955_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */
|
||||
#define WM8955_VREF 0x0040 /* VREF */
|
||||
#define WM8955_VREF_MASK 0x0040 /* VREF */
|
||||
#define WM8955_VREF_SHIFT 6 /* VREF */
|
||||
#define WM8955_VREF_WIDTH 1 /* VREF */
|
||||
#define WM8955_DIGENB 0x0001 /* DIGENB */
|
||||
#define WM8955_DIGENB_MASK 0x0001 /* DIGENB */
|
||||
#define WM8955_DIGENB_SHIFT 0 /* DIGENB */
|
||||
#define WM8955_DIGENB_WIDTH 1 /* DIGENB */
|
||||
|
||||
/*
|
||||
* R26 (0x1A) - Power Management (2)
|
||||
*/
|
||||
#define WM8955_DACL 0x0100 /* DACL */
|
||||
#define WM8955_DACL_MASK 0x0100 /* DACL */
|
||||
#define WM8955_DACL_SHIFT 8 /* DACL */
|
||||
#define WM8955_DACL_WIDTH 1 /* DACL */
|
||||
#define WM8955_DACR 0x0080 /* DACR */
|
||||
#define WM8955_DACR_MASK 0x0080 /* DACR */
|
||||
#define WM8955_DACR_SHIFT 7 /* DACR */
|
||||
#define WM8955_DACR_WIDTH 1 /* DACR */
|
||||
#define WM8955_LOUT1 0x0040 /* LOUT1 */
|
||||
#define WM8955_LOUT1_MASK 0x0040 /* LOUT1 */
|
||||
#define WM8955_LOUT1_SHIFT 6 /* LOUT1 */
|
||||
#define WM8955_LOUT1_WIDTH 1 /* LOUT1 */
|
||||
#define WM8955_ROUT1 0x0020 /* ROUT1 */
|
||||
#define WM8955_ROUT1_MASK 0x0020 /* ROUT1 */
|
||||
#define WM8955_ROUT1_SHIFT 5 /* ROUT1 */
|
||||
#define WM8955_ROUT1_WIDTH 1 /* ROUT1 */
|
||||
#define WM8955_LOUT2 0x0010 /* LOUT2 */
|
||||
#define WM8955_LOUT2_MASK 0x0010 /* LOUT2 */
|
||||
#define WM8955_LOUT2_SHIFT 4 /* LOUT2 */
|
||||
#define WM8955_LOUT2_WIDTH 1 /* LOUT2 */
|
||||
#define WM8955_ROUT2 0x0008 /* ROUT2 */
|
||||
#define WM8955_ROUT2_MASK 0x0008 /* ROUT2 */
|
||||
#define WM8955_ROUT2_SHIFT 3 /* ROUT2 */
|
||||
#define WM8955_ROUT2_WIDTH 1 /* ROUT2 */
|
||||
#define WM8955_MONO 0x0004 /* MONO */
|
||||
#define WM8955_MONO_MASK 0x0004 /* MONO */
|
||||
#define WM8955_MONO_SHIFT 2 /* MONO */
|
||||
#define WM8955_MONO_WIDTH 1 /* MONO */
|
||||
#define WM8955_OUT3 0x0002 /* OUT3 */
|
||||
#define WM8955_OUT3_MASK 0x0002 /* OUT3 */
|
||||
#define WM8955_OUT3_SHIFT 1 /* OUT3 */
|
||||
#define WM8955_OUT3_WIDTH 1 /* OUT3 */
|
||||
|
||||
/*
|
||||
* R27 (0x1B) - Additional Control (3)
|
||||
*/
|
||||
#define WM8955_VROI 0x0040 /* VROI */
|
||||
#define WM8955_VROI_MASK 0x0040 /* VROI */
|
||||
#define WM8955_VROI_SHIFT 6 /* VROI */
|
||||
#define WM8955_VROI_WIDTH 1 /* VROI */
|
||||
|
||||
/*
|
||||
* R34 (0x22) - Left out Mix (1)
|
||||
*/
|
||||
#define WM8955_LD2LO 0x0100 /* LD2LO */
|
||||
#define WM8955_LD2LO_MASK 0x0100 /* LD2LO */
|
||||
#define WM8955_LD2LO_SHIFT 8 /* LD2LO */
|
||||
#define WM8955_LD2LO_WIDTH 1 /* LD2LO */
|
||||
#define WM8955_LI2LO 0x0080 /* LI2LO */
|
||||
#define WM8955_LI2LO_MASK 0x0080 /* LI2LO */
|
||||
#define WM8955_LI2LO_SHIFT 7 /* LI2LO */
|
||||
#define WM8955_LI2LO_WIDTH 1 /* LI2LO */
|
||||
#define WM8955_LI2LOVOL_MASK 0x0070 /* LI2LOVOL - [6:4] */
|
||||
#define WM8955_LI2LOVOL_SHIFT 4 /* LI2LOVOL - [6:4] */
|
||||
#define WM8955_LI2LOVOL_WIDTH 3 /* LI2LOVOL - [6:4] */
|
||||
|
||||
/*
|
||||
* R35 (0x23) - Left out Mix (2)
|
||||
*/
|
||||
#define WM8955_RD2LO 0x0100 /* RD2LO */
|
||||
#define WM8955_RD2LO_MASK 0x0100 /* RD2LO */
|
||||
#define WM8955_RD2LO_SHIFT 8 /* RD2LO */
|
||||
#define WM8955_RD2LO_WIDTH 1 /* RD2LO */
|
||||
#define WM8955_RI2LO 0x0080 /* RI2LO */
|
||||
#define WM8955_RI2LO_MASK 0x0080 /* RI2LO */
|
||||
#define WM8955_RI2LO_SHIFT 7 /* RI2LO */
|
||||
#define WM8955_RI2LO_WIDTH 1 /* RI2LO */
|
||||
#define WM8955_RI2LOVOL_MASK 0x0070 /* RI2LOVOL - [6:4] */
|
||||
#define WM8955_RI2LOVOL_SHIFT 4 /* RI2LOVOL - [6:4] */
|
||||
#define WM8955_RI2LOVOL_WIDTH 3 /* RI2LOVOL - [6:4] */
|
||||
|
||||
/*
|
||||
* R36 (0x24) - Right out Mix (1)
|
||||
*/
|
||||
#define WM8955_LD2RO 0x0100 /* LD2RO */
|
||||
#define WM8955_LD2RO_MASK 0x0100 /* LD2RO */
|
||||
#define WM8955_LD2RO_SHIFT 8 /* LD2RO */
|
||||
#define WM8955_LD2RO_WIDTH 1 /* LD2RO */
|
||||
#define WM8955_LI2RO 0x0080 /* LI2RO */
|
||||
#define WM8955_LI2RO_MASK 0x0080 /* LI2RO */
|
||||
#define WM8955_LI2RO_SHIFT 7 /* LI2RO */
|
||||
#define WM8955_LI2RO_WIDTH 1 /* LI2RO */
|
||||
#define WM8955_LI2ROVOL_MASK 0x0070 /* LI2ROVOL - [6:4] */
|
||||
#define WM8955_LI2ROVOL_SHIFT 4 /* LI2ROVOL - [6:4] */
|
||||
#define WM8955_LI2ROVOL_WIDTH 3 /* LI2ROVOL - [6:4] */
|
||||
|
||||
/*
|
||||
* R37 (0x25) - Right Out Mix (2)
|
||||
*/
|
||||
#define WM8955_RD2RO 0x0100 /* RD2RO */
|
||||
#define WM8955_RD2RO_MASK 0x0100 /* RD2RO */
|
||||
#define WM8955_RD2RO_SHIFT 8 /* RD2RO */
|
||||
#define WM8955_RD2RO_WIDTH 1 /* RD2RO */
|
||||
#define WM8955_RI2RO 0x0080 /* RI2RO */
|
||||
#define WM8955_RI2RO_MASK 0x0080 /* RI2RO */
|
||||
#define WM8955_RI2RO_SHIFT 7 /* RI2RO */
|
||||
#define WM8955_RI2RO_WIDTH 1 /* RI2RO */
|
||||
#define WM8955_RI2ROVOL_MASK 0x0070 /* RI2ROVOL - [6:4] */
|
||||
#define WM8955_RI2ROVOL_SHIFT 4 /* RI2ROVOL - [6:4] */
|
||||
#define WM8955_RI2ROVOL_WIDTH 3 /* RI2ROVOL - [6:4] */
|
||||
|
||||
/*
|
||||
* R38 (0x26) - Mono out Mix (1)
|
||||
*/
|
||||
#define WM8955_LD2MO 0x0100 /* LD2MO */
|
||||
#define WM8955_LD2MO_MASK 0x0100 /* LD2MO */
|
||||
#define WM8955_LD2MO_SHIFT 8 /* LD2MO */
|
||||
#define WM8955_LD2MO_WIDTH 1 /* LD2MO */
|
||||
#define WM8955_LI2MO 0x0080 /* LI2MO */
|
||||
#define WM8955_LI2MO_MASK 0x0080 /* LI2MO */
|
||||
#define WM8955_LI2MO_SHIFT 7 /* LI2MO */
|
||||
#define WM8955_LI2MO_WIDTH 1 /* LI2MO */
|
||||
#define WM8955_LI2MOVOL_MASK 0x0070 /* LI2MOVOL - [6:4] */
|
||||
#define WM8955_LI2MOVOL_SHIFT 4 /* LI2MOVOL - [6:4] */
|
||||
#define WM8955_LI2MOVOL_WIDTH 3 /* LI2MOVOL - [6:4] */
|
||||
#define WM8955_DMEN 0x0001 /* DMEN */
|
||||
#define WM8955_DMEN_MASK 0x0001 /* DMEN */
|
||||
#define WM8955_DMEN_SHIFT 0 /* DMEN */
|
||||
#define WM8955_DMEN_WIDTH 1 /* DMEN */
|
||||
|
||||
/*
|
||||
* R39 (0x27) - Mono out Mix (2)
|
||||
*/
|
||||
#define WM8955_RD2MO 0x0100 /* RD2MO */
|
||||
#define WM8955_RD2MO_MASK 0x0100 /* RD2MO */
|
||||
#define WM8955_RD2MO_SHIFT 8 /* RD2MO */
|
||||
#define WM8955_RD2MO_WIDTH 1 /* RD2MO */
|
||||
#define WM8955_RI2MO 0x0080 /* RI2MO */
|
||||
#define WM8955_RI2MO_MASK 0x0080 /* RI2MO */
|
||||
#define WM8955_RI2MO_SHIFT 7 /* RI2MO */
|
||||
#define WM8955_RI2MO_WIDTH 1 /* RI2MO */
|
||||
#define WM8955_RI2MOVOL_MASK 0x0070 /* RI2MOVOL - [6:4] */
|
||||
#define WM8955_RI2MOVOL_SHIFT 4 /* RI2MOVOL - [6:4] */
|
||||
#define WM8955_RI2MOVOL_WIDTH 3 /* RI2MOVOL - [6:4] */
|
||||
|
||||
/*
|
||||
* R40 (0x28) - LOUT2 volume
|
||||
*/
|
||||
#define WM8955_LO2VU 0x0100 /* LO2VU */
|
||||
#define WM8955_LO2VU_MASK 0x0100 /* LO2VU */
|
||||
#define WM8955_LO2VU_SHIFT 8 /* LO2VU */
|
||||
#define WM8955_LO2VU_WIDTH 1 /* LO2VU */
|
||||
#define WM8955_LO2ZC 0x0080 /* LO2ZC */
|
||||
#define WM8955_LO2ZC_MASK 0x0080 /* LO2ZC */
|
||||
#define WM8955_LO2ZC_SHIFT 7 /* LO2ZC */
|
||||
#define WM8955_LO2ZC_WIDTH 1 /* LO2ZC */
|
||||
#define WM8955_LOUT2VOL_MASK 0x007F /* LOUT2VOL - [6:0] */
|
||||
#define WM8955_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [6:0] */
|
||||
#define WM8955_LOUT2VOL_WIDTH 7 /* LOUT2VOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R41 (0x29) - ROUT2 volume
|
||||
*/
|
||||
#define WM8955_RO2VU 0x0100 /* RO2VU */
|
||||
#define WM8955_RO2VU_MASK 0x0100 /* RO2VU */
|
||||
#define WM8955_RO2VU_SHIFT 8 /* RO2VU */
|
||||
#define WM8955_RO2VU_WIDTH 1 /* RO2VU */
|
||||
#define WM8955_RO2ZC 0x0080 /* RO2ZC */
|
||||
#define WM8955_RO2ZC_MASK 0x0080 /* RO2ZC */
|
||||
#define WM8955_RO2ZC_SHIFT 7 /* RO2ZC */
|
||||
#define WM8955_RO2ZC_WIDTH 1 /* RO2ZC */
|
||||
#define WM8955_ROUT2VOL_MASK 0x007F /* ROUT2VOL - [6:0] */
|
||||
#define WM8955_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [6:0] */
|
||||
#define WM8955_ROUT2VOL_WIDTH 7 /* ROUT2VOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R42 (0x2A) - MONOOUT volume
|
||||
*/
|
||||
#define WM8955_MOZC 0x0080 /* MOZC */
|
||||
#define WM8955_MOZC_MASK 0x0080 /* MOZC */
|
||||
#define WM8955_MOZC_SHIFT 7 /* MOZC */
|
||||
#define WM8955_MOZC_WIDTH 1 /* MOZC */
|
||||
#define WM8955_MOUTVOL_MASK 0x007F /* MOUTVOL - [6:0] */
|
||||
#define WM8955_MOUTVOL_SHIFT 0 /* MOUTVOL - [6:0] */
|
||||
#define WM8955_MOUTVOL_WIDTH 7 /* MOUTVOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R43 (0x2B) - Clocking / PLL
|
||||
*/
|
||||
#define WM8955_MCLKSEL 0x0100 /* MCLKSEL */
|
||||
#define WM8955_MCLKSEL_MASK 0x0100 /* MCLKSEL */
|
||||
#define WM8955_MCLKSEL_SHIFT 8 /* MCLKSEL */
|
||||
#define WM8955_MCLKSEL_WIDTH 1 /* MCLKSEL */
|
||||
#define WM8955_PLLOUTDIV2 0x0020 /* PLLOUTDIV2 */
|
||||
#define WM8955_PLLOUTDIV2_MASK 0x0020 /* PLLOUTDIV2 */
|
||||
#define WM8955_PLLOUTDIV2_SHIFT 5 /* PLLOUTDIV2 */
|
||||
#define WM8955_PLLOUTDIV2_WIDTH 1 /* PLLOUTDIV2 */
|
||||
#define WM8955_PLL_RB 0x0010 /* PLL_RB */
|
||||
#define WM8955_PLL_RB_MASK 0x0010 /* PLL_RB */
|
||||
#define WM8955_PLL_RB_SHIFT 4 /* PLL_RB */
|
||||
#define WM8955_PLL_RB_WIDTH 1 /* PLL_RB */
|
||||
#define WM8955_PLLEN 0x0008 /* PLLEN */
|
||||
#define WM8955_PLLEN_MASK 0x0008 /* PLLEN */
|
||||
#define WM8955_PLLEN_SHIFT 3 /* PLLEN */
|
||||
#define WM8955_PLLEN_WIDTH 1 /* PLLEN */
|
||||
|
||||
/*
|
||||
* R44 (0x2C) - PLL Control 1
|
||||
*/
|
||||
#define WM8955_N_MASK 0x01E0 /* N - [8:5] */
|
||||
#define WM8955_N_SHIFT 5 /* N - [8:5] */
|
||||
#define WM8955_N_WIDTH 4 /* N - [8:5] */
|
||||
#define WM8955_K_21_18_MASK 0x000F /* K(21:18) - [3:0] */
|
||||
#define WM8955_K_21_18_SHIFT 0 /* K(21:18) - [3:0] */
|
||||
#define WM8955_K_21_18_WIDTH 4 /* K(21:18) - [3:0] */
|
||||
|
||||
/*
|
||||
* R45 (0x2D) - PLL Control 2
|
||||
*/
|
||||
#define WM8955_K_17_9_MASK 0x01FF /* K(17:9) - [8:0] */
|
||||
#define WM8955_K_17_9_SHIFT 0 /* K(17:9) - [8:0] */
|
||||
#define WM8955_K_17_9_WIDTH 9 /* K(17:9) - [8:0] */
|
||||
|
||||
/*
|
||||
* R46 (0x2E) - PLL Control 3
|
||||
*/
|
||||
#define WM8955_K_8_0_MASK 0x01FF /* K(8:0) - [8:0] */
|
||||
#define WM8955_K_8_0_SHIFT 0 /* K(8:0) - [8:0] */
|
||||
#define WM8955_K_8_0_WIDTH 9 /* K(8:0) - [8:0] */
|
||||
|
||||
/*
|
||||
* R59 (0x3B) - PLL Control 4
|
||||
*/
|
||||
#define WM8955_KEN 0x0080 /* KEN */
|
||||
#define WM8955_KEN_MASK 0x0080 /* KEN */
|
||||
#define WM8955_KEN_SHIFT 7 /* KEN */
|
||||
#define WM8955_KEN_WIDTH 1 /* KEN */
|
||||
|
||||
#endif
|
|
@ -1022,6 +1022,9 @@ static int wm8961_resume(struct platform_device *pdev)
|
|||
int i;
|
||||
|
||||
for (i = 0; i < codec->reg_cache_size; i++) {
|
||||
if (reg_cache[i] == wm8961_reg_defaults[i])
|
||||
continue;
|
||||
|
||||
if (i == WM8961_SOFTWARE_RESET)
|
||||
continue;
|
||||
|
||||
|
|
|
@ -170,6 +170,10 @@ SOC_ENUM("Aux Mode", wm8974_auxmode),
|
|||
|
||||
SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0),
|
||||
SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
|
||||
|
||||
/* DAC / ADC oversampling */
|
||||
SOC_SINGLE("DAC 128x Oversampling Switch", WM8974_DAC, 8, 1, 0),
|
||||
SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0),
|
||||
};
|
||||
|
||||
/* Speaker Output Mixer */
|
||||
|
@ -381,14 +385,6 @@ static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
reg = snd_soc_read(codec, WM8974_CLOCK) & 0x11f;
|
||||
snd_soc_write(codec, WM8974_CLOCK, reg | div);
|
||||
break;
|
||||
case WM8974_ADCCLK:
|
||||
reg = snd_soc_read(codec, WM8974_ADC) & 0x1f7;
|
||||
snd_soc_write(codec, WM8974_ADC, reg | div);
|
||||
break;
|
||||
case WM8974_DACCLK:
|
||||
reg = snd_soc_read(codec, WM8974_DAC) & 0x1f7;
|
||||
snd_soc_write(codec, WM8974_DAC, reg | div);
|
||||
break;
|
||||
case WM8974_BCLKDIV:
|
||||
reg = snd_soc_read(codec, WM8974_CLOCK) & 0x1e3;
|
||||
snd_soc_write(codec, WM8974_CLOCK, reg | div);
|
||||
|
|
|
@ -57,17 +57,7 @@
|
|||
/* Clock divider Id's */
|
||||
#define WM8974_OPCLKDIV 0
|
||||
#define WM8974_MCLKDIV 1
|
||||
#define WM8974_ADCCLK 2
|
||||
#define WM8974_DACCLK 3
|
||||
#define WM8974_BCLKDIV 4
|
||||
|
||||
/* DAC clock dividers */
|
||||
#define WM8974_DACCLK_F2 (1 << 3)
|
||||
#define WM8974_DACCLK_F4 (0 << 3)
|
||||
|
||||
/* ADC clock dividers */
|
||||
#define WM8974_ADCCLK_F2 (1 << 3)
|
||||
#define WM8974_ADCCLK_F4 (0 << 3)
|
||||
#define WM8974_BCLKDIV 2
|
||||
|
||||
/* PLL Out dividers */
|
||||
#define WM8974_OPCLKDIV_1 (0 << 4)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* wm8978.h -- codec driver for WM8978
|
||||
*
|
||||
* Copyright 2009 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
*
|
||||
* 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 __WM8978_H__
|
||||
#define __WM8978_H__
|
||||
|
||||
/*
|
||||
* Register values.
|
||||
*/
|
||||
#define WM8978_RESET 0x00
|
||||
#define WM8978_POWER_MANAGEMENT_1 0x01
|
||||
#define WM8978_POWER_MANAGEMENT_2 0x02
|
||||
#define WM8978_POWER_MANAGEMENT_3 0x03
|
||||
#define WM8978_AUDIO_INTERFACE 0x04
|
||||
#define WM8978_COMPANDING_CONTROL 0x05
|
||||
#define WM8978_CLOCKING 0x06
|
||||
#define WM8978_ADDITIONAL_CONTROL 0x07
|
||||
#define WM8978_GPIO_CONTROL 0x08
|
||||
#define WM8978_JACK_DETECT_CONTROL_1 0x09
|
||||
#define WM8978_DAC_CONTROL 0x0A
|
||||
#define WM8978_LEFT_DAC_DIGITAL_VOLUME 0x0B
|
||||
#define WM8978_RIGHT_DAC_DIGITAL_VOLUME 0x0C
|
||||
#define WM8978_JACK_DETECT_CONTROL_2 0x0D
|
||||
#define WM8978_ADC_CONTROL 0x0E
|
||||
#define WM8978_LEFT_ADC_DIGITAL_VOLUME 0x0F
|
||||
#define WM8978_RIGHT_ADC_DIGITAL_VOLUME 0x10
|
||||
#define WM8978_EQ1 0x12
|
||||
#define WM8978_EQ2 0x13
|
||||
#define WM8978_EQ3 0x14
|
||||
#define WM8978_EQ4 0x15
|
||||
#define WM8978_EQ5 0x16
|
||||
#define WM8978_DAC_LIMITER_1 0x18
|
||||
#define WM8978_DAC_LIMITER_2 0x19
|
||||
#define WM8978_NOTCH_FILTER_1 0x1b
|
||||
#define WM8978_NOTCH_FILTER_2 0x1c
|
||||
#define WM8978_NOTCH_FILTER_3 0x1d
|
||||
#define WM8978_NOTCH_FILTER_4 0x1e
|
||||
#define WM8978_ALC_CONTROL_1 0x20
|
||||
#define WM8978_ALC_CONTROL_2 0x21
|
||||
#define WM8978_ALC_CONTROL_3 0x22
|
||||
#define WM8978_NOISE_GATE 0x23
|
||||
#define WM8978_PLL_N 0x24
|
||||
#define WM8978_PLL_K1 0x25
|
||||
#define WM8978_PLL_K2 0x26
|
||||
#define WM8978_PLL_K3 0x27
|
||||
#define WM8978_3D_CONTROL 0x29
|
||||
#define WM8978_BEEP_CONTROL 0x2b
|
||||
#define WM8978_INPUT_CONTROL 0x2c
|
||||
#define WM8978_LEFT_INP_PGA_CONTROL 0x2d
|
||||
#define WM8978_RIGHT_INP_PGA_CONTROL 0x2e
|
||||
#define WM8978_LEFT_ADC_BOOST_CONTROL 0x2f
|
||||
#define WM8978_RIGHT_ADC_BOOST_CONTROL 0x30
|
||||
#define WM8978_OUTPUT_CONTROL 0x31
|
||||
#define WM8978_LEFT_MIXER_CONTROL 0x32
|
||||
#define WM8978_RIGHT_MIXER_CONTROL 0x33
|
||||
#define WM8978_LOUT1_HP_CONTROL 0x34
|
||||
#define WM8978_ROUT1_HP_CONTROL 0x35
|
||||
#define WM8978_LOUT2_SPK_CONTROL 0x36
|
||||
#define WM8978_ROUT2_SPK_CONTROL 0x37
|
||||
#define WM8978_OUT3_MIXER_CONTROL 0x38
|
||||
#define WM8978_OUT4_MIXER_CONTROL 0x39
|
||||
|
||||
#define WM8978_CACHEREGNUM 58
|
||||
|
||||
/* Clock divider Id's */
|
||||
enum wm8978_clk_id {
|
||||
WM8978_OPCLKRATE,
|
||||
WM8978_BCLKDIV,
|
||||
};
|
||||
|
||||
enum wm8978_sysclk_src {
|
||||
WM8978_PLL,
|
||||
WM8978_MCLK
|
||||
};
|
||||
|
||||
extern struct snd_soc_dai wm8978_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8978;
|
||||
|
||||
#endif /* __WM8978_H__ */
|
|
@ -1319,10 +1319,6 @@ static int wm8990_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;
|
||||
|
||||
/* we only need to suspend if we are a valid card */
|
||||
if (!codec->card)
|
||||
return 0;
|
||||
|
||||
wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1335,10 +1331,6 @@ static int wm8990_resume(struct platform_device *pdev)
|
|||
u8 data[2];
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
/* we only need to resume if we are a valid card */
|
||||
if (!codec->card)
|
||||
return 0;
|
||||
|
||||
/* Sync reg_cache with the hardware */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8990_reg); i++) {
|
||||
if (i + 1 == WM8990_RESET)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* wm8993.c -- WM8993 ALSA SoC audio driver
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
* Copyright 2009, 2010 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
|
@ -16,6 +16,7 @@
|
|||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
|
@ -29,6 +30,16 @@
|
|||
#include "wm8993.h"
|
||||
#include "wm_hubs.h"
|
||||
|
||||
#define WM8993_NUM_SUPPLIES 6
|
||||
static const char *wm8993_supply_names[WM8993_NUM_SUPPLIES] = {
|
||||
"DCVDD",
|
||||
"DBVDD",
|
||||
"AVDD1",
|
||||
"AVDD2",
|
||||
"CPVDD",
|
||||
"SPKVDD",
|
||||
};
|
||||
|
||||
static u16 wm8993_reg_defaults[WM8993_REGISTER_COUNT] = {
|
||||
0x8993, /* R0 - Software Reset */
|
||||
0x0000, /* R1 - Power Management (1) */
|
||||
|
@ -213,7 +224,9 @@ static struct {
|
|||
};
|
||||
|
||||
struct wm8993_priv {
|
||||
struct wm_hubs_data hubs_data;
|
||||
u16 reg_cache[WM8993_REGISTER_COUNT];
|
||||
struct regulator_bulk_data supplies[WM8993_NUM_SUPPLIES];
|
||||
struct wm8993_platform_data pdata;
|
||||
struct snd_soc_codec codec;
|
||||
int master;
|
||||
|
@ -227,36 +240,9 @@ struct wm8993_priv {
|
|||
int class_w_users;
|
||||
unsigned int fll_fref;
|
||||
unsigned int fll_fout;
|
||||
int fll_src;
|
||||
};
|
||||
|
||||
static unsigned int wm8993_read_hw(struct snd_soc_codec *codec, u8 reg)
|
||||
{
|
||||
struct i2c_msg xfer[2];
|
||||
u16 data;
|
||||
int ret;
|
||||
struct i2c_client *i2c = codec->control_data;
|
||||
|
||||
/* Write register */
|
||||
xfer[0].addr = i2c->addr;
|
||||
xfer[0].flags = 0;
|
||||
xfer[0].len = 1;
|
||||
xfer[0].buf = ®
|
||||
|
||||
/* Read data */
|
||||
xfer[1].addr = i2c->addr;
|
||||
xfer[1].flags = I2C_M_RD;
|
||||
xfer[1].len = 2;
|
||||
xfer[1].buf = (u8 *)&data;
|
||||
|
||||
ret = i2c_transfer(i2c->adapter, xfer, 2);
|
||||
if (ret != 2) {
|
||||
dev_err(codec->dev, "Failed to read 0x%x: %d\n", reg, ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (data >> 8) | ((data & 0xff) << 8);
|
||||
}
|
||||
|
||||
static int wm8993_volatile(unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
|
@ -271,48 +257,6 @@ static int wm8993_volatile(unsigned int reg)
|
|||
}
|
||||
}
|
||||
|
||||
static unsigned int wm8993_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
|
||||
BUG_ON(reg > WM8993_MAX_REGISTER);
|
||||
|
||||
if (wm8993_volatile(reg))
|
||||
return wm8993_read_hw(codec, reg);
|
||||
else
|
||||
return reg_cache[reg];
|
||||
}
|
||||
|
||||
static int wm8993_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
u8 data[3];
|
||||
int ret;
|
||||
|
||||
BUG_ON(reg > WM8993_MAX_REGISTER);
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8993 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = reg;
|
||||
data[1] = value >> 8;
|
||||
data[2] = value & 0x00ff;
|
||||
|
||||
if (!wm8993_volatile(reg))
|
||||
reg_cache[reg] = value;
|
||||
|
||||
ret = codec->hw_write(codec->control_data, data, 3);
|
||||
|
||||
if (ret == 3)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
struct _fll_div {
|
||||
u16 fll_fratio;
|
||||
u16 fll_outdiv;
|
||||
|
@ -441,9 +385,9 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
|
|||
wm8993->fll_fref = 0;
|
||||
wm8993->fll_fout = 0;
|
||||
|
||||
reg1 = wm8993_read(codec, WM8993_FLL_CONTROL_1);
|
||||
reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1);
|
||||
reg1 &= ~WM8993_FLL_ENA;
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1);
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -452,7 +396,7 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
|
|||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
reg5 = wm8993_read(codec, WM8993_FLL_CONTROL_5);
|
||||
reg5 = snd_soc_read(codec, WM8993_FLL_CONTROL_5);
|
||||
reg5 &= ~WM8993_FLL_CLK_SRC_MASK;
|
||||
|
||||
switch (fll_id) {
|
||||
|
@ -474,38 +418,39 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
|
|||
|
||||
/* Any FLL configuration change requires that the FLL be
|
||||
* disabled first. */
|
||||
reg1 = wm8993_read(codec, WM8993_FLL_CONTROL_1);
|
||||
reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1);
|
||||
reg1 &= ~WM8993_FLL_ENA;
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1);
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
|
||||
|
||||
/* Apply the configuration */
|
||||
if (fll_div.k)
|
||||
reg1 |= WM8993_FLL_FRAC_MASK;
|
||||
else
|
||||
reg1 &= ~WM8993_FLL_FRAC_MASK;
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1);
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
|
||||
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_2,
|
||||
(fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) |
|
||||
(fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT));
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_3, fll_div.k);
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_2,
|
||||
(fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) |
|
||||
(fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT));
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_3, fll_div.k);
|
||||
|
||||
reg4 = wm8993_read(codec, WM8993_FLL_CONTROL_4);
|
||||
reg4 = snd_soc_read(codec, WM8993_FLL_CONTROL_4);
|
||||
reg4 &= ~WM8993_FLL_N_MASK;
|
||||
reg4 |= fll_div.n << WM8993_FLL_N_SHIFT;
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_4, reg4);
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_4, reg4);
|
||||
|
||||
reg5 &= ~WM8993_FLL_CLK_REF_DIV_MASK;
|
||||
reg5 |= fll_div.fll_clk_ref_div << WM8993_FLL_CLK_REF_DIV_SHIFT;
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_5, reg5);
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_5, reg5);
|
||||
|
||||
/* Enable the FLL */
|
||||
wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA);
|
||||
snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA);
|
||||
|
||||
dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
|
||||
|
||||
wm8993->fll_fref = Fref;
|
||||
wm8993->fll_fout = Fout;
|
||||
wm8993->fll_src = source;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -520,7 +465,7 @@ static int configure_clock(struct snd_soc_codec *codec)
|
|||
case WM8993_SYSCLK_MCLK:
|
||||
dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8993->mclk_rate);
|
||||
|
||||
reg = wm8993_read(codec, WM8993_CLOCKING_2);
|
||||
reg = snd_soc_read(codec, WM8993_CLOCKING_2);
|
||||
reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC);
|
||||
if (wm8993->mclk_rate > 13500000) {
|
||||
reg |= WM8993_MCLK_DIV;
|
||||
|
@ -529,14 +474,14 @@ static int configure_clock(struct snd_soc_codec *codec)
|
|||
reg &= ~WM8993_MCLK_DIV;
|
||||
wm8993->sysclk_rate = wm8993->mclk_rate;
|
||||
}
|
||||
wm8993_write(codec, WM8993_CLOCKING_2, reg);
|
||||
snd_soc_write(codec, WM8993_CLOCKING_2, reg);
|
||||
break;
|
||||
|
||||
case WM8993_SYSCLK_FLL:
|
||||
dev_dbg(codec->dev, "Using %dHz FLL clock\n",
|
||||
wm8993->fll_fout);
|
||||
|
||||
reg = wm8993_read(codec, WM8993_CLOCKING_2);
|
||||
reg = snd_soc_read(codec, WM8993_CLOCKING_2);
|
||||
reg |= WM8993_SYSCLK_SRC;
|
||||
if (wm8993->fll_fout > 13500000) {
|
||||
reg |= WM8993_MCLK_DIV;
|
||||
|
@ -545,7 +490,7 @@ static int configure_clock(struct snd_soc_codec *codec)
|
|||
reg &= ~WM8993_MCLK_DIV;
|
||||
wm8993->sysclk_rate = wm8993->fll_fout;
|
||||
}
|
||||
wm8993_write(codec, WM8993_CLOCKING_2, reg);
|
||||
snd_soc_write(codec, WM8993_CLOCKING_2, reg);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -978,10 +923,33 @@ static const struct snd_soc_dapm_route routes[] = {
|
|||
{ "Right Headphone Mux", "DAC", "DACR" },
|
||||
};
|
||||
|
||||
static void wm8993_cache_restore(struct snd_soc_codec *codec)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
int i;
|
||||
|
||||
if (!codec->cache_sync)
|
||||
return;
|
||||
|
||||
/* Reenable hardware writes */
|
||||
codec->cache_only = 0;
|
||||
|
||||
/* Restore the register settings */
|
||||
for (i = 1; i < WM8993_MAX_REGISTER; i++) {
|
||||
if (cache[i] == wm8993_reg_defaults[i])
|
||||
continue;
|
||||
snd_soc_write(codec, i, cache[i]);
|
||||
}
|
||||
|
||||
/* We're in sync again */
|
||||
codec->cache_sync = 0;
|
||||
}
|
||||
|
||||
static int wm8993_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct wm8993_priv *wm8993 = codec->private_data;
|
||||
int ret;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
|
@ -995,6 +963,18 @@ static int wm8993_set_bias_level(struct snd_soc_codec *codec,
|
|||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies),
|
||||
wm8993->supplies);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
wm8993_cache_restore(codec);
|
||||
|
||||
/* Tune DC servo configuration */
|
||||
snd_soc_write(codec, 0x44, 3);
|
||||
snd_soc_write(codec, 0x56, 3);
|
||||
snd_soc_write(codec, 0x44, 0);
|
||||
|
||||
/* Bring up VMID with fast soft start */
|
||||
snd_soc_update_bits(codec, WM8993_ANTIPOP2,
|
||||
WM8993_STARTUP_BIAS_ENA |
|
||||
|
@ -1042,6 +1022,18 @@ static int wm8993_set_bias_level(struct snd_soc_codec *codec,
|
|||
snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
|
||||
WM8993_VMID_SEL_MASK | WM8993_BIAS_ENA,
|
||||
0);
|
||||
|
||||
#ifdef CONFIG_REGULATOR
|
||||
/* Post 2.6.34 we will be able to get a callback when
|
||||
* the regulators are disabled which we can use but
|
||||
* for now just assume that the power will be cut if
|
||||
* the regulator API is in use.
|
||||
*/
|
||||
codec->cache_sync = 1;
|
||||
#endif
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies),
|
||||
wm8993->supplies);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1075,8 +1067,8 @@ static int wm8993_set_dai_fmt(struct snd_soc_dai *dai,
|
|||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct wm8993_priv *wm8993 = codec->private_data;
|
||||
unsigned int aif1 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_1);
|
||||
unsigned int aif4 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_4);
|
||||
unsigned int aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1);
|
||||
unsigned int aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4);
|
||||
|
||||
aif1 &= ~(WM8993_BCLK_DIR | WM8993_AIF_BCLK_INV |
|
||||
WM8993_AIF_LRCLK_INV | WM8993_AIF_FMT_MASK);
|
||||
|
@ -1159,8 +1151,8 @@ static int wm8993_set_dai_fmt(struct snd_soc_dai *dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8993_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
|
||||
wm8993_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
|
||||
snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
|
||||
snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1174,16 +1166,16 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream,
|
|||
int ret, i, best, best_val, cur_val;
|
||||
unsigned int clocking1, clocking3, aif1, aif4;
|
||||
|
||||
clocking1 = wm8993_read(codec, WM8993_CLOCKING_1);
|
||||
clocking1 = snd_soc_read(codec, WM8993_CLOCKING_1);
|
||||
clocking1 &= ~WM8993_BCLK_DIV_MASK;
|
||||
|
||||
clocking3 = wm8993_read(codec, WM8993_CLOCKING_3);
|
||||
clocking3 = snd_soc_read(codec, WM8993_CLOCKING_3);
|
||||
clocking3 &= ~(WM8993_CLK_SYS_RATE_MASK | WM8993_SAMPLE_RATE_MASK);
|
||||
|
||||
aif1 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_1);
|
||||
aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1);
|
||||
aif1 &= ~WM8993_AIF_WL_MASK;
|
||||
|
||||
aif4 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_4);
|
||||
aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4);
|
||||
aif4 &= ~WM8993_LRCLK_RATE_MASK;
|
||||
|
||||
/* What BCLK do we need? */
|
||||
|
@ -1276,14 +1268,14 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream,
|
|||
dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8993->bclk / wm8993->fs);
|
||||
aif4 |= wm8993->bclk / wm8993->fs;
|
||||
|
||||
wm8993_write(codec, WM8993_CLOCKING_1, clocking1);
|
||||
wm8993_write(codec, WM8993_CLOCKING_3, clocking3);
|
||||
wm8993_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
|
||||
wm8993_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
|
||||
snd_soc_write(codec, WM8993_CLOCKING_1, clocking1);
|
||||
snd_soc_write(codec, WM8993_CLOCKING_3, clocking3);
|
||||
snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
|
||||
snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
|
||||
|
||||
/* ReTune Mobile? */
|
||||
if (wm8993->pdata.num_retune_configs) {
|
||||
u16 eq1 = wm8993_read(codec, WM8993_EQ1);
|
||||
u16 eq1 = snd_soc_read(codec, WM8993_EQ1);
|
||||
struct wm8993_retune_mobile_setting *s;
|
||||
|
||||
best = 0;
|
||||
|
@ -1306,7 +1298,7 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream,
|
|||
snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, 0);
|
||||
|
||||
for (i = 1; i < ARRAY_SIZE(s->config); i++)
|
||||
wm8993_write(codec, WM8993_EQ1 + i, s->config[i]);
|
||||
snd_soc_write(codec, WM8993_EQ1 + i, s->config[i]);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, eq1);
|
||||
}
|
||||
|
@ -1319,14 +1311,14 @@ static int wm8993_digital_mute(struct snd_soc_dai *codec_dai, int mute)
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
unsigned int reg;
|
||||
|
||||
reg = wm8993_read(codec, WM8993_DAC_CTRL);
|
||||
reg = snd_soc_read(codec, WM8993_DAC_CTRL);
|
||||
|
||||
if (mute)
|
||||
reg |= WM8993_DAC_MUTE;
|
||||
else
|
||||
reg &= ~WM8993_DAC_MUTE;
|
||||
|
||||
wm8993_write(codec, WM8993_DAC_CTRL, reg);
|
||||
snd_soc_write(codec, WM8993_DAC_CTRL, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1480,9 +1472,66 @@ static int wm8993_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8993_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;
|
||||
struct wm8993_priv *wm8993 = codec->private_data;
|
||||
int fll_fout = wm8993->fll_fout;
|
||||
int fll_fref = wm8993->fll_fref;
|
||||
int ret;
|
||||
|
||||
/* Stop the FLL in an orderly fashion */
|
||||
ret = wm8993_set_fll(codec->dai, 0, 0, 0, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "Failed to stop FLL\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
wm8993->fll_fout = fll_fout;
|
||||
wm8993->fll_fref = fll_fref;
|
||||
|
||||
wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8993_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct wm8993_priv *wm8993 = codec->private_data;
|
||||
int ret;
|
||||
|
||||
wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Restart the FLL? */
|
||||
if (wm8993->fll_fout) {
|
||||
int fll_fout = wm8993->fll_fout;
|
||||
int fll_fref = wm8993->fll_fref;
|
||||
|
||||
wm8993->fll_fref = 0;
|
||||
wm8993->fll_fout = 0;
|
||||
|
||||
ret = wm8993_set_fll(codec->dai, 0, wm8993->fll_src,
|
||||
fll_fref, fll_fout);
|
||||
if (ret != 0)
|
||||
dev_err(codec->dev, "Failed to restart FLL\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define wm8993_suspend NULL
|
||||
#define wm8993_resume NULL
|
||||
#endif
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8993 = {
|
||||
.probe = wm8993_probe,
|
||||
.remove = wm8993_remove,
|
||||
.suspend = wm8993_suspend,
|
||||
.resume = wm8993_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8993);
|
||||
|
||||
|
@ -1493,6 +1542,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c,
|
|||
struct snd_soc_codec *codec;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (wm8993_codec) {
|
||||
dev_err(&i2c->dev, "A WM8993 is already registered\n");
|
||||
|
@ -1513,9 +1563,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c,
|
|||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->name = "WM8993";
|
||||
codec->read = wm8993_read;
|
||||
codec->write = wm8993_write;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
codec->volatile_register = wm8993_volatile;
|
||||
codec->reg_cache = wm8993->reg_cache;
|
||||
codec->reg_cache_size = ARRAY_SIZE(wm8993->reg_cache);
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
|
@ -1524,25 +1572,53 @@ static int wm8993_i2c_probe(struct i2c_client *i2c,
|
|||
codec->num_dai = 1;
|
||||
codec->private_data = wm8993;
|
||||
|
||||
wm8993->hubs_data.hp_startup_mode = 1;
|
||||
wm8993->hubs_data.dcs_codes = -2;
|
||||
|
||||
memcpy(wm8993->reg_cache, wm8993_reg_defaults,
|
||||
sizeof(wm8993->reg_cache));
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(i2c, wm8993);
|
||||
codec->control_data = i2c;
|
||||
wm8993_codec = codec;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
val = wm8993_read_hw(codec, WM8993_SOFTWARE_RESET);
|
||||
if (val != wm8993_reg_defaults[WM8993_SOFTWARE_RESET]) {
|
||||
dev_err(codec->dev, "Invalid ID register value %x\n", val);
|
||||
ret = -EINVAL;
|
||||
for (i = 0; i < ARRAY_SIZE(wm8993->supplies); i++)
|
||||
wm8993->supplies[i].supply = wm8993_supply_names[i];
|
||||
|
||||
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8993->supplies),
|
||||
wm8993->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = wm8993_write(codec, WM8993_SOFTWARE_RESET, 0xffff);
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies),
|
||||
wm8993->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
|
||||
goto err_get;
|
||||
}
|
||||
|
||||
val = snd_soc_read(codec, WM8993_SOFTWARE_RESET);
|
||||
if (val != wm8993_reg_defaults[WM8993_SOFTWARE_RESET]) {
|
||||
dev_err(codec->dev, "Invalid ID register value %x\n", val);
|
||||
ret = -EINVAL;
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
ret = snd_soc_write(codec, WM8993_SOFTWARE_RESET, 0xffff);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
goto err_enable;
|
||||
|
||||
codec->cache_only = 1;
|
||||
|
||||
/* By default we're using the output mixers */
|
||||
wm8993->class_w_users = 2;
|
||||
|
@ -1572,7 +1648,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
ret = wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
goto err_enable;
|
||||
|
||||
wm8993_dai.dev = codec->dev;
|
||||
|
||||
|
@ -1586,6 +1662,10 @@ static int wm8993_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
err_bias:
|
||||
wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
err_enable:
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
|
||||
err_get:
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
|
||||
err:
|
||||
wm8993_codec = NULL;
|
||||
kfree(wm8993);
|
||||
|
@ -1600,6 +1680,7 @@ static int wm8993_i2c_remove(struct i2c_client *client)
|
|||
snd_soc_unregister_dai(&wm8993_dai);
|
||||
|
||||
wm8993_set_bias_level(&wm8993->codec, SND_SOC_BIAS_OFF);
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
|
||||
kfree(wm8993);
|
||||
|
||||
return 0;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* wm8994.h -- WM8994 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 _WM8994_H
|
||||
#define _WM8994_H
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8994;
|
||||
extern struct snd_soc_dai wm8994_dai[];
|
||||
|
||||
/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */
|
||||
#define WM8994_SYSCLK_MCLK1 1
|
||||
#define WM8994_SYSCLK_MCLK2 2
|
||||
#define WM8994_SYSCLK_FLL1 3
|
||||
#define WM8994_SYSCLK_FLL2 4
|
||||
|
||||
#define WM8994_FLL1 1
|
||||
#define WM8994_FLL2 2
|
||||
|
||||
#endif
|
|
@ -23,13 +23,12 @@
|
|||
#include <sound/ac97_codec.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include "wm9713.h"
|
||||
|
||||
#define WM9713_VERSION "0.15"
|
||||
|
||||
struct wm9713_priv {
|
||||
u32 pll_in; /* PLL input frequency */
|
||||
};
|
||||
|
@ -115,15 +114,27 @@ SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18
|
|||
SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
|
||||
};
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(main_tlv, -3450, 150, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(misc_tlv, -1500, 300, 0);
|
||||
static unsigned int mic_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(2),
|
||||
0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0),
|
||||
3, 3, TLV_DB_SCALE_ITEM(3000, 0, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = {
|
||||
SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
|
||||
SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, out_tlv),
|
||||
SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1),
|
||||
SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
|
||||
SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1,
|
||||
out_tlv),
|
||||
SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1),
|
||||
SOC_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1),
|
||||
SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1),
|
||||
SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
|
||||
SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
|
||||
SOC_DOUBLE_TLV("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1, main_tlv),
|
||||
SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1, main_tlv),
|
||||
SOC_SINGLE_TLV("Mic 1 Volume", AC97_MIC, 8, 31, 1, main_tlv),
|
||||
SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv),
|
||||
SOC_SINGLE_TLV("Mic 1 Preamp Volume", AC97_3D_CONTROL, 10, 3, 0, mic_tlv),
|
||||
SOC_SINGLE_TLV("Mic 2 Preamp Volume", AC97_3D_CONTROL, 12, 3, 0, mic_tlv),
|
||||
|
||||
SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
|
||||
SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
|
||||
|
@ -133,7 +144,7 @@ SOC_ENUM("Capture Volume Steps", wm9713_enum[5]),
|
|||
SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0),
|
||||
SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
|
||||
|
||||
SOC_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1),
|
||||
SOC_SINGLE_TLV("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1, misc_tlv),
|
||||
SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
|
||||
SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
|
||||
|
||||
|
@ -154,28 +165,43 @@ SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
|
|||
|
||||
SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
|
||||
SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0),
|
||||
SOC_SINGLE("Out4 Playback Volume", AC97_MASTER_MONO, 8, 63, 1),
|
||||
SOC_SINGLE_TLV("Out4 Playback Volume", AC97_MASTER_MONO, 8, 31, 1, out_tlv),
|
||||
|
||||
SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1),
|
||||
SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0),
|
||||
SOC_SINGLE("Out3 Playback Volume", AC97_MASTER_MONO, 0, 63, 1),
|
||||
SOC_SINGLE_TLV("Out3 Playback Volume", AC97_MASTER_MONO, 0, 31, 1, out_tlv),
|
||||
|
||||
SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1),
|
||||
SOC_SINGLE_TLV("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1, main_tlv),
|
||||
SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1),
|
||||
SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
|
||||
SOC_SINGLE("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1),
|
||||
SOC_SINGLE_TLV("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1, out_tlv),
|
||||
|
||||
SOC_SINGLE("Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1),
|
||||
SOC_SINGLE("Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1),
|
||||
SOC_SINGLE("Beep Playback Mono Volume", AC97_AUX, 4, 7, 1),
|
||||
SOC_SINGLE_TLV("Headphone Mixer Beep Playback Volume", AC97_AUX, 12, 7, 1,
|
||||
misc_tlv),
|
||||
SOC_SINGLE_TLV("Speaker Mixer Beep Playback Volume", AC97_AUX, 8, 7, 1,
|
||||
misc_tlv),
|
||||
SOC_SINGLE_TLV("Mono Mixer Beep Playback Volume", AC97_AUX, 4, 7, 1, misc_tlv),
|
||||
|
||||
SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1),
|
||||
SOC_SINGLE_TLV("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1,
|
||||
misc_tlv),
|
||||
SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1),
|
||||
SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1),
|
||||
|
||||
SOC_SINGLE_TLV("Headphone Mixer Aux Playback Volume", AC97_REC_SEL, 12, 7, 1,
|
||||
misc_tlv),
|
||||
|
||||
SOC_SINGLE_TLV("Speaker Mixer Voice Playback Volume", AC97_PCM, 8, 7, 1,
|
||||
misc_tlv),
|
||||
SOC_SINGLE_TLV("Speaker Mixer Aux Playback Volume", AC97_REC_SEL, 8, 7, 1,
|
||||
misc_tlv),
|
||||
|
||||
SOC_SINGLE_TLV("Mono Mixer Voice Playback Volume", AC97_PCM, 4, 7, 1,
|
||||
misc_tlv),
|
||||
SOC_SINGLE_TLV("Mono Mixer Aux Playback Volume", AC97_REC_SEL, 4, 7, 1,
|
||||
misc_tlv),
|
||||
|
||||
SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1),
|
||||
SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1),
|
||||
SOC_SINGLE("Aux Playback Mono Volume", AC97_REC_SEL, 4, 7, 1),
|
||||
|
||||
SOC_ENUM("Bass Control", wm9713_enum[16]),
|
||||
SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
|
||||
|
@ -1186,8 +1212,6 @@ static int wm9713_soc_probe(struct platform_device *pdev)
|
|||
struct snd_soc_codec *codec;
|
||||
int ret = 0, reg;
|
||||
|
||||
printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION);
|
||||
|
||||
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec),
|
||||
GFP_KERNEL);
|
||||
if (socdev->card->codec == NULL)
|
||||
|
|
|
@ -68,24 +68,77 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec)
|
|||
int count = 0;
|
||||
|
||||
dev_dbg(codec->dev, "Waiting for DC servo...\n");
|
||||
|
||||
do {
|
||||
count++;
|
||||
msleep(1);
|
||||
reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_0);
|
||||
dev_dbg(codec->dev, "DC servo status: %x\n", reg);
|
||||
} while ((reg & WM8993_DCS_CAL_COMPLETE_MASK)
|
||||
!= WM8993_DCS_CAL_COMPLETE_MASK && count < 1000);
|
||||
dev_dbg(codec->dev, "DC servo: %x\n", reg);
|
||||
} while (reg & WM8993_DCS_DATAPATH_BUSY);
|
||||
|
||||
if ((reg & WM8993_DCS_CAL_COMPLETE_MASK)
|
||||
!= WM8993_DCS_CAL_COMPLETE_MASK)
|
||||
if (reg & WM8993_DCS_DATAPATH_BUSY)
|
||||
dev_err(codec->dev, "Timed out waiting for DC Servo\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Startup calibration of the DC servo
|
||||
*/
|
||||
static void calibrate_dc_servo(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct wm_hubs_data *hubs = codec->private_data;
|
||||
u16 reg, dcs_cfg;
|
||||
|
||||
/* Set for 32 series updates */
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
|
||||
WM8993_DCS_SERIES_NO_01_MASK,
|
||||
32 << WM8993_DCS_SERIES_NO_01_SHIFT);
|
||||
|
||||
/* Enable the DC servo. Write all bits to avoid triggering startup
|
||||
* or write calibration.
|
||||
*/
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
|
||||
0xFFFF,
|
||||
WM8993_DCS_ENA_CHAN_0 |
|
||||
WM8993_DCS_ENA_CHAN_1 |
|
||||
WM8993_DCS_TRIG_SERIES_1 |
|
||||
WM8993_DCS_TRIG_SERIES_0);
|
||||
|
||||
wait_for_dc_servo(codec);
|
||||
|
||||
/* Apply correction to DC servo result */
|
||||
if (hubs->dcs_codes) {
|
||||
dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
|
||||
hubs->dcs_codes);
|
||||
|
||||
/* HPOUT1L */
|
||||
reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1) &
|
||||
WM8993_DCS_INTEG_CHAN_0_MASK;;
|
||||
reg += hubs->dcs_codes;
|
||||
dcs_cfg = reg << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
|
||||
|
||||
/* HPOUT1R */
|
||||
reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2) &
|
||||
WM8993_DCS_INTEG_CHAN_1_MASK;
|
||||
reg += hubs->dcs_codes;
|
||||
dcs_cfg |= reg;
|
||||
|
||||
/* Do it */
|
||||
snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg);
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
|
||||
WM8993_DCS_TRIG_DAC_WR_0 |
|
||||
WM8993_DCS_TRIG_DAC_WR_1,
|
||||
WM8993_DCS_TRIG_DAC_WR_0 |
|
||||
WM8993_DCS_TRIG_DAC_WR_1);
|
||||
|
||||
wait_for_dc_servo(codec);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the DC servo calibration on gain changes
|
||||
*/
|
||||
static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
int ret;
|
||||
|
@ -251,6 +304,47 @@ SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1,
|
|||
line_tlv),
|
||||
};
|
||||
|
||||
static int hp_supply_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
struct wm_hubs_data *hubs = codec->private_data;
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_PRE_PMU:
|
||||
switch (hubs->hp_startup_mode) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
/* Enable the headphone amp */
|
||||
snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
|
||||
WM8993_HPOUT1L_ENA |
|
||||
WM8993_HPOUT1R_ENA,
|
||||
WM8993_HPOUT1L_ENA |
|
||||
WM8993_HPOUT1R_ENA);
|
||||
|
||||
/* Enable the second stage */
|
||||
snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
|
||||
WM8993_HPOUT1L_DLY |
|
||||
WM8993_HPOUT1R_DLY,
|
||||
WM8993_HPOUT1L_DLY |
|
||||
WM8993_HPOUT1R_DLY);
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "Unknown HP startup mode %d\n",
|
||||
hubs->hp_startup_mode);
|
||||
break;
|
||||
}
|
||||
|
||||
case SND_SOC_DAPM_PRE_PMD:
|
||||
snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
|
||||
WM8993_CP_ENA, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
|
@ -271,14 +365,11 @@ static int hp_event(struct snd_soc_dapm_widget *w,
|
|||
reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY;
|
||||
snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
|
||||
|
||||
/* Start the DC servo */
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
|
||||
0xFFFF,
|
||||
WM8993_DCS_ENA_CHAN_0 |
|
||||
WM8993_DCS_ENA_CHAN_1 |
|
||||
WM8993_DCS_TRIG_STARTUP_1 |
|
||||
WM8993_DCS_TRIG_STARTUP_0);
|
||||
wait_for_dc_servo(codec);
|
||||
/* Smallest supported update interval */
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
|
||||
WM8993_DCS_TIMER_PERIOD_01_MASK, 1);
|
||||
|
||||
calibrate_dc_servo(codec);
|
||||
|
||||
reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT |
|
||||
WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT;
|
||||
|
@ -286,23 +377,19 @@ static int hp_event(struct snd_soc_dapm_widget *w,
|
|||
break;
|
||||
|
||||
case SND_SOC_DAPM_PRE_PMD:
|
||||
reg &= ~(WM8993_HPOUT1L_RMV_SHORT |
|
||||
WM8993_HPOUT1L_DLY |
|
||||
WM8993_HPOUT1L_OUTP |
|
||||
WM8993_HPOUT1R_RMV_SHORT |
|
||||
WM8993_HPOUT1R_DLY |
|
||||
WM8993_HPOUT1R_OUTP);
|
||||
snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
|
||||
WM8993_HPOUT1L_DLY |
|
||||
WM8993_HPOUT1R_DLY |
|
||||
WM8993_HPOUT1L_RMV_SHORT |
|
||||
WM8993_HPOUT1R_RMV_SHORT, 0);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
|
||||
0xffff, 0);
|
||||
snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
|
||||
WM8993_HPOUT1L_OUTP |
|
||||
WM8993_HPOUT1R_OUTP, 0);
|
||||
|
||||
snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
|
||||
snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
|
||||
WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
|
||||
0);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
|
||||
WM8993_CP_ENA, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -473,6 +560,8 @@ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0,
|
|||
SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, hp_supply_event,
|
||||
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0,
|
||||
NULL, 0,
|
||||
hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
|
@ -626,6 +715,7 @@ static const struct snd_soc_dapm_route analogue_routes[] = {
|
|||
{ "Headphone PGA", NULL, "Left Headphone Mux" },
|
||||
{ "Headphone PGA", NULL, "Right Headphone Mux" },
|
||||
{ "Headphone PGA", NULL, "CLK_SYS" },
|
||||
{ "Headphone PGA", NULL, "Headphone Supply" },
|
||||
|
||||
{ "HPOUT1L", NULL, "Headphone PGA" },
|
||||
{ "HPOUT1R", NULL, "Headphone PGA" },
|
||||
|
@ -753,6 +843,12 @@ int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *codec,
|
|||
WM8993_LINEOUT2_MODE,
|
||||
WM8993_LINEOUT2_MODE);
|
||||
|
||||
/* If the line outputs are differential then we aren't presenting
|
||||
* VMID as an output and can disable it.
|
||||
*/
|
||||
if (lineout1_diff && lineout2_diff)
|
||||
codec->idle_bias_off = 1;
|
||||
|
||||
if (lineout1fb)
|
||||
snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
|
||||
WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB);
|
||||
|
|
|
@ -18,6 +18,12 @@ struct snd_soc_codec;
|
|||
|
||||
extern const unsigned int wm_hubs_spkmix_tlv[];
|
||||
|
||||
/* This *must* be the first element of the codec->private_data struct */
|
||||
struct wm_hubs_data {
|
||||
int dcs_codes;
|
||||
int hp_startup_mode;
|
||||
};
|
||||
|
||||
extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
|
||||
extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int);
|
||||
extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *,
|
||||
|
|
|
@ -767,14 +767,26 @@ static int davinci_mcasp_trigger(struct snd_pcm_substream *substream,
|
|||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (!dev->clk_active) {
|
||||
clk_enable(dev->clk);
|
||||
dev->clk_active = 1;
|
||||
}
|
||||
davinci_mcasp_start(dev, substream->stream);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
davinci_mcasp_stop(dev, substream->stream);
|
||||
if (dev->clk_active) {
|
||||
clk_disable(dev->clk);
|
||||
dev->clk_active = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
davinci_mcasp_stop(dev, substream->stream);
|
||||
break;
|
||||
|
@ -866,6 +878,7 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
clk_enable(dev->clk);
|
||||
dev->clk_active = 1;
|
||||
|
||||
dev->base = (void __iomem *)IO_ADDRESS(mem->start);
|
||||
dev->op_mode = pdata->op_mode;
|
||||
|
|
|
@ -44,6 +44,7 @@ struct davinci_audio_dev {
|
|||
int sample_rate;
|
||||
struct clk *clk;
|
||||
unsigned int codec_fmt;
|
||||
u8 clk_active;
|
||||
|
||||
/* McASP specific data */
|
||||
int tdm_slots;
|
||||
|
|
|
@ -49,7 +49,7 @@ static void print_buf_info(int slot, char *name)
|
|||
static struct snd_pcm_hardware pcm_hardware_playback = {
|
||||
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE),
|
||||
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
|
||||
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
|
||||
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
|
||||
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
config SND_MX1_MX2_SOC
|
||||
tristate "SoC Audio for Freecale i.MX1x i.MX2x CPUs"
|
||||
depends on ARCH_MX2 || ARCH_MX1
|
||||
config SND_IMX_SOC
|
||||
tristate "SoC Audio for Freescale i.MX CPUs"
|
||||
depends on ARCH_MXC && BROKEN
|
||||
select SND_PCM
|
||||
select FIQ
|
||||
select SND_SOC_AC97_BUS
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the MX1 or MX2 SSI interface.
|
||||
the i.MX SSI interface.
|
||||
|
||||
config SND_MXC_SOC_SSI
|
||||
tristate
|
||||
|
||||
config SND_SOC_MX27VIS_WM8974
|
||||
tristate "SoC Audio support for MX27 - WM8974 Visstrim_sm10 board"
|
||||
depends on SND_MX1_MX2_SOC && MACH_MX27 && MACH_IMX27_VISSTRIM_M10
|
||||
select SND_MXC_SOC_SSI
|
||||
select SND_SOC_WM8974
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on Visstrim SM10
|
||||
board with WM8974.
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
# i.MX Platform Support
|
||||
snd-soc-mx1_mx2-objs := mx1_mx2-pcm.o
|
||||
snd-soc-mxc-ssi-objs := mxc-ssi.o
|
||||
snd-soc-imx-objs := imx-ssi.o imx-pcm-fiq.o
|
||||
|
||||
obj-$(CONFIG_SND_MX1_MX2_SOC) += snd-soc-mx1_mx2.o
|
||||
obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-mxc-ssi.o
|
||||
ifdef CONFIG_MACH_MX27
|
||||
snd-soc-imx-objs += imx-pcm-dma-mx2.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
|
||||
|
||||
# i.MX Machine Support
|
||||
snd-soc-mx27vis-wm8974-objs := mx27vis_wm8974.o
|
||||
obj-$(CONFIG_SND_SOC_MX27VIS_WM8974) += snd-soc-mx27vis-wm8974.o
|
||||
snd-soc-phycore-ac97-objs := phycore-ac97.o
|
||||
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
* imx-pcm-dma-mx2.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This code is based on code copyrighted by Freescale,
|
||||
* Liam Girdwood, Javier Martin and probably others.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <mach/dma-mx1-mx2.h>
|
||||
|
||||
#include "imx-ssi.h"
|
||||
|
||||
struct imx_pcm_runtime_data {
|
||||
int sg_count;
|
||||
struct scatterlist *sg_list;
|
||||
int period;
|
||||
int periods;
|
||||
unsigned long dma_addr;
|
||||
int dma;
|
||||
struct snd_pcm_substream *substream;
|
||||
unsigned long offset;
|
||||
unsigned long size;
|
||||
unsigned long period_cnt;
|
||||
void *buf;
|
||||
int period_time;
|
||||
};
|
||||
|
||||
/* Called by the DMA framework when a period has elapsed */
|
||||
static void imx_ssi_dma_progression(int channel, void *data,
|
||||
struct scatterlist *sg)
|
||||
{
|
||||
struct snd_pcm_substream *substream = data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
if (!sg)
|
||||
return;
|
||||
|
||||
runtime = iprtd->substream->runtime;
|
||||
|
||||
iprtd->offset = sg->dma_address - runtime->dma_addr;
|
||||
|
||||
snd_pcm_period_elapsed(iprtd->substream);
|
||||
}
|
||||
|
||||
static void imx_ssi_dma_callback(int channel, void *data)
|
||||
{
|
||||
pr_err("%s shouldn't be called\n", __func__);
|
||||
}
|
||||
|
||||
static void snd_imx_dma_err_callback(int channel, void *data, int err)
|
||||
{
|
||||
pr_err("DMA error callback called\n");
|
||||
|
||||
pr_err("DMA timeout on channel %d -%s%s%s%s\n",
|
||||
channel,
|
||||
err & IMX_DMA_ERR_BURST ? " burst" : "",
|
||||
err & IMX_DMA_ERR_REQUEST ? " request" : "",
|
||||
err & IMX_DMA_ERR_TRANSFER ? " transfer" : "",
|
||||
err & IMX_DMA_ERR_BUFFER ? " buffer" : "");
|
||||
}
|
||||
|
||||
static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
int ret;
|
||||
|
||||
iprtd->dma = imx_dma_request_by_prio(DRV_NAME, DMA_PRIO_HIGH);
|
||||
if (iprtd->dma < 0) {
|
||||
pr_err("Failed to claim the audio DMA\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = imx_dma_setup_handlers(iprtd->dma,
|
||||
imx_ssi_dma_callback,
|
||||
snd_imx_dma_err_callback, substream);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = imx_dma_setup_progression_handler(iprtd->dma,
|
||||
imx_ssi_dma_progression);
|
||||
if (ret) {
|
||||
pr_err("Failed to setup the DMA handler\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = imx_dma_config_channel(iprtd->dma,
|
||||
IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
dma_params->dma, 1);
|
||||
if (ret < 0) {
|
||||
pr_err("Cannot configure DMA channel: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
imx_dma_config_burstlen(iprtd->dma, dma_params->burstsize * 2);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
imx_dma_free(iprtd->dma);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
int i;
|
||||
unsigned long dma_addr;
|
||||
|
||||
imx_ssi_dma_alloc(substream);
|
||||
|
||||
iprtd->size = params_buffer_bytes(params);
|
||||
iprtd->periods = params_periods(params);
|
||||
iprtd->period = params_period_bytes(params);
|
||||
iprtd->offset = 0;
|
||||
iprtd->period_time = HZ / (params_rate(params) /
|
||||
params_period_size(params));
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
if (iprtd->sg_count != iprtd->periods) {
|
||||
kfree(iprtd->sg_list);
|
||||
|
||||
iprtd->sg_list = kcalloc(iprtd->periods + 1,
|
||||
sizeof(struct scatterlist), GFP_KERNEL);
|
||||
if (!iprtd->sg_list)
|
||||
return -ENOMEM;
|
||||
iprtd->sg_count = iprtd->periods + 1;
|
||||
}
|
||||
|
||||
sg_init_table(iprtd->sg_list, iprtd->sg_count);
|
||||
dma_addr = runtime->dma_addr;
|
||||
|
||||
for (i = 0; i < iprtd->periods; i++) {
|
||||
iprtd->sg_list[i].page_link = 0;
|
||||
iprtd->sg_list[i].offset = 0;
|
||||
iprtd->sg_list[i].dma_address = dma_addr;
|
||||
iprtd->sg_list[i].length = iprtd->period;
|
||||
dma_addr += iprtd->period;
|
||||
}
|
||||
|
||||
/* close the loop */
|
||||
iprtd->sg_list[iprtd->sg_count - 1].offset = 0;
|
||||
iprtd->sg_list[iprtd->sg_count - 1].length = 0;
|
||||
iprtd->sg_list[iprtd->sg_count - 1].page_link =
|
||||
((unsigned long) iprtd->sg_list | 0x01) & ~0x02;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
if (iprtd->dma >= 0) {
|
||||
imx_dma_free(iprtd->dma);
|
||||
iprtd->dma = -EINVAL;
|
||||
}
|
||||
|
||||
kfree(iprtd->sg_list);
|
||||
iprtd->sg_list = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
int err;
|
||||
|
||||
iprtd->substream = substream;
|
||||
iprtd->buf = (unsigned int *)substream->dma_buffer.area;
|
||||
iprtd->period_cnt = 0;
|
||||
|
||||
pr_debug("%s: buf: %p period: %d periods: %d\n",
|
||||
__func__, iprtd->buf, iprtd->period, iprtd->periods);
|
||||
|
||||
err = imx_dma_setup_sg(iprtd->dma, iprtd->sg_list, iprtd->sg_count,
|
||||
IMX_DMA_LENGTH_LOOP, dma_params->dma_addr,
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
||||
DMA_MODE_WRITE : DMA_MODE_READ);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
imx_dma_enable(iprtd->dma);
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
imx_dma_disable(iprtd->dma);
|
||||
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
return bytes_to_frames(substream->runtime, iprtd->offset);
|
||||
}
|
||||
|
||||
static struct snd_pcm_hardware snd_imx_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.rate_min = 8000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
|
||||
.period_bytes_min = 128,
|
||||
.period_bytes_max = 16 * 1024,
|
||||
.periods_min = 2,
|
||||
.periods_max = 255,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static int snd_imx_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd;
|
||||
int ret;
|
||||
|
||||
iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
|
||||
runtime->private_data = iprtd;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops imx_pcm_ops = {
|
||||
.open = snd_imx_open,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_imx_pcm_hw_params,
|
||||
.hw_free = snd_imx_pcm_hw_free,
|
||||
.prepare = snd_imx_pcm_prepare,
|
||||
.trigger = snd_imx_pcm_trigger,
|
||||
.pointer = snd_imx_pcm_pointer,
|
||||
.mmap = snd_imx_pcm_mmap,
|
||||
};
|
||||
|
||||
static struct snd_soc_platform imx_soc_platform_dma = {
|
||||
.name = "imx-audio",
|
||||
.pcm_ops = &imx_pcm_ops,
|
||||
.pcm_new = imx_pcm_new,
|
||||
.pcm_free = imx_pcm_free,
|
||||
};
|
||||
|
||||
struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
|
||||
struct imx_ssi *ssi)
|
||||
{
|
||||
ssi->dma_params_tx.burstsize = DMA_TXFIFO_BURST;
|
||||
ssi->dma_params_rx.burstsize = DMA_RXFIFO_BURST;
|
||||
|
||||
return &imx_soc_platform_dma;
|
||||
}
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* imx-pcm-fiq.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This code is based on code copyrighted by Freescale,
|
||||
* Liam Girdwood, Javier Martin and probably others.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/fiq.h>
|
||||
|
||||
#include <mach/ssi.h>
|
||||
|
||||
#include "imx-ssi.h"
|
||||
|
||||
struct imx_pcm_runtime_data {
|
||||
int period;
|
||||
int periods;
|
||||
unsigned long offset;
|
||||
unsigned long last_offset;
|
||||
unsigned long size;
|
||||
struct timer_list timer;
|
||||
int poll_time;
|
||||
};
|
||||
|
||||
static inline void imx_ssi_set_next_poll(struct imx_pcm_runtime_data *iprtd)
|
||||
{
|
||||
iprtd->timer.expires = jiffies + iprtd->poll_time;
|
||||
}
|
||||
|
||||
static void imx_ssi_timer_callback(unsigned long data)
|
||||
{
|
||||
struct snd_pcm_substream *substream = (void *)data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
struct pt_regs regs;
|
||||
unsigned long delta;
|
||||
|
||||
get_fiq_regs(®s);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
iprtd->offset = regs.ARM_r8 & 0xffff;
|
||||
else
|
||||
iprtd->offset = regs.ARM_r9 & 0xffff;
|
||||
|
||||
/* How much data have we transferred since the last period report? */
|
||||
if (iprtd->offset >= iprtd->last_offset)
|
||||
delta = iprtd->offset - iprtd->last_offset;
|
||||
else
|
||||
delta = runtime->buffer_size + iprtd->offset
|
||||
- iprtd->last_offset;
|
||||
|
||||
/* If we've transferred at least a period then report it and
|
||||
* reset our poll time */
|
||||
if (delta >= runtime->period_size) {
|
||||
snd_pcm_period_elapsed(substream);
|
||||
iprtd->last_offset = iprtd->offset;
|
||||
|
||||
imx_ssi_set_next_poll(iprtd);
|
||||
}
|
||||
|
||||
/* Restart the timer; if we didn't report we'll run on the next tick */
|
||||
add_timer(&iprtd->timer);
|
||||
|
||||
}
|
||||
|
||||
static struct fiq_handler fh = {
|
||||
.name = DRV_NAME,
|
||||
};
|
||||
|
||||
static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
iprtd->size = params_buffer_bytes(params);
|
||||
iprtd->periods = params_periods(params);
|
||||
iprtd->period = params_period_bytes(params) ;
|
||||
iprtd->offset = 0;
|
||||
iprtd->last_offset = 0;
|
||||
iprtd->poll_time = HZ / (params_rate(params) / params_period_size(params));
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
struct pt_regs regs;
|
||||
|
||||
get_fiq_regs(®s);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16;
|
||||
else
|
||||
regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16;
|
||||
|
||||
set_fiq_regs(®s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fiq_enable;
|
||||
static int imx_pcm_fiq;
|
||||
|
||||
static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
imx_ssi_set_next_poll(iprtd);
|
||||
add_timer(&iprtd->timer);
|
||||
if (++fiq_enable == 1)
|
||||
enable_fiq(imx_pcm_fiq);
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
del_timer(&iprtd->timer);
|
||||
if (--fiq_enable == 0)
|
||||
disable_fiq(imx_pcm_fiq);
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
return bytes_to_frames(substream->runtime, iprtd->offset);
|
||||
}
|
||||
|
||||
static struct snd_pcm_hardware snd_imx_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.rate_min = 8000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
|
||||
.period_bytes_min = 128,
|
||||
.period_bytes_max = 16 * 1024,
|
||||
.periods_min = 2,
|
||||
.periods_max = 255,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static int snd_imx_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd;
|
||||
int ret;
|
||||
|
||||
iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
|
||||
runtime->private_data = iprtd;
|
||||
|
||||
init_timer(&iprtd->timer);
|
||||
iprtd->timer.data = (unsigned long)substream;
|
||||
iprtd->timer.function = imx_ssi_timer_callback;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
del_timer_sync(&iprtd->timer);
|
||||
kfree(iprtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops imx_pcm_ops = {
|
||||
.open = snd_imx_open,
|
||||
.close = snd_imx_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_imx_pcm_hw_params,
|
||||
.prepare = snd_imx_pcm_prepare,
|
||||
.trigger = snd_imx_pcm_trigger,
|
||||
.pointer = snd_imx_pcm_pointer,
|
||||
.mmap = snd_imx_pcm_mmap,
|
||||
};
|
||||
|
||||
static int imx_pcm_fiq_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = imx_pcm_new(card, dai, pcm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (dai->playback.channels_min) {
|
||||
struct snd_pcm_substream *substream =
|
||||
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
|
||||
imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;
|
||||
}
|
||||
|
||||
if (dai->capture.channels_min) {
|
||||
struct snd_pcm_substream *substream =
|
||||
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
|
||||
imx_ssi_fiq_rx_buffer = (unsigned long)buf->area;
|
||||
}
|
||||
|
||||
set_fiq_handler(&imx_ssi_fiq_start,
|
||||
&imx_ssi_fiq_end - &imx_ssi_fiq_start);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_platform imx_soc_platform_fiq = {
|
||||
.pcm_ops = &imx_pcm_ops,
|
||||
.pcm_new = imx_pcm_fiq_new,
|
||||
.pcm_free = imx_pcm_free,
|
||||
};
|
||||
|
||||
struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
|
||||
struct imx_ssi *ssi)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = claim_fiq(&fh);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
mxc_set_irq_fiq(ssi->irq, 1);
|
||||
|
||||
imx_pcm_fiq = ssi->irq;
|
||||
|
||||
imx_ssi_fiq_base = (unsigned long)ssi->base;
|
||||
|
||||
ssi->dma_params_tx.burstsize = 4;
|
||||
ssi->dma_params_rx.burstsize = 6;
|
||||
|
||||
return &imx_soc_platform_fiq;
|
||||
}
|
||||
|
||||
void imx_ssi_fiq_exit(struct platform_device *pdev,
|
||||
struct imx_ssi *ssi)
|
||||
{
|
||||
mxc_set_irq_fiq(ssi->irq, 0);
|
||||
release_fiq(&fh);
|
||||
}
|
||||
|
|
@ -0,0 +1,758 @@
|
|||
/*
|
||||
* imx-ssi.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This code is based on code copyrighted by Freescale,
|
||||
* Liam Girdwood, Javier Martin and probably others.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* The i.MX SSI core has some nasty limitations in AC97 mode. While most
|
||||
* sane processor vendors have a FIFO per AC97 slot, the i.MX has only
|
||||
* one FIFO which combines all valid receive slots. We cannot even select
|
||||
* which slots we want to receive. The WM9712 with which this driver
|
||||
* was developped with always sends GPIO status data in slot 12 which
|
||||
* we receive in our (PCM-) data stream. The only chance we have is to
|
||||
* manually skip this data in the FIQ handler. With sampling rates different
|
||||
* from 48000Hz not every frame has valid receive data, so the ratio
|
||||
* between pcm data and GPIO status data changes. Our FIQ handler is not
|
||||
* able to handle this, hence this driver only works with 48000Hz sampling
|
||||
* rate.
|
||||
* Reading and writing AC97 registers is another challange. The core
|
||||
* provides us status bits when the read register is updated with *another*
|
||||
* value. When we read the same register two times (and the register still
|
||||
* contains the same value) these status bits are not set. We work
|
||||
* around this by not polling these bits but only wait a fixed delay.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <mach/ssi.h>
|
||||
#include <mach/hardware.h>
|
||||
|
||||
#include "imx-ssi.h"
|
||||
|
||||
#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
|
||||
|
||||
/*
|
||||
* SSI Network Mode or TDM slots configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
|
||||
{
|
||||
struct imx_ssi *ssi = cpu_dai->private_data;
|
||||
u32 sccr;
|
||||
|
||||
sccr = readl(ssi->base + SSI_STCCR);
|
||||
sccr &= ~SSI_STCCR_DC_MASK;
|
||||
sccr |= SSI_STCCR_DC(slots - 1);
|
||||
writel(sccr, ssi->base + SSI_STCCR);
|
||||
|
||||
sccr = readl(ssi->base + SSI_SRCCR);
|
||||
sccr &= ~SSI_STCCR_DC_MASK;
|
||||
sccr |= SSI_STCCR_DC(slots - 1);
|
||||
writel(sccr, ssi->base + SSI_SRCCR);
|
||||
|
||||
writel(tx_mask, ssi->base + SSI_STMSK);
|
||||
writel(rx_mask, ssi->base + SSI_SRMSK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI DAI format configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
* Note: We don't use the I2S modes but instead manually configure the
|
||||
* SSI for I2S because the I2S mode is only a register preset.
|
||||
*/
|
||||
static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
|
||||
{
|
||||
struct imx_ssi *ssi = cpu_dai->private_data;
|
||||
u32 strcr = 0, scr;
|
||||
|
||||
scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
|
||||
|
||||
/* DAI mode */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
/* data on rising edge of bclk, frame low 1clk before data */
|
||||
strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
|
||||
scr |= SSI_SCR_NET;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
/* data on rising edge of bclk, frame high with data */
|
||||
strcr |= SSI_STCR_TXBIT0;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
/* data on rising edge of bclk, frame high with data */
|
||||
strcr |= SSI_STCR_TFSL;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
/* data on rising edge of bclk, frame high 1clk before data */
|
||||
strcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
|
||||
break;
|
||||
}
|
||||
|
||||
/* DAI clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
strcr |= SSI_STCR_TFSI;
|
||||
strcr &= ~SSI_STCR_TSCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
strcr &= ~SSI_STCR_TFSI;
|
||||
strcr |= SSI_STCR_TSCKP;
|
||||
break;
|
||||
}
|
||||
|
||||
/* DAI clock master masks */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
break;
|
||||
default:
|
||||
/* Master mode not implemented, needs handling of clocks. */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
strcr |= SSI_STCR_TFEN0;
|
||||
|
||||
writel(strcr, ssi->base + SSI_STCR);
|
||||
writel(strcr, ssi->base + SSI_SRCR);
|
||||
writel(scr, ssi->base + SSI_SCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI system clock configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct imx_ssi *ssi = cpu_dai->private_data;
|
||||
u32 scr;
|
||||
|
||||
scr = readl(ssi->base + SSI_SCR);
|
||||
|
||||
switch (clk_id) {
|
||||
case IMX_SSP_SYS_CLK:
|
||||
if (dir == SND_SOC_CLOCK_OUT)
|
||||
scr |= SSI_SCR_SYS_CLK_EN;
|
||||
else
|
||||
scr &= ~SSI_SCR_SYS_CLK_EN;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(scr, ssi->base + SSI_SCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI Clock dividers
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
struct imx_ssi *ssi = cpu_dai->private_data;
|
||||
u32 stccr, srccr;
|
||||
|
||||
stccr = readl(ssi->base + SSI_STCCR);
|
||||
srccr = readl(ssi->base + SSI_SRCCR);
|
||||
|
||||
switch (div_id) {
|
||||
case IMX_SSI_TX_DIV_2:
|
||||
stccr &= ~SSI_STCCR_DIV2;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_TX_DIV_PSR:
|
||||
stccr &= ~SSI_STCCR_PSR;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_TX_DIV_PM:
|
||||
stccr &= ~0xff;
|
||||
stccr |= SSI_STCCR_PM(div);
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_2:
|
||||
stccr &= ~SSI_STCCR_DIV2;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_PSR:
|
||||
stccr &= ~SSI_STCCR_PSR;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_PM:
|
||||
stccr &= ~0xff;
|
||||
stccr |= SSI_STCCR_PM(div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(stccr, ssi->base + SSI_STCCR);
|
||||
writel(srccr, ssi->base + SSI_SRCCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0),
|
||||
* although can be called multiple times by upper layers.
|
||||
*/
|
||||
static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct imx_ssi *ssi = cpu_dai->private_data;
|
||||
u32 reg, sccr;
|
||||
|
||||
/* Tx/Rx config */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
reg = SSI_STCCR;
|
||||
cpu_dai->dma_data = &ssi->dma_params_tx;
|
||||
} else {
|
||||
reg = SSI_SRCCR;
|
||||
cpu_dai->dma_data = &ssi->dma_params_rx;
|
||||
}
|
||||
|
||||
sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
|
||||
|
||||
/* DAI data (word) size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
sccr |= SSI_SRCCR_WL(16);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
sccr |= SSI_SRCCR_WL(20);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
sccr |= SSI_SRCCR_WL(24);
|
||||
break;
|
||||
}
|
||||
|
||||
writel(sccr, ssi->base + reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct imx_ssi *ssi = cpu_dai->private_data;
|
||||
unsigned int sier_bits, sier;
|
||||
unsigned int scr;
|
||||
|
||||
scr = readl(ssi->base + SSI_SCR);
|
||||
sier = readl(ssi->base + SSI_SIER);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
if (ssi->flags & IMX_SSI_DMA)
|
||||
sier_bits = SSI_SIER_TDMAE;
|
||||
else
|
||||
sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
|
||||
} else {
|
||||
if (ssi->flags & IMX_SSI_DMA)
|
||||
sier_bits = SSI_SIER_RDMAE;
|
||||
else
|
||||
sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
scr |= SSI_SCR_TE;
|
||||
else
|
||||
scr |= SSI_SCR_RE;
|
||||
sier |= sier_bits;
|
||||
|
||||
if (++ssi->enabled == 1)
|
||||
scr |= SSI_SCR_SSIEN;
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
scr &= ~SSI_SCR_TE;
|
||||
else
|
||||
scr &= ~SSI_SCR_RE;
|
||||
sier &= ~sier_bits;
|
||||
|
||||
if (--ssi->enabled == 0)
|
||||
scr &= ~SSI_SCR_SSIEN;
|
||||
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!(ssi->flags & IMX_SSI_USE_AC97))
|
||||
/* rx/tx are always enabled to access ac97 registers */
|
||||
writel(scr, ssi->base + SSI_SCR);
|
||||
|
||||
writel(sier, ssi->base + SSI_SIER);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
|
||||
.hw_params = imx_ssi_hw_params,
|
||||
.set_fmt = imx_ssi_set_dai_fmt,
|
||||
.set_clkdiv = imx_ssi_set_dai_clkdiv,
|
||||
.set_sysclk = imx_ssi_set_dai_sysclk,
|
||||
.set_tdm_slot = imx_ssi_set_dai_tdm_slot,
|
||||
.trigger = imx_ssi_trigger,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai imx_ssi_dai = {
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
};
|
||||
|
||||
int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int ret;
|
||||
|
||||
ret = dma_mmap_coherent(NULL, vma, runtime->dma_area,
|
||||
runtime->dma_addr, runtime->dma_bytes);
|
||||
|
||||
pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,
|
||||
runtime->dma_area,
|
||||
runtime->dma_addr,
|
||||
runtime->dma_bytes);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = IMX_SSI_DMABUF_SIZE;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
buf->area = dma_alloc_writecombine(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
buf->bytes = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &imx_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
||||
if (dai->playback.channels_min) {
|
||||
ret = imx_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dai->capture.channels_min) {
|
||||
ret = imx_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void imx_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
|
||||
dma_free_writecombine(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
struct snd_soc_platform imx_soc_platform = {
|
||||
.name = "imx-audio",
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(imx_soc_platform);
|
||||
|
||||
static struct snd_soc_dai imx_ac97_dai = {
|
||||
.name = "AC97",
|
||||
.ac97_control = 1,
|
||||
.playback = {
|
||||
.stream_name = "AC97 Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "AC97 Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
};
|
||||
|
||||
static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
|
||||
{
|
||||
void __iomem *base = imx_ssi->base;
|
||||
|
||||
writel(0x0, base + SSI_SCR);
|
||||
writel(0x0, base + SSI_STCR);
|
||||
writel(0x0, base + SSI_SRCR);
|
||||
|
||||
writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
|
||||
|
||||
writel(SSI_SFCSR_RFWM0(8) |
|
||||
SSI_SFCSR_TFWM0(8) |
|
||||
SSI_SFCSR_RFWM1(8) |
|
||||
SSI_SFCSR_TFWM1(8), base + SSI_SFCSR);
|
||||
|
||||
writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
|
||||
writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
|
||||
|
||||
writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
|
||||
writel(SSI_SOR_WAIT(3), base + SSI_SOR);
|
||||
|
||||
writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
|
||||
SSI_SCR_TE | SSI_SCR_RE,
|
||||
base + SSI_SCR);
|
||||
|
||||
writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
|
||||
writel(0xff, base + SSI_SACCDIS);
|
||||
writel(0x300, base + SSI_SACCEN);
|
||||
}
|
||||
|
||||
static struct imx_ssi *ac97_ssi;
|
||||
|
||||
static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
void __iomem *base = imx_ssi->base;
|
||||
unsigned int lreg;
|
||||
unsigned int lval;
|
||||
|
||||
if (reg > 0x7f)
|
||||
return;
|
||||
|
||||
pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
|
||||
|
||||
lreg = reg << 12;
|
||||
writel(lreg, base + SSI_SACADD);
|
||||
|
||||
lval = val << 4;
|
||||
writel(lval , base + SSI_SACDAT);
|
||||
|
||||
writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
|
||||
unsigned short reg)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
void __iomem *base = imx_ssi->base;
|
||||
|
||||
unsigned short val = -1;
|
||||
unsigned int lreg;
|
||||
|
||||
lreg = (reg & 0x7f) << 12 ;
|
||||
writel(lreg, base + SSI_SACADD);
|
||||
writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
|
||||
|
||||
udelay(100);
|
||||
|
||||
val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
|
||||
|
||||
pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
|
||||
if (imx_ssi->ac97_reset)
|
||||
imx_ssi->ac97_reset(ac97);
|
||||
}
|
||||
|
||||
static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
|
||||
if (imx_ssi->ac97_warm_reset)
|
||||
imx_ssi->ac97_warm_reset(ac97);
|
||||
}
|
||||
|
||||
struct snd_ac97_bus_ops soc_ac97_ops = {
|
||||
.read = imx_ssi_ac97_read,
|
||||
.write = imx_ssi_ac97_write,
|
||||
.reset = imx_ssi_ac97_reset,
|
||||
.warm_reset = imx_ssi_ac97_warm_reset
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_ac97_ops);
|
||||
|
||||
struct snd_soc_dai imx_ssi_pcm_dai[2];
|
||||
EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai);
|
||||
|
||||
static int imx_ssi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
struct imx_ssi *ssi;
|
||||
struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct snd_soc_platform *platform;
|
||||
int ret = 0;
|
||||
unsigned int val;
|
||||
struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
|
||||
|
||||
if (dai->id >= ARRAY_SIZE(imx_ssi_pcm_dai))
|
||||
return -EINVAL;
|
||||
|
||||
ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
|
||||
if (!ssi)
|
||||
return -ENOMEM;
|
||||
|
||||
if (pdata) {
|
||||
ssi->ac97_reset = pdata->ac97_reset;
|
||||
ssi->ac97_warm_reset = pdata->ac97_warm_reset;
|
||||
ssi->flags = pdata->flags;
|
||||
}
|
||||
|
||||
ssi->irq = platform_get_irq(pdev, 0);
|
||||
|
||||
ssi->clk = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(ssi->clk)) {
|
||||
ret = PTR_ERR(ssi->clk);
|
||||
dev_err(&pdev->dev, "Cannot get the clock: %d\n",
|
||||
ret);
|
||||
goto failed_clk;
|
||||
}
|
||||
clk_enable(ssi->clk);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
ret = -ENODEV;
|
||||
goto failed_get_resource;
|
||||
}
|
||||
|
||||
if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
|
||||
dev_err(&pdev->dev, "request_mem_region failed\n");
|
||||
ret = -EBUSY;
|
||||
goto failed_get_resource;
|
||||
}
|
||||
|
||||
ssi->base = ioremap(res->start, resource_size(res));
|
||||
if (!ssi->base) {
|
||||
dev_err(&pdev->dev, "ioremap failed\n");
|
||||
ret = -ENODEV;
|
||||
goto failed_ioremap;
|
||||
}
|
||||
|
||||
if (ssi->flags & IMX_SSI_USE_AC97) {
|
||||
if (ac97_ssi) {
|
||||
ret = -EBUSY;
|
||||
goto failed_ac97;
|
||||
}
|
||||
ac97_ssi = ssi;
|
||||
setup_channel_to_ac97(ssi);
|
||||
memcpy(dai, &imx_ac97_dai, sizeof(imx_ac97_dai));
|
||||
} else
|
||||
memcpy(dai, &imx_ssi_dai, sizeof(imx_ssi_dai));
|
||||
|
||||
writel(0x0, ssi->base + SSI_SIER);
|
||||
|
||||
ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
|
||||
ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
|
||||
if (res)
|
||||
ssi->dma_params_tx.dma = res->start;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
|
||||
if (res)
|
||||
ssi->dma_params_rx.dma = res->start;
|
||||
|
||||
dai->id = pdev->id;
|
||||
dai->dev = &pdev->dev;
|
||||
dai->name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id);
|
||||
dai->private_data = ssi;
|
||||
|
||||
if ((cpu_is_mx27() || cpu_is_mx21()) &&
|
||||
!(ssi->flags & IMX_SSI_USE_AC97)) {
|
||||
ssi->flags |= IMX_SSI_DMA;
|
||||
platform = imx_ssi_dma_mx2_init(pdev, ssi);
|
||||
} else
|
||||
platform = imx_ssi_fiq_init(pdev, ssi);
|
||||
|
||||
imx_soc_platform.pcm_ops = platform->pcm_ops;
|
||||
imx_soc_platform.pcm_new = platform->pcm_new;
|
||||
imx_soc_platform.pcm_free = platform->pcm_free;
|
||||
|
||||
val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) |
|
||||
SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize);
|
||||
writel(val, ssi->base + SSI_SFCSR);
|
||||
|
||||
ret = snd_soc_register_dai(dai);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "register DAI failed\n");
|
||||
goto failed_register;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ssi);
|
||||
|
||||
return 0;
|
||||
|
||||
failed_register:
|
||||
failed_ac97:
|
||||
iounmap(ssi->base);
|
||||
failed_ioremap:
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
failed_get_resource:
|
||||
clk_disable(ssi->clk);
|
||||
clk_put(ssi->clk);
|
||||
failed_clk:
|
||||
kfree(ssi);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit imx_ssi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
struct imx_ssi *ssi = platform_get_drvdata(pdev);
|
||||
struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
|
||||
|
||||
snd_soc_unregister_dai(dai);
|
||||
|
||||
if (ssi->flags & IMX_SSI_USE_AC97)
|
||||
ac97_ssi = NULL;
|
||||
|
||||
if (!(ssi->flags & IMX_SSI_DMA))
|
||||
imx_ssi_fiq_exit(pdev, ssi);
|
||||
|
||||
iounmap(ssi->base);
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
clk_disable(ssi->clk);
|
||||
clk_put(ssi->clk);
|
||||
kfree(ssi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver imx_ssi_driver = {
|
||||
.probe = imx_ssi_probe,
|
||||
.remove = __devexit_p(imx_ssi_remove),
|
||||
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init imx_ssi_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_register_platform(&imx_soc_platform);
|
||||
if (ret) {
|
||||
pr_err("failed to register soc platform: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&imx_ssi_driver);
|
||||
if (ret) {
|
||||
snd_soc_unregister_platform(&imx_soc_platform);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit imx_ssi_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&imx_ssi_driver);
|
||||
snd_soc_unregister_platform(&imx_soc_platform);
|
||||
}
|
||||
|
||||
module_init(imx_ssi_init);
|
||||
module_exit(imx_ssi_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
|
||||
MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* 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 _IMX_SSI_H
|
||||
#define _IMX_SSI_H
|
||||
|
||||
#define SSI_STX0 0x00
|
||||
#define SSI_STX1 0x04
|
||||
#define SSI_SRX0 0x08
|
||||
#define SSI_SRX1 0x0c
|
||||
|
||||
#define SSI_SCR 0x10
|
||||
#define SSI_SCR_CLK_IST (1 << 9)
|
||||
#define SSI_SCR_CLK_IST_SHIFT 9
|
||||
#define SSI_SCR_TCH_EN (1 << 8)
|
||||
#define SSI_SCR_SYS_CLK_EN (1 << 7)
|
||||
#define SSI_SCR_I2S_MODE_NORM (0 << 5)
|
||||
#define SSI_SCR_I2S_MODE_MSTR (1 << 5)
|
||||
#define SSI_SCR_I2S_MODE_SLAVE (2 << 5)
|
||||
#define SSI_I2S_MODE_MASK (3 << 5)
|
||||
#define SSI_SCR_SYN (1 << 4)
|
||||
#define SSI_SCR_NET (1 << 3)
|
||||
#define SSI_SCR_RE (1 << 2)
|
||||
#define SSI_SCR_TE (1 << 1)
|
||||
#define SSI_SCR_SSIEN (1 << 0)
|
||||
|
||||
#define SSI_SISR 0x14
|
||||
#define SSI_SISR_MASK ((1 << 19) - 1)
|
||||
#define SSI_SISR_CMDAU (1 << 18)
|
||||
#define SSI_SISR_CMDDU (1 << 17)
|
||||
#define SSI_SISR_RXT (1 << 16)
|
||||
#define SSI_SISR_RDR1 (1 << 15)
|
||||
#define SSI_SISR_RDR0 (1 << 14)
|
||||
#define SSI_SISR_TDE1 (1 << 13)
|
||||
#define SSI_SISR_TDE0 (1 << 12)
|
||||
#define SSI_SISR_ROE1 (1 << 11)
|
||||
#define SSI_SISR_ROE0 (1 << 10)
|
||||
#define SSI_SISR_TUE1 (1 << 9)
|
||||
#define SSI_SISR_TUE0 (1 << 8)
|
||||
#define SSI_SISR_TFS (1 << 7)
|
||||
#define SSI_SISR_RFS (1 << 6)
|
||||
#define SSI_SISR_TLS (1 << 5)
|
||||
#define SSI_SISR_RLS (1 << 4)
|
||||
#define SSI_SISR_RFF1 (1 << 3)
|
||||
#define SSI_SISR_RFF0 (1 << 2)
|
||||
#define SSI_SISR_TFE1 (1 << 1)
|
||||
#define SSI_SISR_TFE0 (1 << 0)
|
||||
|
||||
#define SSI_SIER 0x18
|
||||
#define SSI_SIER_RDMAE (1 << 22)
|
||||
#define SSI_SIER_RIE (1 << 21)
|
||||
#define SSI_SIER_TDMAE (1 << 20)
|
||||
#define SSI_SIER_TIE (1 << 19)
|
||||
#define SSI_SIER_CMDAU_EN (1 << 18)
|
||||
#define SSI_SIER_CMDDU_EN (1 << 17)
|
||||
#define SSI_SIER_RXT_EN (1 << 16)
|
||||
#define SSI_SIER_RDR1_EN (1 << 15)
|
||||
#define SSI_SIER_RDR0_EN (1 << 14)
|
||||
#define SSI_SIER_TDE1_EN (1 << 13)
|
||||
#define SSI_SIER_TDE0_EN (1 << 12)
|
||||
#define SSI_SIER_ROE1_EN (1 << 11)
|
||||
#define SSI_SIER_ROE0_EN (1 << 10)
|
||||
#define SSI_SIER_TUE1_EN (1 << 9)
|
||||
#define SSI_SIER_TUE0_EN (1 << 8)
|
||||
#define SSI_SIER_TFS_EN (1 << 7)
|
||||
#define SSI_SIER_RFS_EN (1 << 6)
|
||||
#define SSI_SIER_TLS_EN (1 << 5)
|
||||
#define SSI_SIER_RLS_EN (1 << 4)
|
||||
#define SSI_SIER_RFF1_EN (1 << 3)
|
||||
#define SSI_SIER_RFF0_EN (1 << 2)
|
||||
#define SSI_SIER_TFE1_EN (1 << 1)
|
||||
#define SSI_SIER_TFE0_EN (1 << 0)
|
||||
|
||||
#define SSI_STCR 0x1c
|
||||
#define SSI_STCR_TXBIT0 (1 << 9)
|
||||
#define SSI_STCR_TFEN1 (1 << 8)
|
||||
#define SSI_STCR_TFEN0 (1 << 7)
|
||||
#define SSI_FIFO_ENABLE_0_SHIFT 7
|
||||
#define SSI_STCR_TFDIR (1 << 6)
|
||||
#define SSI_STCR_TXDIR (1 << 5)
|
||||
#define SSI_STCR_TSHFD (1 << 4)
|
||||
#define SSI_STCR_TSCKP (1 << 3)
|
||||
#define SSI_STCR_TFSI (1 << 2)
|
||||
#define SSI_STCR_TFSL (1 << 1)
|
||||
#define SSI_STCR_TEFS (1 << 0)
|
||||
|
||||
#define SSI_SRCR 0x20
|
||||
#define SSI_SRCR_RXBIT0 (1 << 9)
|
||||
#define SSI_SRCR_RFEN1 (1 << 8)
|
||||
#define SSI_SRCR_RFEN0 (1 << 7)
|
||||
#define SSI_FIFO_ENABLE_0_SHIFT 7
|
||||
#define SSI_SRCR_RFDIR (1 << 6)
|
||||
#define SSI_SRCR_RXDIR (1 << 5)
|
||||
#define SSI_SRCR_RSHFD (1 << 4)
|
||||
#define SSI_SRCR_RSCKP (1 << 3)
|
||||
#define SSI_SRCR_RFSI (1 << 2)
|
||||
#define SSI_SRCR_RFSL (1 << 1)
|
||||
#define SSI_SRCR_REFS (1 << 0)
|
||||
|
||||
#define SSI_SRCCR 0x28
|
||||
#define SSI_SRCCR_DIV2 (1 << 18)
|
||||
#define SSI_SRCCR_PSR (1 << 17)
|
||||
#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13)
|
||||
#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8)
|
||||
#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0)
|
||||
#define SSI_SRCCR_WL_MASK (0xf << 13)
|
||||
#define SSI_SRCCR_DC_MASK (0x1f << 8)
|
||||
#define SSI_SRCCR_PM_MASK (0xff << 0)
|
||||
|
||||
#define SSI_STCCR 0x24
|
||||
#define SSI_STCCR_DIV2 (1 << 18)
|
||||
#define SSI_STCCR_PSR (1 << 17)
|
||||
#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13)
|
||||
#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8)
|
||||
#define SSI_STCCR_PM(x) (((x) & 0xff) << 0)
|
||||
#define SSI_STCCR_WL_MASK (0xf << 13)
|
||||
#define SSI_STCCR_DC_MASK (0x1f << 8)
|
||||
#define SSI_STCCR_PM_MASK (0xff << 0)
|
||||
|
||||
#define SSI_SFCSR 0x2c
|
||||
#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28)
|
||||
#define SSI_RX_FIFO_1_COUNT_SHIFT 28
|
||||
#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24)
|
||||
#define SSI_TX_FIFO_1_COUNT_SHIFT 24
|
||||
#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20)
|
||||
#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16)
|
||||
#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12)
|
||||
#define SSI_RX_FIFO_0_COUNT_SHIFT 12
|
||||
#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8)
|
||||
#define SSI_TX_FIFO_0_COUNT_SHIFT 8
|
||||
#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4)
|
||||
#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0)
|
||||
#define SSI_SFCSR_RFWM0_MASK (0xf << 4)
|
||||
#define SSI_SFCSR_TFWM0_MASK (0xf << 0)
|
||||
|
||||
#define SSI_STR 0x30
|
||||
#define SSI_STR_TEST (1 << 15)
|
||||
#define SSI_STR_RCK2TCK (1 << 14)
|
||||
#define SSI_STR_RFS2TFS (1 << 13)
|
||||
#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8)
|
||||
#define SSI_STR_TXD2RXD (1 << 7)
|
||||
#define SSI_STR_TCK2RCK (1 << 6)
|
||||
#define SSI_STR_TFS2RFS (1 << 5)
|
||||
#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0)
|
||||
|
||||
#define SSI_SOR 0x34
|
||||
#define SSI_SOR_CLKOFF (1 << 6)
|
||||
#define SSI_SOR_RX_CLR (1 << 5)
|
||||
#define SSI_SOR_TX_CLR (1 << 4)
|
||||
#define SSI_SOR_INIT (1 << 3)
|
||||
#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1)
|
||||
#define SSI_SOR_WAIT_MASK (0x3 << 1)
|
||||
#define SSI_SOR_SYNRST (1 << 0)
|
||||
|
||||
#define SSI_SACNT 0x38
|
||||
#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
|
||||
#define SSI_SACNT_WR (1 << 4)
|
||||
#define SSI_SACNT_RD (1 << 3)
|
||||
#define SSI_SACNT_TIF (1 << 2)
|
||||
#define SSI_SACNT_FV (1 << 1)
|
||||
#define SSI_SACNT_AC97EN (1 << 0)
|
||||
|
||||
#define SSI_SACADD 0x3c
|
||||
#define SSI_SACDAT 0x40
|
||||
#define SSI_SATAG 0x44
|
||||
#define SSI_STMSK 0x48
|
||||
#define SSI_SRMSK 0x4c
|
||||
#define SSI_SACCST 0x50
|
||||
#define SSI_SACCEN 0x54
|
||||
#define SSI_SACCDIS 0x58
|
||||
|
||||
/* SSI clock sources */
|
||||
#define IMX_SSP_SYS_CLK 0
|
||||
|
||||
/* SSI audio dividers */
|
||||
#define IMX_SSI_TX_DIV_2 0
|
||||
#define IMX_SSI_TX_DIV_PSR 1
|
||||
#define IMX_SSI_TX_DIV_PM 2
|
||||
#define IMX_SSI_RX_DIV_2 3
|
||||
#define IMX_SSI_RX_DIV_PSR 4
|
||||
#define IMX_SSI_RX_DIV_PM 5
|
||||
|
||||
extern struct snd_soc_dai imx_ssi_pcm_dai[2];
|
||||
extern struct snd_soc_platform imx_soc_platform;
|
||||
|
||||
#define DRV_NAME "imx-ssi"
|
||||
|
||||
struct imx_pcm_dma_params {
|
||||
int dma;
|
||||
unsigned long dma_addr;
|
||||
int burstsize;
|
||||
};
|
||||
|
||||
struct imx_ssi {
|
||||
struct platform_device *ac97_dev;
|
||||
|
||||
struct snd_soc_device imx_ac97;
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
int fiq_enable;
|
||||
unsigned int offset;
|
||||
|
||||
unsigned int flags;
|
||||
|
||||
void (*ac97_reset) (struct snd_ac97 *ac97);
|
||||
void (*ac97_warm_reset)(struct snd_ac97 *ac97);
|
||||
|
||||
struct imx_pcm_dma_params dma_params_rx;
|
||||
struct imx_pcm_dma_params dma_params_tx;
|
||||
|
||||
int enabled;
|
||||
};
|
||||
|
||||
struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
|
||||
struct imx_ssi *ssi);
|
||||
void imx_ssi_fiq_exit(struct platform_device *pdev, struct imx_ssi *ssi);
|
||||
struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
|
||||
struct imx_ssi *ssi);
|
||||
|
||||
int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
|
||||
int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm);
|
||||
void imx_pcm_free(struct snd_pcm *pcm);
|
||||
|
||||
/*
|
||||
* Do not change this as the FIQ handler depends on this size
|
||||
*/
|
||||
#define IMX_SSI_DMABUF_SIZE (64 * 1024)
|
||||
|
||||
#define DMA_RXFIFO_BURST 0x4
|
||||
#define DMA_TXFIFO_BURST 0x6
|
||||
|
||||
#endif /* _IMX_SSI_H */
|
|
@ -1,488 +0,0 @@
|
|||
/*
|
||||
* mx1_mx2-pcm.c -- ALSA SoC interface for Freescale i.MX1x, i.MX2x CPUs
|
||||
*
|
||||
* Copyright 2009 Vista Silicon S.L.
|
||||
* Author: Javier Martin
|
||||
* javier.martin@vista-silicon.com
|
||||
*
|
||||
* Based on mxc-pcm.c by 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/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/dma.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <mach/dma-mx1-mx2.h>
|
||||
|
||||
#include "mx1_mx2-pcm.h"
|
||||
|
||||
|
||||
static const struct snd_pcm_hardware mx1_mx2_pcm_hardware = {
|
||||
.info = (SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID),
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.buffer_bytes_max = 32 * 1024,
|
||||
.period_bytes_min = 64,
|
||||
.period_bytes_max = 8 * 1024,
|
||||
.periods_min = 2,
|
||||
.periods_max = 255,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
struct mx1_mx2_runtime_data {
|
||||
int dma_ch;
|
||||
int active;
|
||||
unsigned int period;
|
||||
unsigned int periods;
|
||||
int tx_spin;
|
||||
spinlock_t dma_lock;
|
||||
struct mx1_mx2_pcm_dma_params *dma_params;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This function stops the current dma transfer for playback
|
||||
* and clears the dma pointers.
|
||||
*
|
||||
* @param substream pointer to the structure of the current stream.
|
||||
*
|
||||
*/
|
||||
static int audio_stop_dma(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prtd->dma_lock, flags);
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
prtd->active = 0;
|
||||
prtd->period = 0;
|
||||
prtd->periods = 0;
|
||||
|
||||
/* this stops the dma channel and clears the buffer ptrs */
|
||||
|
||||
imx_dma_disable(prtd->dma_ch);
|
||||
|
||||
spin_unlock_irqrestore(&prtd->dma_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called whenever a new audio block needs to be
|
||||
* transferred to the codec. The function receives the address and the size
|
||||
* of the new block and start a new DMA transfer.
|
||||
*
|
||||
* @param substream pointer to the structure of the current stream.
|
||||
*
|
||||
*/
|
||||
static int dma_new_period(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
unsigned int dma_size;
|
||||
unsigned int offset;
|
||||
int ret = 0;
|
||||
dma_addr_t mem_addr;
|
||||
unsigned int dev_addr;
|
||||
|
||||
if (prtd->active) {
|
||||
dma_size = frames_to_bytes(runtime, runtime->period_size);
|
||||
offset = dma_size * prtd->period;
|
||||
|
||||
pr_debug("%s: period (%d) out of (%d)\n", __func__,
|
||||
prtd->period,
|
||||
runtime->periods);
|
||||
pr_debug("period_size %d frames\n offset %d bytes\n",
|
||||
(unsigned int)runtime->period_size,
|
||||
offset);
|
||||
pr_debug("dma_size %d bytes\n", dma_size);
|
||||
|
||||
snd_BUG_ON(dma_size > mx1_mx2_pcm_hardware.period_bytes_max);
|
||||
|
||||
mem_addr = (dma_addr_t)(runtime->dma_addr + offset);
|
||||
dev_addr = prtd->dma_params->per_address;
|
||||
pr_debug("%s: mem_addr is %x\n dev_addr is %x\n",
|
||||
__func__, mem_addr, dev_addr);
|
||||
|
||||
ret = imx_dma_setup_single(prtd->dma_ch, mem_addr,
|
||||
dma_size, dev_addr,
|
||||
prtd->dma_params->transfer_type);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error %d configuring DMA\n", ret);
|
||||
return ret;
|
||||
}
|
||||
imx_dma_enable(prtd->dma_ch);
|
||||
|
||||
pr_debug("%s: transfer enabled\nmem_addr = %x\n",
|
||||
__func__, (unsigned int) mem_addr);
|
||||
pr_debug("dev_addr = %x\ndma_size = %d\n",
|
||||
(unsigned int) dev_addr, dma_size);
|
||||
|
||||
prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */
|
||||
prtd->period++;
|
||||
prtd->period %= runtime->periods;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a callback which will be called
|
||||
* when a TX transfer finishes. The call occurs
|
||||
* in interrupt context.
|
||||
*
|
||||
* @param dat pointer to the structure of the current stream.
|
||||
*
|
||||
*/
|
||||
static void audio_dma_irq(int channel, void *data)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_pcm_runtime *runtime;
|
||||
struct mx1_mx2_runtime_data *prtd;
|
||||
unsigned int dma_size;
|
||||
unsigned int previous_period;
|
||||
unsigned int offset;
|
||||
|
||||
substream = data;
|
||||
runtime = substream->runtime;
|
||||
prtd = runtime->private_data;
|
||||
previous_period = prtd->periods;
|
||||
dma_size = frames_to_bytes(runtime, runtime->period_size);
|
||||
offset = dma_size * previous_period;
|
||||
|
||||
prtd->tx_spin = 0;
|
||||
prtd->periods++;
|
||||
prtd->periods %= runtime->periods;
|
||||
|
||||
pr_debug("%s: irq per %d offset %x\n", __func__, prtd->periods, offset);
|
||||
|
||||
/*
|
||||
* If we are getting a callback for an active stream then we inform
|
||||
* the PCM middle layer we've finished a period
|
||||
*/
|
||||
if (prtd->active)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
/*
|
||||
* Trig next DMA transfer
|
||||
*/
|
||||
dma_new_period(substream);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function configures the hardware to allow audio
|
||||
* playback operations. It is called by ALSA framework.
|
||||
*
|
||||
* @param substream pointer to the structure of the current stream.
|
||||
*
|
||||
* @return 0 on success, -1 otherwise.
|
||||
*/
|
||||
static int
|
||||
snd_mx1_mx2_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
prtd->period = 0;
|
||||
prtd->periods = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int ret;
|
||||
|
||||
ret = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "%s: Error %d failed to malloc pcm pages \n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_addr 0x(%x)\n",
|
||||
__func__, (unsigned int)runtime->dma_addr);
|
||||
pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_area 0x(%x)\n",
|
||||
__func__, (unsigned int)runtime->dma_area);
|
||||
pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_bytes 0x(%x)\n",
|
||||
__func__, (unsigned int)runtime->dma_bytes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
imx_dma_free(prtd->dma_ch);
|
||||
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct mx1_mx2_runtime_data *prtd = substream->runtime->private_data;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
prtd->tx_spin = 0;
|
||||
/* requested stream startup */
|
||||
prtd->active = 1;
|
||||
pr_debug("%s: starting dma_new_period\n", __func__);
|
||||
ret = dma_new_period(substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
/* requested stream shutdown */
|
||||
pr_debug("%s: stopping dma transfer\n", __func__);
|
||||
ret = audio_stop_dma(substream);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
mx1_mx2_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
unsigned int offset = 0;
|
||||
|
||||
/* tx_spin value is used here to check if a transfer is active */
|
||||
if (prtd->tx_spin) {
|
||||
offset = (runtime->period_size * (prtd->periods)) +
|
||||
(runtime->period_size >> 1);
|
||||
if (offset >= runtime->buffer_size)
|
||||
offset = runtime->period_size >> 1;
|
||||
} else {
|
||||
offset = (runtime->period_size * (prtd->periods));
|
||||
if (offset >= runtime->buffer_size)
|
||||
offset = 0;
|
||||
}
|
||||
pr_debug("%s: pointer offset %x\n", __func__, offset);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct mx1_mx2_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
|
||||
int ret;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &mx1_mx2_pcm_hardware);
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
prtd = kzalloc(sizeof(struct mx1_mx2_runtime_data), GFP_KERNEL);
|
||||
if (prtd == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
runtime->private_data = prtd;
|
||||
|
||||
if (!dma_data)
|
||||
return -ENODEV;
|
||||
|
||||
prtd->dma_params = dma_data;
|
||||
|
||||
pr_debug("%s: Requesting dma channel (%s)\n", __func__,
|
||||
prtd->dma_params->name);
|
||||
ret = imx_dma_request_by_prio(prtd->dma_params->name, DMA_PRIO_HIGH);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error %d requesting dma channel\n", ret);
|
||||
return ret;
|
||||
}
|
||||
prtd->dma_ch = ret;
|
||||
imx_dma_config_burstlen(prtd->dma_ch,
|
||||
prtd->dma_params->watermark_level);
|
||||
|
||||
ret = imx_dma_config_channel(prtd->dma_ch,
|
||||
prtd->dma_params->per_config,
|
||||
prtd->dma_params->mem_config,
|
||||
prtd->dma_params->event_id, 0);
|
||||
|
||||
if (ret) {
|
||||
pr_debug(KERN_ERR "Error %d configuring dma channel %d\n",
|
||||
ret, prtd->dma_ch);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("%s: Setting tx dma callback function\n", __func__);
|
||||
ret = imx_dma_setup_handlers(prtd->dma_ch,
|
||||
audio_dma_irq, NULL,
|
||||
(void *)substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error %d setting dma callback function\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
kfree(prtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
return dma_mmap_writecombine(substream->pcm->card->dev, vma,
|
||||
runtime->dma_area,
|
||||
runtime->dma_addr,
|
||||
runtime->dma_bytes);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops mx1_mx2_pcm_ops = {
|
||||
.open = mx1_mx2_pcm_open,
|
||||
.close = mx1_mx2_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = mx1_mx2_pcm_hw_params,
|
||||
.hw_free = mx1_mx2_pcm_hw_free,
|
||||
.prepare = snd_mx1_mx2_prepare,
|
||||
.trigger = mx1_mx2_pcm_trigger,
|
||||
.pointer = mx1_mx2_pcm_pointer,
|
||||
.mmap = mx1_mx2_pcm_mmap,
|
||||
};
|
||||
|
||||
static u64 mx1_mx2_pcm_dmamask = 0xffffffff;
|
||||
|
||||
static int mx1_mx2_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = mx1_mx2_pcm_hardware.buffer_bytes_max;
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
|
||||
/* Reserve uncached-buffered memory area for DMA */
|
||||
buf->area = dma_alloc_writecombine(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
|
||||
pr_debug("%s: preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
|
||||
__func__, (void *) buf->area, (void *) buf->addr, size);
|
||||
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
|
||||
buf->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mx1_mx2_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
|
||||
dma_free_writecombine(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &mx1_mx2_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = 0xffffffff;
|
||||
|
||||
if (dai->playback.channels_min) {
|
||||
ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
pr_debug("%s: preallocate playback buffer\n", __func__);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dai->capture.channels_min) {
|
||||
ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
pr_debug("%s: preallocate capture buffer\n", __func__);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct snd_soc_platform mx1_mx2_soc_platform = {
|
||||
.name = "mx1_mx2-audio",
|
||||
.pcm_ops = &mx1_mx2_pcm_ops,
|
||||
.pcm_new = mx1_mx2_pcm_new,
|
||||
.pcm_free = mx1_mx2_pcm_free_dma_buffers,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(mx1_mx2_soc_platform);
|
||||
|
||||
static int __init mx1_mx2_soc_platform_init(void)
|
||||
{
|
||||
return snd_soc_register_platform(&mx1_mx2_soc_platform);
|
||||
}
|
||||
module_init(mx1_mx2_soc_platform_init);
|
||||
|
||||
static void __exit mx1_mx2_soc_platform_exit(void)
|
||||
{
|
||||
snd_soc_unregister_platform(&mx1_mx2_soc_platform);
|
||||
}
|
||||
module_exit(mx1_mx2_soc_platform_exit);
|
||||
|
||||
MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com");
|
||||
MODULE_DESCRIPTION("Freescale i.MX2x, i.MX1x PCM DMA module");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* mx1_mx2-pcm.h :- ASoC platform header for Freescale i.MX1x, i.MX2x
|
||||
*
|
||||
* 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 _MX1_MX2_PCM_H
|
||||
#define _MX1_MX2_PCM_H
|
||||
|
||||
/* DMA information for mx1_mx2 platforms */
|
||||
struct mx1_mx2_pcm_dma_params {
|
||||
char *name; /* stream identifier */
|
||||
unsigned int transfer_type; /* READ or WRITE DMA transfer */
|
||||
dma_addr_t per_address; /* physical address of SSI fifo */
|
||||
int event_id; /* fixed DMA number for SSI fifo */
|
||||
int watermark_level; /* SSI fifo watermark level */
|
||||
int per_config; /* DMA Config flags for peripheral */
|
||||
int mem_config; /* DMA Config flags for RAM */
|
||||
};
|
||||
|
||||
/* platform data */
|
||||
extern struct snd_soc_platform mx1_mx2_soc_platform;
|
||||
|
||||
#endif
|
|
@ -1,318 +0,0 @@
|
|||
/*
|
||||
* mx27vis_wm8974.c -- SoC audio for mx27vis
|
||||
*
|
||||
* Copyright 2009 Vista Silicon S.L.
|
||||
* Author: Javier Martin
|
||||
* javier.martin@vista-silicon.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
|
||||
#include "../codecs/wm8974.h"
|
||||
#include "mx1_mx2-pcm.h"
|
||||
#include "mxc-ssi.h"
|
||||
#include <mach/gpio.h>
|
||||
#include <mach/iomux.h>
|
||||
|
||||
#define IGNORED_ARG 0
|
||||
|
||||
|
||||
static struct snd_soc_card mx27vis;
|
||||
|
||||
/**
|
||||
* This function connects SSI1 (HPCR1) as slave to
|
||||
* SSI1 external signals (PPCR1)
|
||||
* As slave, HPCR1 must set TFSDIR and TCLKDIR as inputs from
|
||||
* port 4
|
||||
*/
|
||||
void audmux_connect_1_4(void)
|
||||
{
|
||||
pr_debug("AUDMUX: normal operation mode\n");
|
||||
/* Reset HPCR1 and PPCR1 */
|
||||
|
||||
DAM_HPCR1 = 0x00000000;
|
||||
DAM_PPCR1 = 0x00000000;
|
||||
|
||||
/* set to synchronous */
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_SYN;
|
||||
DAM_PPCR1 |= AUDMUX_PPCR_SYN;
|
||||
|
||||
|
||||
/* set Rx sources 1 <--> 4 */
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_RXDSEL(3); /* port 4 */
|
||||
DAM_PPCR1 |= AUDMUX_PPCR_RXDSEL(0); /* port 1 */
|
||||
|
||||
/* set Tx frame and Clock direction and source 4 --> 1 output */
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_TFSDIR | AUDMUX_HPCR_TCLKDIR;
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_TFCSEL(3); /* TxDS and TxCclk from port 4 */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int mx27vis_hifi_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 pll_out = 0, bclk = 0, fmt = 0, mclk = 0;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* The WM8974 is better at generating accurate audio clocks than the
|
||||
* MX27 SSI controller, so we will use it as master when we can.
|
||||
*/
|
||||
switch (params_rate(params)) {
|
||||
case 8000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
mclk = WM8974_MCLKDIV_12;
|
||||
pll_out = 24576000;
|
||||
break;
|
||||
case 16000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
pll_out = 12288000;
|
||||
break;
|
||||
case 48000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_4;
|
||||
pll_out = 12288000;
|
||||
break;
|
||||
case 96000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_2;
|
||||
pll_out = 12288000;
|
||||
break;
|
||||
case 11025:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_16;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
case 22050:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_8;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
case 44100:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_4;
|
||||
mclk = WM8974_MCLKDIV_2;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
case 88200:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_2;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
}
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = codec_dai->ops->set_fmt(codec_dai,
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
|
||||
SND_SOC_DAIFMT_SYNC | fmt);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error from codec DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = cpu_dai->ops->set_fmt(cpu_dai,
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_SYNC | fmt);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error from cpu DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Put DC field of STCCR to 1 (not zero) */
|
||||
ret = cpu_dai->ops->set_tdm_slot(cpu_dai, 0, 2);
|
||||
|
||||
/* set the SSI system clock as input */
|
||||
ret = cpu_dai->ops->set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting system SSI clk\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set codec BCLK division for sample rate */
|
||||
ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_BCLKDIV, bclk);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting BCLK division\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* codec PLL input is 25 MHz */
|
||||
ret = codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, IGNORED_ARG,
|
||||
25000000, pll_out);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting PLL input\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*set codec MCLK division for sample rate */
|
||||
ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_MCLKDIV, mclk);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting MCLK division\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_hifi_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
|
||||
/* disable the PLL */
|
||||
return codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, IGNORED_ARG,
|
||||
0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* mx27vis WM8974 HiFi DAI opserations.
|
||||
*/
|
||||
static struct snd_soc_ops mx27vis_hifi_ops = {
|
||||
.hw_params = mx27vis_hifi_hw_params,
|
||||
.hw_free = mx27vis_hifi_hw_free,
|
||||
};
|
||||
|
||||
|
||||
static int mx27vis_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_resume(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = get_ssi_clk(0, &pdev->dev);
|
||||
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "%s: cant get ssi clock\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_remove(struct platform_device *pdev)
|
||||
{
|
||||
put_ssi_clk(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_link mx27vis_dai[] = {
|
||||
{ /* Hifi Playback*/
|
||||
.name = "WM8974",
|
||||
.stream_name = "WM8974 HiFi",
|
||||
.cpu_dai = &imx_ssi_pcm_dai[0],
|
||||
.codec_dai = &wm8974_dai,
|
||||
.ops = &mx27vis_hifi_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card mx27vis = {
|
||||
.name = "mx27vis",
|
||||
.platform = &mx1_mx2_soc_platform,
|
||||
.probe = mx27vis_probe,
|
||||
.remove = mx27vis_remove,
|
||||
.suspend_pre = mx27vis_suspend,
|
||||
.resume_post = mx27vis_resume,
|
||||
.dai_link = mx27vis_dai,
|
||||
.num_links = ARRAY_SIZE(mx27vis_dai),
|
||||
};
|
||||
|
||||
static struct snd_soc_device mx27vis_snd_devdata = {
|
||||
.card = &mx27vis,
|
||||
.codec_dev = &soc_codec_dev_wm8974,
|
||||
};
|
||||
|
||||
static struct platform_device *mx27vis_snd_device;
|
||||
|
||||
/* Temporal definition of board specific behaviour */
|
||||
void gpio_ssi_active(int ssi_num)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
unsigned int ssi1_pins[] = {
|
||||
PC20_PF_SSI1_FS,
|
||||
PC21_PF_SSI1_RXD,
|
||||
PC22_PF_SSI1_TXD,
|
||||
PC23_PF_SSI1_CLK,
|
||||
};
|
||||
unsigned int ssi2_pins[] = {
|
||||
PC24_PF_SSI2_FS,
|
||||
PC25_PF_SSI2_RXD,
|
||||
PC26_PF_SSI2_TXD,
|
||||
PC27_PF_SSI2_CLK,
|
||||
};
|
||||
if (ssi_num == 0)
|
||||
ret = mxc_gpio_setup_multiple_pins(ssi1_pins,
|
||||
ARRAY_SIZE(ssi1_pins), "USB OTG");
|
||||
else
|
||||
ret = mxc_gpio_setup_multiple_pins(ssi2_pins,
|
||||
ARRAY_SIZE(ssi2_pins), "USB OTG");
|
||||
if (ret)
|
||||
printk(KERN_ERR "Error requesting ssi %x pins\n", ssi_num);
|
||||
}
|
||||
|
||||
|
||||
static int __init mx27vis_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mx27vis_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!mx27vis_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(mx27vis_snd_device, &mx27vis_snd_devdata);
|
||||
mx27vis_snd_devdata.dev = &mx27vis_snd_device->dev;
|
||||
ret = platform_device_add(mx27vis_snd_device);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
platform_device_put(mx27vis_snd_device);
|
||||
}
|
||||
|
||||
/* WM8974 uses SSI1 (HPCR1) via AUDMUX port 4 for audio (PPCR1) */
|
||||
gpio_ssi_active(0);
|
||||
audmux_connect_1_4();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mx27vis_exit(void)
|
||||
{
|
||||
/* We should call some "ssi_gpio_inactive()" properly */
|
||||
}
|
||||
|
||||
module_init(mx27vis_init);
|
||||
module_exit(mx27vis_exit);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com");
|
||||
MODULE_DESCRIPTION("ALSA SoC WM8974 mx27vis");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,860 +0,0 @@
|
|||
/*
|
||||
* mxc-ssi.c -- SSI driver for Freescale IMX
|
||||
*
|
||||
* Copyright 2006 Wolfson Microelectronics PLC.
|
||||
* Author: Liam Girdwood
|
||||
* liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
|
||||
*
|
||||
* Based on mxc-alsa-mc13783 (C) 2006 Freescale.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* TODO:
|
||||
* Need to rework SSI register defs when new defs go into mainline.
|
||||
* Add support for TDM and FIFO 1.
|
||||
* Add support for i.mx3x DMA interface.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <mach/dma-mx1-mx2.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "mxc-ssi.h"
|
||||
#include "mx1_mx2-pcm.h"
|
||||
|
||||
#define SSI1_PORT 0
|
||||
#define SSI2_PORT 1
|
||||
|
||||
static int ssi_active[2] = {0, 0};
|
||||
|
||||
/* DMA information for mx1_mx2 platforms */
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out0 = {
|
||||
.name = "SSI1 PCM Stereo out 0",
|
||||
.transfer_type = DMA_MODE_WRITE,
|
||||
.per_address = SSI1_BASE_ADDR + STX0,
|
||||
.event_id = DMA_REQ_SSI1_TX0,
|
||||
.watermark_level = TXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out1 = {
|
||||
.name = "SSI1 PCM Stereo out 1",
|
||||
.transfer_type = DMA_MODE_WRITE,
|
||||
.per_address = SSI1_BASE_ADDR + STX1,
|
||||
.event_id = DMA_REQ_SSI1_TX1,
|
||||
.watermark_level = TXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in0 = {
|
||||
.name = "SSI1 PCM Stereo in 0",
|
||||
.transfer_type = DMA_MODE_READ,
|
||||
.per_address = SSI1_BASE_ADDR + SRX0,
|
||||
.event_id = DMA_REQ_SSI1_RX0,
|
||||
.watermark_level = RXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in1 = {
|
||||
.name = "SSI1 PCM Stereo in 1",
|
||||
.transfer_type = DMA_MODE_READ,
|
||||
.per_address = SSI1_BASE_ADDR + SRX1,
|
||||
.event_id = DMA_REQ_SSI1_RX1,
|
||||
.watermark_level = RXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out0 = {
|
||||
.name = "SSI2 PCM Stereo out 0",
|
||||
.transfer_type = DMA_MODE_WRITE,
|
||||
.per_address = SSI2_BASE_ADDR + STX0,
|
||||
.event_id = DMA_REQ_SSI2_TX0,
|
||||
.watermark_level = TXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out1 = {
|
||||
.name = "SSI2 PCM Stereo out 1",
|
||||
.transfer_type = DMA_MODE_WRITE,
|
||||
.per_address = SSI2_BASE_ADDR + STX1,
|
||||
.event_id = DMA_REQ_SSI2_TX1,
|
||||
.watermark_level = TXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in0 = {
|
||||
.name = "SSI2 PCM Stereo in 0",
|
||||
.transfer_type = DMA_MODE_READ,
|
||||
.per_address = SSI2_BASE_ADDR + SRX0,
|
||||
.event_id = DMA_REQ_SSI2_RX0,
|
||||
.watermark_level = RXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in1 = {
|
||||
.name = "SSI2 PCM Stereo in 1",
|
||||
.transfer_type = DMA_MODE_READ,
|
||||
.per_address = SSI2_BASE_ADDR + SRX1,
|
||||
.event_id = DMA_REQ_SSI2_RX1,
|
||||
.watermark_level = RXFIFO_WATERMARK,
|
||||
.per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
|
||||
.mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
static struct clk *ssi_clk0, *ssi_clk1;
|
||||
|
||||
int get_ssi_clk(int ssi, struct device *dev)
|
||||
{
|
||||
switch (ssi) {
|
||||
case 0:
|
||||
ssi_clk0 = clk_get(dev, "ssi1");
|
||||
if (IS_ERR(ssi_clk0))
|
||||
return PTR_ERR(ssi_clk0);
|
||||
return 0;
|
||||
case 1:
|
||||
ssi_clk1 = clk_get(dev, "ssi2");
|
||||
if (IS_ERR(ssi_clk1))
|
||||
return PTR_ERR(ssi_clk1);
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(get_ssi_clk);
|
||||
|
||||
void put_ssi_clk(int ssi)
|
||||
{
|
||||
switch (ssi) {
|
||||
case 0:
|
||||
clk_put(ssi_clk0);
|
||||
ssi_clk0 = NULL;
|
||||
break;
|
||||
case 1:
|
||||
clk_put(ssi_clk1);
|
||||
ssi_clk1 = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(put_ssi_clk);
|
||||
|
||||
/*
|
||||
* SSI system clock configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
u32 scr;
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
scr = SSI1_SCR;
|
||||
pr_debug("%s: SCR for SSI1 is %x\n", __func__, scr);
|
||||
} else {
|
||||
scr = SSI2_SCR;
|
||||
pr_debug("%s: SCR for SSI2 is %x\n", __func__, scr);
|
||||
}
|
||||
|
||||
if (scr & SSI_SCR_SSIEN) {
|
||||
printk(KERN_WARNING "Warning ssi already enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (clk_id) {
|
||||
case IMX_SSP_SYS_CLK:
|
||||
if (dir == SND_SOC_CLOCK_OUT) {
|
||||
scr |= SSI_SCR_SYS_CLK_EN;
|
||||
pr_debug("%s: clk of is output\n", __func__);
|
||||
} else {
|
||||
scr &= ~SSI_SCR_SYS_CLK_EN;
|
||||
pr_debug("%s: clk of is input\n", __func__);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
pr_debug("%s: writeback of SSI1_SCR\n", __func__);
|
||||
SSI1_SCR = scr;
|
||||
} else {
|
||||
pr_debug("%s: writeback of SSI2_SCR\n", __func__);
|
||||
SSI2_SCR = scr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI Clock dividers
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
u32 stccr, srccr;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
if (SSI1_SCR & SSI_SCR_SSIEN)
|
||||
return 0;
|
||||
srccr = SSI1_STCCR;
|
||||
stccr = SSI1_STCCR;
|
||||
} else {
|
||||
if (SSI2_SCR & SSI_SCR_SSIEN)
|
||||
return 0;
|
||||
srccr = SSI2_STCCR;
|
||||
stccr = SSI2_STCCR;
|
||||
}
|
||||
|
||||
switch (div_id) {
|
||||
case IMX_SSI_TX_DIV_2:
|
||||
stccr &= ~SSI_STCCR_DIV2;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_TX_DIV_PSR:
|
||||
stccr &= ~SSI_STCCR_PSR;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_TX_DIV_PM:
|
||||
stccr &= ~0xff;
|
||||
stccr |= SSI_STCCR_PM(div);
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_2:
|
||||
stccr &= ~SSI_STCCR_DIV2;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_PSR:
|
||||
stccr &= ~SSI_STCCR_PSR;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_PM:
|
||||
stccr &= ~0xff;
|
||||
stccr |= SSI_STCCR_PM(div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
SSI1_STCCR = stccr;
|
||||
SSI1_SRCCR = srccr;
|
||||
} else {
|
||||
SSI2_STCCR = stccr;
|
||||
SSI2_SRCCR = srccr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI Network Mode or TDM slots configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int mask, int slots)
|
||||
{
|
||||
u32 stmsk, srmsk, stccr;
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
if (SSI1_SCR & SSI_SCR_SSIEN) {
|
||||
printk(KERN_WARNING "Warning ssi already enabled\n");
|
||||
return 0;
|
||||
}
|
||||
stccr = SSI1_STCCR;
|
||||
} else {
|
||||
if (SSI2_SCR & SSI_SCR_SSIEN) {
|
||||
printk(KERN_WARNING "Warning ssi already enabled\n");
|
||||
return 0;
|
||||
}
|
||||
stccr = SSI2_STCCR;
|
||||
}
|
||||
|
||||
stmsk = srmsk = mask;
|
||||
stccr &= ~SSI_STCCR_DC_MASK;
|
||||
stccr |= SSI_STCCR_DC(slots - 1);
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
SSI1_STMSK = stmsk;
|
||||
SSI1_SRMSK = srmsk;
|
||||
SSI1_SRCCR = SSI1_STCCR = stccr;
|
||||
} else {
|
||||
SSI2_STMSK = stmsk;
|
||||
SSI2_SRMSK = srmsk;
|
||||
SSI2_SRCCR = SSI2_STCCR = stccr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI DAI format configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
* Note: We don't use the I2S modes but instead manually configure the
|
||||
* SSI for I2S.
|
||||
*/
|
||||
static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
u32 stcr = 0, srcr = 0, scr;
|
||||
|
||||
/*
|
||||
* This is done to avoid this function to modify
|
||||
* previous set values in stcr
|
||||
*/
|
||||
stcr = SSI1_STCR;
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
|
||||
scr = SSI1_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET);
|
||||
else
|
||||
scr = SSI2_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET);
|
||||
|
||||
if (scr & SSI_SCR_SSIEN) {
|
||||
printk(KERN_WARNING "Warning ssi already enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DAI mode */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
/* data on rising edge of bclk, frame low 1clk before data */
|
||||
stcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
|
||||
srcr |= SSI_SRCR_RFSI | SSI_SRCR_REFS | SSI_SRCR_RXBIT0;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
/* data on rising edge of bclk, frame high with data */
|
||||
stcr |= SSI_STCR_TXBIT0;
|
||||
srcr |= SSI_SRCR_RXBIT0;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
/* data on rising edge of bclk, frame high with data */
|
||||
stcr |= SSI_STCR_TFSL;
|
||||
srcr |= SSI_SRCR_RFSL;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
/* data on rising edge of bclk, frame high 1clk before data */
|
||||
stcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
|
||||
srcr |= SSI_SRCR_RFSL | SSI_SRCR_REFS;
|
||||
break;
|
||||
}
|
||||
|
||||
/* DAI clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
stcr |= SSI_STCR_TFSI;
|
||||
stcr &= ~SSI_STCR_TSCKP;
|
||||
srcr |= SSI_SRCR_RFSI;
|
||||
srcr &= ~SSI_SRCR_RSCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
stcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
|
||||
srcr &= ~(SSI_SRCR_RSCKP | SSI_SRCR_RFSI);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
|
||||
srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
stcr &= ~SSI_STCR_TFSI;
|
||||
stcr |= SSI_STCR_TSCKP;
|
||||
srcr &= ~SSI_SRCR_RFSI;
|
||||
srcr |= SSI_SRCR_RSCKP;
|
||||
break;
|
||||
}
|
||||
|
||||
/* DAI clock master masks */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
|
||||
srcr |= SSI_SRCR_RFDIR | SSI_SRCR_RXDIR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
stcr |= SSI_STCR_TFDIR;
|
||||
srcr |= SSI_SRCR_RFDIR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFM:
|
||||
stcr |= SSI_STCR_TXDIR;
|
||||
srcr |= SSI_SRCR_RXDIR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
SSI1_STCR = stcr;
|
||||
SSI1_SRCR = srcr;
|
||||
SSI1_SCR = scr;
|
||||
} else {
|
||||
SSI2_STCR = stcr;
|
||||
SSI2_SRCR = srcr;
|
||||
SSI2_SCR = scr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_ssi_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/* set up TX DMA params */
|
||||
switch (cpu_dai->id) {
|
||||
case IMX_DAI_SSI0:
|
||||
cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out0;
|
||||
break;
|
||||
case IMX_DAI_SSI1:
|
||||
cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out1;
|
||||
break;
|
||||
case IMX_DAI_SSI2:
|
||||
cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out0;
|
||||
break;
|
||||
case IMX_DAI_SSI3:
|
||||
cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out1;
|
||||
}
|
||||
pr_debug("%s: (playback)\n", __func__);
|
||||
} else {
|
||||
/* set up RX DMA params */
|
||||
switch (cpu_dai->id) {
|
||||
case IMX_DAI_SSI0:
|
||||
cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in0;
|
||||
break;
|
||||
case IMX_DAI_SSI1:
|
||||
cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in1;
|
||||
break;
|
||||
case IMX_DAI_SSI2:
|
||||
cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in0;
|
||||
break;
|
||||
case IMX_DAI_SSI3:
|
||||
cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in1;
|
||||
}
|
||||
pr_debug("%s: (capture)\n", __func__);
|
||||
}
|
||||
|
||||
/*
|
||||
* we cant really change any SSI values after SSI is enabled
|
||||
* need to fix in software for max flexibility - lrg
|
||||
*/
|
||||
if (cpu_dai->active) {
|
||||
printk(KERN_WARNING "Warning ssi already enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* reset the SSI port - Sect 45.4.4 */
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
|
||||
if (!ssi_clk0)
|
||||
return -EINVAL;
|
||||
|
||||
if (ssi_active[SSI1_PORT]++) {
|
||||
pr_debug("%s: exit before reset\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SSI1 Reset */
|
||||
SSI1_SCR = 0;
|
||||
|
||||
SSI1_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) |
|
||||
SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) |
|
||||
SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) |
|
||||
SSI_SFCSR_TFWM0(TXFIFO_WATERMARK);
|
||||
} else {
|
||||
|
||||
if (!ssi_clk1)
|
||||
return -EINVAL;
|
||||
|
||||
if (ssi_active[SSI2_PORT]++) {
|
||||
pr_debug("%s: exit before reset\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SSI2 Reset */
|
||||
SSI2_SCR = 0;
|
||||
|
||||
SSI2_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) |
|
||||
SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) |
|
||||
SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) |
|
||||
SSI_SFCSR_TFWM0(TXFIFO_WATERMARK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int imx_ssi_hw_tx_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
u32 stccr, stcr, sier;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
stccr = SSI1_STCCR & ~SSI_STCCR_WL_MASK;
|
||||
stcr = SSI1_STCR;
|
||||
sier = SSI1_SIER;
|
||||
} else {
|
||||
stccr = SSI2_STCCR & ~SSI_STCCR_WL_MASK;
|
||||
stcr = SSI2_STCR;
|
||||
sier = SSI2_SIER;
|
||||
}
|
||||
|
||||
/* DAI data (word) size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
stccr |= SSI_STCCR_WL(16);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
stccr |= SSI_STCCR_WL(20);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
stccr |= SSI_STCCR_WL(24);
|
||||
break;
|
||||
}
|
||||
|
||||
/* enable interrupts */
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
|
||||
stcr |= SSI_STCR_TFEN0;
|
||||
else
|
||||
stcr |= SSI_STCR_TFEN1;
|
||||
sier |= SSI_SIER_TDMAE;
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
SSI1_STCR = stcr;
|
||||
SSI1_STCCR = stccr;
|
||||
SSI1_SIER = sier;
|
||||
} else {
|
||||
SSI2_STCR = stcr;
|
||||
SSI2_STCCR = stccr;
|
||||
SSI2_SIER = sier;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int imx_ssi_hw_rx_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
u32 srccr, srcr, sier;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
srccr = SSI1_SRCCR & ~SSI_SRCCR_WL_MASK;
|
||||
srcr = SSI1_SRCR;
|
||||
sier = SSI1_SIER;
|
||||
} else {
|
||||
srccr = SSI2_SRCCR & ~SSI_SRCCR_WL_MASK;
|
||||
srcr = SSI2_SRCR;
|
||||
sier = SSI2_SIER;
|
||||
}
|
||||
|
||||
/* DAI data (word) size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
srccr |= SSI_SRCCR_WL(16);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
srccr |= SSI_SRCCR_WL(20);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
srccr |= SSI_SRCCR_WL(24);
|
||||
break;
|
||||
}
|
||||
|
||||
/* enable interrupts */
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
|
||||
srcr |= SSI_SRCR_RFEN0;
|
||||
else
|
||||
srcr |= SSI_SRCR_RFEN1;
|
||||
sier |= SSI_SIER_RDMAE;
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
SSI1_SRCR = srcr;
|
||||
SSI1_SRCCR = srccr;
|
||||
SSI1_SIER = sier;
|
||||
} else {
|
||||
SSI2_SRCR = srcr;
|
||||
SSI2_SRCCR = srccr;
|
||||
SSI2_SIER = sier;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0),
|
||||
* although can be called multiple times by upper layers.
|
||||
*/
|
||||
int imx_ssi_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_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
int ret;
|
||||
|
||||
/* cant change any parameters when SSI is running */
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
if (SSI1_SCR & SSI_SCR_SSIEN) {
|
||||
printk(KERN_WARNING "Warning ssi already enabled\n");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (SSI2_SCR & SSI_SCR_SSIEN) {
|
||||
printk(KERN_WARNING "Warning ssi already enabled\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure both tx and rx params with the same settings. This is
|
||||
* really a harware restriction because SSI must be disabled until
|
||||
* we can change those values. If there is an active audio stream in
|
||||
* one direction, enabling the other direction with different
|
||||
* settings would mean disturbing the running one.
|
||||
*/
|
||||
ret = imx_ssi_hw_tx_params(substream, params);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return imx_ssi_hw_rx_params(substream, params);
|
||||
}
|
||||
|
||||
int imx_ssi_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
int ret;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
/* Enable clks here to follow SSI recommended init sequence */
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
|
||||
ret = clk_enable(ssi_clk0);
|
||||
if (ret < 0)
|
||||
printk(KERN_ERR "Unable to enable ssi_clk0\n");
|
||||
} else {
|
||||
ret = clk_enable(ssi_clk1);
|
||||
if (ret < 0)
|
||||
printk(KERN_ERR "Unable to enable ssi_clk1\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
u32 scr;
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
|
||||
scr = SSI1_SCR;
|
||||
else
|
||||
scr = SSI2_SCR;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
scr |= SSI_SCR_TE | SSI_SCR_SSIEN;
|
||||
else
|
||||
scr |= SSI_SCR_RE | SSI_SCR_SSIEN;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
scr &= ~SSI_SCR_TE;
|
||||
else
|
||||
scr &= ~SSI_SCR_RE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
|
||||
SSI1_SCR = scr;
|
||||
else
|
||||
SSI2_SCR = scr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void imx_ssi_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
/* shutdown SSI if neither Tx or Rx is active */
|
||||
if (!cpu_dai->active) {
|
||||
|
||||
if (cpu_dai->id == IMX_DAI_SSI0 ||
|
||||
cpu_dai->id == IMX_DAI_SSI2) {
|
||||
|
||||
if (--ssi_active[SSI1_PORT] > 1)
|
||||
return;
|
||||
|
||||
SSI1_SCR = 0;
|
||||
clk_disable(ssi_clk0);
|
||||
} else {
|
||||
if (--ssi_active[SSI2_PORT])
|
||||
return;
|
||||
SSI2_SCR = 0;
|
||||
clk_disable(ssi_clk1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int imx_ssi_suspend(struct platform_device *dev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_ssi_resume(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define imx_ssi_suspend NULL
|
||||
#define imx_ssi_resume NULL
|
||||
#endif
|
||||
|
||||
#define IMX_SSI_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 IMX_SSI_BITS \
|
||||
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
|
||||
.startup = imx_ssi_startup,
|
||||
.shutdown = imx_ssi_shutdown,
|
||||
.trigger = imx_ssi_trigger,
|
||||
.prepare = imx_ssi_prepare,
|
||||
.hw_params = imx_ssi_hw_params,
|
||||
.set_sysclk = imx_ssi_set_dai_sysclk,
|
||||
.set_clkdiv = imx_ssi_set_dai_clkdiv,
|
||||
.set_fmt = imx_ssi_set_dai_fmt,
|
||||
.set_tdm_slot = imx_ssi_set_dai_tdm_slot,
|
||||
};
|
||||
|
||||
struct snd_soc_dai imx_ssi_pcm_dai[] = {
|
||||
{
|
||||
.name = "imx-i2s-1-0",
|
||||
.id = IMX_DAI_SSI0,
|
||||
.suspend = imx_ssi_suspend,
|
||||
.resume = imx_ssi_resume,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
},
|
||||
{
|
||||
.name = "imx-i2s-2-0",
|
||||
.id = IMX_DAI_SSI1,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
},
|
||||
{
|
||||
.name = "imx-i2s-1-1",
|
||||
.id = IMX_DAI_SSI2,
|
||||
.suspend = imx_ssi_suspend,
|
||||
.resume = imx_ssi_resume,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
},
|
||||
{
|
||||
.name = "imx-i2s-2-1",
|
||||
.id = IMX_DAI_SSI3,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.formats = IMX_SSI_BITS,
|
||||
.rates = IMX_SSI_RATES,},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai);
|
||||
|
||||
static int __init imx_ssi_init(void)
|
||||
{
|
||||
return snd_soc_register_dais(imx_ssi_pcm_dai,
|
||||
ARRAY_SIZE(imx_ssi_pcm_dai));
|
||||
}
|
||||
|
||||
static void __exit imx_ssi_exit(void)
|
||||
{
|
||||
snd_soc_unregister_dais(imx_ssi_pcm_dai,
|
||||
ARRAY_SIZE(imx_ssi_pcm_dai));
|
||||
}
|
||||
|
||||
module_init(imx_ssi_init);
|
||||
module_exit(imx_ssi_exit);
|
||||
MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com");
|
||||
MODULE_DESCRIPTION("i.MX ASoC I2S driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,238 +0,0 @@
|
|||
/*
|
||||
* 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 _IMX_SSI_H
|
||||
#define _IMX_SSI_H
|
||||
|
||||
#include <mach/hardware.h>
|
||||
|
||||
/* SSI regs definition - MOVE to /arch/arm/plat-mxc/include/mach/ when stable */
|
||||
#define SSI1_IO_BASE_ADDR IO_ADDRESS(SSI1_BASE_ADDR)
|
||||
#define SSI2_IO_BASE_ADDR IO_ADDRESS(SSI2_BASE_ADDR)
|
||||
|
||||
#define STX0 0x00
|
||||
#define STX1 0x04
|
||||
#define SRX0 0x08
|
||||
#define SRX1 0x0c
|
||||
#define SCR 0x10
|
||||
#define SISR 0x14
|
||||
#define SIER 0x18
|
||||
#define STCR 0x1c
|
||||
#define SRCR 0x20
|
||||
#define STCCR 0x24
|
||||
#define SRCCR 0x28
|
||||
#define SFCSR 0x2c
|
||||
#define STR 0x30
|
||||
#define SOR 0x34
|
||||
#define SACNT 0x38
|
||||
#define SACADD 0x3c
|
||||
#define SACDAT 0x40
|
||||
#define SATAG 0x44
|
||||
#define STMSK 0x48
|
||||
#define SRMSK 0x4c
|
||||
|
||||
#define SSI1_STX0 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STX0)))
|
||||
#define SSI1_STX1 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STX1)))
|
||||
#define SSI1_SRX0 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRX0)))
|
||||
#define SSI1_SRX1 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRX1)))
|
||||
#define SSI1_SCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SCR)))
|
||||
#define SSI1_SISR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SISR)))
|
||||
#define SSI1_SIER (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SIER)))
|
||||
#define SSI1_STCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STCR)))
|
||||
#define SSI1_SRCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRCR)))
|
||||
#define SSI1_STCCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STCCR)))
|
||||
#define SSI1_SRCCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRCCR)))
|
||||
#define SSI1_SFCSR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SFCSR)))
|
||||
#define SSI1_STR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STR)))
|
||||
#define SSI1_SOR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SOR)))
|
||||
#define SSI1_SACNT (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACNT)))
|
||||
#define SSI1_SACADD (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACADD)))
|
||||
#define SSI1_SACDAT (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACDAT)))
|
||||
#define SSI1_SATAG (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SATAG)))
|
||||
#define SSI1_STMSK (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STMSK)))
|
||||
#define SSI1_SRMSK (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRMSK)))
|
||||
|
||||
|
||||
#define SSI2_STX0 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STX0)))
|
||||
#define SSI2_STX1 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STX1)))
|
||||
#define SSI2_SRX0 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRX0)))
|
||||
#define SSI2_SRX1 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRX1)))
|
||||
#define SSI2_SCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SCR)))
|
||||
#define SSI2_SISR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SISR)))
|
||||
#define SSI2_SIER (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SIER)))
|
||||
#define SSI2_STCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STCR)))
|
||||
#define SSI2_SRCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRCR)))
|
||||
#define SSI2_STCCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STCCR)))
|
||||
#define SSI2_SRCCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRCCR)))
|
||||
#define SSI2_SFCSR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SFCSR)))
|
||||
#define SSI2_STR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STR)))
|
||||
#define SSI2_SOR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SOR)))
|
||||
#define SSI2_SACNT (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACNT)))
|
||||
#define SSI2_SACADD (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACADD)))
|
||||
#define SSI2_SACDAT (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACDAT)))
|
||||
#define SSI2_SATAG (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SATAG)))
|
||||
#define SSI2_STMSK (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STMSK)))
|
||||
#define SSI2_SRMSK (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRMSK)))
|
||||
|
||||
#define SSI_SCR_CLK_IST (1 << 9)
|
||||
#define SSI_SCR_TCH_EN (1 << 8)
|
||||
#define SSI_SCR_SYS_CLK_EN (1 << 7)
|
||||
#define SSI_SCR_I2S_MODE_NORM (0 << 5)
|
||||
#define SSI_SCR_I2S_MODE_MSTR (1 << 5)
|
||||
#define SSI_SCR_I2S_MODE_SLAVE (2 << 5)
|
||||
#define SSI_SCR_SYN (1 << 4)
|
||||
#define SSI_SCR_NET (1 << 3)
|
||||
#define SSI_SCR_RE (1 << 2)
|
||||
#define SSI_SCR_TE (1 << 1)
|
||||
#define SSI_SCR_SSIEN (1 << 0)
|
||||
|
||||
#define SSI_SISR_CMDAU (1 << 18)
|
||||
#define SSI_SISR_CMDDU (1 << 17)
|
||||
#define SSI_SISR_RXT (1 << 16)
|
||||
#define SSI_SISR_RDR1 (1 << 15)
|
||||
#define SSI_SISR_RDR0 (1 << 14)
|
||||
#define SSI_SISR_TDE1 (1 << 13)
|
||||
#define SSI_SISR_TDE0 (1 << 12)
|
||||
#define SSI_SISR_ROE1 (1 << 11)
|
||||
#define SSI_SISR_ROE0 (1 << 10)
|
||||
#define SSI_SISR_TUE1 (1 << 9)
|
||||
#define SSI_SISR_TUE0 (1 << 8)
|
||||
#define SSI_SISR_TFS (1 << 7)
|
||||
#define SSI_SISR_RFS (1 << 6)
|
||||
#define SSI_SISR_TLS (1 << 5)
|
||||
#define SSI_SISR_RLS (1 << 4)
|
||||
#define SSI_SISR_RFF1 (1 << 3)
|
||||
#define SSI_SISR_RFF0 (1 << 2)
|
||||
#define SSI_SISR_TFE1 (1 << 1)
|
||||
#define SSI_SISR_TFE0 (1 << 0)
|
||||
|
||||
#define SSI_SIER_RDMAE (1 << 22)
|
||||
#define SSI_SIER_RIE (1 << 21)
|
||||
#define SSI_SIER_TDMAE (1 << 20)
|
||||
#define SSI_SIER_TIE (1 << 19)
|
||||
#define SSI_SIER_CMDAU_EN (1 << 18)
|
||||
#define SSI_SIER_CMDDU_EN (1 << 17)
|
||||
#define SSI_SIER_RXT_EN (1 << 16)
|
||||
#define SSI_SIER_RDR1_EN (1 << 15)
|
||||
#define SSI_SIER_RDR0_EN (1 << 14)
|
||||
#define SSI_SIER_TDE1_EN (1 << 13)
|
||||
#define SSI_SIER_TDE0_EN (1 << 12)
|
||||
#define SSI_SIER_ROE1_EN (1 << 11)
|
||||
#define SSI_SIER_ROE0_EN (1 << 10)
|
||||
#define SSI_SIER_TUE1_EN (1 << 9)
|
||||
#define SSI_SIER_TUE0_EN (1 << 8)
|
||||
#define SSI_SIER_TFS_EN (1 << 7)
|
||||
#define SSI_SIER_RFS_EN (1 << 6)
|
||||
#define SSI_SIER_TLS_EN (1 << 5)
|
||||
#define SSI_SIER_RLS_EN (1 << 4)
|
||||
#define SSI_SIER_RFF1_EN (1 << 3)
|
||||
#define SSI_SIER_RFF0_EN (1 << 2)
|
||||
#define SSI_SIER_TFE1_EN (1 << 1)
|
||||
#define SSI_SIER_TFE0_EN (1 << 0)
|
||||
|
||||
#define SSI_STCR_TXBIT0 (1 << 9)
|
||||
#define SSI_STCR_TFEN1 (1 << 8)
|
||||
#define SSI_STCR_TFEN0 (1 << 7)
|
||||
#define SSI_STCR_TFDIR (1 << 6)
|
||||
#define SSI_STCR_TXDIR (1 << 5)
|
||||
#define SSI_STCR_TSHFD (1 << 4)
|
||||
#define SSI_STCR_TSCKP (1 << 3)
|
||||
#define SSI_STCR_TFSI (1 << 2)
|
||||
#define SSI_STCR_TFSL (1 << 1)
|
||||
#define SSI_STCR_TEFS (1 << 0)
|
||||
|
||||
#define SSI_SRCR_RXBIT0 (1 << 9)
|
||||
#define SSI_SRCR_RFEN1 (1 << 8)
|
||||
#define SSI_SRCR_RFEN0 (1 << 7)
|
||||
#define SSI_SRCR_RFDIR (1 << 6)
|
||||
#define SSI_SRCR_RXDIR (1 << 5)
|
||||
#define SSI_SRCR_RSHFD (1 << 4)
|
||||
#define SSI_SRCR_RSCKP (1 << 3)
|
||||
#define SSI_SRCR_RFSI (1 << 2)
|
||||
#define SSI_SRCR_RFSL (1 << 1)
|
||||
#define SSI_SRCR_REFS (1 << 0)
|
||||
|
||||
#define SSI_STCCR_DIV2 (1 << 18)
|
||||
#define SSI_STCCR_PSR (1 << 15)
|
||||
#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13)
|
||||
#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8)
|
||||
#define SSI_STCCR_PM(x) (((x) & 0xff) << 0)
|
||||
#define SSI_STCCR_WL_MASK (0xf << 13)
|
||||
#define SSI_STCCR_DC_MASK (0x1f << 8)
|
||||
#define SSI_STCCR_PM_MASK (0xff << 0)
|
||||
|
||||
#define SSI_SRCCR_DIV2 (1 << 18)
|
||||
#define SSI_SRCCR_PSR (1 << 15)
|
||||
#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13)
|
||||
#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8)
|
||||
#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0)
|
||||
#define SSI_SRCCR_WL_MASK (0xf << 13)
|
||||
#define SSI_SRCCR_DC_MASK (0x1f << 8)
|
||||
#define SSI_SRCCR_PM_MASK (0xff << 0)
|
||||
|
||||
|
||||
#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28)
|
||||
#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24)
|
||||
#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20)
|
||||
#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16)
|
||||
#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12)
|
||||
#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8)
|
||||
#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4)
|
||||
#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0)
|
||||
|
||||
#define SSI_STR_TEST (1 << 15)
|
||||
#define SSI_STR_RCK2TCK (1 << 14)
|
||||
#define SSI_STR_RFS2TFS (1 << 13)
|
||||
#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8)
|
||||
#define SSI_STR_TXD2RXD (1 << 7)
|
||||
#define SSI_STR_TCK2RCK (1 << 6)
|
||||
#define SSI_STR_TFS2RFS (1 << 5)
|
||||
#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0)
|
||||
|
||||
#define SSI_SOR_CLKOFF (1 << 6)
|
||||
#define SSI_SOR_RX_CLR (1 << 5)
|
||||
#define SSI_SOR_TX_CLR (1 << 4)
|
||||
#define SSI_SOR_INIT (1 << 3)
|
||||
#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1)
|
||||
#define SSI_SOR_SYNRST (1 << 0)
|
||||
|
||||
#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
|
||||
#define SSI_SACNT_WR (x << 4)
|
||||
#define SSI_SACNT_RD (x << 3)
|
||||
#define SSI_SACNT_TIF (x << 2)
|
||||
#define SSI_SACNT_FV (x << 1)
|
||||
#define SSI_SACNT_AC97EN (x << 0)
|
||||
|
||||
/* Watermarks for FIFO's */
|
||||
#define TXFIFO_WATERMARK 0x4
|
||||
#define RXFIFO_WATERMARK 0x4
|
||||
|
||||
/* i.MX DAI SSP ID's */
|
||||
#define IMX_DAI_SSI0 0 /* SSI1 FIFO 0 */
|
||||
#define IMX_DAI_SSI1 1 /* SSI1 FIFO 1 */
|
||||
#define IMX_DAI_SSI2 2 /* SSI2 FIFO 0 */
|
||||
#define IMX_DAI_SSI3 3 /* SSI2 FIFO 1 */
|
||||
|
||||
/* SSI clock sources */
|
||||
#define IMX_SSP_SYS_CLK 0
|
||||
|
||||
/* SSI audio dividers */
|
||||
#define IMX_SSI_TX_DIV_2 0
|
||||
#define IMX_SSI_TX_DIV_PSR 1
|
||||
#define IMX_SSI_TX_DIV_PM 2
|
||||
#define IMX_SSI_RX_DIV_2 3
|
||||
#define IMX_SSI_RX_DIV_PSR 4
|
||||
#define IMX_SSI_RX_DIV_PM 5
|
||||
|
||||
|
||||
/* SSI Div 2 */
|
||||
#define IMX_SSI_DIV_2_OFF (~SSI_STCCR_DIV2)
|
||||
#define IMX_SSI_DIV_2_ON SSI_STCCR_DIV2
|
||||
|
||||
extern struct snd_soc_dai imx_ssi_pcm_dai[4];
|
||||
extern int get_ssi_clk(int ssi, struct device *dev);
|
||||
extern void put_ssi_clk(int ssi);
|
||||
#endif
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "../codecs/wm9712.h"
|
||||
#include "imx-ssi.h"
|
||||
|
||||
static struct snd_soc_card imx_phycore;
|
||||
|
||||
static struct snd_soc_ops imx_phycore_hifi_ops = {
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link imx_phycore_dai_ac97[] = {
|
||||
{
|
||||
.name = "HiFi",
|
||||
.stream_name = "HiFi",
|
||||
.codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI],
|
||||
.ops = &imx_phycore_hifi_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card imx_phycore = {
|
||||
.name = "PhyCORE-audio",
|
||||
.platform = &imx_soc_platform,
|
||||
.dai_link = imx_phycore_dai_ac97,
|
||||
.num_links = ARRAY_SIZE(imx_phycore_dai_ac97),
|
||||
};
|
||||
|
||||
static struct snd_soc_device imx_phycore_snd_devdata = {
|
||||
.card = &imx_phycore,
|
||||
.codec_dev = &soc_codec_dev_wm9712,
|
||||
};
|
||||
|
||||
static struct platform_device *imx_phycore_snd_device;
|
||||
|
||||
static int __init imx_phycore_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_pcm043() && !machine_is_pca100())
|
||||
/* return happy. We might run on a totally different machine */
|
||||
return 0;
|
||||
|
||||
imx_phycore_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!imx_phycore_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
imx_phycore_dai_ac97[0].cpu_dai = &imx_ssi_pcm_dai[0];
|
||||
|
||||
platform_set_drvdata(imx_phycore_snd_device, &imx_phycore_snd_devdata);
|
||||
imx_phycore_snd_devdata.dev = &imx_phycore_snd_device->dev;
|
||||
ret = platform_device_add(imx_phycore_snd_device);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
platform_device_put(imx_phycore_snd_device);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit imx_phycore_exit(void)
|
||||
{
|
||||
platform_device_unregister(imx_phycore_snd_device);
|
||||
}
|
||||
|
||||
late_initcall(imx_phycore_init);
|
||||
module_exit(imx_phycore_exit);
|
||||
|
||||
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
|
||||
MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -6,6 +6,9 @@ config SND_OMAP_SOC_MCBSP
|
|||
tristate
|
||||
select OMAP_MCBSP
|
||||
|
||||
config SND_OMAP_SOC_MCPDM
|
||||
tristate
|
||||
|
||||
config SND_OMAP_SOC_N810
|
||||
tristate "SoC Audio support for Nokia N810"
|
||||
depends on SND_OMAP_SOC && MACH_NOKIA_N810 && I2C
|
||||
|
@ -94,12 +97,14 @@ config SND_OMAP_SOC_OMAP3_PANDORA
|
|||
Say Y if you want to add support for SoC audio on the OMAP3 Pandora.
|
||||
|
||||
config SND_OMAP_SOC_OMAP3_BEAGLE
|
||||
tristate "SoC Audio support for OMAP3 Beagle"
|
||||
depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3_BEAGLE
|
||||
tristate "SoC Audio support for OMAP3 Beagle and Devkit8000"
|
||||
depends on TWL4030_CORE && SND_OMAP_SOC
|
||||
depends on (MACH_OMAP3_BEAGLE || MACH_DEVKIT8000)
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TWL4030
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the Beagleboard.
|
||||
Say Y if you want to add support for SoC audio on the Beagleboard or
|
||||
the clone Devkit8000.
|
||||
|
||||
config SND_OMAP_SOC_ZOOM2
|
||||
tristate "SoC Audio support for Zoom2"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# OMAP Platform Support
|
||||
snd-soc-omap-objs := omap-pcm.o
|
||||
snd-soc-omap-mcbsp-objs := omap-mcbsp.o
|
||||
snd-soc-omap-mcpdm-objs := omap-mcpdm.o mcpdm.o
|
||||
|
||||
obj-$(CONFIG_SND_OMAP_SOC) += snd-soc-omap.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o
|
||||
|
||||
# OMAP Machine Support
|
||||
snd-soc-n810-objs := n810.o
|
||||
|
|
|
@ -0,0 +1,484 @@
|
|||
/*
|
||||
* mcpdm.c -- McPDM interface driver
|
||||
*
|
||||
* Author: Jorge Eduardo Candelaria <x0107209@ti.com>
|
||||
* Copyright (C) 2009 - Texas Instruments, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include "mcpdm.h"
|
||||
|
||||
static struct omap_mcpdm *mcpdm;
|
||||
|
||||
static inline void omap_mcpdm_write(u16 reg, u32 val)
|
||||
{
|
||||
__raw_writel(val, mcpdm->io_base + reg);
|
||||
}
|
||||
|
||||
static inline int omap_mcpdm_read(u16 reg)
|
||||
{
|
||||
return __raw_readl(mcpdm->io_base + reg);
|
||||
}
|
||||
|
||||
static void omap_mcpdm_reg_dump(void)
|
||||
{
|
||||
dev_dbg(mcpdm->dev, "***********************\n");
|
||||
dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_IRQSTATUS_RAW));
|
||||
dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_IRQSTATUS));
|
||||
dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_IRQENABLE_SET));
|
||||
dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_IRQENABLE_CLR));
|
||||
dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_IRQWAKE_EN));
|
||||
dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_DMAENABLE_SET));
|
||||
dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_DMAENABLE_CLR));
|
||||
dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_DMAWAKEEN));
|
||||
dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_CTRL));
|
||||
dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_DN_DATA));
|
||||
dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_UP_DATA));
|
||||
dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_FIFO_CTRL_DN));
|
||||
dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_FIFO_CTRL_UP));
|
||||
dev_dbg(mcpdm->dev, "DN_OFFSET: 0x%04x\n",
|
||||
omap_mcpdm_read(MCPDM_DN_OFFSET));
|
||||
dev_dbg(mcpdm->dev, "***********************\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes the McPDM module in and out of reset state.
|
||||
* Uplink and downlink can be reset individually.
|
||||
*/
|
||||
static void omap_mcpdm_reset_capture(int reset)
|
||||
{
|
||||
int ctrl = omap_mcpdm_read(MCPDM_CTRL);
|
||||
|
||||
if (reset)
|
||||
ctrl |= SW_UP_RST;
|
||||
else
|
||||
ctrl &= ~SW_UP_RST;
|
||||
|
||||
omap_mcpdm_write(MCPDM_CTRL, ctrl);
|
||||
}
|
||||
|
||||
static void omap_mcpdm_reset_playback(int reset)
|
||||
{
|
||||
int ctrl = omap_mcpdm_read(MCPDM_CTRL);
|
||||
|
||||
if (reset)
|
||||
ctrl |= SW_DN_RST;
|
||||
else
|
||||
ctrl &= ~SW_DN_RST;
|
||||
|
||||
omap_mcpdm_write(MCPDM_CTRL, ctrl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enables the transfer through the PDM interface to/from the Phoenix
|
||||
* codec by enabling the corresponding UP or DN channels.
|
||||
*/
|
||||
void omap_mcpdm_start(int stream)
|
||||
{
|
||||
int ctrl = omap_mcpdm_read(MCPDM_CTRL);
|
||||
|
||||
if (stream)
|
||||
ctrl |= mcpdm->up_channels;
|
||||
else
|
||||
ctrl |= mcpdm->dn_channels;
|
||||
|
||||
omap_mcpdm_write(MCPDM_CTRL, ctrl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disables the transfer through the PDM interface to/from the Phoenix
|
||||
* codec by disabling the corresponding UP or DN channels.
|
||||
*/
|
||||
void omap_mcpdm_stop(int stream)
|
||||
{
|
||||
int ctrl = omap_mcpdm_read(MCPDM_CTRL);
|
||||
|
||||
if (stream)
|
||||
ctrl &= ~mcpdm->up_channels;
|
||||
else
|
||||
ctrl &= ~mcpdm->dn_channels;
|
||||
|
||||
omap_mcpdm_write(MCPDM_CTRL, ctrl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Configures McPDM uplink for audio recording.
|
||||
* This function should be called before omap_mcpdm_start.
|
||||
*/
|
||||
int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink)
|
||||
{
|
||||
int irq_mask = 0;
|
||||
int ctrl;
|
||||
|
||||
if (!uplink)
|
||||
return -EINVAL;
|
||||
|
||||
mcpdm->uplink = uplink;
|
||||
|
||||
/* Enable irq request generation */
|
||||
irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
|
||||
omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
|
||||
|
||||
/* Configure uplink threshold */
|
||||
if (uplink->threshold > UP_THRES_MAX)
|
||||
uplink->threshold = UP_THRES_MAX;
|
||||
|
||||
omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold);
|
||||
|
||||
/* Configure DMA controller */
|
||||
omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE);
|
||||
|
||||
/* Set pdm out format */
|
||||
ctrl = omap_mcpdm_read(MCPDM_CTRL);
|
||||
ctrl &= ~PDMOUTFORMAT;
|
||||
ctrl |= uplink->format & PDMOUTFORMAT;
|
||||
|
||||
/* Uplink channels */
|
||||
mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK);
|
||||
|
||||
omap_mcpdm_write(MCPDM_CTRL, ctrl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configures McPDM downlink for audio playback.
|
||||
* This function should be called before omap_mcpdm_start.
|
||||
*/
|
||||
int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink)
|
||||
{
|
||||
int irq_mask = 0;
|
||||
int ctrl;
|
||||
|
||||
if (!downlink)
|
||||
return -EINVAL;
|
||||
|
||||
mcpdm->downlink = downlink;
|
||||
|
||||
/* Enable irq request generation */
|
||||
irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
|
||||
omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
|
||||
|
||||
/* Configure uplink threshold */
|
||||
if (downlink->threshold > DN_THRES_MAX)
|
||||
downlink->threshold = DN_THRES_MAX;
|
||||
|
||||
omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold);
|
||||
|
||||
/* Enable DMA request generation */
|
||||
omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE);
|
||||
|
||||
/* Set pdm out format */
|
||||
ctrl = omap_mcpdm_read(MCPDM_CTRL);
|
||||
ctrl &= ~PDMOUTFORMAT;
|
||||
ctrl |= downlink->format & PDMOUTFORMAT;
|
||||
|
||||
/* Downlink channels */
|
||||
mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK);
|
||||
|
||||
omap_mcpdm_write(MCPDM_CTRL, ctrl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleans McPDM uplink configuration.
|
||||
* This function should be called when the stream is closed.
|
||||
*/
|
||||
int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink)
|
||||
{
|
||||
int irq_mask = 0;
|
||||
|
||||
if (!uplink)
|
||||
return -EINVAL;
|
||||
|
||||
/* Disable irq request generation */
|
||||
irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
|
||||
omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
|
||||
|
||||
/* Disable DMA request generation */
|
||||
omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE);
|
||||
|
||||
/* Clear Downlink channels */
|
||||
mcpdm->up_channels = 0;
|
||||
|
||||
mcpdm->uplink = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleans McPDM downlink configuration.
|
||||
* This function should be called when the stream is closed.
|
||||
*/
|
||||
int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink)
|
||||
{
|
||||
int irq_mask = 0;
|
||||
|
||||
if (!downlink)
|
||||
return -EINVAL;
|
||||
|
||||
/* Disable irq request generation */
|
||||
irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
|
||||
omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
|
||||
|
||||
/* Disable DMA request generation */
|
||||
omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE);
|
||||
|
||||
/* clear Downlink channels */
|
||||
mcpdm->dn_channels = 0;
|
||||
|
||||
mcpdm->downlink = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm_irq = dev_id;
|
||||
int irq_status;
|
||||
|
||||
irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS);
|
||||
|
||||
/* Acknowledge irq event */
|
||||
omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status);
|
||||
|
||||
if (irq & MCPDM_DN_IRQ_FULL) {
|
||||
dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
|
||||
omap_mcpdm_reset_playback(1);
|
||||
omap_mcpdm_playback_open(mcpdm_irq->downlink);
|
||||
omap_mcpdm_reset_playback(0);
|
||||
}
|
||||
|
||||
if (irq & MCPDM_DN_IRQ_EMPTY) {
|
||||
dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
|
||||
omap_mcpdm_reset_playback(1);
|
||||
omap_mcpdm_playback_open(mcpdm_irq->downlink);
|
||||
omap_mcpdm_reset_playback(0);
|
||||
}
|
||||
|
||||
if (irq & MCPDM_DN_IRQ) {
|
||||
dev_dbg(mcpdm_irq->dev, "DN write request\n");
|
||||
}
|
||||
|
||||
if (irq & MCPDM_UP_IRQ_FULL) {
|
||||
dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
|
||||
omap_mcpdm_reset_capture(1);
|
||||
omap_mcpdm_capture_open(mcpdm_irq->uplink);
|
||||
omap_mcpdm_reset_capture(0);
|
||||
}
|
||||
|
||||
if (irq & MCPDM_UP_IRQ_EMPTY) {
|
||||
dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
|
||||
omap_mcpdm_reset_capture(1);
|
||||
omap_mcpdm_capture_open(mcpdm_irq->uplink);
|
||||
omap_mcpdm_reset_capture(0);
|
||||
}
|
||||
|
||||
if (irq & MCPDM_UP_IRQ) {
|
||||
dev_dbg(mcpdm_irq->dev, "UP write request\n");
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int omap_mcpdm_request(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
clk_enable(mcpdm->clk);
|
||||
|
||||
spin_lock(&mcpdm->lock);
|
||||
|
||||
if (!mcpdm->free) {
|
||||
dev_err(mcpdm->dev, "McPDM interface is in use\n");
|
||||
spin_unlock(&mcpdm->lock);
|
||||
ret = -EBUSY;
|
||||
goto err;
|
||||
}
|
||||
mcpdm->free = 0;
|
||||
|
||||
spin_unlock(&mcpdm->lock);
|
||||
|
||||
/* Disable lines while request is ongoing */
|
||||
omap_mcpdm_write(MCPDM_CTRL, 0x00);
|
||||
|
||||
ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler,
|
||||
0, "McPDM", (void *)mcpdm);
|
||||
if (ret) {
|
||||
dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
clk_disable(mcpdm->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void omap_mcpdm_free(void)
|
||||
{
|
||||
spin_lock(&mcpdm->lock);
|
||||
if (mcpdm->free) {
|
||||
dev_err(mcpdm->dev, "McPDM interface is already free\n");
|
||||
spin_unlock(&mcpdm->lock);
|
||||
return;
|
||||
}
|
||||
mcpdm->free = 1;
|
||||
spin_unlock(&mcpdm->lock);
|
||||
|
||||
clk_disable(mcpdm->clk);
|
||||
|
||||
free_irq(mcpdm->irq, (void *)mcpdm);
|
||||
}
|
||||
|
||||
/* Enable/disable DC offset cancelation for the analog
|
||||
* headset path (PDM channels 1 and 2).
|
||||
*/
|
||||
int omap_mcpdm_set_offset(int offset1, int offset2)
|
||||
{
|
||||
int offset;
|
||||
|
||||
if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX))
|
||||
return -EINVAL;
|
||||
|
||||
offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2);
|
||||
|
||||
/* offset cancellation for channel 1 */
|
||||
if (offset1)
|
||||
offset |= DN_OFST_RX1_EN;
|
||||
else
|
||||
offset &= ~DN_OFST_RX1_EN;
|
||||
|
||||
/* offset cancellation for channel 2 */
|
||||
if (offset2)
|
||||
offset |= DN_OFST_RX2_EN;
|
||||
else
|
||||
offset &= ~DN_OFST_RX2_EN;
|
||||
|
||||
omap_mcpdm_write(MCPDM_DN_OFFSET, offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit omap_mcpdm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
int ret = 0;
|
||||
|
||||
mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL);
|
||||
if (!mcpdm) {
|
||||
ret = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "no resource\n");
|
||||
goto err_resource;
|
||||
}
|
||||
|
||||
spin_lock_init(&mcpdm->lock);
|
||||
mcpdm->free = 1;
|
||||
mcpdm->io_base = ioremap(res->start, resource_size(res));
|
||||
if (!mcpdm->io_base) {
|
||||
ret = -ENOMEM;
|
||||
goto err_resource;
|
||||
}
|
||||
|
||||
mcpdm->irq = platform_get_irq(pdev, 0);
|
||||
|
||||
mcpdm->clk = clk_get(&pdev->dev, "pdm_ck");
|
||||
if (IS_ERR(mcpdm->clk)) {
|
||||
ret = PTR_ERR(mcpdm->clk);
|
||||
dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
mcpdm->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, mcpdm);
|
||||
|
||||
return 0;
|
||||
|
||||
err_clk:
|
||||
iounmap(mcpdm->io_base);
|
||||
err_resource:
|
||||
kfree(mcpdm);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit omap_mcpdm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
clk_put(mcpdm_ptr->clk);
|
||||
|
||||
iounmap(mcpdm_ptr->io_base);
|
||||
|
||||
mcpdm_ptr->clk = NULL;
|
||||
mcpdm_ptr->free = 0;
|
||||
mcpdm_ptr->dev = NULL;
|
||||
|
||||
kfree(mcpdm_ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap_mcpdm_driver = {
|
||||
.probe = omap_mcpdm_probe,
|
||||
.remove = __devexit_p(omap_mcpdm_remove),
|
||||
.driver = {
|
||||
.name = "omap-mcpdm",
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device *omap_mcpdm_device;
|
||||
|
||||
static int __init omap_mcpdm_init(void)
|
||||
{
|
||||
return platform_driver_register(&omap_mcpdm_driver);
|
||||
}
|
||||
arch_initcall(omap_mcpdm_init);
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* mcpdm.h -- Defines for McPDM driver
|
||||
*
|
||||
* Author: Jorge Eduardo Candelaria <x0107209@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
/* McPDM registers */
|
||||
|
||||
#define MCPDM_REVISION 0x00
|
||||
#define MCPDM_SYSCONFIG 0x10
|
||||
#define MCPDM_IRQSTATUS_RAW 0x24
|
||||
#define MCPDM_IRQSTATUS 0x28
|
||||
#define MCPDM_IRQENABLE_SET 0x2C
|
||||
#define MCPDM_IRQENABLE_CLR 0x30
|
||||
#define MCPDM_IRQWAKE_EN 0x34
|
||||
#define MCPDM_DMAENABLE_SET 0x38
|
||||
#define MCPDM_DMAENABLE_CLR 0x3C
|
||||
#define MCPDM_DMAWAKEEN 0x40
|
||||
#define MCPDM_CTRL 0x44
|
||||
#define MCPDM_DN_DATA 0x48
|
||||
#define MCPDM_UP_DATA 0x4C
|
||||
#define MCPDM_FIFO_CTRL_DN 0x50
|
||||
#define MCPDM_FIFO_CTRL_UP 0x54
|
||||
#define MCPDM_DN_OFFSET 0x58
|
||||
|
||||
/*
|
||||
* MCPDM_IRQ bit fields
|
||||
* IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR
|
||||
*/
|
||||
|
||||
#define MCPDM_DN_IRQ (1 << 0)
|
||||
#define MCPDM_DN_IRQ_EMPTY (1 << 1)
|
||||
#define MCPDM_DN_IRQ_ALMST_EMPTY (1 << 2)
|
||||
#define MCPDM_DN_IRQ_FULL (1 << 3)
|
||||
|
||||
#define MCPDM_UP_IRQ (1 << 8)
|
||||
#define MCPDM_UP_IRQ_EMPTY (1 << 9)
|
||||
#define MCPDM_UP_IRQ_ALMST_FULL (1 << 10)
|
||||
#define MCPDM_UP_IRQ_FULL (1 << 11)
|
||||
|
||||
#define MCPDM_DOWNLINK_IRQ_MASK 0x00F
|
||||
#define MCPDM_UPLINK_IRQ_MASK 0xF00
|
||||
|
||||
/*
|
||||
* MCPDM_DMAENABLE bit fields
|
||||
*/
|
||||
|
||||
#define DMA_DN_ENABLE 0x1
|
||||
#define DMA_UP_ENABLE 0x2
|
||||
|
||||
/*
|
||||
* MCPDM_CTRL bit fields
|
||||
*/
|
||||
|
||||
#define PDM_UP1_EN 0x0001
|
||||
#define PDM_UP2_EN 0x0002
|
||||
#define PDM_UP3_EN 0x0004
|
||||
#define PDM_DN1_EN 0x0008
|
||||
#define PDM_DN2_EN 0x0010
|
||||
#define PDM_DN3_EN 0x0020
|
||||
#define PDM_DN4_EN 0x0040
|
||||
#define PDM_DN5_EN 0x0080
|
||||
#define PDMOUTFORMAT 0x0100
|
||||
#define CMD_INT 0x0200
|
||||
#define STATUS_INT 0x0400
|
||||
#define SW_UP_RST 0x0800
|
||||
#define SW_DN_RST 0x1000
|
||||
#define PDM_UP_MASK 0x007
|
||||
#define PDM_DN_MASK 0x0F8
|
||||
#define PDM_CMD_MASK 0x200
|
||||
#define PDM_STATUS_MASK 0x400
|
||||
|
||||
|
||||
#define PDMOUTFORMAT_LJUST (0 << 8)
|
||||
#define PDMOUTFORMAT_RJUST (1 << 8)
|
||||
|
||||
/*
|
||||
* MCPDM_FIFO_CTRL bit fields
|
||||
*/
|
||||
|
||||
#define UP_THRES_MAX 0xF
|
||||
#define DN_THRES_MAX 0xF
|
||||
|
||||
/*
|
||||
* MCPDM_DN_OFFSET bit fields
|
||||
*/
|
||||
|
||||
#define DN_OFST_RX1_EN 0x0001
|
||||
#define DN_OFST_RX2_EN 0x0100
|
||||
|
||||
#define DN_OFST_RX1 1
|
||||
#define DN_OFST_RX2 9
|
||||
#define DN_OFST_MAX 0x1F
|
||||
|
||||
#define MCPDM_UPLINK 1
|
||||
#define MCPDM_DOWNLINK 2
|
||||
|
||||
struct omap_mcpdm_link {
|
||||
int irq_mask;
|
||||
int threshold;
|
||||
int format;
|
||||
int channels;
|
||||
};
|
||||
|
||||
struct omap_mcpdm_platform_data {
|
||||
unsigned long phys_base;
|
||||
u16 irq;
|
||||
};
|
||||
|
||||
struct omap_mcpdm {
|
||||
struct device *dev;
|
||||
unsigned long phys_base;
|
||||
void __iomem *io_base;
|
||||
u8 free;
|
||||
int irq;
|
||||
|
||||
spinlock_t lock;
|
||||
struct omap_mcpdm_platform_data *pdata;
|
||||
struct clk *clk;
|
||||
struct omap_mcpdm_link *downlink;
|
||||
struct omap_mcpdm_link *uplink;
|
||||
struct completion irq_completion;
|
||||
|
||||
int dn_channels;
|
||||
int up_channels;
|
||||
};
|
||||
|
||||
extern void omap_mcpdm_start(int stream);
|
||||
extern void omap_mcpdm_stop(int stream);
|
||||
extern int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink);
|
||||
extern int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink);
|
||||
extern int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink);
|
||||
extern int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink);
|
||||
extern int omap_mcpdm_request(void);
|
||||
extern void omap_mcpdm_free(void);
|
||||
extern int omap_mcpdm_set_offset(int offset1, int offset2);
|
|
@ -287,6 +287,8 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
|
|||
omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma;
|
||||
omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port;
|
||||
omap_mcbsp_dai_dma_params[id][substream->stream].sync_mode = sync_mode;
|
||||
omap_mcbsp_dai_dma_params[id][substream->stream].data_type =
|
||||
OMAP_DMA_DATA_TYPE_S16;
|
||||
cpu_dai->dma_data = &omap_mcbsp_dai_dma_params[id][substream->stream];
|
||||
|
||||
if (mcbsp_data->configured) {
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* omap-mcpdm.c -- OMAP ALSA SoC DAI driver using McPDM port
|
||||
*
|
||||
* Copyright (C) 2009 Texas Instruments
|
||||
*
|
||||
* Author: Misael Lopez Cruz <x0052729@ti.com>
|
||||
* Contact: Jorge Eduardo Candelaria <x0107209@ti.com>
|
||||
* Margarita Olaya <magi.olaya@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <plat/control.h>
|
||||
#include <plat/dma.h>
|
||||
#include <plat/mcbsp.h>
|
||||
#include "mcpdm.h"
|
||||
#include "omap-mcpdm.h"
|
||||
#include "omap-pcm.h"
|
||||
|
||||
struct omap_mcpdm_data {
|
||||
struct omap_mcpdm_link *links;
|
||||
int active;
|
||||
};
|
||||
|
||||
static struct omap_mcpdm_link omap_mcpdm_links[] = {
|
||||
/* downlink */
|
||||
{
|
||||
.irq_mask = MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL,
|
||||
.threshold = 1,
|
||||
.format = PDMOUTFORMAT_LJUST,
|
||||
},
|
||||
/* uplink */
|
||||
{
|
||||
.irq_mask = MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL,
|
||||
.threshold = 1,
|
||||
.format = PDMOUTFORMAT_LJUST,
|
||||
},
|
||||
};
|
||||
|
||||
static struct omap_mcpdm_data mcpdm_data = {
|
||||
.links = omap_mcpdm_links,
|
||||
.active = 0,
|
||||
};
|
||||
|
||||
/*
|
||||
* Stream DMA parameters
|
||||
*/
|
||||
static struct omap_pcm_dma_data omap_mcpdm_dai_dma_params[] = {
|
||||
{
|
||||
.name = "Audio playback",
|
||||
.dma_req = OMAP44XX_DMA_MCPDM_DL,
|
||||
.data_type = OMAP_DMA_DATA_TYPE_S32,
|
||||
.sync_mode = OMAP_DMA_SYNC_PACKET,
|
||||
.packet_size = 16,
|
||||
.port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_DN_DATA,
|
||||
},
|
||||
{
|
||||
.name = "Audio capture",
|
||||
.dma_req = OMAP44XX_DMA_MCPDM_UP,
|
||||
.data_type = OMAP_DMA_DATA_TYPE_S32,
|
||||
.sync_mode = OMAP_DMA_SYNC_PACKET,
|
||||
.packet_size = 16,
|
||||
.port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_UP_DATA,
|
||||
},
|
||||
};
|
||||
|
||||
static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
int err = 0;
|
||||
|
||||
if (!cpu_dai->active)
|
||||
err = omap_mcpdm_request();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
if (!cpu_dai->active)
|
||||
omap_mcpdm_free();
|
||||
}
|
||||
|
||||
static int omap_mcpdm_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct omap_mcpdm_data *mcpdm_priv = cpu_dai->private_data;
|
||||
int stream = substream->stream;
|
||||
int err = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (!mcpdm_priv->active++)
|
||||
omap_mcpdm_start(stream);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (!--mcpdm_priv->active)
|
||||
omap_mcpdm_stop(stream);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int omap_mcpdm_dai_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_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct omap_mcpdm_data *mcpdm_priv = cpu_dai->private_data;
|
||||
struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links;
|
||||
int stream = substream->stream;
|
||||
int channels, err, link_mask = 0;
|
||||
|
||||
cpu_dai->dma_data = &omap_mcpdm_dai_dma_params[stream];
|
||||
|
||||
channels = params_channels(params);
|
||||
switch (channels) {
|
||||
case 4:
|
||||
if (stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
/* up to 2 channels for capture */
|
||||
return -EINVAL;
|
||||
link_mask |= 1 << 3;
|
||||
case 3:
|
||||
if (stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
/* up to 2 channels for capture */
|
||||
return -EINVAL;
|
||||
link_mask |= 1 << 2;
|
||||
case 2:
|
||||
link_mask |= 1 << 1;
|
||||
case 1:
|
||||
link_mask |= 1 << 0;
|
||||
break;
|
||||
default:
|
||||
/* unsupported number of channels */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
mcpdm_links[stream].channels = link_mask << 3;
|
||||
err = omap_mcpdm_playback_open(&mcpdm_links[stream]);
|
||||
} else {
|
||||
mcpdm_links[stream].channels = link_mask << 0;
|
||||
err = omap_mcpdm_capture_open(&mcpdm_links[stream]);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int omap_mcpdm_dai_hw_free(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct omap_mcpdm_data *mcpdm_priv = cpu_dai->private_data;
|
||||
struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links;
|
||||
int stream = substream->stream;
|
||||
int err;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
err = omap_mcpdm_playback_close(&mcpdm_links[stream]);
|
||||
else
|
||||
err = omap_mcpdm_capture_close(&mcpdm_links[stream]);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops omap_mcpdm_dai_ops = {
|
||||
.startup = omap_mcpdm_dai_startup,
|
||||
.shutdown = omap_mcpdm_dai_shutdown,
|
||||
.trigger = omap_mcpdm_dai_trigger,
|
||||
.hw_params = omap_mcpdm_dai_hw_params,
|
||||
.hw_free = omap_mcpdm_dai_hw_free,
|
||||
};
|
||||
|
||||
#define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
|
||||
#define OMAP_MCPDM_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
struct snd_soc_dai omap_mcpdm_dai = {
|
||||
.name = "omap-mcpdm",
|
||||
.id = -1,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 4,
|
||||
.rates = OMAP_MCPDM_RATES,
|
||||
.formats = OMAP_MCPDM_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = OMAP_MCPDM_RATES,
|
||||
.formats = OMAP_MCPDM_FORMATS,
|
||||
},
|
||||
.ops = &omap_mcpdm_dai_ops,
|
||||
.private_data = &mcpdm_data,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(omap_mcpdm_dai);
|
||||
|
||||
static int __init snd_omap_mcpdm_init(void)
|
||||
{
|
||||
return snd_soc_register_dai(&omap_mcpdm_dai);
|
||||
}
|
||||
module_init(snd_omap_mcpdm_init);
|
||||
|
||||
static void __exit snd_omap_mcpdm_exit(void)
|
||||
{
|
||||
snd_soc_unregister_dai(&omap_mcpdm_dai);
|
||||
}
|
||||
module_exit(snd_omap_mcpdm_exit);
|
||||
|
||||
MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
|
||||
MODULE_DESCRIPTION("OMAP PDM SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* omap-mcpdm.h
|
||||
*
|
||||
* Copyright (C) 2009 Texas Instruments
|
||||
*
|
||||
* Contact: Misael Lopez Cruz <x0052729@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __OMAP_MCPDM_H__
|
||||
#define __OMAP_MCPDM_H__
|
||||
|
||||
extern struct snd_soc_dai omap_mcpdm_dai;
|
||||
|
||||
#endif /* End of __OMAP_MCPDM_H__ */
|
|
@ -37,7 +37,8 @@ static const struct snd_pcm_hardware omap_pcm_hardware = {
|
|||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 64 * 1024,
|
||||
.periods_min = 2,
|
||||
|
@ -149,6 +150,7 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
struct omap_runtime_data *prtd = runtime->private_data;
|
||||
struct omap_pcm_dma_data *dma_data = prtd->dma_data;
|
||||
struct omap_dma_channel_params dma_params;
|
||||
int bytes;
|
||||
|
||||
/* return if this is a bufferless transfer e.g.
|
||||
* codec <--> BT codec or GSM modem -- lg FIXME */
|
||||
|
@ -156,11 +158,7 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
return 0;
|
||||
|
||||
memset(&dma_params, 0, sizeof(dma_params));
|
||||
/*
|
||||
* Note: Regardless of interface data formats supported by OMAP McBSP
|
||||
* or EAC blocks, internal representation is always fixed 16-bit/sample
|
||||
*/
|
||||
dma_params.data_type = OMAP_DMA_DATA_TYPE_S16;
|
||||
dma_params.data_type = dma_data->data_type;
|
||||
dma_params.trigger = dma_data->dma_req;
|
||||
dma_params.sync_mode = dma_data->sync_mode;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
|
@ -170,6 +168,7 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
dma_params.src_start = runtime->dma_addr;
|
||||
dma_params.dst_start = dma_data->port_addr;
|
||||
dma_params.dst_port = OMAP_DMA_PORT_MPUI;
|
||||
dma_params.dst_fi = dma_data->packet_size;
|
||||
} else {
|
||||
dma_params.src_amode = OMAP_DMA_AMODE_CONSTANT;
|
||||
dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC;
|
||||
|
@ -177,6 +176,7 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
dma_params.src_start = dma_data->port_addr;
|
||||
dma_params.dst_start = runtime->dma_addr;
|
||||
dma_params.src_port = OMAP_DMA_PORT_MPUI;
|
||||
dma_params.src_fi = dma_data->packet_size;
|
||||
}
|
||||
/*
|
||||
* Set DMA transfer frame size equal to ALSA period size and frame
|
||||
|
@ -184,7 +184,8 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
* we can transfer the whole ALSA buffer with single DMA transfer but
|
||||
* still can get an interrupt at each period bounary
|
||||
*/
|
||||
dma_params.elem_count = snd_pcm_lib_period_bytes(substream) / 2;
|
||||
bytes = snd_pcm_lib_period_bytes(substream);
|
||||
dma_params.elem_count = bytes >> dma_data->data_type;
|
||||
dma_params.frame_count = runtime->periods;
|
||||
omap_set_dma_params(prtd->dma_ch, &dma_params);
|
||||
|
||||
|
|
|
@ -29,8 +29,10 @@ struct omap_pcm_dma_data {
|
|||
char *name; /* stream identifier */
|
||||
int dma_req; /* DMA request line */
|
||||
unsigned long port_addr; /* transmit/receive register */
|
||||
int sync_mode; /* DMA sync mode */
|
||||
void (*set_threshold)(struct snd_pcm_substream *substream);
|
||||
int data_type; /* data type 8,16,32 */
|
||||
int sync_mode; /* DMA sync mode */
|
||||
int packet_size; /* packet size only in PACKET mode */
|
||||
};
|
||||
|
||||
extern struct snd_soc_platform omap_soc_platform;
|
||||
|
|
|
@ -117,11 +117,11 @@ static int __init omap3beagle_soc_init(void)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_omap3_beagle()) {
|
||||
pr_debug("Not OMAP3 Beagle!\n");
|
||||
if (!(machine_is_omap3_beagle() || machine_is_devkit8000())) {
|
||||
pr_debug("Not OMAP3 Beagle or Devkit8000!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
pr_info("OMAP3 Beagle SoC init\n");
|
||||
pr_info("OMAP3 Beagle/Devkit8000 SoC init\n");
|
||||
|
||||
omap3beagle_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!omap3beagle_snd_device) {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
|
@ -40,6 +41,8 @@
|
|||
|
||||
#define PREFIX "ASoC omap3pandora: "
|
||||
|
||||
static struct regulator *omap3pandora_dac_reg;
|
||||
|
||||
static int omap3pandora_cmn_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params, unsigned int fmt)
|
||||
{
|
||||
|
@ -106,17 +109,33 @@ static int omap3pandora_in_hw_params(struct snd_pcm_substream *substream,
|
|||
SND_SOC_DAIFMT_CBS_CFS);
|
||||
}
|
||||
|
||||
static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
/*
|
||||
* The PCM1773 DAC datasheet requires 1ms delay between switching
|
||||
* VCC power on/off and /PD pin high/low
|
||||
*/
|
||||
if (SND_SOC_DAPM_EVENT_ON(event)) {
|
||||
regulator_enable(omap3pandora_dac_reg);
|
||||
mdelay(1);
|
||||
gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
|
||||
} else {
|
||||
gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
|
||||
mdelay(1);
|
||||
regulator_disable(omap3pandora_dac_reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
if (SND_SOC_DAPM_EVENT_ON(event)) {
|
||||
gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
|
||||
if (SND_SOC_DAPM_EVENT_ON(event))
|
||||
gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1);
|
||||
} else {
|
||||
else
|
||||
gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
|
||||
mdelay(1);
|
||||
gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -130,7 +149,9 @@ static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
|
|||
* |P| <--- TWL4030 <--------- Line In and MICs
|
||||
*/
|
||||
static const struct snd_soc_dapm_widget omap3pandora_out_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("PCM DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_DAC_E("PCM DAC", "HiFi Playback", SND_SOC_NOPM,
|
||||
0, 0, omap3pandora_dac_event,
|
||||
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
SND_SOC_DAPM_PGA_E("Headphone Amplifier", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, omap3pandora_hp_event,
|
||||
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
|
@ -306,8 +327,18 @@ static int __init omap3pandora_soc_init(void)
|
|||
goto fail2;
|
||||
}
|
||||
|
||||
omap3pandora_dac_reg = regulator_get(&omap3pandora_snd_device->dev, "vcc");
|
||||
if (IS_ERR(omap3pandora_dac_reg)) {
|
||||
pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n",
|
||||
dev_name(&omap3pandora_snd_device->dev),
|
||||
PTR_ERR(omap3pandora_dac_reg));
|
||||
goto fail3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail3:
|
||||
platform_device_del(omap3pandora_snd_device);
|
||||
fail2:
|
||||
platform_device_put(omap3pandora_snd_device);
|
||||
fail1:
|
||||
|
@ -320,6 +351,7 @@ module_init(omap3pandora_soc_init);
|
|||
|
||||
static void __exit omap3pandora_soc_exit(void)
|
||||
{
|
||||
regulator_put(omap3pandora_dac_reg);
|
||||
platform_device_unregister(omap3pandora_snd_device);
|
||||
gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
|
||||
gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
|
||||
|
|
|
@ -135,10 +135,11 @@ static int pxa_ssp_suspend(struct snd_soc_dai *cpu_dai)
|
|||
struct ssp_priv *priv = cpu_dai->private_data;
|
||||
|
||||
if (!cpu_dai->active)
|
||||
return 0;
|
||||
clk_enable(priv->dev.ssp->clk);
|
||||
|
||||
ssp_save_state(&priv->dev, &priv->state);
|
||||
clk_disable(priv->dev.ssp->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -146,12 +147,13 @@ static int pxa_ssp_resume(struct snd_soc_dai *cpu_dai)
|
|||
{
|
||||
struct ssp_priv *priv = cpu_dai->private_data;
|
||||
|
||||
if (!cpu_dai->active)
|
||||
return 0;
|
||||
|
||||
clk_enable(priv->dev.ssp->clk);
|
||||
ssp_restore_state(&priv->dev, &priv->state);
|
||||
ssp_enable(&priv->dev);
|
||||
|
||||
if (cpu_dai->active)
|
||||
ssp_enable(&priv->dev);
|
||||
else
|
||||
clk_disable(priv->dev.ssp->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,9 @@ static struct i2c_board_info max9486_hwmon_info = {
|
|||
};
|
||||
|
||||
#define MAX9485_MCLK_FREQ_112896 0x22
|
||||
#define MAX9485_MCLK_FREQ_122880 0x23
|
||||
#define MAX9485_MCLK_FREQ_122880 0x23
|
||||
#define MAX9485_MCLK_FREQ_225792 0x32
|
||||
#define MAX9485_MCLK_FREQ_245760 0x33
|
||||
|
||||
static void set_max9485_clk(char clk)
|
||||
{
|
||||
|
@ -71,9 +73,17 @@ static int raumfeld_cs4270_startup(struct snd_pcm_substream *substream)
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_112896);
|
||||
/* set freq to 0 to enable all possible codec sample rates */
|
||||
return snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
|
||||
}
|
||||
|
||||
return snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, 0);
|
||||
static void raumfeld_cs4270_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
|
||||
/* set freq to 0 to enable all possible codec sample rates */
|
||||
snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
|
||||
}
|
||||
|
||||
static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream,
|
||||
|
@ -86,20 +96,24 @@ static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream,
|
|||
int ret = 0;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 8000:
|
||||
case 16000:
|
||||
case 48000:
|
||||
case 96000:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_122880);
|
||||
clk = 12288000;
|
||||
break;
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
case 88200:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_112896);
|
||||
clk = 11289600;
|
||||
break;
|
||||
case 48000:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_122880);
|
||||
clk = 12288000;
|
||||
break;
|
||||
case 88200:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_225792);
|
||||
clk = 22579200;
|
||||
break;
|
||||
case 96000:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_245760);
|
||||
clk = 24576000;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fmt = SND_SOC_DAIFMT_I2S |
|
||||
|
@ -128,7 +142,7 @@ static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, 0, 1);
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -137,6 +151,7 @@ static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
static struct snd_soc_ops raumfeld_cs4270_ops = {
|
||||
.startup = raumfeld_cs4270_startup,
|
||||
.shutdown = raumfeld_cs4270_shutdown,
|
||||
.hw_params = raumfeld_cs4270_hw_params,
|
||||
};
|
||||
|
||||
|
@ -181,20 +196,24 @@ static int raumfeld_ak4104_hw_params(struct snd_pcm_substream *substream,
|
|||
int fmt, ret = 0, clk = 0;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 8000:
|
||||
case 16000:
|
||||
case 48000:
|
||||
case 96000:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_122880);
|
||||
clk = 12288000;
|
||||
break;
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
case 88200:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_112896);
|
||||
clk = 11289600;
|
||||
break;
|
||||
case 48000:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_122880);
|
||||
clk = 12288000;
|
||||
break;
|
||||
case 88200:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_225792);
|
||||
clk = 22579200;
|
||||
break;
|
||||
case 96000:
|
||||
set_max9485_clk(MAX9485_MCLK_FREQ_245760);
|
||||
clk = 24576000;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
|
||||
|
@ -217,7 +236,7 @@ static int raumfeld_ak4104_hw_params(struct snd_pcm_substream *substream,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, 0, 1);
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -27,12 +27,10 @@ config SND_S3C64XX_SOC_I2S
|
|||
config SND_S3C_SOC_PCM
|
||||
tristate
|
||||
|
||||
config SND_S3C2443_SOC_AC97
|
||||
config SND_S3C_SOC_AC97
|
||||
tristate
|
||||
select S3C2410_DMA
|
||||
select AC97_BUS
|
||||
select SND_SOC_AC97_BUS
|
||||
|
||||
|
||||
config SND_S3C24XX_SOC_NEO1973_WM8753
|
||||
tristate "SoC I2S Audio support for NEO1973 - WM8753"
|
||||
depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01
|
||||
|
@ -71,8 +69,10 @@ config SND_S3C64XX_SOC_WM8580
|
|||
config SND_S3C24XX_SOC_SMDK2443_WM9710
|
||||
tristate "SoC AC97 Audio support for SMDK2443 - WM9710"
|
||||
depends on SND_S3C24XX_SOC && MACH_SMDK2443
|
||||
select SND_S3C2443_SOC_AC97
|
||||
select S3C2410_DMA
|
||||
select AC97_BUS
|
||||
select SND_SOC_AC97_CODEC
|
||||
select SND_S3C_SOC_AC97
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on smdk2443
|
||||
with the WM9710.
|
||||
|
@ -80,8 +80,10 @@ config SND_S3C24XX_SOC_SMDK2443_WM9710
|
|||
config SND_S3C24XX_SOC_LN2440SBC_ALC650
|
||||
tristate "SoC AC97 Audio support for LN2440SBC - ALC650"
|
||||
depends on SND_S3C24XX_SOC && ARCH_S3C2410
|
||||
select SND_S3C2443_SOC_AC97
|
||||
select S3C2410_DMA
|
||||
select AC97_BUS
|
||||
select SND_SOC_AC97_CODEC
|
||||
select SND_S3C_SOC_AC97
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on ln2440sbc
|
||||
with the ALC650.
|
||||
|
@ -111,3 +113,11 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES
|
|||
select SND_S3C24XX_SOC_I2S
|
||||
select SND_SOC_TLV320AIC3X
|
||||
select SND_S3C24XX_SOC_SIMTEC
|
||||
|
||||
config SND_SOC_SMDK_WM9713
|
||||
tristate "SoC AC97 Audio support for SMDK with WM9713"
|
||||
depends on SND_S3C24XX_SOC && MACH_SMDK6410
|
||||
select SND_SOC_WM9713
|
||||
select SND_S3C_SOC_AC97
|
||||
help
|
||||
Sat Y if you want to add support for SoC audio on the SMDK.
|
||||
|
|
|
@ -3,13 +3,13 @@ snd-soc-s3c24xx-objs := s3c-dma.o
|
|||
snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
|
||||
snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
|
||||
snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o
|
||||
snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o
|
||||
snd-soc-s3c-ac97-objs := s3c-ac97.o
|
||||
snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o
|
||||
snd-soc-s3c-pcm-objs := s3c-pcm.o
|
||||
|
||||
obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o
|
||||
obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o
|
||||
obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o
|
||||
obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o
|
||||
obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
|
||||
obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o
|
||||
obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o
|
||||
|
@ -26,6 +26,7 @@ snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
|
|||
snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
|
||||
snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
|
||||
snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o
|
||||
snd-soc-smdk-wm9713-objs := smdk_wm9713.o
|
||||
|
||||
obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o
|
||||
obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
|
||||
|
@ -37,4 +38,4 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o
|
|||
obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
|
||||
obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
|
||||
obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_SMDK_WM9713) += snd-soc-smdk-wm9713.o
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
#include "../codecs/ac97.h"
|
||||
#include "s3c-dma.h"
|
||||
#include "s3c24xx-ac97.h"
|
||||
#include "s3c-ac97.h"
|
||||
|
||||
static struct snd_soc_card ln2440sbc;
|
||||
|
||||
|
@ -33,7 +33,7 @@ static struct snd_soc_dai_link ln2440sbc_dai[] = {
|
|||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 HiFi",
|
||||
.cpu_dai = &s3c2443_ac97_dai[0],
|
||||
.cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_PCM],
|
||||
.codec_dai = &ac97_dai,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,518 @@
|
|||
/* sound/soc/s3c24xx/s3c-ac97.c
|
||||
*
|
||||
* ALSA SoC Audio Layer - S3C AC97 Controller driver
|
||||
* Evolved from s3c2443-ac97.c
|
||||
*
|
||||
* Copyright (c) 2010 Samsung Electronics Co. Ltd
|
||||
* Author: Jaswinder Singh <jassi.brar@samsung.com>
|
||||
* Credits: Graeme Gregory, Sean Choi
|
||||
*
|
||||
* 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/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <plat/regs-ac97.h>
|
||||
#include <mach/dma.h>
|
||||
#include <plat/audio.h>
|
||||
|
||||
#include "s3c-dma.h"
|
||||
#include "s3c-ac97.h"
|
||||
|
||||
#define AC_CMD_ADDR(x) (x << 16)
|
||||
#define AC_CMD_DATA(x) (x & 0xffff)
|
||||
|
||||
struct s3c_ac97_info {
|
||||
unsigned state;
|
||||
struct clk *ac97_clk;
|
||||
void __iomem *regs;
|
||||
struct mutex lock;
|
||||
struct completion done;
|
||||
};
|
||||
static struct s3c_ac97_info s3c_ac97;
|
||||
|
||||
static struct s3c2410_dma_client s3c_dma_client_out = {
|
||||
.name = "AC97 PCMOut"
|
||||
};
|
||||
|
||||
static struct s3c2410_dma_client s3c_dma_client_in = {
|
||||
.name = "AC97 PCMIn"
|
||||
};
|
||||
|
||||
static struct s3c2410_dma_client s3c_dma_client_micin = {
|
||||
.name = "AC97 MicIn"
|
||||
};
|
||||
|
||||
static struct s3c_dma_params s3c_ac97_pcm_out = {
|
||||
.client = &s3c_dma_client_out,
|
||||
.dma_size = 4,
|
||||
};
|
||||
|
||||
static struct s3c_dma_params s3c_ac97_pcm_in = {
|
||||
.client = &s3c_dma_client_in,
|
||||
.dma_size = 4,
|
||||
};
|
||||
|
||||
static struct s3c_dma_params s3c_ac97_mic_in = {
|
||||
.client = &s3c_dma_client_micin,
|
||||
.dma_size = 4,
|
||||
};
|
||||
|
||||
static void s3c_ac97_activate(struct snd_ac97 *ac97)
|
||||
{
|
||||
u32 ac_glbctrl, stat;
|
||||
|
||||
stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
|
||||
if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
|
||||
return; /* Return if already active */
|
||||
|
||||
INIT_COMPLETION(s3c_ac97.done);
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
|
||||
printk(KERN_ERR "AC97: Unable to activate!");
|
||||
}
|
||||
|
||||
static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
|
||||
unsigned short reg)
|
||||
{
|
||||
u32 ac_glbctrl, ac_codec_cmd;
|
||||
u32 stat, addr, data;
|
||||
|
||||
mutex_lock(&s3c_ac97.lock);
|
||||
|
||||
s3c_ac97_activate(ac97);
|
||||
|
||||
INIT_COMPLETION(s3c_ac97.done);
|
||||
|
||||
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
|
||||
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
|
||||
udelay(50);
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
|
||||
printk(KERN_ERR "AC97: Unable to read!");
|
||||
|
||||
stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
|
||||
addr = (stat >> 16) & 0x7f;
|
||||
data = (stat & 0xffff);
|
||||
|
||||
if (addr != reg)
|
||||
printk(KERN_ERR "s3c-ac97: req addr = %02x, rep addr = %02x\n", reg, addr);
|
||||
|
||||
mutex_unlock(&s3c_ac97.lock);
|
||||
|
||||
return (unsigned short)data;
|
||||
}
|
||||
|
||||
static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
u32 ac_glbctrl, ac_codec_cmd;
|
||||
|
||||
mutex_lock(&s3c_ac97.lock);
|
||||
|
||||
s3c_ac97_activate(ac97);
|
||||
|
||||
INIT_COMPLETION(s3c_ac97.done);
|
||||
|
||||
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
|
||||
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
|
||||
udelay(50);
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
|
||||
printk(KERN_ERR "AC97: Unable to write!");
|
||||
|
||||
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
|
||||
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
|
||||
mutex_unlock(&s3c_ac97.lock);
|
||||
}
|
||||
|
||||
static void s3c_ac97_cold_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
writel(S3C_AC97_GLBCTRL_COLDRESET,
|
||||
s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
static void s3c_ac97_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
u32 stat;
|
||||
|
||||
stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
|
||||
if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
|
||||
return; /* Return if already active */
|
||||
|
||||
writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
s3c_ac97_activate(ac97);
|
||||
}
|
||||
|
||||
static irqreturn_t s3c_ac97_irq(int irq, void *dev_id)
|
||||
{
|
||||
u32 ac_glbctrl, ac_glbstat;
|
||||
|
||||
ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
|
||||
|
||||
if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
complete(&s3c_ac97.done);
|
||||
}
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl |= (1<<30); /* Clear interrupt */
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
struct snd_ac97_bus_ops soc_ac97_ops = {
|
||||
.read = s3c_ac97_read,
|
||||
.write = s3c_ac97_write,
|
||||
.warm_reset = s3c_ac97_warm_reset,
|
||||
.reset = s3c_ac97_cold_reset,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_ac97_ops);
|
||||
|
||||
static int s3c_ac97_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_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
cpu_dai->dma_data = &s3c_ac97_pcm_out;
|
||||
else
|
||||
cpu_dai->dma_data = &s3c_ac97_pcm_in;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channel = ((struct s3c_dma_params *)
|
||||
rtd->dai->cpu_dai->dma_data)->channel;
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
|
||||
else
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
|
||||
|
||||
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)
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
|
||||
else
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
break;
|
||||
}
|
||||
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_ac97_hw_mic_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_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
return -ENODEV;
|
||||
else
|
||||
cpu_dai->dma_data = &s3c_ac97_mic_in;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channel = ((struct s3c_dma_params *)
|
||||
rtd->dai->cpu_dai->dma_data)->channel;
|
||||
|
||||
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
break;
|
||||
}
|
||||
|
||||
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
|
||||
.hw_params = s3c_ac97_hw_params,
|
||||
.trigger = s3c_ac97_trigger,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
|
||||
.hw_params = s3c_ac97_hw_mic_params,
|
||||
.trigger = s3c_ac97_mic_trigger,
|
||||
};
|
||||
|
||||
struct snd_soc_dai s3c_ac97_dai[] = {
|
||||
[S3C_AC97_DAI_PCM] = {
|
||||
.name = "s3c-ac97",
|
||||
.id = S3C_AC97_DAI_PCM,
|
||||
.ac97_control = 1,
|
||||
.playback = {
|
||||
.stream_name = "AC97 Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.capture = {
|
||||
.stream_name = "AC97 Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.ops = &s3c_ac97_dai_ops,
|
||||
},
|
||||
[S3C_AC97_DAI_MIC] = {
|
||||
.name = "s3c-ac97-mic",
|
||||
.id = S3C_AC97_DAI_MIC,
|
||||
.ac97_control = 1,
|
||||
.capture = {
|
||||
.stream_name = "AC97 Mic Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.ops = &s3c_ac97_mic_dai_ops,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(s3c_ac97_dai);
|
||||
|
||||
static __devinit int s3c_ac97_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
|
||||
struct s3c_audio_pdata *ac97_pdata;
|
||||
int ret;
|
||||
|
||||
ac97_pdata = pdev->dev.platform_data;
|
||||
if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
|
||||
dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check for availability of necessary resource */
|
||||
dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!dmatx_res) {
|
||||
dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
|
||||
if (!dmarx_res) {
|
||||
dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
|
||||
if (!dmamic_res) {
|
||||
dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!mem_res) {
|
||||
dev_err(&pdev->dev, "Unable to get register resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (!irq_res) {
|
||||
dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (!request_mem_region(mem_res->start,
|
||||
resource_size(mem_res), "s3c-ac97")) {
|
||||
dev_err(&pdev->dev, "Unable to request register region\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
s3c_ac97_pcm_out.channel = dmatx_res->start;
|
||||
s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
|
||||
s3c_ac97_pcm_in.channel = dmarx_res->start;
|
||||
s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
|
||||
s3c_ac97_mic_in.channel = dmamic_res->start;
|
||||
s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
|
||||
|
||||
init_completion(&s3c_ac97.done);
|
||||
mutex_init(&s3c_ac97.lock);
|
||||
|
||||
s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
|
||||
if (s3c_ac97.regs == NULL) {
|
||||
dev_err(&pdev->dev, "Unable to ioremap register region\n");
|
||||
ret = -ENXIO;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
|
||||
if (IS_ERR(s3c_ac97.ac97_clk)) {
|
||||
dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n");
|
||||
ret = -ENODEV;
|
||||
goto err2;
|
||||
}
|
||||
clk_enable(s3c_ac97.ac97_clk);
|
||||
|
||||
if (ac97_pdata->cfg_gpio(pdev)) {
|
||||
dev_err(&pdev->dev, "Unable to configure gpio\n");
|
||||
ret = -EINVAL;
|
||||
goto err3;
|
||||
}
|
||||
|
||||
ret = request_irq(irq_res->start, s3c_ac97_irq,
|
||||
IRQF_DISABLED, "AC97", NULL);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "s3c-ac97: interrupt request failed.\n");
|
||||
goto err4;
|
||||
}
|
||||
|
||||
s3c_ac97_dai[S3C_AC97_DAI_PCM].dev = &pdev->dev;
|
||||
s3c_ac97_dai[S3C_AC97_DAI_MIC].dev = &pdev->dev;
|
||||
|
||||
ret = snd_soc_register_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
|
||||
if (ret)
|
||||
goto err5;
|
||||
|
||||
return 0;
|
||||
|
||||
err5:
|
||||
free_irq(irq_res->start, NULL);
|
||||
err4:
|
||||
err3:
|
||||
clk_disable(s3c_ac97.ac97_clk);
|
||||
clk_put(s3c_ac97.ac97_clk);
|
||||
err2:
|
||||
iounmap(s3c_ac97.regs);
|
||||
err1:
|
||||
release_mem_region(mem_res->start, resource_size(mem_res));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __devexit int s3c_ac97_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *mem_res, *irq_res;
|
||||
|
||||
snd_soc_unregister_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
|
||||
|
||||
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (irq_res)
|
||||
free_irq(irq_res->start, NULL);
|
||||
|
||||
clk_disable(s3c_ac97.ac97_clk);
|
||||
clk_put(s3c_ac97.ac97_clk);
|
||||
|
||||
iounmap(s3c_ac97.regs);
|
||||
|
||||
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (mem_res)
|
||||
release_mem_region(mem_res->start, resource_size(mem_res));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver s3c_ac97_driver = {
|
||||
.probe = s3c_ac97_probe,
|
||||
.remove = s3c_ac97_remove,
|
||||
.driver = {
|
||||
.name = "s3c-ac97",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s3c_ac97_init(void)
|
||||
{
|
||||
return platform_driver_register(&s3c_ac97_driver);
|
||||
}
|
||||
module_init(s3c_ac97_init);
|
||||
|
||||
static void __exit s3c_ac97_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s3c_ac97_driver);
|
||||
}
|
||||
module_exit(s3c_ac97_exit);
|
||||
|
||||
MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
|
||||
MODULE_DESCRIPTION("AC97 driver for the Samsung SoC");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,23 @@
|
|||
/* sound/soc/s3c24xx/s3c-ac97.h
|
||||
*
|
||||
* ALSA SoC Audio Layer - S3C AC97 Controller driver
|
||||
* Evolved from s3c2443-ac97.h
|
||||
*
|
||||
* Copyright (c) 2010 Samsung Electronics Co. Ltd
|
||||
* Author: Jaswinder Singh <jassi.brar@samsung.com>
|
||||
* Credits: Graeme Gregory, Sean Choi
|
||||
*
|
||||
* 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 __S3C_AC97_H_
|
||||
#define __S3C_AC97_H_
|
||||
|
||||
#define S3C_AC97_DAI_PCM 0
|
||||
#define S3C_AC97_DAI_MIC 1
|
||||
|
||||
extern struct snd_soc_dai s3c_ac97_dai[];
|
||||
|
||||
#endif /* __S3C_AC97_H_ */
|
|
@ -229,8 +229,7 @@ static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
spin_unlock_irqrestore(&pcm->lock, flags);
|
||||
|
||||
dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs \
|
||||
SCLK_DIV=%d SYNC_DIV=%d\n",
|
||||
dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n",
|
||||
clk_get_rate(clk), pcm->sclk_per_fs,
|
||||
sclk_div, sync_div);
|
||||
|
||||
|
|
|
@ -1,432 +0,0 @@
|
|||
/*
|
||||
* s3c2443-ac97.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* (c) 2007 Wolfson Microelectronics PLC.
|
||||
* Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
|
||||
*
|
||||
* Copyright (C) 2005, Sean Choi <sh428.choi@samsung.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/ac97_codec.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
#include <plat/regs-ac97.h>
|
||||
#include <mach/regs-gpio.h>
|
||||
#include <mach/regs-clock.h>
|
||||
#include <asm/dma.h>
|
||||
#include <mach/dma.h>
|
||||
|
||||
#include "s3c-dma.h"
|
||||
#include "s3c24xx-ac97.h"
|
||||
|
||||
struct s3c24xx_ac97_info {
|
||||
void __iomem *regs;
|
||||
struct clk *ac97_clk;
|
||||
};
|
||||
static struct s3c24xx_ac97_info s3c24xx_ac97;
|
||||
|
||||
static DECLARE_COMPLETION(ac97_completion);
|
||||
static u32 codec_ready;
|
||||
static DEFINE_MUTEX(ac97_mutex);
|
||||
|
||||
static unsigned short s3c2443_ac97_read(struct snd_ac97 *ac97,
|
||||
unsigned short reg)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
u32 ac_codec_cmd;
|
||||
u32 stat, addr, data;
|
||||
|
||||
mutex_lock(&ac97_mutex);
|
||||
|
||||
codec_ready = S3C_AC97_GLBSTAT_CODECREADY;
|
||||
ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
|
||||
writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
|
||||
udelay(50);
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
wait_for_completion(&ac97_completion);
|
||||
|
||||
stat = readl(s3c24xx_ac97.regs + S3C_AC97_STAT);
|
||||
addr = (stat >> 16) & 0x7f;
|
||||
data = (stat & 0xffff);
|
||||
|
||||
if (addr != reg)
|
||||
printk(KERN_ERR "s3c24xx-ac97: req addr = %02x,"
|
||||
" rep addr = %02x\n", reg, addr);
|
||||
|
||||
mutex_unlock(&ac97_mutex);
|
||||
|
||||
return (unsigned short)data;
|
||||
}
|
||||
|
||||
static void s3c2443_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
u32 ac_codec_cmd;
|
||||
|
||||
mutex_lock(&ac97_mutex);
|
||||
|
||||
codec_ready = S3C_AC97_GLBSTAT_CODECREADY;
|
||||
ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
|
||||
writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
|
||||
udelay(50);
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
wait_for_completion(&ac97_completion);
|
||||
|
||||
ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
|
||||
writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
|
||||
|
||||
mutex_unlock(&ac97_mutex);
|
||||
|
||||
}
|
||||
|
||||
static void s3c2443_ac97_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl = S3C_AC97_GLBCTRL_WARMRESET;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl = 0;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
static void s3c2443_ac97_cold_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl = 0;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA |
|
||||
S3C_AC97_GLBCTRL_PCMINTM_DMA | S3C_AC97_GLBCTRL_MICINTM_DMA;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
}
|
||||
|
||||
static irqreturn_t s3c2443_ac97_irq(int irq, void *dev_id)
|
||||
{
|
||||
int status;
|
||||
u32 ac_glbctrl;
|
||||
|
||||
status = readl(s3c24xx_ac97.regs + S3C_AC97_GLBSTAT) & codec_ready;
|
||||
|
||||
if (status) {
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
complete(&ac97_completion);
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
struct snd_ac97_bus_ops soc_ac97_ops = {
|
||||
.read = s3c2443_ac97_read,
|
||||
.write = s3c2443_ac97_write,
|
||||
.warm_reset = s3c2443_ac97_warm_reset,
|
||||
.reset = s3c2443_ac97_cold_reset,
|
||||
};
|
||||
|
||||
static struct s3c2410_dma_client s3c2443_dma_client_out = {
|
||||
.name = "AC97 PCM Stereo out"
|
||||
};
|
||||
|
||||
static struct s3c2410_dma_client s3c2443_dma_client_in = {
|
||||
.name = "AC97 PCM Stereo in"
|
||||
};
|
||||
|
||||
static struct s3c2410_dma_client s3c2443_dma_client_micin = {
|
||||
.name = "AC97 Mic Mono in"
|
||||
};
|
||||
|
||||
static struct s3c_dma_params s3c2443_ac97_pcm_stereo_out = {
|
||||
.client = &s3c2443_dma_client_out,
|
||||
.channel = DMACH_PCM_OUT,
|
||||
.dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
|
||||
.dma_size = 4,
|
||||
};
|
||||
|
||||
static struct s3c_dma_params s3c2443_ac97_pcm_stereo_in = {
|
||||
.client = &s3c2443_dma_client_in,
|
||||
.channel = DMACH_PCM_IN,
|
||||
.dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
|
||||
.dma_size = 4,
|
||||
};
|
||||
|
||||
static struct s3c_dma_params s3c2443_ac97_mic_mono_in = {
|
||||
.client = &s3c2443_dma_client_micin,
|
||||
.channel = DMACH_MIC_IN,
|
||||
.dma_addr = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA,
|
||||
.dma_size = 4,
|
||||
};
|
||||
|
||||
static int s3c2443_ac97_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int ret;
|
||||
u32 ac_glbctrl;
|
||||
|
||||
s3c24xx_ac97.regs = ioremap(S3C2440_PA_AC97, 0x100);
|
||||
if (s3c24xx_ac97.regs == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
s3c24xx_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
|
||||
if (s3c24xx_ac97.ac97_clk == NULL) {
|
||||
printk(KERN_ERR "s3c2443-ac97 failed to get ac97_clock\n");
|
||||
iounmap(s3c24xx_ac97.regs);
|
||||
return -ENODEV;
|
||||
}
|
||||
clk_enable(s3c24xx_ac97.ac97_clk);
|
||||
|
||||
s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2443_GPE0_AC_nRESET);
|
||||
s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2443_GPE1_AC_SYNC);
|
||||
s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2443_GPE2_AC_BITCLK);
|
||||
s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2443_GPE3_AC_SDI);
|
||||
s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2443_GPE4_AC_SDO);
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl = 0;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
msleep(1);
|
||||
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
ret = request_irq(IRQ_S3C244x_AC97, s3c2443_ac97_irq,
|
||||
IRQF_DISABLED, "AC97", NULL);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "s3c24xx-ac97: interrupt request failed.\n");
|
||||
clk_disable(s3c24xx_ac97.ac97_clk);
|
||||
clk_put(s3c24xx_ac97.ac97_clk);
|
||||
iounmap(s3c24xx_ac97.regs);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void s3c2443_ac97_remove(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
free_irq(IRQ_S3C244x_AC97, NULL);
|
||||
clk_disable(s3c24xx_ac97.ac97_clk);
|
||||
clk_put(s3c24xx_ac97.ac97_clk);
|
||||
iounmap(s3c24xx_ac97.regs);
|
||||
}
|
||||
|
||||
static int s3c2443_ac97_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_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
cpu_dai->dma_data = &s3c2443_ac97_pcm_stereo_out;
|
||||
else
|
||||
cpu_dai->dma_data = &s3c2443_ac97_pcm_stereo_in;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2443_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channel = ((struct s3c_dma_params *)
|
||||
rtd->dai->cpu_dai->dma_data)->channel;
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
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)
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
|
||||
else
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
|
||||
else
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
|
||||
break;
|
||||
}
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2443_ac97_hw_mic_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_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
return -ENODEV;
|
||||
else
|
||||
cpu_dai->dma_data = &s3c2443_ac97_mic_mono_in;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2443_ac97_mic_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
u32 ac_glbctrl;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channel = ((struct s3c_dma_params *)
|
||||
rtd->dai->cpu_dai->dma_data)->channel;
|
||||
|
||||
ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
|
||||
}
|
||||
writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
|
||||
|
||||
s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define s3c2443_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
|
||||
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
|
||||
|
||||
static struct snd_soc_dai_ops s3c2443_ac97_dai_ops = {
|
||||
.hw_params = s3c2443_ac97_hw_params,
|
||||
.trigger = s3c2443_ac97_trigger,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops s3c2443_ac97_mic_dai_ops = {
|
||||
.hw_params = s3c2443_ac97_hw_mic_params,
|
||||
.trigger = s3c2443_ac97_mic_trigger,
|
||||
};
|
||||
|
||||
struct snd_soc_dai s3c2443_ac97_dai[] = {
|
||||
{
|
||||
.name = "s3c2443-ac97",
|
||||
.id = 0,
|
||||
.ac97_control = 1,
|
||||
.probe = s3c2443_ac97_probe,
|
||||
.remove = s3c2443_ac97_remove,
|
||||
.playback = {
|
||||
.stream_name = "AC97 Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = s3c2443_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.capture = {
|
||||
.stream_name = "AC97 Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = s3c2443_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.ops = &s3c2443_ac97_dai_ops,
|
||||
},
|
||||
{
|
||||
.name = "pxa2xx-ac97-mic",
|
||||
.id = 1,
|
||||
.ac97_control = 1,
|
||||
.capture = {
|
||||
.stream_name = "AC97 Mic Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.rates = s3c2443_AC97_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.ops = &s3c2443_ac97_mic_dai_ops,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(s3c2443_ac97_dai);
|
||||
EXPORT_SYMBOL_GPL(soc_ac97_ops);
|
||||
|
||||
static int __init s3c2443_ac97_init(void)
|
||||
{
|
||||
return snd_soc_register_dais(s3c2443_ac97_dai,
|
||||
ARRAY_SIZE(s3c2443_ac97_dai));
|
||||
}
|
||||
module_init(s3c2443_ac97_init);
|
||||
|
||||
static void __exit s3c2443_ac97_exit(void)
|
||||
{
|
||||
snd_soc_unregister_dais(s3c2443_ac97_dai,
|
||||
ARRAY_SIZE(s3c2443_ac97_dai));
|
||||
}
|
||||
module_exit(s3c2443_ac97_exit);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Graeme Gregory");
|
||||
MODULE_DESCRIPTION("AC97 driver for the Samsung s3c2443 chip");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* s3c24xx-ac97.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* (c) 2007 Wolfson Microelectronics PLC.
|
||||
* Author: Graeme Gregory
|
||||
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Revision history
|
||||
* 10th Nov 2006 Initial version.
|
||||
*/
|
||||
|
||||
#ifndef S3C24XXAC97_H_
|
||||
#define S3C24XXAC97_H_
|
||||
|
||||
#define AC_CMD_ADDR(x) (x << 16)
|
||||
#define AC_CMD_DATA(x) (x & 0xffff)
|
||||
|
||||
extern struct snd_soc_dai s3c2443_ac97_dai[];
|
||||
|
||||
#endif /*S3C24XXAC97_H_*/
|
|
@ -15,16 +15,10 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gpio.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 <plat/regs-s3c2412-iis.h>
|
||||
|
@ -38,6 +32,11 @@
|
|||
#include "s3c-dma.h"
|
||||
#include "s3c64xx-i2s.h"
|
||||
|
||||
/* The value should be set to maximum of the total number
|
||||
* of I2Sv3 controllers that any supported SoC has.
|
||||
*/
|
||||
#define MAX_I2SV3 2
|
||||
|
||||
static struct s3c2410_dma_client s3c64xx_dma_client_out = {
|
||||
.name = "I2S PCM Stereo out"
|
||||
};
|
||||
|
@ -46,37 +45,12 @@ static struct s3c2410_dma_client s3c64xx_dma_client_in = {
|
|||
.name = "I2S PCM Stereo in"
|
||||
};
|
||||
|
||||
static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_out[2] = {
|
||||
[0] = {
|
||||
.channel = DMACH_I2S0_OUT,
|
||||
.client = &s3c64xx_dma_client_out,
|
||||
.dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISTXD,
|
||||
.dma_size = 4,
|
||||
},
|
||||
[1] = {
|
||||
.channel = DMACH_I2S1_OUT,
|
||||
.client = &s3c64xx_dma_client_out,
|
||||
.dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISTXD,
|
||||
.dma_size = 4,
|
||||
},
|
||||
};
|
||||
static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_out[MAX_I2SV3];
|
||||
static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_in[MAX_I2SV3];
|
||||
static struct s3c_i2sv2_info s3c64xx_i2s[MAX_I2SV3];
|
||||
|
||||
static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_in[2] = {
|
||||
[0] = {
|
||||
.channel = DMACH_I2S0_IN,
|
||||
.client = &s3c64xx_dma_client_in,
|
||||
.dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISRXD,
|
||||
.dma_size = 4,
|
||||
},
|
||||
[1] = {
|
||||
.channel = DMACH_I2S1_IN,
|
||||
.client = &s3c64xx_dma_client_in,
|
||||
.dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISRXD,
|
||||
.dma_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c_i2sv2_info s3c64xx_i2s[2];
|
||||
struct snd_soc_dai s3c64xx_i2s_dai[MAX_I2SV3];
|
||||
EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai);
|
||||
|
||||
static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
|
@ -169,55 +143,13 @@ 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,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = S3C64XX_I2S_RATES,
|
||||
.formats = S3C64XX_I2S_FMTS,
|
||||
},
|
||||
.ops = &s3c64xx_i2s_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
},
|
||||
{
|
||||
.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,
|
||||
},
|
||||
};
|
||||
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)) {
|
||||
if (pdev->id >= MAX_I2SV3) {
|
||||
dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -225,10 +157,40 @@ static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev)
|
|||
i2s = &s3c64xx_i2s[pdev->id];
|
||||
dai = &s3c64xx_i2s_dai[pdev->id];
|
||||
dai->dev = &pdev->dev;
|
||||
dai->name = "s3c64xx-i2s";
|
||||
dai->id = pdev->id;
|
||||
dai->symmetric_rates = 1;
|
||||
dai->playback.channels_min = 2;
|
||||
dai->playback.channels_max = 2;
|
||||
dai->playback.rates = S3C64XX_I2S_RATES;
|
||||
dai->playback.formats = S3C64XX_I2S_FMTS;
|
||||
dai->capture.channels_min = 2;
|
||||
dai->capture.channels_max = 2;
|
||||
dai->capture.rates = S3C64XX_I2S_RATES;
|
||||
dai->capture.formats = S3C64XX_I2S_FMTS;
|
||||
dai->probe = s3c64xx_i2s_probe;
|
||||
dai->ops = &s3c64xx_i2s_dai_ops;
|
||||
|
||||
i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id];
|
||||
i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id];
|
||||
|
||||
if (pdev->id == 0) {
|
||||
i2s->dma_capture->channel = DMACH_I2S0_IN;
|
||||
i2s->dma_capture->dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISRXD;
|
||||
i2s->dma_playback->channel = DMACH_I2S0_OUT;
|
||||
i2s->dma_playback->dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISTXD;
|
||||
} else {
|
||||
i2s->dma_capture->channel = DMACH_I2S1_IN;
|
||||
i2s->dma_capture->dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISRXD;
|
||||
i2s->dma_playback->channel = DMACH_I2S1_OUT;
|
||||
i2s->dma_playback->dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISTXD;
|
||||
}
|
||||
|
||||
i2s->dma_capture->client = &s3c64xx_dma_client_in;
|
||||
i2s->dma_capture->dma_size = 4;
|
||||
i2s->dma_playback->client = &s3c64xx_dma_client_out;
|
||||
i2s->dma_playback->dma_size = 4;
|
||||
|
||||
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");
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
#include "../codecs/ac97.h"
|
||||
#include "s3c-dma.h"
|
||||
#include "s3c24xx-ac97.h"
|
||||
#include "s3c-ac97.h"
|
||||
|
||||
static struct snd_soc_card smdk2443;
|
||||
|
||||
|
@ -29,7 +29,7 @@ static struct snd_soc_dai_link smdk2443_dai[] = {
|
|||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 HiFi",
|
||||
.cpu_dai = &s3c2443_ac97_dai[0],
|
||||
.cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_PCM],
|
||||
.codec_dai = &ac97_dai,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* smdk_wm9713.c -- SoC audio for SMDK
|
||||
*
|
||||
* Copyright 2010 Samsung Electronics Co. Ltd.
|
||||
* Author: Jaswinder Singh Brar <jassi.brar@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "../codecs/wm9713.h"
|
||||
#include "s3c-dma.h"
|
||||
#include "s3c-ac97.h"
|
||||
|
||||
static struct snd_soc_card smdk;
|
||||
|
||||
/*
|
||||
* Default CFG switch settings to use this driver:
|
||||
*
|
||||
* SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off
|
||||
*/
|
||||
|
||||
/*
|
||||
Playback (HeadPhone):-
|
||||
$ amixer sset 'Headphone' unmute
|
||||
$ amixer sset 'Right Headphone Out Mux' 'Headphone'
|
||||
$ amixer sset 'Left Headphone Out Mux' 'Headphone'
|
||||
$ amixer sset 'Right HP Mixer PCM' unmute
|
||||
$ amixer sset 'Left HP Mixer PCM' unmute
|
||||
|
||||
Capture (LineIn):-
|
||||
$ amixer sset 'Right Capture Source' 'Line'
|
||||
$ amixer sset 'Left Capture Source' 'Line'
|
||||
*/
|
||||
|
||||
static struct snd_soc_dai_link smdk_dai = {
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 PCM",
|
||||
.cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_PCM],
|
||||
.codec_dai = &wm9713_dai[WM9713_DAI_AC97_HIFI],
|
||||
};
|
||||
|
||||
static struct snd_soc_card smdk = {
|
||||
.name = "SMDK",
|
||||
.platform = &s3c24xx_soc_platform,
|
||||
.dai_link = &smdk_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct snd_soc_device smdk_snd_ac97_devdata = {
|
||||
.card = &smdk,
|
||||
.codec_dev = &soc_codec_dev_wm9713,
|
||||
};
|
||||
|
||||
static struct platform_device *smdk_snd_ac97_device;
|
||||
|
||||
static int __init smdk_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
smdk_snd_ac97_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!smdk_snd_ac97_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(smdk_snd_ac97_device,
|
||||
&smdk_snd_ac97_devdata);
|
||||
smdk_snd_ac97_devdata.dev = &smdk_snd_ac97_device->dev;
|
||||
|
||||
ret = platform_device_add(smdk_snd_ac97_device);
|
||||
if (ret)
|
||||
platform_device_put(smdk_snd_ac97_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit smdk_exit(void)
|
||||
{
|
||||
platform_device_unregister(smdk_snd_ac97_device);
|
||||
}
|
||||
|
||||
module_init(smdk_init);
|
||||
module_exit(smdk_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Jaswinder Singh Brar, jassi.brar@samsung.com");
|
||||
MODULE_DESCRIPTION("ALSA SoC SMDK+WM9713");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -26,6 +26,13 @@ config SND_SOC_SH4_FSI
|
|||
help
|
||||
This option enables FSI sound support
|
||||
|
||||
config SND_SOC_SH4_SIU
|
||||
tristate
|
||||
depends on (SUPERH || ARCH_SHMOBILE) && HAVE_CLK
|
||||
select DMA_ENGINE
|
||||
select DMADEVICES
|
||||
select SH_DMAE
|
||||
|
||||
##
|
||||
## Boards
|
||||
##
|
||||
|
@ -47,4 +54,20 @@ config SND_FSI_AK4642
|
|||
This option enables generic sound support for the
|
||||
FSI - AK4642 unit
|
||||
|
||||
config SND_FSI_DA7210
|
||||
bool "FSI-DA7210 sound support"
|
||||
depends on SND_SOC_SH4_FSI
|
||||
select SND_SOC_DA7210
|
||||
help
|
||||
This option enables generic sound support for the
|
||||
FSI - DA7210 unit
|
||||
|
||||
config SND_SIU_MIGOR
|
||||
tristate "SIU sound support on Migo-R"
|
||||
depends on SH_MIGOR
|
||||
select SND_SOC_SH4_SIU
|
||||
select SND_SOC_WM8978
|
||||
help
|
||||
This option enables sound support for the SH7722 Migo-R board
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -6,13 +6,19 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o
|
|||
snd-soc-hac-objs := hac.o
|
||||
snd-soc-ssi-objs := ssi.o
|
||||
snd-soc-fsi-objs := fsi.o
|
||||
snd-soc-siu-objs := siu_pcm.o siu_dai.o
|
||||
obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o
|
||||
obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o
|
||||
obj-$(CONFIG_SND_SOC_SH4_FSI) += snd-soc-fsi.o
|
||||
obj-$(CONFIG_SND_SOC_SH4_SIU) += snd-soc-siu.o
|
||||
|
||||
## boards
|
||||
snd-soc-sh7760-ac97-objs := sh7760-ac97.o
|
||||
snd-soc-fsi-ak4642-objs := fsi-ak4642.o
|
||||
snd-soc-fsi-da7210-objs := fsi-da7210.o
|
||||
snd-soc-migor-objs := migor.o
|
||||
|
||||
obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o
|
||||
obj-$(CONFIG_SND_FSI_AK4642) += snd-soc-fsi-ak4642.o
|
||||
obj-$(CONFIG_SND_FSI_DA7210) += snd-soc-fsi-da7210.o
|
||||
obj-$(CONFIG_SND_SIU_MIGOR) += snd-soc-migor.o
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* fsi-da7210.c
|
||||
*
|
||||
* Copyright (C) 2009 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/i2c.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/sh_fsi.h>
|
||||
#include "../codecs/da7210.h"
|
||||
|
||||
static int fsi_da7210_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
return snd_soc_dai_set_fmt(&da7210_dai,
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_link fsi_da7210_dai = {
|
||||
.name = "DA7210",
|
||||
.stream_name = "DA7210",
|
||||
.cpu_dai = &fsi_soc_dai[1], /* FSI B */
|
||||
.codec_dai = &da7210_dai,
|
||||
.init = fsi_da7210_init,
|
||||
};
|
||||
|
||||
static struct snd_soc_card fsi_soc_card = {
|
||||
.name = "FSI",
|
||||
.platform = &fsi_soc_platform,
|
||||
.dai_link = &fsi_da7210_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct snd_soc_device fsi_da7210_snd_devdata = {
|
||||
.card = &fsi_soc_card,
|
||||
.codec_dev = &soc_codec_dev_da7210,
|
||||
};
|
||||
|
||||
static struct platform_device *fsi_da7210_snd_device;
|
||||
|
||||
static int __init fsi_da7210_sound_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
fsi_da7210_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!fsi_da7210_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(fsi_da7210_snd_device, &fsi_da7210_snd_devdata);
|
||||
fsi_da7210_snd_devdata.dev = &fsi_da7210_snd_device->dev;
|
||||
ret = platform_device_add(fsi_da7210_snd_device);
|
||||
if (ret)
|
||||
platform_device_put(fsi_da7210_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit fsi_da7210_sound_exit(void)
|
||||
{
|
||||
platform_device_unregister(fsi_da7210_snd_device);
|
||||
}
|
||||
|
||||
module_init(fsi_da7210_sound_init);
|
||||
module_exit(fsi_da7210_sound_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_DESCRIPTION("ALSA SoC FSI DA2710");
|
||||
MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -67,6 +67,7 @@
|
|||
/* DOFF_ST */
|
||||
#define ERR_OVER 0x00000010
|
||||
#define ERR_UNDER 0x00000001
|
||||
#define ST_ERR (ERR_OVER | ERR_UNDER)
|
||||
|
||||
/* CLK_RST */
|
||||
#define B_CLK 0x00000010
|
||||
|
@ -92,6 +93,7 @@
|
|||
struct fsi_priv {
|
||||
void __iomem *base;
|
||||
struct snd_pcm_substream *substream;
|
||||
struct fsi_master *master;
|
||||
|
||||
int fifo_max;
|
||||
int chan;
|
||||
|
@ -108,10 +110,9 @@ struct fsi_master {
|
|||
struct fsi_priv fsia;
|
||||
struct fsi_priv fsib;
|
||||
struct sh_fsi_platform_info *info;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static struct fsi_master *master;
|
||||
|
||||
/************************************************************************
|
||||
|
||||
|
||||
|
@ -119,35 +120,35 @@ static struct fsi_master *master;
|
|||
|
||||
|
||||
************************************************************************/
|
||||
static int __fsi_reg_write(u32 reg, u32 data)
|
||||
static void __fsi_reg_write(u32 reg, u32 data)
|
||||
{
|
||||
/* valid data area is 24bit */
|
||||
data &= 0x00ffffff;
|
||||
|
||||
return ctrl_outl(data, reg);
|
||||
__raw_writel(data, reg);
|
||||
}
|
||||
|
||||
static u32 __fsi_reg_read(u32 reg)
|
||||
{
|
||||
return ctrl_inl(reg);
|
||||
return __raw_readl(reg);
|
||||
}
|
||||
|
||||
static int __fsi_reg_mask_set(u32 reg, u32 mask, u32 data)
|
||||
static void __fsi_reg_mask_set(u32 reg, u32 mask, u32 data)
|
||||
{
|
||||
u32 val = __fsi_reg_read(reg);
|
||||
|
||||
val &= ~mask;
|
||||
val |= data & mask;
|
||||
|
||||
return __fsi_reg_write(reg, val);
|
||||
__fsi_reg_write(reg, val);
|
||||
}
|
||||
|
||||
static int fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data)
|
||||
static void fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data)
|
||||
{
|
||||
if (reg > REG_END)
|
||||
return -1;
|
||||
return;
|
||||
|
||||
return __fsi_reg_write((u32)(fsi->base + reg), data);
|
||||
__fsi_reg_write((u32)(fsi->base + reg), data);
|
||||
}
|
||||
|
||||
static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg)
|
||||
|
@ -158,39 +159,55 @@ static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg)
|
|||
return __fsi_reg_read((u32)(fsi->base + reg));
|
||||
}
|
||||
|
||||
static int fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data)
|
||||
static void fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data)
|
||||
{
|
||||
if (reg > REG_END)
|
||||
return -1;
|
||||
return;
|
||||
|
||||
return __fsi_reg_mask_set((u32)(fsi->base + reg), mask, data);
|
||||
__fsi_reg_mask_set((u32)(fsi->base + reg), mask, data);
|
||||
}
|
||||
|
||||
static int fsi_master_write(u32 reg, u32 data)
|
||||
static void fsi_master_write(struct fsi_master *master, u32 reg, u32 data)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if ((reg < MREG_START) ||
|
||||
(reg > MREG_END))
|
||||
return -1;
|
||||
return;
|
||||
|
||||
return __fsi_reg_write((u32)(master->base + reg), data);
|
||||
spin_lock_irqsave(&master->lock, flags);
|
||||
__fsi_reg_write((u32)(master->base + reg), data);
|
||||
spin_unlock_irqrestore(&master->lock, flags);
|
||||
}
|
||||
|
||||
static u32 fsi_master_read(u32 reg)
|
||||
static u32 fsi_master_read(struct fsi_master *master, u32 reg)
|
||||
{
|
||||
u32 ret;
|
||||
unsigned long flags;
|
||||
|
||||
if ((reg < MREG_START) ||
|
||||
(reg > MREG_END))
|
||||
return 0;
|
||||
|
||||
return __fsi_reg_read((u32)(master->base + reg));
|
||||
spin_lock_irqsave(&master->lock, flags);
|
||||
ret = __fsi_reg_read((u32)(master->base + reg));
|
||||
spin_unlock_irqrestore(&master->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsi_master_mask_set(u32 reg, u32 mask, u32 data)
|
||||
static void fsi_master_mask_set(struct fsi_master *master,
|
||||
u32 reg, u32 mask, u32 data)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if ((reg < MREG_START) ||
|
||||
(reg > MREG_END))
|
||||
return -1;
|
||||
return;
|
||||
|
||||
return __fsi_reg_mask_set((u32)(master->base + reg), mask, data);
|
||||
spin_lock_irqsave(&master->lock, flags);
|
||||
__fsi_reg_mask_set((u32)(master->base + reg), mask, data);
|
||||
spin_unlock_irqrestore(&master->lock, flags);
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
|
@ -200,43 +217,35 @@ static int fsi_master_mask_set(u32 reg, u32 mask, u32 data)
|
|||
|
||||
|
||||
************************************************************************/
|
||||
static struct fsi_priv *fsi_get(struct snd_pcm_substream *substream)
|
||||
static struct fsi_master *fsi_get_master(struct fsi_priv *fsi)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd;
|
||||
struct fsi_priv *fsi = NULL;
|
||||
|
||||
if (!substream || !master)
|
||||
return NULL;
|
||||
|
||||
rtd = substream->private_data;
|
||||
switch (rtd->dai->cpu_dai->id) {
|
||||
case 0:
|
||||
fsi = &master->fsia;
|
||||
break;
|
||||
case 1:
|
||||
fsi = &master->fsib;
|
||||
break;
|
||||
}
|
||||
|
||||
return fsi;
|
||||
return fsi->master;
|
||||
}
|
||||
|
||||
static int fsi_is_port_a(struct fsi_priv *fsi)
|
||||
{
|
||||
/* return
|
||||
* 1 : port a
|
||||
* 0 : port b
|
||||
*/
|
||||
return fsi->master->base == fsi->base;
|
||||
}
|
||||
|
||||
if (fsi == &master->fsia)
|
||||
return 1;
|
||||
static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai_link *machine = rtd->dai;
|
||||
|
||||
return 0;
|
||||
return machine->cpu_dai;
|
||||
}
|
||||
|
||||
static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_dai *dai = fsi_get_dai(substream);
|
||||
|
||||
return dai->private_data;
|
||||
}
|
||||
|
||||
static u32 fsi_get_info_flags(struct fsi_priv *fsi)
|
||||
{
|
||||
int is_porta = fsi_is_port_a(fsi);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
|
||||
return is_porta ? master->info->porta_flags :
|
||||
master->info->portb_flags;
|
||||
|
@ -314,27 +323,30 @@ static int fsi_get_fifo_residue(struct fsi_priv *fsi, int is_play)
|
|||
static void fsi_irq_enable(struct fsi_priv *fsi, int is_play)
|
||||
{
|
||||
u32 data = fsi_port_ab_io_bit(fsi, is_play);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
|
||||
fsi_master_mask_set(IMSK, data, data);
|
||||
fsi_master_mask_set(IEMSK, data, data);
|
||||
fsi_master_mask_set(master, IMSK, data, data);
|
||||
fsi_master_mask_set(master, IEMSK, data, data);
|
||||
}
|
||||
|
||||
static void fsi_irq_disable(struct fsi_priv *fsi, int is_play)
|
||||
{
|
||||
u32 data = fsi_port_ab_io_bit(fsi, is_play);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
|
||||
fsi_master_mask_set(IMSK, data, 0);
|
||||
fsi_master_mask_set(IEMSK, data, 0);
|
||||
fsi_master_mask_set(master, IMSK, data, 0);
|
||||
fsi_master_mask_set(master, IEMSK, data, 0);
|
||||
}
|
||||
|
||||
static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable)
|
||||
{
|
||||
u32 val = fsi_is_port_a(fsi) ? (1 << 0) : (1 << 4);
|
||||
struct fsi_master *master = fsi_get_master(fsi);
|
||||
|
||||
if (enable)
|
||||
fsi_master_mask_set(CLK_RST, val, val);
|
||||
fsi_master_mask_set(master, CLK_RST, val, val);
|
||||
else
|
||||
fsi_master_mask_set(CLK_RST, val, 0);
|
||||
fsi_master_mask_set(master, CLK_RST, val, 0);
|
||||
}
|
||||
|
||||
static void fsi_irq_init(struct fsi_priv *fsi, int is_play)
|
||||
|
@ -355,43 +367,46 @@ static void fsi_irq_init(struct fsi_priv *fsi, int is_play)
|
|||
fsi_reg_mask_set(fsi, ctrl, FIFO_CLR, FIFO_CLR);
|
||||
|
||||
/* clear interrupt factor */
|
||||
fsi_master_mask_set(INT_ST, data, 0);
|
||||
fsi_master_mask_set(fsi_get_master(fsi), INT_ST, data, 0);
|
||||
}
|
||||
|
||||
static void fsi_soft_all_reset(void)
|
||||
static void fsi_soft_all_reset(struct fsi_master *master)
|
||||
{
|
||||
u32 status = fsi_master_read(SOFT_RST);
|
||||
u32 status = fsi_master_read(master, SOFT_RST);
|
||||
|
||||
/* port AB reset */
|
||||
status &= 0x000000ff;
|
||||
fsi_master_write(SOFT_RST, status);
|
||||
fsi_master_write(master, SOFT_RST, status);
|
||||
mdelay(10);
|
||||
|
||||
/* soft reset */
|
||||
status &= 0x000000f0;
|
||||
fsi_master_write(SOFT_RST, status);
|
||||
fsi_master_write(master, SOFT_RST, status);
|
||||
status |= 0x00000001;
|
||||
fsi_master_write(SOFT_RST, status);
|
||||
fsi_master_write(master, SOFT_RST, status);
|
||||
mdelay(10);
|
||||
}
|
||||
|
||||
/* playback interrupt */
|
||||
static int fsi_data_push(struct fsi_priv *fsi)
|
||||
static int fsi_data_push(struct fsi_priv *fsi, int startup)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime;
|
||||
struct snd_pcm_substream *substream = NULL;
|
||||
u32 status;
|
||||
int send;
|
||||
int fifo_free;
|
||||
int width;
|
||||
u8 *start;
|
||||
int i;
|
||||
int i, over_period;
|
||||
|
||||
if (!fsi ||
|
||||
!fsi->substream ||
|
||||
!fsi->substream->runtime)
|
||||
return -EINVAL;
|
||||
|
||||
runtime = fsi->substream->runtime;
|
||||
over_period = 0;
|
||||
substream = fsi->substream;
|
||||
runtime = substream->runtime;
|
||||
|
||||
/* FSI FIFO has limit.
|
||||
* So, this driver can not send periods data at a time
|
||||
|
@ -399,7 +414,7 @@ static int fsi_data_push(struct fsi_priv *fsi)
|
|||
if (fsi->byte_offset >=
|
||||
fsi->period_len * (fsi->periods + 1)) {
|
||||
|
||||
substream = fsi->substream;
|
||||
over_period = 1;
|
||||
fsi->periods = (fsi->periods + 1) % runtime->periods;
|
||||
|
||||
if (0 == fsi->periods)
|
||||
|
@ -438,30 +453,44 @@ static int fsi_data_push(struct fsi_priv *fsi)
|
|||
|
||||
fsi->byte_offset += send * width;
|
||||
|
||||
status = fsi_reg_read(fsi, DOFF_ST);
|
||||
if (!startup) {
|
||||
struct snd_soc_dai *dai = fsi_get_dai(substream);
|
||||
|
||||
if (status & ERR_OVER)
|
||||
dev_err(dai->dev, "over run\n");
|
||||
if (status & ERR_UNDER)
|
||||
dev_err(dai->dev, "under run\n");
|
||||
}
|
||||
fsi_reg_write(fsi, DOFF_ST, 0);
|
||||
|
||||
fsi_irq_enable(fsi, 1);
|
||||
|
||||
if (substream)
|
||||
if (over_period)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsi_data_pop(struct fsi_priv *fsi)
|
||||
static int fsi_data_pop(struct fsi_priv *fsi, int startup)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime;
|
||||
struct snd_pcm_substream *substream = NULL;
|
||||
u32 status;
|
||||
int free;
|
||||
int fifo_fill;
|
||||
int width;
|
||||
u8 *start;
|
||||
int i;
|
||||
int i, over_period;
|
||||
|
||||
if (!fsi ||
|
||||
!fsi->substream ||
|
||||
!fsi->substream->runtime)
|
||||
return -EINVAL;
|
||||
|
||||
runtime = fsi->substream->runtime;
|
||||
over_period = 0;
|
||||
substream = fsi->substream;
|
||||
runtime = substream->runtime;
|
||||
|
||||
/* FSI FIFO has limit.
|
||||
* So, this driver can not send periods data at a time
|
||||
|
@ -469,7 +498,7 @@ static int fsi_data_pop(struct fsi_priv *fsi)
|
|||
if (fsi->byte_offset >=
|
||||
fsi->period_len * (fsi->periods + 1)) {
|
||||
|
||||
substream = fsi->substream;
|
||||
over_period = 1;
|
||||
fsi->periods = (fsi->periods + 1) % runtime->periods;
|
||||
|
||||
if (0 == fsi->periods)
|
||||
|
@ -507,9 +536,20 @@ static int fsi_data_pop(struct fsi_priv *fsi)
|
|||
|
||||
fsi->byte_offset += fifo_fill * width;
|
||||
|
||||
status = fsi_reg_read(fsi, DIFF_ST);
|
||||
if (!startup) {
|
||||
struct snd_soc_dai *dai = fsi_get_dai(substream);
|
||||
|
||||
if (status & ERR_OVER)
|
||||
dev_err(dai->dev, "over run\n");
|
||||
if (status & ERR_UNDER)
|
||||
dev_err(dai->dev, "under run\n");
|
||||
}
|
||||
fsi_reg_write(fsi, DIFF_ST, 0);
|
||||
|
||||
fsi_irq_enable(fsi, 0);
|
||||
|
||||
if (substream)
|
||||
if (over_period)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
return 0;
|
||||
|
@ -517,23 +557,24 @@ static int fsi_data_pop(struct fsi_priv *fsi)
|
|||
|
||||
static irqreturn_t fsi_interrupt(int irq, void *data)
|
||||
{
|
||||
u32 status = fsi_master_read(SOFT_RST) & ~0x00000010;
|
||||
u32 int_st = fsi_master_read(INT_ST);
|
||||
struct fsi_master *master = data;
|
||||
u32 status = fsi_master_read(master, SOFT_RST) & ~0x00000010;
|
||||
u32 int_st = fsi_master_read(master, INT_ST);
|
||||
|
||||
/* clear irq status */
|
||||
fsi_master_write(SOFT_RST, status);
|
||||
fsi_master_write(SOFT_RST, status | 0x00000010);
|
||||
fsi_master_write(master, SOFT_RST, status);
|
||||
fsi_master_write(master, SOFT_RST, status | 0x00000010);
|
||||
|
||||
if (int_st & INT_A_OUT)
|
||||
fsi_data_push(&master->fsia);
|
||||
fsi_data_push(&master->fsia, 0);
|
||||
if (int_st & INT_B_OUT)
|
||||
fsi_data_push(&master->fsib);
|
||||
fsi_data_push(&master->fsib, 0);
|
||||
if (int_st & INT_A_IN)
|
||||
fsi_data_pop(&master->fsia);
|
||||
fsi_data_pop(&master->fsia, 0);
|
||||
if (int_st & INT_B_IN)
|
||||
fsi_data_pop(&master->fsib);
|
||||
fsi_data_pop(&master->fsib, 0);
|
||||
|
||||
fsi_master_write(INT_ST, 0x0000000);
|
||||
fsi_master_write(master, INT_ST, 0x0000000);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
@ -548,7 +589,7 @@ static irqreturn_t fsi_interrupt(int irq, void *data)
|
|||
static int fsi_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get(substream);
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
const char *msg;
|
||||
u32 flags = fsi_get_info_flags(fsi);
|
||||
u32 fmt;
|
||||
|
@ -667,7 +708,7 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream,
|
|||
static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get(substream);
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
|
||||
fsi_irq_disable(fsi, is_play);
|
||||
|
@ -679,7 +720,7 @@ static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
|
|||
static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsi_priv *fsi = fsi_get(substream);
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
int ret = 0;
|
||||
|
@ -689,7 +730,7 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|||
fsi_stream_push(fsi, substream,
|
||||
frames_to_bytes(runtime, runtime->buffer_size),
|
||||
frames_to_bytes(runtime, runtime->period_size));
|
||||
ret = is_play ? fsi_data_push(fsi) : fsi_data_pop(fsi);
|
||||
ret = is_play ? fsi_data_push(fsi, 1) : fsi_data_pop(fsi, 1);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
fsi_irq_disable(fsi, is_play);
|
||||
|
@ -760,7 +801,7 @@ static int fsi_hw_free(struct snd_pcm_substream *substream)
|
|||
static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsi_priv *fsi = fsi_get(substream);
|
||||
struct fsi_priv *fsi = fsi_get_priv(substream);
|
||||
long location;
|
||||
|
||||
location = (fsi->byte_offset - 1);
|
||||
|
@ -870,10 +911,16 @@ EXPORT_SYMBOL_GPL(fsi_soc_platform);
|
|||
************************************************************************/
|
||||
static int fsi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct fsi_master *master;
|
||||
struct resource *res;
|
||||
unsigned int irq;
|
||||
int ret;
|
||||
|
||||
if (0 != pdev->id) {
|
||||
dev_err(&pdev->dev, "current fsi support id 0 only now\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (!res || (int)irq <= 0) {
|
||||
|
@ -899,15 +946,20 @@ static int fsi_probe(struct platform_device *pdev)
|
|||
master->irq = irq;
|
||||
master->info = pdev->dev.platform_data;
|
||||
master->fsia.base = master->base;
|
||||
master->fsia.master = master;
|
||||
master->fsib.base = master->base + 0x40;
|
||||
master->fsib.master = master;
|
||||
spin_lock_init(&master->lock);
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_resume(&pdev->dev);
|
||||
|
||||
fsi_soc_dai[0].dev = &pdev->dev;
|
||||
fsi_soc_dai[0].private_data = &master->fsia;
|
||||
fsi_soc_dai[1].dev = &pdev->dev;
|
||||
fsi_soc_dai[1].private_data = &master->fsib;
|
||||
|
||||
fsi_soft_all_reset();
|
||||
fsi_soft_all_reset(master);
|
||||
|
||||
ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, "fsi", master);
|
||||
if (ret) {
|
||||
|
@ -937,6 +989,10 @@ exit:
|
|||
|
||||
static int fsi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct fsi_master *master;
|
||||
|
||||
master = fsi_get_master(fsi_soc_dai[0].private_data);
|
||||
|
||||
snd_soc_unregister_dais(fsi_soc_dai, ARRAY_SIZE(fsi_soc_dai));
|
||||
snd_soc_unregister_platform(&fsi_soc_platform);
|
||||
|
||||
|
@ -946,7 +1002,12 @@ static int fsi_remove(struct platform_device *pdev)
|
|||
|
||||
iounmap(master->base);
|
||||
kfree(master);
|
||||
master = NULL;
|
||||
|
||||
fsi_soc_dai[0].dev = NULL;
|
||||
fsi_soc_dai[0].private_data = NULL;
|
||||
fsi_soc_dai[1].dev = NULL;
|
||||
fsi_soc_dai[1].private_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* ALSA SoC driver for Migo-R
|
||||
*
|
||||
* Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <asm/clock.h>
|
||||
|
||||
#include <cpu/sh7722.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include "../codecs/wm8978.h"
|
||||
#include "siu.h"
|
||||
|
||||
/* Default 8000Hz sampling frequency */
|
||||
static unsigned long codec_freq = 8000 * 512;
|
||||
|
||||
static unsigned int use_count;
|
||||
|
||||
/* External clock, sourced from the codec at the SIUMCKB pin */
|
||||
static unsigned long siumckb_recalc(struct clk *clk)
|
||||
{
|
||||
return codec_freq;
|
||||
}
|
||||
|
||||
static struct clk_ops siumckb_clk_ops = {
|
||||
.recalc = siumckb_recalc,
|
||||
};
|
||||
|
||||
static struct clk siumckb_clk = {
|
||||
.name = "siumckb_clk",
|
||||
.id = -1,
|
||||
.ops = &siumckb_clk_ops,
|
||||
.rate = 0, /* initialised at run-time */
|
||||
};
|
||||
|
||||
static int migor_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;
|
||||
int ret;
|
||||
unsigned int rate = params_rate(params);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_NB_IF |
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(rtd->dai->cpu_dai, SND_SOC_DAIFMT_NB_IF |
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
codec_freq = rate * 512;
|
||||
/*
|
||||
* This propagates the parent frequency change to children and
|
||||
* recalculates the frequency table
|
||||
*/
|
||||
clk_set_rate(&siumckb_clk, codec_freq);
|
||||
dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, SIU_CLKB_EXT,
|
||||
codec_freq / 2, SND_SOC_CLOCK_IN);
|
||||
|
||||
if (!ret)
|
||||
use_count++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int migor_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
|
||||
if (use_count) {
|
||||
use_count--;
|
||||
|
||||
if (!use_count)
|
||||
snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0,
|
||||
SND_SOC_CLOCK_IN);
|
||||
} else {
|
||||
dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops migor_dai_ops = {
|
||||
.hw_params = migor_hw_params,
|
||||
.hw_free = migor_hw_free,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget migor_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone", NULL),
|
||||
SND_SOC_DAPM_MIC("Onboard Microphone", NULL),
|
||||
SND_SOC_DAPM_MIC("External Microphone", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Headphone output connected to LHP/RHP, enable OUT4 for VMID */
|
||||
{ "Headphone", NULL, "OUT4 VMID" },
|
||||
{ "OUT4 VMID", NULL, "LHP" },
|
||||
{ "OUT4 VMID", NULL, "RHP" },
|
||||
|
||||
/* On-board microphone */
|
||||
{ "RMICN", NULL, "Mic Bias" },
|
||||
{ "RMICP", NULL, "Mic Bias" },
|
||||
{ "Mic Bias", NULL, "Onboard Microphone" },
|
||||
|
||||
/* External microphone */
|
||||
{ "LMICN", NULL, "Mic Bias" },
|
||||
{ "LMICP", NULL, "Mic Bias" },
|
||||
{ "Mic Bias", NULL, "External Microphone" },
|
||||
};
|
||||
|
||||
static int migor_dai_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
snd_soc_dapm_new_controls(codec, migor_dapm_widgets,
|
||||
ARRAY_SIZE(migor_dapm_widgets));
|
||||
|
||||
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* migor digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link migor_dai = {
|
||||
.name = "wm8978",
|
||||
.stream_name = "WM8978",
|
||||
.cpu_dai = &siu_i2s_dai,
|
||||
.codec_dai = &wm8978_dai,
|
||||
.ops = &migor_dai_ops,
|
||||
.init = migor_dai_init,
|
||||
};
|
||||
|
||||
/* migor audio machine driver */
|
||||
static struct snd_soc_card snd_soc_migor = {
|
||||
.name = "Migo-R",
|
||||
.platform = &siu_platform,
|
||||
.dai_link = &migor_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
/* migor audio subsystem */
|
||||
static struct snd_soc_device migor_snd_devdata = {
|
||||
.card = &snd_soc_migor,
|
||||
.codec_dev = &soc_codec_dev_wm8978,
|
||||
};
|
||||
|
||||
static struct platform_device *migor_snd_device;
|
||||
|
||||
static int __init migor_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_register(&siumckb_clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Port number used on this machine: port B */
|
||||
migor_snd_device = platform_device_alloc("soc-audio", 1);
|
||||
if (!migor_snd_device) {
|
||||
ret = -ENOMEM;
|
||||
goto epdevalloc;
|
||||
}
|
||||
|
||||
platform_set_drvdata(migor_snd_device, &migor_snd_devdata);
|
||||
|
||||
migor_snd_devdata.dev = &migor_snd_device->dev;
|
||||
|
||||
ret = platform_device_add(migor_snd_device);
|
||||
if (ret)
|
||||
goto epdevadd;
|
||||
|
||||
return 0;
|
||||
|
||||
epdevadd:
|
||||
platform_device_put(migor_snd_device);
|
||||
epdevalloc:
|
||||
clk_unregister(&siumckb_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit migor_exit(void)
|
||||
{
|
||||
clk_unregister(&siumckb_clk);
|
||||
platform_device_unregister(migor_snd_device);
|
||||
}
|
||||
|
||||
module_init(migor_init);
|
||||
module_exit(migor_exit);
|
||||
|
||||
MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
|
||||
MODULE_DESCRIPTION("ALSA SoC Migor");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* siu.h - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
|
||||
*
|
||||
* Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
* Copyright (C) 2006 Carlos Munoz <carlos@kenati.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef SIU_H
|
||||
#define SIU_H
|
||||
|
||||
/* Common kernel and user-space firmware-building defines and types */
|
||||
|
||||
#define YRAM0_SIZE (0x0040 / 4) /* 16 */
|
||||
#define YRAM1_SIZE (0x0080 / 4) /* 32 */
|
||||
#define YRAM2_SIZE (0x0040 / 4) /* 16 */
|
||||
#define YRAM3_SIZE (0x0080 / 4) /* 32 */
|
||||
#define YRAM4_SIZE (0x0080 / 4) /* 32 */
|
||||
#define YRAM_DEF_SIZE (YRAM0_SIZE + YRAM1_SIZE + YRAM2_SIZE + \
|
||||
YRAM3_SIZE + YRAM4_SIZE)
|
||||
#define YRAM_FIR_SIZE (0x0400 / 4) /* 256 */
|
||||
#define YRAM_IIR_SIZE (0x0200 / 4) /* 128 */
|
||||
|
||||
#define XRAM0_SIZE (0x0400 / 4) /* 256 */
|
||||
#define XRAM1_SIZE (0x0200 / 4) /* 128 */
|
||||
#define XRAM2_SIZE (0x0200 / 4) /* 128 */
|
||||
|
||||
/* PRAM program array size */
|
||||
#define PRAM0_SIZE (0x0100 / 4) /* 64 */
|
||||
#define PRAM1_SIZE ((0x2000 - 0x0100) / 4) /* 1984 */
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct siu_spb_param {
|
||||
__u32 ab1a; /* input FIFO address */
|
||||
__u32 ab0a; /* output FIFO address */
|
||||
__u32 dir; /* 0=the ather except CPUOUTPUT, 1=CPUINPUT */
|
||||
__u32 event; /* SPB program starting conditions */
|
||||
__u32 stfifo; /* STFIFO register setting value */
|
||||
__u32 trdat; /* TRDAT register setting value */
|
||||
};
|
||||
|
||||
struct siu_firmware {
|
||||
__u32 yram_fir_coeff[YRAM_FIR_SIZE];
|
||||
__u32 pram0[PRAM0_SIZE];
|
||||
__u32 pram1[PRAM1_SIZE];
|
||||
__u32 yram0[YRAM0_SIZE];
|
||||
__u32 yram1[YRAM1_SIZE];
|
||||
__u32 yram2[YRAM2_SIZE];
|
||||
__u32 yram3[YRAM3_SIZE];
|
||||
__u32 yram4[YRAM4_SIZE];
|
||||
__u32 spbpar_num;
|
||||
struct siu_spb_param spbpar[32];
|
||||
};
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <asm/dma-sh.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc-dai.h>
|
||||
|
||||
#define SIU_PERIOD_BYTES_MAX 8192 /* DMA transfer/period size */
|
||||
#define SIU_PERIOD_BYTES_MIN 256 /* DMA transfer/period size */
|
||||
#define SIU_PERIODS_MAX 64 /* Max periods in buffer */
|
||||
#define SIU_PERIODS_MIN 4 /* Min periods in buffer */
|
||||
#define SIU_BUFFER_BYTES_MAX (SIU_PERIOD_BYTES_MAX * SIU_PERIODS_MAX)
|
||||
|
||||
/* SIU ports: only one can be used at a time */
|
||||
enum {
|
||||
SIU_PORT_A,
|
||||
SIU_PORT_B,
|
||||
SIU_PORT_NUM,
|
||||
};
|
||||
|
||||
/* SIU clock configuration */
|
||||
enum {
|
||||
SIU_CLKA_PLL,
|
||||
SIU_CLKA_EXT,
|
||||
SIU_CLKB_PLL,
|
||||
SIU_CLKB_EXT
|
||||
};
|
||||
|
||||
struct siu_info {
|
||||
int port_id;
|
||||
u32 __iomem *pram;
|
||||
u32 __iomem *xram;
|
||||
u32 __iomem *yram;
|
||||
u32 __iomem *reg;
|
||||
struct siu_firmware fw;
|
||||
};
|
||||
|
||||
struct siu_stream {
|
||||
struct tasklet_struct tasklet;
|
||||
struct snd_pcm_substream *substream;
|
||||
snd_pcm_format_t format;
|
||||
size_t buf_bytes;
|
||||
size_t period_bytes;
|
||||
int cur_period; /* Period currently in dma */
|
||||
u32 volume;
|
||||
snd_pcm_sframes_t xfer_cnt; /* Number of frames */
|
||||
u8 rw_flg; /* transfer status */
|
||||
/* DMA status */
|
||||
struct dma_chan *chan; /* DMA channel */
|
||||
struct dma_async_tx_descriptor *tx_desc;
|
||||
dma_cookie_t cookie;
|
||||
struct sh_dmae_slave param;
|
||||
};
|
||||
|
||||
struct siu_port {
|
||||
unsigned long play_cap; /* Used to track full duplex */
|
||||
struct snd_pcm *pcm;
|
||||
struct siu_stream playback;
|
||||
struct siu_stream capture;
|
||||
u32 stfifo; /* STFIFO value from firmware */
|
||||
u32 trdat; /* TRDAT value from firmware */
|
||||
};
|
||||
|
||||
extern struct siu_port *siu_ports[SIU_PORT_NUM];
|
||||
|
||||
static inline struct siu_port *siu_port_info(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct platform_device *pdev =
|
||||
to_platform_device(substream->pcm->card->dev);
|
||||
return siu_ports[pdev->id];
|
||||
}
|
||||
|
||||
/* Register access */
|
||||
static inline void siu_write32(u32 __iomem *addr, u32 val)
|
||||
{
|
||||
__raw_writel(val, addr);
|
||||
}
|
||||
|
||||
static inline u32 siu_read32(u32 __iomem *addr)
|
||||
{
|
||||
return __raw_readl(addr);
|
||||
}
|
||||
|
||||
/* SIU registers */
|
||||
#define SIU_IFCTL (0x000 / sizeof(u32))
|
||||
#define SIU_SRCTL (0x004 / sizeof(u32))
|
||||
#define SIU_SFORM (0x008 / sizeof(u32))
|
||||
#define SIU_CKCTL (0x00c / sizeof(u32))
|
||||
#define SIU_TRDAT (0x010 / sizeof(u32))
|
||||
#define SIU_STFIFO (0x014 / sizeof(u32))
|
||||
#define SIU_DPAK (0x01c / sizeof(u32))
|
||||
#define SIU_CKREV (0x020 / sizeof(u32))
|
||||
#define SIU_EVNTC (0x028 / sizeof(u32))
|
||||
#define SIU_SBCTL (0x040 / sizeof(u32))
|
||||
#define SIU_SBPSET (0x044 / sizeof(u32))
|
||||
#define SIU_SBFSTS (0x068 / sizeof(u32))
|
||||
#define SIU_SBDVCA (0x06c / sizeof(u32))
|
||||
#define SIU_SBDVCB (0x070 / sizeof(u32))
|
||||
#define SIU_SBACTIV (0x074 / sizeof(u32))
|
||||
#define SIU_DMAIA (0x090 / sizeof(u32))
|
||||
#define SIU_DMAIB (0x094 / sizeof(u32))
|
||||
#define SIU_DMAOA (0x098 / sizeof(u32))
|
||||
#define SIU_DMAOB (0x09c / sizeof(u32))
|
||||
#define SIU_DMAML (0x0a0 / sizeof(u32))
|
||||
#define SIU_SPSTS (0x0cc / sizeof(u32))
|
||||
#define SIU_SPCTL (0x0d0 / sizeof(u32))
|
||||
#define SIU_BRGASEL (0x100 / sizeof(u32))
|
||||
#define SIU_BRRA (0x104 / sizeof(u32))
|
||||
#define SIU_BRGBSEL (0x108 / sizeof(u32))
|
||||
#define SIU_BRRB (0x10c / sizeof(u32))
|
||||
|
||||
extern struct snd_soc_platform siu_platform;
|
||||
extern struct snd_soc_dai siu_i2s_dai;
|
||||
|
||||
int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card);
|
||||
void siu_free_port(struct siu_port *port_info);
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* SIU_H */
|
|
@ -0,0 +1,847 @@
|
|||
/*
|
||||
* siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
|
||||
*
|
||||
* Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
* Copyright (C) 2006 Carlos Munoz <carlos@kenati.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include <asm/clock.h>
|
||||
#include <asm/siu.h>
|
||||
|
||||
#include <sound/control.h>
|
||||
#include <sound/soc-dai.h>
|
||||
|
||||
#include "siu.h"
|
||||
|
||||
/* Board specifics */
|
||||
#if defined(CONFIG_CPU_SUBTYPE_SH7722)
|
||||
# define SIU_MAX_VOLUME 0x1000
|
||||
#else
|
||||
# define SIU_MAX_VOLUME 0x7fff
|
||||
#endif
|
||||
|
||||
#define PRAM_SIZE 0x2000
|
||||
#define XRAM_SIZE 0x800
|
||||
#define YRAM_SIZE 0x800
|
||||
|
||||
#define XRAM_OFFSET 0x4000
|
||||
#define YRAM_OFFSET 0x6000
|
||||
#define REG_OFFSET 0xc000
|
||||
|
||||
#define PLAYBACK_ENABLED 1
|
||||
#define CAPTURE_ENABLED 2
|
||||
|
||||
#define VOLUME_CAPTURE 0
|
||||
#define VOLUME_PLAYBACK 1
|
||||
#define DFLT_VOLUME_LEVEL 0x08000800
|
||||
|
||||
/*
|
||||
* SPDIF is only available on port A and on some SIU implementations it is only
|
||||
* available for input. Due to the lack of hardware to test it, SPDIF is left
|
||||
* disabled in this driver version
|
||||
*/
|
||||
struct format_flag {
|
||||
u32 i2s;
|
||||
u32 pcm;
|
||||
u32 spdif;
|
||||
u32 mask;
|
||||
};
|
||||
|
||||
struct port_flag {
|
||||
struct format_flag playback;
|
||||
struct format_flag capture;
|
||||
};
|
||||
|
||||
static struct port_flag siu_flags[SIU_PORT_NUM] = {
|
||||
[SIU_PORT_A] = {
|
||||
.playback = {
|
||||
.i2s = 0x50000000,
|
||||
.pcm = 0x40000000,
|
||||
.spdif = 0x80000000, /* not on all SIU versions */
|
||||
.mask = 0xd0000000,
|
||||
},
|
||||
.capture = {
|
||||
.i2s = 0x05000000,
|
||||
.pcm = 0x04000000,
|
||||
.spdif = 0x08000000,
|
||||
.mask = 0x0d000000,
|
||||
},
|
||||
},
|
||||
[SIU_PORT_B] = {
|
||||
.playback = {
|
||||
.i2s = 0x00500000,
|
||||
.pcm = 0x00400000,
|
||||
.spdif = 0, /* impossible - turn off */
|
||||
.mask = 0x00500000,
|
||||
},
|
||||
.capture = {
|
||||
.i2s = 0x00050000,
|
||||
.pcm = 0x00040000,
|
||||
.spdif = 0, /* impossible - turn off */
|
||||
.mask = 0x00050000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static void siu_dai_start(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
|
||||
dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
|
||||
|
||||
/* Turn on SIU clock */
|
||||
pm_runtime_get_sync(siu_i2s_dai.dev);
|
||||
|
||||
/* Issue software reset to siu */
|
||||
siu_write32(base + SIU_SRCTL, 0);
|
||||
|
||||
/* Wait for the reset to take effect */
|
||||
udelay(1);
|
||||
|
||||
port_info->stfifo = 0;
|
||||
port_info->trdat = 0;
|
||||
|
||||
/* portA, portB, SIU operate */
|
||||
siu_write32(base + SIU_SRCTL, 0x301);
|
||||
|
||||
/* portA=256fs, portB=256fs */
|
||||
siu_write32(base + SIU_CKCTL, 0x40400000);
|
||||
|
||||
/* portA's BRG does not divide SIUCKA */
|
||||
siu_write32(base + SIU_BRGASEL, 0);
|
||||
siu_write32(base + SIU_BRRA, 0);
|
||||
|
||||
/* portB's BRG divides SIUCKB by half */
|
||||
siu_write32(base + SIU_BRGBSEL, 1);
|
||||
siu_write32(base + SIU_BRRB, 0);
|
||||
|
||||
siu_write32(base + SIU_IFCTL, 0x44440000);
|
||||
|
||||
/* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
|
||||
siu_write32(base + SIU_SFORM, 0x0c0c0000);
|
||||
|
||||
/*
|
||||
* Volume levels: looks like the DSP firmware implements volume controls
|
||||
* differently from what's described in the datasheet
|
||||
*/
|
||||
siu_write32(base + SIU_SBDVCA, port_info->playback.volume);
|
||||
siu_write32(base + SIU_SBDVCB, port_info->capture.volume);
|
||||
}
|
||||
|
||||
static void siu_dai_stop(void)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
|
||||
/* SIU software reset */
|
||||
siu_write32(base + SIU_SRCTL, 0);
|
||||
|
||||
/* Turn off SIU clock */
|
||||
pm_runtime_put_sync(siu_i2s_dai.dev);
|
||||
}
|
||||
|
||||
static void siu_dai_spbAselect(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct siu_firmware *fw = &info->fw;
|
||||
u32 *ydef = fw->yram0;
|
||||
u32 idx;
|
||||
|
||||
/* path A use */
|
||||
if (!info->port_id)
|
||||
idx = 1; /* portA */
|
||||
else
|
||||
idx = 2; /* portB */
|
||||
|
||||
ydef[0] = (fw->spbpar[idx].ab1a << 16) |
|
||||
(fw->spbpar[idx].ab0a << 8) |
|
||||
(fw->spbpar[idx].dir << 7) | 3;
|
||||
ydef[1] = fw->yram0[1]; /* 0x03000300 */
|
||||
ydef[2] = (16 / 2) << 24;
|
||||
ydef[3] = fw->yram0[3]; /* 0 */
|
||||
ydef[4] = fw->yram0[4]; /* 0 */
|
||||
ydef[7] = fw->spbpar[idx].event;
|
||||
port_info->stfifo |= fw->spbpar[idx].stfifo;
|
||||
port_info->trdat |= fw->spbpar[idx].trdat;
|
||||
}
|
||||
|
||||
static void siu_dai_spbBselect(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct siu_firmware *fw = &info->fw;
|
||||
u32 *ydef = fw->yram0;
|
||||
u32 idx;
|
||||
|
||||
/* path B use */
|
||||
if (!info->port_id)
|
||||
idx = 7; /* portA */
|
||||
else
|
||||
idx = 8; /* portB */
|
||||
|
||||
ydef[5] = (fw->spbpar[idx].ab1a << 16) |
|
||||
(fw->spbpar[idx].ab0a << 8) | 1;
|
||||
ydef[6] = fw->spbpar[idx].event;
|
||||
port_info->stfifo |= fw->spbpar[idx].stfifo;
|
||||
port_info->trdat |= fw->spbpar[idx].trdat;
|
||||
}
|
||||
|
||||
static void siu_dai_open(struct siu_stream *siu_stream)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
u32 srctl, ifctl;
|
||||
|
||||
srctl = siu_read32(base + SIU_SRCTL);
|
||||
ifctl = siu_read32(base + SIU_IFCTL);
|
||||
|
||||
switch (info->port_id) {
|
||||
case SIU_PORT_A:
|
||||
/* portA operates */
|
||||
srctl |= 0x200;
|
||||
ifctl &= ~0xc2;
|
||||
break;
|
||||
case SIU_PORT_B:
|
||||
/* portB operates */
|
||||
srctl |= 0x100;
|
||||
ifctl &= ~0x31;
|
||||
break;
|
||||
}
|
||||
|
||||
siu_write32(base + SIU_SRCTL, srctl);
|
||||
/* Unmute and configure portA */
|
||||
siu_write32(base + SIU_IFCTL, ifctl);
|
||||
}
|
||||
|
||||
/*
|
||||
* At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower
|
||||
* packing is supported
|
||||
*/
|
||||
static void siu_dai_pcmdatapack(struct siu_stream *siu_stream)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
u32 dpak;
|
||||
|
||||
dpak = siu_read32(base + SIU_DPAK);
|
||||
|
||||
switch (info->port_id) {
|
||||
case SIU_PORT_A:
|
||||
dpak &= ~0xc0000000;
|
||||
break;
|
||||
case SIU_PORT_B:
|
||||
dpak &= ~0x00c00000;
|
||||
break;
|
||||
}
|
||||
|
||||
siu_write32(base + SIU_DPAK, dpak);
|
||||
}
|
||||
|
||||
static int siu_dai_spbstart(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
struct siu_firmware *fw = &info->fw;
|
||||
u32 *ydef = fw->yram0;
|
||||
int cnt;
|
||||
u32 __iomem *add;
|
||||
u32 *ptr;
|
||||
|
||||
/* Load SPB Program in PRAM */
|
||||
ptr = fw->pram0;
|
||||
add = info->pram;
|
||||
for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++)
|
||||
siu_write32(add, *ptr);
|
||||
|
||||
ptr = fw->pram1;
|
||||
add = info->pram + (0x0100 / sizeof(u32));
|
||||
for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++)
|
||||
siu_write32(add, *ptr);
|
||||
|
||||
/* XRAM initialization */
|
||||
add = info->xram;
|
||||
for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++)
|
||||
siu_write32(add, 0);
|
||||
|
||||
/* YRAM variable area initialization */
|
||||
add = info->yram;
|
||||
for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++)
|
||||
siu_write32(add, ydef[cnt]);
|
||||
|
||||
/* YRAM FIR coefficient area initialization */
|
||||
add = info->yram + (0x0200 / sizeof(u32));
|
||||
for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++)
|
||||
siu_write32(add, fw->yram_fir_coeff[cnt]);
|
||||
|
||||
/* YRAM IIR coefficient area initialization */
|
||||
add = info->yram + (0x0600 / sizeof(u32));
|
||||
for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++)
|
||||
siu_write32(add, 0);
|
||||
|
||||
siu_write32(base + SIU_TRDAT, port_info->trdat);
|
||||
port_info->trdat = 0x0;
|
||||
|
||||
|
||||
/* SPB start condition: software */
|
||||
siu_write32(base + SIU_SBACTIV, 0);
|
||||
/* Start SPB */
|
||||
siu_write32(base + SIU_SBCTL, 0xc0000000);
|
||||
/* Wait for program to halt */
|
||||
cnt = 0x10000;
|
||||
while (--cnt && siu_read32(base + SIU_SBCTL) != 0x80000000)
|
||||
cpu_relax();
|
||||
|
||||
if (!cnt)
|
||||
return -EBUSY;
|
||||
|
||||
/* SPB program start address setting */
|
||||
siu_write32(base + SIU_SBPSET, 0x00400000);
|
||||
/* SPB hardware start(FIFOCTL source) */
|
||||
siu_write32(base + SIU_SBACTIV, 0xc0000000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void siu_dai_spbstop(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
|
||||
siu_write32(base + SIU_SBACTIV, 0);
|
||||
/* SPB stop */
|
||||
siu_write32(base + SIU_SBCTL, 0);
|
||||
|
||||
port_info->stfifo = 0;
|
||||
}
|
||||
|
||||
/* API functions */
|
||||
|
||||
/* Playback and capture hardware properties are identical */
|
||||
static struct snd_pcm_hardware siu_dai_pcm_hw = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.rate_min = 8000,
|
||||
.rate_max = 48000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = SIU_BUFFER_BYTES_MAX,
|
||||
.period_bytes_min = SIU_PERIOD_BYTES_MIN,
|
||||
.period_bytes_max = SIU_PERIOD_BYTES_MAX,
|
||||
.periods_min = SIU_PERIODS_MIN,
|
||||
.periods_max = SIU_PERIODS_MAX,
|
||||
};
|
||||
|
||||
static int siu_dai_info_volume(struct snd_kcontrol *kctrl,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
struct siu_port *port_info = snd_kcontrol_chip(kctrl);
|
||||
|
||||
dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 2;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = SIU_MAX_VOLUME;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_dai_get_volume(struct snd_kcontrol *kctrl,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct siu_port *port_info = snd_kcontrol_chip(kctrl);
|
||||
struct device *dev = port_info->pcm->card->dev;
|
||||
u32 vol;
|
||||
|
||||
dev_dbg(dev, "%s\n", __func__);
|
||||
|
||||
switch (kctrl->private_value) {
|
||||
case VOLUME_PLAYBACK:
|
||||
/* Playback is always on port 0 */
|
||||
vol = port_info->playback.volume;
|
||||
ucontrol->value.integer.value[0] = vol & 0xffff;
|
||||
ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
|
||||
break;
|
||||
case VOLUME_CAPTURE:
|
||||
/* Capture is always on port 1 */
|
||||
vol = port_info->capture.volume;
|
||||
ucontrol->value.integer.value[0] = vol & 0xffff;
|
||||
ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "%s() invalid private_value=%ld\n",
|
||||
__func__, kctrl->private_value);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_dai_put_volume(struct snd_kcontrol *kctrl,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct siu_port *port_info = snd_kcontrol_chip(kctrl);
|
||||
struct device *dev = port_info->pcm->card->dev;
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
u32 new_vol;
|
||||
u32 cur_vol;
|
||||
|
||||
dev_dbg(dev, "%s\n", __func__);
|
||||
|
||||
if (ucontrol->value.integer.value[0] < 0 ||
|
||||
ucontrol->value.integer.value[0] > SIU_MAX_VOLUME ||
|
||||
ucontrol->value.integer.value[1] < 0 ||
|
||||
ucontrol->value.integer.value[1] > SIU_MAX_VOLUME)
|
||||
return -EINVAL;
|
||||
|
||||
new_vol = ucontrol->value.integer.value[0] |
|
||||
ucontrol->value.integer.value[1] << 16;
|
||||
|
||||
/* See comment above - DSP firmware implementation */
|
||||
switch (kctrl->private_value) {
|
||||
case VOLUME_PLAYBACK:
|
||||
/* Playback is always on port 0 */
|
||||
cur_vol = port_info->playback.volume;
|
||||
siu_write32(base + SIU_SBDVCA, new_vol);
|
||||
port_info->playback.volume = new_vol;
|
||||
break;
|
||||
case VOLUME_CAPTURE:
|
||||
/* Capture is always on port 1 */
|
||||
cur_vol = port_info->capture.volume;
|
||||
siu_write32(base + SIU_SBDVCB, new_vol);
|
||||
port_info->capture.volume = new_vol;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "%s() invalid private_value=%ld\n",
|
||||
__func__, kctrl->private_value);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cur_vol != new_vol)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new playback_controls = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Playback Volume",
|
||||
.index = 0,
|
||||
.info = siu_dai_info_volume,
|
||||
.get = siu_dai_get_volume,
|
||||
.put = siu_dai_put_volume,
|
||||
.private_value = VOLUME_PLAYBACK,
|
||||
};
|
||||
|
||||
static struct snd_kcontrol_new capture_controls = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Capture Volume",
|
||||
.index = 0,
|
||||
.info = siu_dai_info_volume,
|
||||
.get = siu_dai_get_volume,
|
||||
.put = siu_dai_put_volume,
|
||||
.private_value = VOLUME_CAPTURE,
|
||||
};
|
||||
|
||||
int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card)
|
||||
{
|
||||
struct device *dev = card->dev;
|
||||
struct snd_kcontrol *kctrl;
|
||||
int ret;
|
||||
|
||||
*port_info = kzalloc(sizeof(**port_info), GFP_KERNEL);
|
||||
if (!*port_info)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_dbg(dev, "%s: port #%d@%p\n", __func__, port, *port_info);
|
||||
|
||||
(*port_info)->playback.volume = DFLT_VOLUME_LEVEL;
|
||||
(*port_info)->capture.volume = DFLT_VOLUME_LEVEL;
|
||||
|
||||
/*
|
||||
* Add mixer support. The SPB is used to change the volume. Both
|
||||
* ports use the same SPB. Therefore, we only register one
|
||||
* control instance since it will be used by both channels.
|
||||
* In error case we continue without controls.
|
||||
*/
|
||||
kctrl = snd_ctl_new1(&playback_controls, *port_info);
|
||||
ret = snd_ctl_add(card, kctrl);
|
||||
if (ret < 0)
|
||||
dev_err(dev,
|
||||
"failed to add playback controls %p port=%d err=%d\n",
|
||||
kctrl, port, ret);
|
||||
|
||||
kctrl = snd_ctl_new1(&capture_controls, *port_info);
|
||||
ret = snd_ctl_add(card, kctrl);
|
||||
if (ret < 0)
|
||||
dev_err(dev,
|
||||
"failed to add capture controls %p port=%d err=%d\n",
|
||||
kctrl, port, ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void siu_free_port(struct siu_port *port_info)
|
||||
{
|
||||
kfree(port_info);
|
||||
}
|
||||
|
||||
static int siu_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct snd_pcm_runtime *rt = substream->runtime;
|
||||
struct siu_port *port_info = siu_port_info(substream);
|
||||
int ret;
|
||||
|
||||
dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
|
||||
info->port_id, port_info);
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &siu_dai_pcm_hw);
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
|
||||
siu_dai_start(port_info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void siu_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct siu_port *port_info = siu_port_info(substream);
|
||||
|
||||
dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
|
||||
info->port_id, port_info);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
port_info->play_cap &= ~PLAYBACK_ENABLED;
|
||||
else
|
||||
port_info->play_cap &= ~CAPTURE_ENABLED;
|
||||
|
||||
/* Stop the siu if the other stream is not using it */
|
||||
if (!port_info->play_cap) {
|
||||
/* during stmread or stmwrite ? */
|
||||
BUG_ON(port_info->playback.rw_flg || port_info->capture.rw_flg);
|
||||
siu_dai_spbstop(port_info);
|
||||
siu_dai_stop();
|
||||
}
|
||||
}
|
||||
|
||||
/* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */
|
||||
static int siu_dai_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct snd_pcm_runtime *rt = substream->runtime;
|
||||
struct siu_port *port_info = siu_port_info(substream);
|
||||
struct siu_stream *siu_stream;
|
||||
int self, ret;
|
||||
|
||||
dev_dbg(substream->pcm->card->dev,
|
||||
"%s: port %d, active streams %lx, %d channels\n",
|
||||
__func__, info->port_id, port_info->play_cap, rt->channels);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
self = PLAYBACK_ENABLED;
|
||||
siu_stream = &port_info->playback;
|
||||
} else {
|
||||
self = CAPTURE_ENABLED;
|
||||
siu_stream = &port_info->capture;
|
||||
}
|
||||
|
||||
/* Set up the siu if not already done */
|
||||
if (!port_info->play_cap) {
|
||||
siu_stream->rw_flg = 0; /* stream-data transfer flag */
|
||||
|
||||
siu_dai_spbAselect(port_info);
|
||||
siu_dai_spbBselect(port_info);
|
||||
|
||||
siu_dai_open(siu_stream);
|
||||
|
||||
siu_dai_pcmdatapack(siu_stream);
|
||||
|
||||
ret = siu_dai_spbstart(port_info);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
port_info->play_cap |= self;
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* SIU can set bus format to I2S / PCM / SPDIF independently for playback and
|
||||
* capture, however, the current API sets the bus format globally for a DAI.
|
||||
*/
|
||||
static int siu_dai_set_fmt(struct snd_soc_dai *dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
u32 ifctl;
|
||||
|
||||
dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n",
|
||||
__func__, fmt, info->port_id);
|
||||
|
||||
if (info->port_id < 0)
|
||||
return -ENODEV;
|
||||
|
||||
/* Here select between I2S / PCM / SPDIF */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
ifctl = siu_flags[info->port_id].playback.i2s |
|
||||
siu_flags[info->port_id].capture.i2s;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
ifctl = siu_flags[info->port_id].playback.pcm |
|
||||
siu_flags[info->port_id].capture.pcm;
|
||||
break;
|
||||
/* SPDIF disabled - see comment at the top */
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ifctl |= ~(siu_flags[info->port_id].playback.mask |
|
||||
siu_flags[info->port_id].capture.mask) &
|
||||
siu_read32(base + SIU_IFCTL);
|
||||
siu_write32(base + SIU_IFCTL, ifctl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
|
||||
unsigned int freq, int dir)
|
||||
{
|
||||
struct clk *siu_clk, *parent_clk;
|
||||
char *siu_name, *parent_name;
|
||||
int ret;
|
||||
|
||||
if (dir != SND_SOC_CLOCK_IN)
|
||||
return -EINVAL;
|
||||
|
||||
dev_dbg(dai->dev, "%s: using clock %d\n", __func__, clk_id);
|
||||
|
||||
switch (clk_id) {
|
||||
case SIU_CLKA_PLL:
|
||||
siu_name = "siua_clk";
|
||||
parent_name = "pll_clk";
|
||||
break;
|
||||
case SIU_CLKA_EXT:
|
||||
siu_name = "siua_clk";
|
||||
parent_name = "siumcka_clk";
|
||||
break;
|
||||
case SIU_CLKB_PLL:
|
||||
siu_name = "siub_clk";
|
||||
parent_name = "pll_clk";
|
||||
break;
|
||||
case SIU_CLKB_EXT:
|
||||
siu_name = "siub_clk";
|
||||
parent_name = "siumckb_clk";
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
siu_clk = clk_get(siu_i2s_dai.dev, siu_name);
|
||||
if (IS_ERR(siu_clk))
|
||||
return PTR_ERR(siu_clk);
|
||||
|
||||
parent_clk = clk_get(siu_i2s_dai.dev, parent_name);
|
||||
if (!IS_ERR(parent_clk)) {
|
||||
ret = clk_set_parent(siu_clk, parent_clk);
|
||||
if (!ret)
|
||||
clk_set_rate(siu_clk, freq);
|
||||
clk_put(parent_clk);
|
||||
}
|
||||
|
||||
clk_put(siu_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops siu_dai_ops = {
|
||||
.startup = siu_dai_startup,
|
||||
.shutdown = siu_dai_shutdown,
|
||||
.prepare = siu_dai_prepare,
|
||||
.set_sysclk = siu_dai_set_sysclk,
|
||||
.set_fmt = siu_dai_set_fmt,
|
||||
};
|
||||
|
||||
struct snd_soc_dai siu_i2s_dai = {
|
||||
.name = "sh-siu",
|
||||
.id = 0,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
},
|
||||
.ops = &siu_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(siu_i2s_dai);
|
||||
|
||||
static int __devinit siu_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct firmware *fw_entry;
|
||||
struct resource *res, *region;
|
||||
struct siu_info *info;
|
||||
int ret;
|
||||
|
||||
info = kmalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev);
|
||||
if (ret)
|
||||
goto ereqfw;
|
||||
|
||||
/*
|
||||
* Loaded firmware is "const" - read only, but we have to modify it in
|
||||
* snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect()
|
||||
*/
|
||||
memcpy(&info->fw, fw_entry->data, fw_entry->size);
|
||||
|
||||
release_firmware(fw_entry);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
ret = -ENODEV;
|
||||
goto egetres;
|
||||
}
|
||||
|
||||
region = request_mem_region(res->start, resource_size(res),
|
||||
pdev->name);
|
||||
if (!region) {
|
||||
dev_err(&pdev->dev, "SIU region already claimed\n");
|
||||
ret = -EBUSY;
|
||||
goto ereqmemreg;
|
||||
}
|
||||
|
||||
ret = -ENOMEM;
|
||||
info->pram = ioremap(res->start, PRAM_SIZE);
|
||||
if (!info->pram)
|
||||
goto emappram;
|
||||
info->xram = ioremap(res->start + XRAM_OFFSET, XRAM_SIZE);
|
||||
if (!info->xram)
|
||||
goto emapxram;
|
||||
info->yram = ioremap(res->start + YRAM_OFFSET, YRAM_SIZE);
|
||||
if (!info->yram)
|
||||
goto emapyram;
|
||||
info->reg = ioremap(res->start + REG_OFFSET, resource_size(res) -
|
||||
REG_OFFSET);
|
||||
if (!info->reg)
|
||||
goto emapreg;
|
||||
|
||||
siu_i2s_dai.dev = &pdev->dev;
|
||||
siu_i2s_dai.private_data = info;
|
||||
|
||||
ret = snd_soc_register_dais(&siu_i2s_dai, 1);
|
||||
if (ret < 0)
|
||||
goto edaiinit;
|
||||
|
||||
ret = snd_soc_register_platform(&siu_platform);
|
||||
if (ret < 0)
|
||||
goto esocregp;
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
return ret;
|
||||
|
||||
esocregp:
|
||||
snd_soc_unregister_dais(&siu_i2s_dai, 1);
|
||||
edaiinit:
|
||||
iounmap(info->reg);
|
||||
emapreg:
|
||||
iounmap(info->yram);
|
||||
emapyram:
|
||||
iounmap(info->xram);
|
||||
emapxram:
|
||||
iounmap(info->pram);
|
||||
emappram:
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
ereqmemreg:
|
||||
egetres:
|
||||
ereqfw:
|
||||
kfree(info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit siu_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct resource *res;
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
snd_soc_unregister_platform(&siu_platform);
|
||||
snd_soc_unregister_dais(&siu_i2s_dai, 1);
|
||||
|
||||
iounmap(info->reg);
|
||||
iounmap(info->yram);
|
||||
iounmap(info->xram);
|
||||
iounmap(info->pram);
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res)
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
kfree(info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver siu_driver = {
|
||||
.driver = {
|
||||
.name = "sh_siu",
|
||||
},
|
||||
.probe = siu_probe,
|
||||
.remove = __devexit_p(siu_remove),
|
||||
};
|
||||
|
||||
static int __init siu_init(void)
|
||||
{
|
||||
return platform_driver_register(&siu_driver);
|
||||
}
|
||||
|
||||
static void __exit siu_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&siu_driver);
|
||||
}
|
||||
|
||||
module_init(siu_init)
|
||||
module_exit(siu_exit)
|
||||
|
||||
MODULE_AUTHOR("Carlos Munoz <carlos@kenati.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,616 @@
|
|||
/*
|
||||
* siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral.
|
||||
*
|
||||
* Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
* Copyright (C) 2006 Carlos Munoz <carlos@kenati.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <sound/control.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dai.h>
|
||||
|
||||
#include <asm/dma-sh.h>
|
||||
#include <asm/siu.h>
|
||||
|
||||
#include "siu.h"
|
||||
|
||||
#define GET_MAX_PERIODS(buf_bytes, period_bytes) \
|
||||
((buf_bytes) / (period_bytes))
|
||||
#define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \
|
||||
((buf_addr) + ((period_num) * (period_bytes)))
|
||||
|
||||
#define RWF_STM_RD 0x01 /* Read in progress */
|
||||
#define RWF_STM_WT 0x02 /* Write in progress */
|
||||
|
||||
struct siu_port *siu_ports[SIU_PORT_NUM];
|
||||
|
||||
/* transfersize is number of u32 dma transfers per period */
|
||||
static int siu_pcm_stmwrite_stop(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
struct siu_stream *siu_stream = &port_info->playback;
|
||||
u32 stfifo;
|
||||
|
||||
if (!siu_stream->rw_flg)
|
||||
return -EPERM;
|
||||
|
||||
/* output FIFO disable */
|
||||
stfifo = siu_read32(base + SIU_STFIFO);
|
||||
siu_write32(base + SIU_STFIFO, stfifo & ~0x0c180c18);
|
||||
pr_debug("%s: STFIFO %x -> %x\n", __func__,
|
||||
stfifo, stfifo & ~0x0c180c18);
|
||||
|
||||
/* during stmwrite clear */
|
||||
siu_stream->rw_flg = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_pcm_stmwrite_start(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_stream *siu_stream = &port_info->playback;
|
||||
|
||||
if (siu_stream->rw_flg)
|
||||
return -EPERM;
|
||||
|
||||
/* Current period in buffer */
|
||||
port_info->playback.cur_period = 0;
|
||||
|
||||
/* during stmwrite flag set */
|
||||
siu_stream->rw_flg = RWF_STM_WT;
|
||||
|
||||
/* DMA transfer start */
|
||||
tasklet_schedule(&siu_stream->tasklet);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void siu_dma_tx_complete(void *arg)
|
||||
{
|
||||
struct siu_stream *siu_stream = arg;
|
||||
|
||||
if (!siu_stream->rw_flg)
|
||||
return;
|
||||
|
||||
/* Update completed period count */
|
||||
if (++siu_stream->cur_period >=
|
||||
GET_MAX_PERIODS(siu_stream->buf_bytes,
|
||||
siu_stream->period_bytes))
|
||||
siu_stream->cur_period = 0;
|
||||
|
||||
pr_debug("%s: done period #%d (%u/%u bytes), cookie %d\n",
|
||||
__func__, siu_stream->cur_period,
|
||||
siu_stream->cur_period * siu_stream->period_bytes,
|
||||
siu_stream->buf_bytes, siu_stream->cookie);
|
||||
|
||||
tasklet_schedule(&siu_stream->tasklet);
|
||||
|
||||
/* Notify alsa: a period is done */
|
||||
snd_pcm_period_elapsed(siu_stream->substream);
|
||||
}
|
||||
|
||||
static int siu_pcm_wr_set(struct siu_port *port_info,
|
||||
dma_addr_t buff, u32 size)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
struct siu_stream *siu_stream = &port_info->playback;
|
||||
struct snd_pcm_substream *substream = siu_stream->substream;
|
||||
struct device *dev = substream->pcm->card->dev;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
dma_cookie_t cookie;
|
||||
struct scatterlist sg;
|
||||
u32 stfifo;
|
||||
|
||||
sg_init_table(&sg, 1);
|
||||
sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
|
||||
size, offset_in_page(buff));
|
||||
sg_dma_address(&sg) = buff;
|
||||
|
||||
desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
|
||||
&sg, 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc) {
|
||||
dev_err(dev, "Failed to allocate a dma descriptor\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
desc->callback = siu_dma_tx_complete;
|
||||
desc->callback_param = siu_stream;
|
||||
cookie = desc->tx_submit(desc);
|
||||
if (cookie < 0) {
|
||||
dev_err(dev, "Failed to submit a dma transfer\n");
|
||||
return cookie;
|
||||
}
|
||||
|
||||
siu_stream->tx_desc = desc;
|
||||
siu_stream->cookie = cookie;
|
||||
|
||||
dma_async_issue_pending(siu_stream->chan);
|
||||
|
||||
/* only output FIFO enable */
|
||||
stfifo = siu_read32(base + SIU_STFIFO);
|
||||
siu_write32(base + SIU_STFIFO, stfifo | (port_info->stfifo & 0x0c180c18));
|
||||
dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
|
||||
stfifo, stfifo | (port_info->stfifo & 0x0c180c18));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_pcm_rd_set(struct siu_port *port_info,
|
||||
dma_addr_t buff, size_t size)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
struct siu_stream *siu_stream = &port_info->capture;
|
||||
struct snd_pcm_substream *substream = siu_stream->substream;
|
||||
struct device *dev = substream->pcm->card->dev;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
dma_cookie_t cookie;
|
||||
struct scatterlist sg;
|
||||
u32 stfifo;
|
||||
|
||||
dev_dbg(dev, "%s: %u@%llx\n", __func__, size, (unsigned long long)buff);
|
||||
|
||||
sg_init_table(&sg, 1);
|
||||
sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
|
||||
size, offset_in_page(buff));
|
||||
sg_dma_address(&sg) = buff;
|
||||
|
||||
desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
|
||||
&sg, 1, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc) {
|
||||
dev_err(dev, "Failed to allocate dma descriptor\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
desc->callback = siu_dma_tx_complete;
|
||||
desc->callback_param = siu_stream;
|
||||
cookie = desc->tx_submit(desc);
|
||||
if (cookie < 0) {
|
||||
dev_err(dev, "Failed to submit dma descriptor\n");
|
||||
return cookie;
|
||||
}
|
||||
|
||||
siu_stream->tx_desc = desc;
|
||||
siu_stream->cookie = cookie;
|
||||
|
||||
dma_async_issue_pending(siu_stream->chan);
|
||||
|
||||
/* only input FIFO enable */
|
||||
stfifo = siu_read32(base + SIU_STFIFO);
|
||||
siu_write32(base + SIU_STFIFO, siu_read32(base + SIU_STFIFO) |
|
||||
(port_info->stfifo & 0x13071307));
|
||||
dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
|
||||
stfifo, stfifo | (port_info->stfifo & 0x13071307));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void siu_io_tasklet(unsigned long data)
|
||||
{
|
||||
struct siu_stream *siu_stream = (struct siu_stream *)data;
|
||||
struct snd_pcm_substream *substream = siu_stream->substream;
|
||||
struct device *dev = substream->pcm->card->dev;
|
||||
struct snd_pcm_runtime *rt = substream->runtime;
|
||||
struct siu_port *port_info = siu_port_info(substream);
|
||||
|
||||
dev_dbg(dev, "%s: flags %x\n", __func__, siu_stream->rw_flg);
|
||||
|
||||
if (!siu_stream->rw_flg) {
|
||||
dev_dbg(dev, "%s: stream inactive\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
dma_addr_t buff;
|
||||
size_t count;
|
||||
u8 *virt;
|
||||
|
||||
buff = (dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
|
||||
siu_stream->cur_period,
|
||||
siu_stream->period_bytes);
|
||||
virt = PERIOD_OFFSET(rt->dma_area,
|
||||
siu_stream->cur_period,
|
||||
siu_stream->period_bytes);
|
||||
count = siu_stream->period_bytes;
|
||||
|
||||
/* DMA transfer start */
|
||||
siu_pcm_rd_set(port_info, buff, count);
|
||||
} else {
|
||||
siu_pcm_wr_set(port_info,
|
||||
(dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
|
||||
siu_stream->cur_period,
|
||||
siu_stream->period_bytes),
|
||||
siu_stream->period_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/* Capture */
|
||||
static int siu_pcm_stmread_start(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_stream *siu_stream = &port_info->capture;
|
||||
|
||||
if (siu_stream->xfer_cnt > 0x1000000)
|
||||
return -EINVAL;
|
||||
if (siu_stream->rw_flg)
|
||||
return -EPERM;
|
||||
|
||||
/* Current period in buffer */
|
||||
siu_stream->cur_period = 0;
|
||||
|
||||
/* during stmread flag set */
|
||||
siu_stream->rw_flg = RWF_STM_RD;
|
||||
|
||||
tasklet_schedule(&siu_stream->tasklet);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_pcm_stmread_stop(struct siu_port *port_info)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
struct siu_stream *siu_stream = &port_info->capture;
|
||||
struct device *dev = siu_stream->substream->pcm->card->dev;
|
||||
u32 stfifo;
|
||||
|
||||
if (!siu_stream->rw_flg)
|
||||
return -EPERM;
|
||||
|
||||
/* input FIFO disable */
|
||||
stfifo = siu_read32(base + SIU_STFIFO);
|
||||
siu_write32(base + SIU_STFIFO, stfifo & ~0x13071307);
|
||||
dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
|
||||
stfifo, stfifo & ~0x13071307);
|
||||
|
||||
/* during stmread flag clear */
|
||||
siu_stream->rw_flg = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_pcm_hw_params(struct snd_pcm_substream *ss,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct device *dev = ss->pcm->card->dev;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
|
||||
|
||||
ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params));
|
||||
if (ret < 0)
|
||||
dev_err(dev, "snd_pcm_lib_malloc_pages() failed\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int siu_pcm_hw_free(struct snd_pcm_substream *ss)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct siu_port *port_info = siu_port_info(ss);
|
||||
struct device *dev = ss->pcm->card->dev;
|
||||
struct siu_stream *siu_stream;
|
||||
|
||||
if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
siu_stream = &port_info->playback;
|
||||
else
|
||||
siu_stream = &port_info->capture;
|
||||
|
||||
dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
|
||||
|
||||
return snd_pcm_lib_free_pages(ss);
|
||||
}
|
||||
|
||||
static bool filter(struct dma_chan *chan, void *slave)
|
||||
{
|
||||
struct sh_dmae_slave *param = slave;
|
||||
|
||||
pr_debug("%s: slave ID %d\n", __func__, param->slave_id);
|
||||
|
||||
if (unlikely(param->dma_dev != chan->device->dev))
|
||||
return false;
|
||||
|
||||
chan->private = param;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int siu_pcm_open(struct snd_pcm_substream *ss)
|
||||
{
|
||||
/* Playback / Capture */
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct siu_port *port_info = siu_port_info(ss);
|
||||
struct siu_stream *siu_stream;
|
||||
u32 port = info->port_id;
|
||||
struct siu_platform *pdata = siu_i2s_dai.dev->platform_data;
|
||||
struct device *dev = ss->pcm->card->dev;
|
||||
dma_cap_mask_t mask;
|
||||
struct sh_dmae_slave *param;
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
|
||||
dev_dbg(dev, "%s, port=%d@%p\n", __func__, port, port_info);
|
||||
|
||||
if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
siu_stream = &port_info->playback;
|
||||
param = &siu_stream->param;
|
||||
param->slave_id = port ? SHDMA_SLAVE_SIUB_TX :
|
||||
SHDMA_SLAVE_SIUA_TX;
|
||||
} else {
|
||||
siu_stream = &port_info->capture;
|
||||
param = &siu_stream->param;
|
||||
param->slave_id = port ? SHDMA_SLAVE_SIUB_RX :
|
||||
SHDMA_SLAVE_SIUA_RX;
|
||||
}
|
||||
|
||||
param->dma_dev = pdata->dma_dev;
|
||||
/* Get DMA channel */
|
||||
siu_stream->chan = dma_request_channel(mask, filter, param);
|
||||
if (!siu_stream->chan) {
|
||||
dev_err(dev, "DMA channel allocation failed!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
siu_stream->substream = ss;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_pcm_close(struct snd_pcm_substream *ss)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct device *dev = ss->pcm->card->dev;
|
||||
struct siu_port *port_info = siu_port_info(ss);
|
||||
struct siu_stream *siu_stream;
|
||||
|
||||
dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
|
||||
|
||||
if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
siu_stream = &port_info->playback;
|
||||
else
|
||||
siu_stream = &port_info->capture;
|
||||
|
||||
dma_release_channel(siu_stream->chan);
|
||||
siu_stream->chan = NULL;
|
||||
|
||||
siu_stream->substream = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_pcm_prepare(struct snd_pcm_substream *ss)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct siu_port *port_info = siu_port_info(ss);
|
||||
struct device *dev = ss->pcm->card->dev;
|
||||
struct snd_pcm_runtime *rt = ss->runtime;
|
||||
struct siu_stream *siu_stream;
|
||||
snd_pcm_sframes_t xfer_cnt;
|
||||
|
||||
if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
siu_stream = &port_info->playback;
|
||||
else
|
||||
siu_stream = &port_info->capture;
|
||||
|
||||
rt = siu_stream->substream->runtime;
|
||||
|
||||
siu_stream->buf_bytes = snd_pcm_lib_buffer_bytes(ss);
|
||||
siu_stream->period_bytes = snd_pcm_lib_period_bytes(ss);
|
||||
|
||||
dev_dbg(dev, "%s: port=%d, %d channels, period=%u bytes\n", __func__,
|
||||
info->port_id, rt->channels, siu_stream->period_bytes);
|
||||
|
||||
/* We only support buffers that are multiples of the period */
|
||||
if (siu_stream->buf_bytes % siu_stream->period_bytes) {
|
||||
dev_err(dev, "%s() - buffer=%d not multiple of period=%d\n",
|
||||
__func__, siu_stream->buf_bytes,
|
||||
siu_stream->period_bytes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
xfer_cnt = bytes_to_frames(rt, siu_stream->period_bytes);
|
||||
if (!xfer_cnt || xfer_cnt > 0x1000000)
|
||||
return -EINVAL;
|
||||
|
||||
siu_stream->format = rt->format;
|
||||
siu_stream->xfer_cnt = xfer_cnt;
|
||||
|
||||
dev_dbg(dev, "port=%d buf=%lx buf_bytes=%d period_bytes=%d "
|
||||
"format=%d channels=%d xfer_cnt=%d\n", info->port_id,
|
||||
(unsigned long)rt->dma_addr, siu_stream->buf_bytes,
|
||||
siu_stream->period_bytes,
|
||||
siu_stream->format, rt->channels, (int)xfer_cnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int siu_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
|
||||
{
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct device *dev = ss->pcm->card->dev;
|
||||
struct siu_port *port_info = siu_port_info(ss);
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "%s: port=%d@%p, cmd=%d\n", __func__,
|
||||
info->port_id, port_info, cmd);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
ret = siu_pcm_stmwrite_start(port_info);
|
||||
else
|
||||
ret = siu_pcm_stmread_start(port_info);
|
||||
|
||||
if (ret < 0)
|
||||
dev_warn(dev, "%s: start failed on port=%d\n",
|
||||
__func__, info->port_id);
|
||||
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
siu_pcm_stmwrite_stop(port_info);
|
||||
else
|
||||
siu_pcm_stmread_stop(port_info);
|
||||
ret = 0;
|
||||
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "%s() unsupported cmd=%d\n", __func__, cmd);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* So far only resolution of one period is supported, subject to extending the
|
||||
* dmangine API
|
||||
*/
|
||||
static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss)
|
||||
{
|
||||
struct device *dev = ss->pcm->card->dev;
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
u32 __iomem *base = info->reg;
|
||||
struct siu_port *port_info = siu_port_info(ss);
|
||||
struct snd_pcm_runtime *rt = ss->runtime;
|
||||
size_t ptr;
|
||||
struct siu_stream *siu_stream;
|
||||
|
||||
if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
siu_stream = &port_info->playback;
|
||||
else
|
||||
siu_stream = &port_info->capture;
|
||||
|
||||
/*
|
||||
* ptr is the offset into the buffer where the dma is currently at. We
|
||||
* check if the dma buffer has just wrapped.
|
||||
*/
|
||||
ptr = PERIOD_OFFSET(rt->dma_addr,
|
||||
siu_stream->cur_period,
|
||||
siu_stream->period_bytes) - rt->dma_addr;
|
||||
|
||||
dev_dbg(dev,
|
||||
"%s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d\n",
|
||||
__func__, info->port_id, siu_read32(base + SIU_EVNTC),
|
||||
siu_read32(base + SIU_SBFSTS), ptr, siu_stream->buf_bytes,
|
||||
siu_stream->cookie);
|
||||
|
||||
if (ptr >= siu_stream->buf_bytes)
|
||||
ptr = 0;
|
||||
|
||||
return bytes_to_frames(ss->runtime, ptr);
|
||||
}
|
||||
|
||||
static int siu_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
/* card->dev == socdev->dev, see snd_soc_new_pcms() */
|
||||
struct siu_info *info = siu_i2s_dai.private_data;
|
||||
struct platform_device *pdev = to_platform_device(card->dev);
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
/* pdev->id selects between SIUA and SIUB */
|
||||
if (pdev->id < 0 || pdev->id >= SIU_PORT_NUM)
|
||||
return -EINVAL;
|
||||
|
||||
info->port_id = pdev->id;
|
||||
|
||||
/*
|
||||
* While the siu has 2 ports, only one port can be on at a time (only 1
|
||||
* SPB). So far all the boards using the siu had only one of the ports
|
||||
* wired to a codec. To simplify things, we only register one port with
|
||||
* alsa. In case both ports are needed, it should be changed here
|
||||
*/
|
||||
for (i = pdev->id; i < pdev->id + 1; i++) {
|
||||
struct siu_port **port_info = &siu_ports[i];
|
||||
|
||||
ret = siu_init_port(i, port_info, card);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_DEV, NULL,
|
||||
SIU_BUFFER_BYTES_MAX, SIU_BUFFER_BYTES_MAX);
|
||||
if (ret < 0) {
|
||||
dev_err(card->dev,
|
||||
"snd_pcm_lib_preallocate_pages_for_all() err=%d",
|
||||
ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
(*port_info)->pcm = pcm;
|
||||
|
||||
/* IO tasklets */
|
||||
tasklet_init(&(*port_info)->playback.tasklet, siu_io_tasklet,
|
||||
(unsigned long)&(*port_info)->playback);
|
||||
tasklet_init(&(*port_info)->capture.tasklet, siu_io_tasklet,
|
||||
(unsigned long)&(*port_info)->capture);
|
||||
}
|
||||
|
||||
dev_info(card->dev, "SuperH SIU driver initialized.\n");
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
siu_free_port(siu_ports[pdev->id]);
|
||||
dev_err(card->dev, "SIU: failed to initialize.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void siu_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(pcm->card->dev);
|
||||
struct siu_port *port_info = siu_ports[pdev->id];
|
||||
|
||||
tasklet_kill(&port_info->capture.tasklet);
|
||||
tasklet_kill(&port_info->playback.tasklet);
|
||||
|
||||
siu_free_port(port_info);
|
||||
snd_pcm_lib_preallocate_free_for_all(pcm);
|
||||
|
||||
dev_dbg(pcm->card->dev, "%s\n", __func__);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops siu_pcm_ops = {
|
||||
.open = siu_pcm_open,
|
||||
.close = siu_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = siu_pcm_hw_params,
|
||||
.hw_free = siu_pcm_hw_free,
|
||||
.prepare = siu_pcm_prepare,
|
||||
.trigger = siu_pcm_trigger,
|
||||
.pointer = siu_pcm_pointer_dma,
|
||||
};
|
||||
|
||||
struct snd_soc_platform siu_platform = {
|
||||
.name = "siu-audio",
|
||||
.pcm_ops = &siu_pcm_ops,
|
||||
.pcm_new = siu_pcm_new,
|
||||
.pcm_free = siu_pcm_free,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(siu_platform);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue