forked from Minki/linux
525 lines
16 KiB
C
525 lines
16 KiB
C
|
/*
|
||
|
* SiRF audio codec driver
|
||
|
*
|
||
|
* Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company.
|
||
|
*
|
||
|
* Licensed under GPLv2 or later.
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/pm_runtime.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/regmap.h>
|
||
|
#include <sound/core.h>
|
||
|
#include <sound/pcm.h>
|
||
|
#include <sound/pcm_params.h>
|
||
|
#include <sound/initval.h>
|
||
|
#include <sound/tlv.h>
|
||
|
#include <sound/soc.h>
|
||
|
#include <sound/dmaengine_pcm.h>
|
||
|
|
||
|
#include "sirf-audio-codec.h"
|
||
|
|
||
|
struct sirf_audio_codec {
|
||
|
struct clk *clk;
|
||
|
struct regmap *regmap;
|
||
|
u32 reg_ctrl0, reg_ctrl1;
|
||
|
};
|
||
|
|
||
|
static const char * const input_mode_mux[] = {"Single-ended",
|
||
|
"Differential"};
|
||
|
|
||
|
static const struct soc_enum input_mode_mux_enum =
|
||
|
SOC_ENUM_SINGLE(AUDIO_IC_CODEC_CTRL1, 4, 2, input_mode_mux);
|
||
|
|
||
|
static const struct snd_kcontrol_new sirf_audio_codec_input_mode_control =
|
||
|
SOC_DAPM_ENUM("Route", input_mode_mux_enum);
|
||
|
|
||
|
static const DECLARE_TLV_DB_SCALE(playback_vol_tlv, -12400, 100, 0);
|
||
|
static const DECLARE_TLV_DB_SCALE(capture_vol_tlv_prima2, 500, 100, 0);
|
||
|
static const DECLARE_TLV_DB_RANGE(capture_vol_tlv_atlas6,
|
||
|
0, 7, TLV_DB_SCALE_ITEM(-100, 100, 0),
|
||
|
0x22, 0x3F, TLV_DB_SCALE_ITEM(700, 100, 0),
|
||
|
);
|
||
|
|
||
|
static struct snd_kcontrol_new volume_controls_atlas6[] = {
|
||
|
SOC_DOUBLE_TLV("Playback Volume", AUDIO_IC_CODEC_CTRL0, 21, 14,
|
||
|
0x7F, 0, playback_vol_tlv),
|
||
|
SOC_DOUBLE_TLV("Capture Volume", AUDIO_IC_CODEC_CTRL1, 16, 10,
|
||
|
0x3F, 0, capture_vol_tlv_atlas6),
|
||
|
};
|
||
|
|
||
|
static struct snd_kcontrol_new volume_controls_prima2[] = {
|
||
|
SOC_DOUBLE_TLV("Speaker Volume", AUDIO_IC_CODEC_CTRL0, 21, 14,
|
||
|
0x7F, 0, playback_vol_tlv),
|
||
|
SOC_DOUBLE_TLV("Capture Volume", AUDIO_IC_CODEC_CTRL1, 15, 10,
|
||
|
0x1F, 0, capture_vol_tlv_prima2),
|
||
|
};
|
||
|
|
||
|
static struct snd_kcontrol_new left_input_path_controls[] = {
|
||
|
SOC_DAPM_SINGLE("Line Left Switch", AUDIO_IC_CODEC_CTRL1, 6, 1, 0),
|
||
|
SOC_DAPM_SINGLE("Mic Left Switch", AUDIO_IC_CODEC_CTRL1, 3, 1, 0),
|
||
|
};
|
||
|
|
||
|
static struct snd_kcontrol_new right_input_path_controls[] = {
|
||
|
SOC_DAPM_SINGLE("Line Right Switch", AUDIO_IC_CODEC_CTRL1, 5, 1, 0),
|
||
|
SOC_DAPM_SINGLE("Mic Right Switch", AUDIO_IC_CODEC_CTRL1, 2, 1, 0),
|
||
|
};
|
||
|
|
||
|
static struct snd_kcontrol_new left_dac_to_hp_left_amp_switch_control =
|
||
|
SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 9, 1, 0);
|
||
|
|
||
|
static struct snd_kcontrol_new left_dac_to_hp_right_amp_switch_control =
|
||
|
SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 8, 1, 0);
|
||
|
|
||
|
static struct snd_kcontrol_new right_dac_to_hp_left_amp_switch_control =
|
||
|
SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 7, 1, 0);
|
||
|
|
||
|
static struct snd_kcontrol_new right_dac_to_hp_right_amp_switch_control =
|
||
|
SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 6, 1, 0);
|
||
|
|
||
|
static struct snd_kcontrol_new left_dac_to_speaker_lineout_switch_control =
|
||
|
SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 11, 1, 0);
|
||
|
|
||
|
static struct snd_kcontrol_new right_dac_to_speaker_lineout_switch_control =
|
||
|
SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 10, 1, 0);
|
||
|
|
||
|
/* After enable adc, Delay 200ms to avoid pop noise */
|
||
|
static int adc_enable_delay_event(struct snd_soc_dapm_widget *w,
|
||
|
struct snd_kcontrol *kcontrol, int event)
|
||
|
{
|
||
|
switch (event) {
|
||
|
case SND_SOC_DAPM_POST_PMU:
|
||
|
msleep(200);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void enable_and_reset_codec(struct regmap *regmap,
|
||
|
u32 codec_enable_bits, u32 codec_reset_bits)
|
||
|
{
|
||
|
regmap_update_bits(regmap, AUDIO_IC_CODEC_CTRL1,
|
||
|
codec_enable_bits | codec_reset_bits,
|
||
|
codec_enable_bits | ~codec_reset_bits);
|
||
|
msleep(20);
|
||
|
regmap_update_bits(regmap, AUDIO_IC_CODEC_CTRL1,
|
||
|
codec_reset_bits, codec_reset_bits);
|
||
|
}
|
||
|
|
||
|
static int atlas6_codec_enable_and_reset_event(struct snd_soc_dapm_widget *w,
|
||
|
struct snd_kcontrol *kcontrol, int event)
|
||
|
{
|
||
|
#define ATLAS6_CODEC_ENABLE_BITS (1 << 29)
|
||
|
#define ATLAS6_CODEC_RESET_BITS (1 << 28)
|
||
|
struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(w->codec->dev);
|
||
|
switch (event) {
|
||
|
case SND_SOC_DAPM_PRE_PMU:
|
||
|
enable_and_reset_codec(sirf_audio_codec->regmap,
|
||
|
ATLAS6_CODEC_ENABLE_BITS, ATLAS6_CODEC_RESET_BITS);
|
||
|
break;
|
||
|
case SND_SOC_DAPM_POST_PMD:
|
||
|
regmap_update_bits(sirf_audio_codec->regmap,
|
||
|
AUDIO_IC_CODEC_CTRL1, ATLAS6_CODEC_ENABLE_BITS,
|
||
|
~ATLAS6_CODEC_ENABLE_BITS);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int prima2_codec_enable_and_reset_event(struct snd_soc_dapm_widget *w,
|
||
|
struct snd_kcontrol *kcontrol, int event)
|
||
|
{
|
||
|
#define PRIMA2_CODEC_ENABLE_BITS (1 << 27)
|
||
|
#define PRIMA2_CODEC_RESET_BITS (1 << 26)
|
||
|
struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(w->codec->dev);
|
||
|
switch (event) {
|
||
|
case SND_SOC_DAPM_POST_PMU:
|
||
|
enable_and_reset_codec(sirf_audio_codec->regmap,
|
||
|
PRIMA2_CODEC_ENABLE_BITS, PRIMA2_CODEC_RESET_BITS);
|
||
|
break;
|
||
|
case SND_SOC_DAPM_POST_PMD:
|
||
|
regmap_update_bits(sirf_audio_codec->regmap,
|
||
|
AUDIO_IC_CODEC_CTRL1, PRIMA2_CODEC_ENABLE_BITS,
|
||
|
~PRIMA2_CODEC_ENABLE_BITS);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct snd_soc_dapm_widget atlas6_output_driver_dapm_widgets[] = {
|
||
|
SND_SOC_DAPM_OUT_DRV("HP Left Driver", AUDIO_IC_CODEC_CTRL1,
|
||
|
25, 0, NULL, 0),
|
||
|
SND_SOC_DAPM_OUT_DRV("HP Right Driver", AUDIO_IC_CODEC_CTRL1,
|
||
|
26, 0, NULL, 0),
|
||
|
SND_SOC_DAPM_OUT_DRV("Speaker Driver", AUDIO_IC_CODEC_CTRL1,
|
||
|
27, 0, NULL, 0),
|
||
|
};
|
||
|
|
||
|
static const struct snd_soc_dapm_widget prima2_output_driver_dapm_widgets[] = {
|
||
|
SND_SOC_DAPM_OUT_DRV("HP Left Driver", AUDIO_IC_CODEC_CTRL1,
|
||
|
23, 0, NULL, 0),
|
||
|
SND_SOC_DAPM_OUT_DRV("HP Right Driver", AUDIO_IC_CODEC_CTRL1,
|
||
|
24, 0, NULL, 0),
|
||
|
SND_SOC_DAPM_OUT_DRV("Speaker Driver", AUDIO_IC_CODEC_CTRL1,
|
||
|
25, 0, NULL, 0),
|
||
|
};
|
||
|
|
||
|
static const struct snd_soc_dapm_widget atlas6_codec_clock_dapm_widget =
|
||
|
SND_SOC_DAPM_SUPPLY("codecclk", SND_SOC_NOPM, 0, 0,
|
||
|
atlas6_codec_enable_and_reset_event,
|
||
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD);
|
||
|
|
||
|
static const struct snd_soc_dapm_widget prima2_codec_clock_dapm_widget =
|
||
|
SND_SOC_DAPM_SUPPLY("codecclk", SND_SOC_NOPM, 0, 0,
|
||
|
prima2_codec_enable_and_reset_event,
|
||
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD);
|
||
|
|
||
|
static const struct snd_soc_dapm_widget sirf_audio_codec_dapm_widgets[] = {
|
||
|
SND_SOC_DAPM_DAC("DAC left", NULL, AUDIO_IC_CODEC_CTRL0, 1, 0),
|
||
|
SND_SOC_DAPM_DAC("DAC right", NULL, AUDIO_IC_CODEC_CTRL0, 0, 0),
|
||
|
SND_SOC_DAPM_SWITCH("Left dac to hp left amp", SND_SOC_NOPM, 0, 0,
|
||
|
&left_dac_to_hp_left_amp_switch_control),
|
||
|
SND_SOC_DAPM_SWITCH("Left dac to hp right amp", SND_SOC_NOPM, 0, 0,
|
||
|
&left_dac_to_hp_right_amp_switch_control),
|
||
|
SND_SOC_DAPM_SWITCH("Right dac to hp left amp", SND_SOC_NOPM, 0, 0,
|
||
|
&right_dac_to_hp_left_amp_switch_control),
|
||
|
SND_SOC_DAPM_SWITCH("Right dac to hp right amp", SND_SOC_NOPM, 0, 0,
|
||
|
&right_dac_to_hp_right_amp_switch_control),
|
||
|
SND_SOC_DAPM_OUT_DRV("HP amp left driver", AUDIO_IC_CODEC_CTRL0, 3, 0,
|
||
|
NULL, 0),
|
||
|
SND_SOC_DAPM_OUT_DRV("HP amp right driver", AUDIO_IC_CODEC_CTRL0, 3, 0,
|
||
|
NULL, 0),
|
||
|
|
||
|
SND_SOC_DAPM_SWITCH("Left dac to speaker lineout", SND_SOC_NOPM, 0, 0,
|
||
|
&left_dac_to_speaker_lineout_switch_control),
|
||
|
SND_SOC_DAPM_SWITCH("Right dac to speaker lineout", SND_SOC_NOPM, 0, 0,
|
||
|
&right_dac_to_speaker_lineout_switch_control),
|
||
|
SND_SOC_DAPM_OUT_DRV("Speaker amp driver", AUDIO_IC_CODEC_CTRL0, 4, 0,
|
||
|
NULL, 0),
|
||
|
|
||
|
SND_SOC_DAPM_OUTPUT("HPOUTL"),
|
||
|
SND_SOC_DAPM_OUTPUT("HPOUTR"),
|
||
|
SND_SOC_DAPM_OUTPUT("SPKOUT"),
|
||
|
|
||
|
SND_SOC_DAPM_ADC_E("ADC left", NULL, AUDIO_IC_CODEC_CTRL1, 8, 0,
|
||
|
adc_enable_delay_event, SND_SOC_DAPM_POST_PMU),
|
||
|
SND_SOC_DAPM_ADC_E("ADC right", NULL, AUDIO_IC_CODEC_CTRL1, 7, 0,
|
||
|
adc_enable_delay_event, SND_SOC_DAPM_POST_PMU),
|
||
|
SND_SOC_DAPM_MIXER("Left PGA mixer", AUDIO_IC_CODEC_CTRL1, 1, 0,
|
||
|
&left_input_path_controls[0],
|
||
|
ARRAY_SIZE(left_input_path_controls)),
|
||
|
SND_SOC_DAPM_MIXER("Right PGA mixer", AUDIO_IC_CODEC_CTRL1, 0, 0,
|
||
|
&right_input_path_controls[0],
|
||
|
ARRAY_SIZE(right_input_path_controls)),
|
||
|
|
||
|
SND_SOC_DAPM_MUX("Mic input mode mux", SND_SOC_NOPM, 0, 0,
|
||
|
&sirf_audio_codec_input_mode_control),
|
||
|
SND_SOC_DAPM_MICBIAS("Mic Bias", AUDIO_IC_CODEC_PWR, 3, 0),
|
||
|
SND_SOC_DAPM_INPUT("MICIN1"),
|
||
|
SND_SOC_DAPM_INPUT("MICIN2"),
|
||
|
SND_SOC_DAPM_INPUT("LINEIN1"),
|
||
|
SND_SOC_DAPM_INPUT("LINEIN2"),
|
||
|
|
||
|
SND_SOC_DAPM_SUPPLY("HSL Phase Opposite", AUDIO_IC_CODEC_CTRL0,
|
||
|
30, 0, NULL, 0),
|
||
|
};
|
||
|
|
||
|
static const struct snd_soc_dapm_route sirf_audio_codec_map[] = {
|
||
|
{"SPKOUT", NULL, "Speaker Driver"},
|
||
|
{"Speaker Driver", NULL, "Speaker amp driver"},
|
||
|
{"Speaker amp driver", NULL, "Left dac to speaker lineout"},
|
||
|
{"Speaker amp driver", NULL, "Right dac to speaker lineout"},
|
||
|
{"Left dac to speaker lineout", "Switch", "DAC left"},
|
||
|
{"Right dac to speaker lineout", "Switch", "DAC right"},
|
||
|
{"HPOUTL", NULL, "HP Left Driver"},
|
||
|
{"HPOUTR", NULL, "HP Right Driver"},
|
||
|
{"HP Left Driver", NULL, "HP amp left driver"},
|
||
|
{"HP Right Driver", NULL, "HP amp right driver"},
|
||
|
{"HP amp left driver", NULL, "Right dac to hp left amp"},
|
||
|
{"HP amp right driver", NULL , "Right dac to hp right amp"},
|
||
|
{"HP amp left driver", NULL, "Left dac to hp left amp"},
|
||
|
{"HP amp right driver", NULL , "Right dac to hp right amp"},
|
||
|
{"Right dac to hp left amp", "Switch", "DAC left"},
|
||
|
{"Right dac to hp right amp", "Switch", "DAC right"},
|
||
|
{"Left dac to hp left amp", "Switch", "DAC left"},
|
||
|
{"Left dac to hp right amp", "Switch", "DAC right"},
|
||
|
{"DAC left", NULL, "codecclk"},
|
||
|
{"DAC right", NULL, "codecclk"},
|
||
|
{"DAC left", NULL, "Playback"},
|
||
|
{"DAC right", NULL, "Playback"},
|
||
|
{"DAC left", NULL, "HSL Phase Opposite"},
|
||
|
{"DAC right", NULL, "HSL Phase Opposite"},
|
||
|
|
||
|
{"Capture", NULL, "ADC left"},
|
||
|
{"Capture", NULL, "ADC right"},
|
||
|
{"ADC left", NULL, "codecclk"},
|
||
|
{"ADC right", NULL, "codecclk"},
|
||
|
{"ADC left", NULL, "Left PGA mixer"},
|
||
|
{"ADC right", NULL, "Right PGA mixer"},
|
||
|
{"Left PGA mixer", "Line Left Switch", "LINEIN2"},
|
||
|
{"Right PGA mixer", "Line Right Switch", "LINEIN1"},
|
||
|
{"Left PGA mixer", "Mic Left Switch", "MICIN2"},
|
||
|
{"Right PGA mixer", "Mic Right Switch", "Mic input mode mux"},
|
||
|
{"Mic input mode mux", "Single-ended", "MICIN1"},
|
||
|
{"Mic input mode mux", "Differential", "MICIN1"},
|
||
|
};
|
||
|
|
||
|
static int sirf_audio_codec_trigger(struct snd_pcm_substream *substream,
|
||
|
int cmd,
|
||
|
struct snd_soc_dai *dai)
|
||
|
{
|
||
|
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||
|
struct snd_soc_codec *codec = dai->codec;
|
||
|
u32 val = 0;
|
||
|
|
||
|
/*
|
||
|
* This is a workaround, When stop playback,
|
||
|
* need disable HP amp, avoid the current noise.
|
||
|
*/
|
||
|
switch (cmd) {
|
||
|
case SNDRV_PCM_TRIGGER_STOP:
|
||
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||
|
break;
|
||
|
case SNDRV_PCM_TRIGGER_START:
|
||
|
case SNDRV_PCM_TRIGGER_RESUME:
|
||
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||
|
if (playback)
|
||
|
val = IC_HSLEN | IC_HSREN;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (playback)
|
||
|
snd_soc_update_bits(codec, AUDIO_IC_CODEC_CTRL0,
|
||
|
IC_HSLEN | IC_HSREN, val);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
struct snd_soc_dai_ops sirf_audio_codec_dai_ops = {
|
||
|
.trigger = sirf_audio_codec_trigger,
|
||
|
};
|
||
|
|
||
|
struct snd_soc_dai_driver sirf_audio_codec_dai = {
|
||
|
.name = "sirf-audio-codec",
|
||
|
.playback = {
|
||
|
.stream_name = "Playback",
|
||
|
.channels_min = 2,
|
||
|
.channels_max = 2,
|
||
|
.rates = SNDRV_PCM_RATE_48000,
|
||
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||
|
},
|
||
|
.capture = {
|
||
|
.stream_name = "Capture",
|
||
|
.channels_min = 1,
|
||
|
.channels_max = 2,
|
||
|
.rates = SNDRV_PCM_RATE_48000,
|
||
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||
|
},
|
||
|
.ops = &sirf_audio_codec_dai_ops,
|
||
|
};
|
||
|
|
||
|
static int sirf_audio_codec_probe(struct snd_soc_codec *codec)
|
||
|
{
|
||
|
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||
|
|
||
|
pm_runtime_enable(codec->dev);
|
||
|
|
||
|
if (of_device_is_compatible(codec->dev->of_node, "sirf,prima2-audio-codec")) {
|
||
|
snd_soc_dapm_new_controls(dapm,
|
||
|
prima2_output_driver_dapm_widgets,
|
||
|
ARRAY_SIZE(prima2_output_driver_dapm_widgets));
|
||
|
snd_soc_dapm_new_controls(dapm,
|
||
|
&prima2_codec_clock_dapm_widget, 1);
|
||
|
return snd_soc_add_codec_controls(codec,
|
||
|
volume_controls_prima2,
|
||
|
ARRAY_SIZE(volume_controls_prima2));
|
||
|
}
|
||
|
if (of_device_is_compatible(codec->dev->of_node, "sirf,atlas6-audio-codec")) {
|
||
|
snd_soc_dapm_new_controls(dapm,
|
||
|
atlas6_output_driver_dapm_widgets,
|
||
|
ARRAY_SIZE(atlas6_output_driver_dapm_widgets));
|
||
|
snd_soc_dapm_new_controls(dapm,
|
||
|
&atlas6_codec_clock_dapm_widget, 1);
|
||
|
return snd_soc_add_codec_controls(codec,
|
||
|
volume_controls_atlas6,
|
||
|
ARRAY_SIZE(volume_controls_atlas6));
|
||
|
}
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
static int sirf_audio_codec_remove(struct snd_soc_codec *codec)
|
||
|
{
|
||
|
pm_runtime_disable(codec->dev);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct snd_soc_codec_driver soc_codec_device_sirf_audio_codec = {
|
||
|
.probe = sirf_audio_codec_probe,
|
||
|
.remove = sirf_audio_codec_remove,
|
||
|
.dapm_widgets = sirf_audio_codec_dapm_widgets,
|
||
|
.num_dapm_widgets = ARRAY_SIZE(sirf_audio_codec_dapm_widgets),
|
||
|
.dapm_routes = sirf_audio_codec_map,
|
||
|
.num_dapm_routes = ARRAY_SIZE(sirf_audio_codec_map),
|
||
|
.idle_bias_off = true,
|
||
|
};
|
||
|
|
||
|
static const struct of_device_id sirf_audio_codec_of_match[] = {
|
||
|
{ .compatible = "sirf,prima2-audio-codec" },
|
||
|
{ .compatible = "sirf,atlas6-audio-codec" },
|
||
|
{}
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, sirf_audio_codec_of_match);
|
||
|
|
||
|
static const struct regmap_config sirf_audio_codec_regmap_config = {
|
||
|
.reg_bits = 32,
|
||
|
.reg_stride = 4,
|
||
|
.val_bits = 32,
|
||
|
.max_register = AUDIO_IC_CODEC_CTRL3,
|
||
|
.cache_type = REGCACHE_NONE,
|
||
|
};
|
||
|
|
||
|
static int sirf_audio_codec_driver_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
int ret;
|
||
|
struct sirf_audio_codec *sirf_audio_codec;
|
||
|
void __iomem *base;
|
||
|
struct resource *mem_res;
|
||
|
const struct of_device_id *match;
|
||
|
|
||
|
match = of_match_node(sirf_audio_codec_of_match, pdev->dev.of_node);
|
||
|
|
||
|
sirf_audio_codec = devm_kzalloc(&pdev->dev,
|
||
|
sizeof(struct sirf_audio_codec), GFP_KERNEL);
|
||
|
if (!sirf_audio_codec)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
platform_set_drvdata(pdev, sirf_audio_codec);
|
||
|
|
||
|
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
base = devm_ioremap_resource(&pdev->dev, mem_res);
|
||
|
if (base == NULL)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
sirf_audio_codec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
|
||
|
&sirf_audio_codec_regmap_config);
|
||
|
if (IS_ERR(sirf_audio_codec->regmap))
|
||
|
return PTR_ERR(sirf_audio_codec->regmap);
|
||
|
|
||
|
sirf_audio_codec->clk = devm_clk_get(&pdev->dev, NULL);
|
||
|
if (IS_ERR(sirf_audio_codec->clk)) {
|
||
|
dev_err(&pdev->dev, "Get clock failed.\n");
|
||
|
return PTR_ERR(sirf_audio_codec->clk);
|
||
|
}
|
||
|
|
||
|
ret = clk_prepare_enable(sirf_audio_codec->clk);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Enable clock failed.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = snd_soc_register_codec(&(pdev->dev),
|
||
|
&soc_codec_device_sirf_audio_codec,
|
||
|
&sirf_audio_codec_dai, 1);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Register Audio Codec dai failed.\n");
|
||
|
goto err_clk_put;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Always open charge pump, if not, when the charge pump closed the
|
||
|
* adc will not stable
|
||
|
*/
|
||
|
regmap_update_bits(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL0,
|
||
|
IC_CPFREQ, IC_CPFREQ);
|
||
|
|
||
|
if (of_device_is_compatible(pdev->dev.of_node, "sirf,atlas6-audio-codec"))
|
||
|
regmap_update_bits(sirf_audio_codec->regmap,
|
||
|
AUDIO_IC_CODEC_CTRL0, IC_CPEN, IC_CPEN);
|
||
|
return 0;
|
||
|
|
||
|
err_clk_put:
|
||
|
clk_disable_unprepare(sirf_audio_codec->clk);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sirf_audio_codec_driver_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct sirf_audio_codec *sirf_audio_codec = platform_get_drvdata(pdev);
|
||
|
|
||
|
clk_disable_unprepare(sirf_audio_codec->clk);
|
||
|
snd_soc_unregister_codec(&(pdev->dev));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_PM_SLEEP
|
||
|
static int sirf_audio_codec_suspend(struct device *dev)
|
||
|
{
|
||
|
struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(dev);
|
||
|
|
||
|
regmap_read(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL0,
|
||
|
&sirf_audio_codec->reg_ctrl0);
|
||
|
regmap_read(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL1,
|
||
|
&sirf_audio_codec->reg_ctrl1);
|
||
|
clk_disable_unprepare(sirf_audio_codec->clk);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sirf_audio_codec_resume(struct device *dev)
|
||
|
{
|
||
|
struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(dev);
|
||
|
int ret;
|
||
|
|
||
|
ret = clk_prepare_enable(sirf_audio_codec->clk);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
regmap_write(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL0,
|
||
|
sirf_audio_codec->reg_ctrl0);
|
||
|
regmap_write(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL1,
|
||
|
sirf_audio_codec->reg_ctrl1);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static const struct dev_pm_ops sirf_audio_codec_pm_ops = {
|
||
|
SET_SYSTEM_SLEEP_PM_OPS(sirf_audio_codec_suspend, sirf_audio_codec_resume)
|
||
|
};
|
||
|
|
||
|
static struct platform_driver sirf_audio_codec_driver = {
|
||
|
.driver = {
|
||
|
.name = "sirf-audio-codec",
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = sirf_audio_codec_of_match,
|
||
|
.pm = &sirf_audio_codec_pm_ops,
|
||
|
},
|
||
|
.probe = sirf_audio_codec_driver_probe,
|
||
|
.remove = sirf_audio_codec_driver_remove,
|
||
|
};
|
||
|
|
||
|
module_platform_driver(sirf_audio_codec_driver);
|
||
|
|
||
|
MODULE_DESCRIPTION("SiRF audio codec driver");
|
||
|
MODULE_AUTHOR("RongJun Ying <Rongjun.Ying@csr.com>");
|
||
|
MODULE_LICENSE("GPL v2");
|