1.wm8976驱动程序:

/*
* wm8976.h -- WM8976 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 _WM8976_H
#define _WM8976_H /* WM8976 register space */ #define WM8976_RESET 0x0
#define WM8976_POWER1 0x1
#define WM8976_POWER2 0x2
#define WM8976_POWER3 0x3
#define WM8976_IFACE 0x4
#define WM8976_COMP 0x5
#define WM8976_CLOCK 0x6
#define WM8976_ADD 0x7
#define WM8976_GPIO 0x8
#define WM8976_JACK1 0x9
#define WM8976_DAC 0xa
#define WM8976_DACVOLL 0xb
#define WM8976_DACVOLR 0xc
#define WM8976_JACK2 0xd
#define WM8976_ADC 0xe
#define WM8976_ADCVOL 0xf
#define WM8976_EQ1 0x12
#define WM8976_EQ2 0x13
#define WM8976_EQ3 0x14
#define WM8976_EQ4 0x15
#define WM8976_EQ5 0x16
#define WM8976_DACLIM1 0x18
#define WM8976_DACLIM2 0x19
#define WM8976_NOTCH1 0x1b
#define WM8976_NOTCH2 0x1c
#define WM8976_NOTCH3 0x1d
#define WM8976_NOTCH4 0x1e
#define WM8976_ALC1 0x20
#define WM8976_ALC2 0x21
#define WM8976_ALC3 0x22
#define WM8976_NGATE 0x23
#define WM8976_PLLN 0x24
#define WM8976_PLLK1 0x25
#define WM8976_PLLK2 0x26
#define WM8976_PLLK3 0x27
#define WM8976_3D 0x29
#define WM8976_BEEP 0x2b
#define WM8976_INPUT 0x2c
#define WM8976_INPPGA 0x2d
#define WM8976_ADCBOOST 0x2f
#define WM8976_OUTPUT 0x31
#define WM8976_MIXL 0x32
#define WM8976_MIXR 0x33
#define WM8976_HPVOLL 0x34
#define WM8976_HPVOLR 0x35
#define WM8976_SPKVOLL 0x36
#define WM8976_SPKVOLR 0x37
#define WM8976_OUT3MIX 0x38
#define WM8976_MONOMIX 0x39 #define WM8976_CACHEREGNUM 58 /*
* WM8976 Clock dividers
*/
#define WM8976_MCLKDIV 0
#define WM8976_BCLKDIV 1
#define WM8976_OPCLKDIV 2
#define WM8976_DACOSR 3
#define WM8976_ADCOSR 4
#define WM8976_MCLKSEL 5 #define WM8976_MCLK_MCLK (0 << 8)
#define WM8976_MCLK_PLL (1 << 8) #define WM8976_MCLK_DIV_1 (0 << 5)
#define WM8976_MCLK_DIV_1_5 (1 << 5)
#define WM8976_MCLK_DIV_2 (2 << 5)
#define WM8976_MCLK_DIV_3 (3 << 5)
#define WM8976_MCLK_DIV_4 (4 << 5)
#define WM8976_MCLK_DIV_5_5 (5 << 5)
#define WM8976_MCLK_DIV_6 (6 << 5) #define WM8976_BCLK_DIV_1 (0 << 2)
#define WM8976_BCLK_DIV_2 (1 << 2)
#define WM8976_BCLK_DIV_4 (2 << 2)
#define WM8976_BCLK_DIV_8 (3 << 2)
#define WM8976_BCLK_DIV_16 (4 << 2)
#define WM8976_BCLK_DIV_32 (5 << 2) #define WM8976_DACOSR_64 (0 << 3)
#define WM8976_DACOSR_128 (1 << 3) #define WM8976_ADCOSR_64 (0 << 3)
#define WM8976_ADCOSR_128 (1 << 3) #define WM8976_OPCLK_DIV_1 (0 << 4)
#define WM8976_OPCLK_DIV_2 (1 << 4)
#define WM8976_OPCLK_DIV_3 (2 << 4)
#define WM8976_OPCLK_DIV_4 (3 << 4) #endif
/*
* wm8976.c -- WM8976 ALSA Soc Audio driver
*
* Copyright 2007-9 Wolfson Microelectronics PLC.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/ #include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <asm/io.h> #include "wm8976.h" static volatile unsigned int *gpbdat;
static volatile unsigned int *gpbcon; /*
* wm8976 register cache
* We can't read the WM8976 register space when we are
* using 2 wire for device control, so we cache them instead.
*/
static const u16 wm8976_reg[WM8976_CACHEREGNUM] = {
0x0000, 0x0000, 0x0000, 0x0000,
0x0050, 0x0000, 0x0140, 0x0000,
0x0000, 0x0000, 0x0000, 0x00ff,
0x00ff, 0x0000, 0x0100, 0x00ff,
0x00ff, 0x0000, 0x012c, 0x002c,
0x002c, 0x002c, 0x002c, 0x0000,
0x0032, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0038, 0x000b, 0x0032, 0x0000,
0x0008, 0x000c, 0x0093, 0x00e9,
0x0000, 0x0000, 0x0000, 0x0000,
0x0033, 0x0010, 0x0010, 0x0100,
0x0100, 0x0002, 0x0001, 0x0001,
0x0039, 0x0039, 0x0039, 0x0039,
0x0001, 0x0001,
}; struct wm8976_priv {
struct snd_soc_codec codec;
u16 reg_cache[WM8976_CACHEREGNUM];
}; /*
* read wm8976 register cache
*/
static inline unsigned int wm8976_read_reg_cache(struct snd_soc_codec *codec,
unsigned int reg)
{
u16 *cache = codec->reg_cache;
if (reg == WM8976_RESET)
return ;
if (reg >= WM8976_CACHEREGNUM)
return -;
return cache[reg];
} /*
* write wm8976 register cache
*/
static inline void wm8976_write_reg_cache(struct snd_soc_codec *codec,
u16 reg, unsigned int value)
{
u16 *cache = codec->reg_cache;
if (reg >= WM8976_CACHEREGNUM)
return;
cache[reg] = value;
} static void set_csb(int val)
{
if (val)
{
*gpbdat |= (<<);
}
else
{
*gpbdat &= ~(<<);
}
} static void set_clk(int val)
{
if (val)
{
*gpbdat |= (<<);
}
else
{
*gpbdat &= ~(<<);
}
} static void set_dat(int val)
{
if (val)
{
*gpbdat |= (<<);
}
else
{
*gpbdat &= ~(<<);
}
} /*
* write to the WM8976 register space
*/
static int wm8976_write(struct snd_soc_codec *codec, unsigned int reg,
unsigned int value)
{
int i;
unsigned short val = (reg << ) | (value & 0x1ff); /* save */
wm8976_write_reg_cache (codec, reg, value); if ((reg == WM8976_HPVOLL) || (reg == WM8976_HPVOLR))
val |= (<<); /* write to register */
set_csb();
set_dat();
set_clk(); for (i = ; i < ; i++){
if (val & (<<))
{
set_clk();
set_dat();
udelay();
set_clk();
}
else
{
set_clk();
set_dat();
udelay();
set_clk();
} val = val << ;
} set_csb();
udelay();
set_csb();
set_dat();
set_clk(); return ;
} #define wm8976_reset(c) wm8976_write(c, WM8976_RESET, 0) static const char *wm8976_companding[] = {"Off", "NC", "u-law", "A-law" };
static const char *wm8976_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
static const char *wm8976_eqmode[] = {"Capture", "Playback" };
static const char *wm8976_bw[] = {"Narrow", "Wide" };
static const char *wm8976_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
static const char *wm8976_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
static const char *wm8976_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
static const char *wm8976_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
static const char *wm8976_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
static const char *wm8976_alc[] =
{"ALC both on", "ALC left only", "ALC right only", "Limiter" }; static const struct soc_enum wm8976_enum[] = {
SOC_ENUM_SINGLE(WM8976_COMP, , , wm8976_companding), /* adc */
SOC_ENUM_SINGLE(WM8976_COMP, , , wm8976_companding), /* dac */
SOC_ENUM_SINGLE(WM8976_DAC, , , wm8976_deemp),
SOC_ENUM_SINGLE(WM8976_EQ1, , , wm8976_eqmode), SOC_ENUM_SINGLE(WM8976_EQ1, , , wm8976_eq1),
SOC_ENUM_SINGLE(WM8976_EQ2, , , wm8976_bw),
SOC_ENUM_SINGLE(WM8976_EQ2, , , wm8976_eq2),
SOC_ENUM_SINGLE(WM8976_EQ3, , , wm8976_bw), SOC_ENUM_SINGLE(WM8976_EQ3, , , wm8976_eq3),
SOC_ENUM_SINGLE(WM8976_EQ4, , , wm8976_bw),
SOC_ENUM_SINGLE(WM8976_EQ4, , , wm8976_eq4),
SOC_ENUM_SINGLE(WM8976_EQ5, , , wm8976_bw), SOC_ENUM_SINGLE(WM8976_EQ5, , , wm8976_eq5),
SOC_ENUM_SINGLE(WM8976_ALC3, , , wm8976_alc),
}; static const struct snd_kcontrol_new wm8976_snd_controls[] = {
SOC_SINGLE("Digital Loopback Switch", WM8976_COMP, , , ), SOC_ENUM("ADC Companding", wm8976_enum[]),
SOC_ENUM("DAC Companding", wm8976_enum[]), SOC_SINGLE("Jack Detection Enable", WM8976_JACK1, , , ), SOC_DOUBLE("DAC Inversion Switch", WM8976_DAC, , , , ), //SOC_DOUBLE_R("Headphone Playback Volume", WM8976_DACVOLL, WM8976_DACVOLR, 0, 127, 0), SOC_SINGLE("High Pass Filter Switch", WM8976_ADC, , , ),
//SOC_SINGLE("High Pass Filter Switch", WM8976_ADC, 8, 1, 0),
SOC_SINGLE("High Pass Cut Off", WM8976_ADC, , , ), SOC_DOUBLE("ADC Inversion Switch", WM8976_ADC, , , , ), SOC_SINGLE("Capture Volume", WM8976_ADCVOL, , , ), SOC_ENUM("Equaliser Function", wm8976_enum[]),
SOC_ENUM("EQ1 Cut Off", wm8976_enum[]),
SOC_SINGLE("EQ1 Volume", WM8976_EQ1, , , ), SOC_ENUM("Equaliser EQ2 Bandwith", wm8976_enum[]),
SOC_ENUM("EQ2 Cut Off", wm8976_enum[]),
SOC_SINGLE("EQ2 Volume", WM8976_EQ2, , , ), SOC_ENUM("Equaliser EQ3 Bandwith", wm8976_enum[]),
SOC_ENUM("EQ3 Cut Off", wm8976_enum[]),
SOC_SINGLE("EQ3 Volume", WM8976_EQ3, , , ), SOC_ENUM("Equaliser EQ4 Bandwith", wm8976_enum[]),
SOC_ENUM("EQ4 Cut Off", wm8976_enum[]),
SOC_SINGLE("EQ4 Volume", WM8976_EQ4, , , ), SOC_ENUM("Equaliser EQ5 Bandwith", wm8976_enum[]),
SOC_ENUM("EQ5 Cut Off", wm8976_enum[]),
SOC_SINGLE("EQ5 Volume", WM8976_EQ5, , , ), SOC_SINGLE("DAC Playback Limiter Switch", WM8976_DACLIM1, , , ),
SOC_SINGLE("DAC Playback Limiter Decay", WM8976_DACLIM1, , , ),
SOC_SINGLE("DAC Playback Limiter Attack", WM8976_DACLIM1, , , ), SOC_SINGLE("DAC Playback Limiter Threshold", WM8976_DACLIM2, , , ),
SOC_SINGLE("DAC Playback Limiter Boost", WM8976_DACLIM2, , , ), SOC_SINGLE("ALC Enable Switch", WM8976_ALC1, , , ),
SOC_SINGLE("ALC Capture Max Gain", WM8976_ALC1, , , ),
SOC_SINGLE("ALC Capture Min Gain", WM8976_ALC1, , , ), SOC_SINGLE("ALC Capture ZC Switch", WM8976_ALC2, , , ),
SOC_SINGLE("ALC Capture Hold", WM8976_ALC2, , , ),
SOC_SINGLE("ALC Capture Target", WM8976_ALC2, , , ), SOC_ENUM("ALC Capture Mode", wm8976_enum[]),
SOC_SINGLE("ALC Capture Decay", WM8976_ALC3, , , ),
SOC_SINGLE("ALC Capture Attack", WM8976_ALC3, , , ), SOC_SINGLE("ALC Capture Noise Gate Switch", WM8976_NGATE, , , ),
SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8976_NGATE, , , ), SOC_SINGLE("Capture PGA ZC Switch", WM8976_INPPGA, , , ),
SOC_SINGLE("Capture PGA Volume", WM8976_INPPGA, , , ), SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8976_HPVOLL, WM8976_HPVOLR, , , ),
SOC_DOUBLE_R("Headphone Playback Switch", WM8976_HPVOLL, WM8976_HPVOLR, , , ),
SOC_DOUBLE_R("Headphone Playback Volume", WM8976_HPVOLL, WM8976_HPVOLR, , , ), SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8976_SPKVOLL, WM8976_SPKVOLR, , , ),
SOC_DOUBLE_R("Speaker Playback Switch", WM8976_SPKVOLL, WM8976_SPKVOLR, , , ),
SOC_DOUBLE_R("Speaker Playback Volume", WM8976_SPKVOLL, WM8976_SPKVOLR, , , ), SOC_SINGLE("Capture Boost(+20dB)", WM8976_ADCBOOST, , , ),
}; /* Left Output Mixer */
static const struct snd_kcontrol_new wm8976_left_mixer_controls[] = {
SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8976_OUTPUT, , , ),
SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8976_MIXL, , , ),
SOC_DAPM_SINGLE("Line Bypass Switch", WM8976_MIXL, , , ),
SOC_DAPM_SINGLE("Aux Playback Switch", WM8976_MIXL, , , ),
}; /* Right Output Mixer */
static const struct snd_kcontrol_new wm8976_right_mixer_controls[] = {
SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8976_OUTPUT, , , ),
SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8976_MIXR, , , ),
SOC_DAPM_SINGLE("Line Bypass Switch", WM8976_MIXR, , , ),
SOC_DAPM_SINGLE("Aux Playback Switch", WM8976_MIXR, , , ),
}; /* Left AUX Input boost vol */
static const struct snd_kcontrol_new wm8976_laux_boost_controls =
SOC_DAPM_SINGLE("Aux Volume", WM8976_ADCBOOST, , , ); /* Left Input boost vol */
static const struct snd_kcontrol_new wm8976_lmic_boost_controls =
SOC_DAPM_SINGLE("Input Volume", WM8976_ADCBOOST, , , ); /* Left Aux In to PGA */
static const struct snd_kcontrol_new wm8976_laux_capture_boost_controls =
SOC_DAPM_SINGLE("Capture Switch", WM8976_ADCBOOST, , , ); /* Left Input P In to PGA */
static const struct snd_kcontrol_new wm8976_lmicp_capture_boost_controls =
SOC_DAPM_SINGLE("Input P Capture Boost Switch", WM8976_INPUT, , , ); /* Left Input N In to PGA */
static const struct snd_kcontrol_new wm8976_lmicn_capture_boost_controls =
SOC_DAPM_SINGLE("Input N Capture Boost Switch", WM8976_INPUT, , , ); // TODO Widgets
static const struct snd_soc_dapm_widget wm8976_dapm_widgets[] = {
#if 0
//SND_SOC_DAPM_MUTE("Mono Mute", WM8976_MONOMIX, 6, 0),
//SND_SOC_DAPM_MUTE("Speaker Mute", WM8976_SPKMIX, 6, 0), SND_SOC_DAPM_MIXER("Speaker Mixer", WM8976_POWER3, , ,
&wm8976_speaker_mixer_controls[],
ARRAY_SIZE(wm8976_speaker_mixer_controls)),
SND_SOC_DAPM_MIXER("Mono Mixer", WM8976_POWER3, , ,
&wm8976_mono_mixer_controls[],
ARRAY_SIZE(wm8976_mono_mixer_controls)),
SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8976_POWER3, , ),
SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8976_POWER3, , ),
SND_SOC_DAPM_PGA("Aux Input", WM8976_POWER1, , , NULL, ),
SND_SOC_DAPM_PGA("SpkN Out", WM8976_POWER3, , , NULL, ),
SND_SOC_DAPM_PGA("SpkP Out", WM8976_POWER3, , , NULL, ),
SND_SOC_DAPM_PGA("Mono Out", WM8976_POWER3, , , NULL, ),
SND_SOC_DAPM_PGA("Mic PGA", WM8976_POWER2, , , NULL, ), SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, , ,
&wm8976_aux_boost_controls, ),
SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, , ,
&wm8976_mic_boost_controls, ),
SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, , ,
&wm8976_capture_boost_controls), SND_SOC_DAPM_MIXER("Boost Mixer", WM8976_POWER2, , , NULL, ), SND_SOC_DAPM_MICBIAS("Mic Bias", WM8976_POWER1, , ), SND_SOC_DAPM_INPUT("MICN"),
SND_SOC_DAPM_INPUT("MICP"),
SND_SOC_DAPM_INPUT("AUX"),
SND_SOC_DAPM_OUTPUT("MONOOUT"),
SND_SOC_DAPM_OUTPUT("SPKOUTP"),
SND_SOC_DAPM_OUTPUT("SPKOUTN"),
#endif
}; static const struct snd_soc_dapm_route audio_map[] = {
/* Mono output mixer */
{"Mono Mixer", "PCM Playback Switch", "DAC"},
{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, /* Speaker output mixer */
{"Speaker Mixer", "PCM Playback Switch", "DAC"},
{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, /* Outputs */
{"Mono Out", NULL, "Mono Mixer"},
{"MONOOUT", NULL, "Mono Out"},
{"SpkN Out", NULL, "Speaker Mixer"},
{"SpkP Out", NULL, "Speaker Mixer"},
{"SPKOUTN", NULL, "SpkN Out"},
{"SPKOUTP", NULL, "SpkP Out"}, /* Boost Mixer */
{"Boost Mixer", NULL, "ADC"},
{"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"},
{"Aux Boost", "Aux Volume", "Boost Mixer"},
{"Capture Boost", "Capture Switch", "Boost Mixer"},
{"Mic Boost", "Mic Volume", "Boost Mixer"}, /* Inputs */
{"MICP", NULL, "Mic Boost"},
{"MICN", NULL, "Mic PGA"},
{"Mic PGA", NULL, "Capture Boost"},
{"AUX", NULL, "Aux Input"},
}; static int wm8976_add_widgets(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = &codec->dapm;
snd_soc_dapm_new_controls(dapm, wm8976_dapm_widgets,
ARRAY_SIZE(wm8976_dapm_widgets)); snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(dapm);
return ;
} struct _pll_div {
unsigned int pre:; /* prescale - 1 */
unsigned int n:;
unsigned int k;
}; static struct _pll_div pll_div; /* The size in bits of the pll divide multiplied by 10
* to allow rounding later */
#define FIXED_PLL_SIZE ((1 << 24) * 10) static void pll_factors(unsigned int target, unsigned int source)
{
unsigned long long Kpart;
unsigned int K, Ndiv, Nmod; Ndiv = target / source;
if (Ndiv < ) {
source >>= ;
pll_div.pre = ;
Ndiv = target / source;
} else
pll_div.pre = ; if ((Ndiv < ) || (Ndiv > ))
printk(KERN_WARNING
"WM8976 N value outwith recommended range! N = %d\n",Ndiv); pll_div.n = Ndiv;
Nmod = target % source;
Kpart = FIXED_PLL_SIZE * (long long)Nmod; do_div(Kpart, source); K = Kpart & 0xFFFFFFFF; /* Check if we need to round */
if ((K % ) >= )
K += ; /* Move down to proper range now rounding is done */
K /= ; pll_div.k = K;
} static int wm8976_set_dai_pll(struct snd_soc_dai *codec_dai,
int pll_id, int source, unsigned int freq_in, unsigned int freq_out)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 reg; if(freq_in == || freq_out == ) {
reg = wm8976_read_reg_cache(codec, WM8976_POWER1);
wm8976_write(codec, WM8976_POWER1, reg & 0x1df);
return ;
} pll_factors(freq_out * , freq_in); wm8976_write(codec, WM8976_PLLN, (pll_div.pre << ) | pll_div.n);
wm8976_write(codec, WM8976_PLLK1, pll_div.k >> );
wm8976_write(codec, WM8976_PLLK1, (pll_div.k >> ) && 0x1ff);
wm8976_write(codec, WM8976_PLLK1, pll_div.k && 0x1ff);
reg = wm8976_read_reg_cache(codec, WM8976_POWER1);
wm8976_write(codec, WM8976_POWER1, reg | 0x020); return ;
} static int wm8976_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 iface = wm8976_read_reg_cache(codec, WM8976_IFACE) & 0x3;
u16 clk = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0xfffe; /* set master/slave audio interface */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
clk |= 0x0001;
break;
case SND_SOC_DAIFMT_CBS_CFS:
break;
default:
return -EINVAL;
} /* interface format */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
iface |= 0x0010;
break;
case SND_SOC_DAIFMT_RIGHT_J:
break;
case SND_SOC_DAIFMT_LEFT_J:
iface |= 0x0008;
break;
case SND_SOC_DAIFMT_DSP_A:
iface |= 0x00018;
break;
default:
return -EINVAL;
} /* clock inversion */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_IF:
iface |= 0x0180;
break;
case SND_SOC_DAIFMT_IB_NF:
iface |= 0x0100;
break;
case SND_SOC_DAIFMT_NB_IF:
iface |= 0x0080;
break;
default:
return -EINVAL;
} wm8976_write(codec, WM8976_IFACE, iface);
wm8976_write(codec, WM8976_CLOCK, clk); return ;
} static int wm8976_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_codec *codec = rtd->codec;
u16 iface = wm8976_read_reg_cache(codec, WM8976_IFACE) & 0xff9f;
u16 adn = wm8976_read_reg_cache(codec, WM8976_ADD) & 0x1f1; /* bit size */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
break;
case SNDRV_PCM_FORMAT_S20_3LE:
iface |= 0x0020;
break;
case SNDRV_PCM_FORMAT_S24_LE:
iface |= 0x0040;
break;
} /* filter coefficient */
switch (params_rate(params)) {
case SNDRV_PCM_RATE_8000:
adn |= 0x5 << ;
break;
case SNDRV_PCM_RATE_11025:
adn |= 0x4 << ;
break;
case SNDRV_PCM_RATE_16000:
adn |= 0x3 << ;
break;
case SNDRV_PCM_RATE_22050:
adn |= 0x2 << ;
break;
case SNDRV_PCM_RATE_32000:
adn |= 0x1 << ;
break;
} /* set iface */
wm8976_write(codec, WM8976_IFACE, iface);
wm8976_write(codec, WM8976_ADD, adn);
return ;
} static int wm8976_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
int div_id, int div)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 reg; switch (div_id) {
case WM8976_MCLKDIV:
reg = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0x11f;
wm8976_write(codec, WM8976_CLOCK, reg | div);
break;
case WM8976_BCLKDIV:
reg = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0x1c7;
wm8976_write(codec, WM8976_CLOCK, reg | div);
break;
case WM8976_OPCLKDIV:
reg = wm8976_read_reg_cache(codec, WM8976_GPIO) & 0x1cf;
wm8976_write(codec, WM8976_GPIO, reg | div);
break;
case WM8976_DACOSR:
reg = wm8976_read_reg_cache(codec, WM8976_DAC) & 0x1f7;
wm8976_write(codec, WM8976_DAC, reg | div);
break;
case WM8976_ADCOSR:
reg = wm8976_read_reg_cache(codec, WM8976_ADC) & 0x1f7;
wm8976_write(codec, WM8976_ADC, reg | div);
break;
case WM8976_MCLKSEL:
reg = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0x0ff;
wm8976_write(codec, WM8976_CLOCK, reg | div);
break;
default:
return -EINVAL;
} return ;
} static int wm8976_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
u16 mute_reg = wm8976_read_reg_cache(codec, WM8976_DAC) & 0xffbf; if(mute)
wm8976_write(codec, WM8976_DAC, mute_reg | 0x40);
else {
wm8976_write(codec, WM8976_DAC, mute_reg);
} return ;
} /* TODO: liam need to make this lower power with dapm */
static int wm8976_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{ switch (level) {
case SND_SOC_BIAS_ON:
wm8976_write(codec, WM8976_POWER1, 0x1ff);
wm8976_write(codec, WM8976_POWER2, 0x1ff & ~(<<));
wm8976_write(codec, WM8976_POWER3, 0x1ff);
break;
case SND_SOC_BIAS_STANDBY:
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_OFF:
wm8976_write(codec, WM8976_POWER1, 0x0);
wm8976_write(codec, WM8976_POWER2, 0x0);
wm8976_write(codec, WM8976_POWER3, 0x0);
break;
}
codec->dapm.bias_level = level;
return ;
} #define WM8976_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) #define WM8976_FORMATS \
(SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
SNDRV_PCM_FORMAT_S24_3LE | SNDRV_PCM_FORMAT_S24_LE) static struct snd_soc_dai_ops wm8976_dai_ops = {
.hw_params = wm8976_hw_params,
.digital_mute = wm8976_mute,
.set_fmt = wm8976_set_dai_fmt,
.set_clkdiv = wm8976_set_dai_clkdiv,
.set_pll = wm8976_set_dai_pll,
}; struct snd_soc_dai_driver wm8976_dai = {
.name = "wm8976-iis",
.playback = {
.stream_name = "Playback",
.channels_min = ,
.channels_max = ,
.rates = WM8976_RATES,
.formats = WM8976_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = ,
.channels_max = ,
.rates = WM8976_RATES,
.formats = WM8976_FORMATS,},
.ops = &wm8976_dai_ops,
}; static int snd_soc_wm8976_suspend(struct snd_soc_codec *codec)
{
wm8976_set_bias_level(codec, SND_SOC_BIAS_OFF);
return ;
} static int snd_soc_wm8976_resume(struct snd_soc_codec *codec)
{
int i;
u16 *cache = codec->reg_cache; /* Sync reg_cache with the hardware */
for (i = ; i < ARRAY_SIZE(wm8976_reg); i++) {
codec->write(codec->control_data, i, cache[i]);
}
wm8976_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
wm8976_set_bias_level(codec, SND_SOC_BIAS_ON);
return ;
} static int snd_soc_wm8976_probe(struct snd_soc_codec *codec)
{
gpbcon = ioremap(0x56000010, );
gpbdat = ioremap(0x56000014, ); /* GPB 4: L3CLOCK */
/* GPB 3: L3DATA */
/* GPB 2: L3MODE */
*gpbcon &= ~((<<) | (<<) | (<<));
*gpbcon |= ((<<) | (<<) | (<<)); snd_soc_add_codec_controls(codec, wm8976_snd_controls,
ARRAY_SIZE(wm8976_snd_controls));
//wm8976_add_widgets(codec); return ; } /* power down chip */
static int snd_soc_wm8976_remove(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = &codec->dapm; //snd_soc_dapm_free(dapm); iounmap(gpbcon);
iounmap(gpbdat); return ;
} struct snd_soc_codec_driver soc_codec_dev_wm8976 = {
.probe = snd_soc_wm8976_probe,
.remove = snd_soc_wm8976_remove,
.suspend = snd_soc_wm8976_suspend,
.resume = snd_soc_wm8976_resume,
.reg_cache_size = sizeof(wm8976_reg),
.reg_word_size = sizeof(u16),
.reg_cache_default = wm8976_reg,
.reg_cache_step = ,
.read = wm8976_read_reg_cache,
.write = wm8976_write,
.set_bias_level = wm8976_set_bias_level,
}; /* ͨ¹ý×¢²áƽ̨É豸¡¢Æ½Ì¨Çý¶¯À´ÊµÏÖ¶Ôsnd_soc_register_codecµÄµ÷ÓÃ
*
*/ static void wm8976_dev_release(struct device * dev)
{
} static int wm8976_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev,
&soc_codec_dev_wm8976, &wm8976_dai, );
} static int wm8976_remove(struct platform_device *pdev)
{
snd_soc_unregister_codec(&pdev->dev);
return ;
} static struct platform_device wm8976_dev = {
.name = "wm8976-codec",
.id = -,
.dev = {
.release = wm8976_dev_release,
},
};
struct platform_driver wm8976_drv = {
.probe = wm8976_probe,
.remove = wm8976_remove,
.driver = {
.name = "wm8976-codec",
}
}; static int wm8976_init(void)
{
platform_device_register(&wm8976_dev);
platform_driver_register(&wm8976_drv);
return ;
} static void wm8976_exit(void)
{
platform_device_unregister(&wm8976_dev);
platform_driver_unregister(&wm8976_drv);
} module_init(wm8976_init);
module_exit(wm8976_exit); MODULE_LICENSE("GPL");
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h> snd_pcm_t *open_sound_dev(snd_pcm_stream_t type)
{
int err;
snd_pcm_t *handle;
snd_pcm_hw_params_t *hw_params;
unsigned int rate = ; if ((err = snd_pcm_open (&handle, "default", type, )) < ) {
return NULL;
} if ((err = snd_pcm_hw_params_malloc (&hw_params)) < ) {
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
return NULL;
} if ((err = snd_pcm_hw_params_any (handle, hw_params)) < ) {
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
return NULL;
} if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < ) {
fprintf (stderr, "cannot set access type (%s)\n",
snd_strerror (err));
return NULL;
} if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S16_LE)) < ) {
fprintf (stderr, "cannot set sample format (%s)\n",
snd_strerror (err));
return NULL;
} if ((err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &rate, )) < ) {
fprintf (stderr, "cannot set sample rate (%s)\n",
snd_strerror (err));
return NULL;
} if ((err = snd_pcm_hw_params_set_channels (handle, hw_params, )) < ) {
fprintf (stderr, "cannot set channel count (%s)\n",
snd_strerror (err));
return NULL;
} if ((err = snd_pcm_hw_params (handle, hw_params)) < ) {
fprintf (stderr, "cannot set parameters (%s)\n",
snd_strerror (err));
return NULL;
} snd_pcm_hw_params_free (hw_params); return handle;
} void close_sound_dev(snd_pcm_t *handle)
{
snd_pcm_close (handle);
} snd_pcm_t *open_playback(void)
{
return open_sound_dev(SND_PCM_STREAM_PLAYBACK);
} snd_pcm_t *open_capture(void)
{
return open_sound_dev(SND_PCM_STREAM_CAPTURE);
} int main (int argc, char *argv[])
{
int err;
char buf[];
snd_pcm_t *playback_handle;
snd_pcm_t *capture_handle; playback_handle = open_playback();
if (!playback_handle)
{
fprintf (stderr, "cannot open for playback\n");
return -;
} capture_handle = open_capture();
if (!capture_handle)
{
fprintf (stderr, "cannot open for capture\n");
return -;
} if ((err = snd_pcm_prepare (playback_handle)) < ) {
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
return -;
} if ((err = snd_pcm_prepare (capture_handle)) < ) {
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
return -;
} while () {
if ((err = snd_pcm_readi (capture_handle, buf, )) != ) {
fprintf (stderr, "read from audio interface failed (%s)\n",
snd_strerror (err));
return -;
} if ((err = snd_pcm_writei (playback_handle, buf, )) != ) {
fprintf (stderr, "write to audio interface failed (%s)\n",
snd_strerror (err));
return -;
}
} snd_pcm_close (playback_handle);
snd_pcm_close (capture_handle);
return ;
}

Makefile

capture_playback : capture_playback.c
arm-linux-gcc -Wall -o capture_playback capture_playback.c -lasound clean:
rm capture_playback

 

Linux音频驱动学习之:(2)移植wm8976声卡驱动(linux-3.4.2)的更多相关文章

  1. 十七、S3C2440 音频解码芯片WM8976声卡驱动移植、madplay测试

    学习目标:1. WM9876接口和工作原理:2. WM9876驱动移植:3. WM9876应用测试:4. 问题总结 1. WM9876接口和工作原理  本节使用了JZ2440开发板移植WM9876驱动 ...

  2. Linux内核驱动学习(四)Platform设备驱动模型

    Linux platform设备驱动模型 文章目录 Linux platform设备驱动模型 前言 框架 设备与驱动的分离 设备(device) 驱动(driver) 匹配(match) 参考 前言 ...

  3. tiny4412学习(三)之移植linux-4.x驱动(1)支持网卡驱动【转】

    本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/74160686 一.思路 上一节我们通过DNW将内核.文件系统.设备树文件烧入到内 ...

  4. Linux内核驱动学习(一)编写最简单Linux内核模块HelloWorld

    文章目录 准备工作 什么是内核模块 编写 hello.c 模块编译 相关指令 测试结果 模块加载 模块卸载 准备工作 在进行以下操作前,首先我准备了一台电脑,并且安装了虚拟机,系统是Ubuntu16. ...

  5. 嵌入式Linux驱动学习之路(十八)LCD驱动

    驱动代码: /************************************************************************* > File Name: lcd ...

  6. 嵌入式Linux驱动学习之路(十五)按键驱动-定时器防抖

    在之前的定时器驱动程序中,我们发现在连续按下按键的时候,正常情况下应该是一次按下对应一次松开.而程序有时候会显示是两次按下,一次松开.这个问题是因为在按下的时候,因为是机械按键,所以电压信号会产生一定 ...

  7. 嵌入式Linux驱动学习之路(十四)按键驱动-同步、互斥、阻塞

    目的:同一个时刻,只能有一个应用程序打开我们的驱动程序. ①原子操作: v = ATOMIC_INIT( i )  定义原子变量v并初始化为i atomic_read(v)        返回原子变量 ...

  8. 嵌入式Linux驱动学习之路(十二)按键驱动-poll机制

    实现的功能是在读取按键信息的时候,如果没有产生按键,则程序休眠在read函数中,利用poll机制,可以在没有退出的情况下让程序自动退出. 下面的程序就是在读取按键信息的时候,如果5000ms内没有按键 ...

  9. 嵌入式Linux驱动学习之路(十)字符设备驱动-my_led

    首先贴上代码: 字符设备驱动代码: /** *file name: led.c */#include <linux/sched.h> #include <linux/signal.h ...

随机推荐

  1. hdu-6194 string string string 后缀数组 出现恰好K次的串的数量

    最少出现K次我们可以用Height数组的lcp来得出,而恰好出现K次,我们只要除去最少出现K+1次的lcp即可. #include <cstdio> #include <cstrin ...

  2. MyEclipse6.5的反编译插件的安装

    常用的几种反编译工具 1. JD-GUI[推荐] JD-GUI是属于Java Decompiler项目(JD项目)下个的图形化运行方式的反编译器.JD-Eclipse属于Java Decompiler ...

  3. python基础之字典以及增删改查

    字典:字典是python中唯一的一个映射类型,主要形式为 dic = {key1:value,key2:value2,....} 字典中key的值是唯一的,主要关系到HASH算法,并且key的值必须是 ...

  4. SQL SERVER 字符合并多行为一列

    [字符合并多行为一列] 思路1:行转列,在与字符拼接(适用每组列数名相同) 思路2:转xml,去掉多余字符(适用所有) 假设兴趣表Hobbys Name Hobby 小张 打篮球 小张 踢足球 Nam ...

  5. python记录_day06

    一.小数据池 注意大前提!!!! 小数据池只针对整数.字符串和bool值,因为这些数据是不可变的,这样数据的共享才安全 小数据池也称为小整数缓存机制或驻留机制,是指在不同代码块创建部分小数据对象(具体 ...

  6. array_column的作用

    从记录集中取出 last_name 列,用相应的 "id" 列作为键值: <?php // 表示由数据库返回的可能记录集的数组 $a = array( array( 'id' ...

  7. PAT 1023 Have Fun with Numbers

    1023 Have Fun with Numbers (20 分)   Notice that the number 123456789 is a 9-digit number consisting ...

  8. Buffer和Stream

    Buffer JavaScript 语言自身只有字符串数据类型,没有二进制数据类型.但在处理像TCP流或文件流时,必须使用到二进制数据. 因此在 Node.js中,定义了一个 Buffer 类,该类用 ...

  9. HDFS shell操作及HDFS Java API编程

    HDFS shell操作及HDFS Java API编程 1.熟悉Hadoop文件结构. 2.进行HDFS shell操作. 3.掌握通过Hadoop Java API对HDFS操作. 4.了解Had ...

  10. button disable and enable

    1. disable <button id="buttonId" disabled>......</button> $("#buttonId&qu ...