/* * wm8350.c -- WM8350 ALSA SoC audio driver * * Copyright (C) 2007-12 Wolfson Microelectronics PLC. * * Author: Liam Girdwood * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wm8350.h" #define WM8350_OUTn_0dB 0x39 #define WM8350_RAMP_NONE 0 #define WM8350_RAMP_UP 1 #define WM8350_RAMP_DOWN 2 /* We only include the analogue supplies here; the digital supplies * need to be available well before this driver can be probed. */ static const char *supply_names[] = { "AVDD", "HPVDD", }; struct wm8350_output { u16 active; u16 left_vol; u16 right_vol; u16 ramp; u16 mute; }; struct wm8350_jack_data { struct snd_soc_jack *jack; struct delayed_work work; int report; int short_report; }; struct wm8350_data { struct wm8350 *wm8350; struct wm8350_output out1; struct wm8350_output out2; struct wm8350_jack_data hpl; struct wm8350_jack_data hpr; struct wm8350_jack_data mic; struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; int fll_freq_out; int fll_freq_in; }; /* * Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown. */ static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) { struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1; struct wm8350 *wm8350 = wm8350_data->wm8350; int left_complete = 0, right_complete = 0; u16 reg, val; /* left channel */ reg = wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME); val = (reg & WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; if (out1->ramp == WM8350_RAMP_UP) { /* ramp step up */ if (val < out1->left_vol) { val++; reg &= ~WM8350_OUT1L_VOL_MASK; wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, reg | (val << WM8350_OUT1L_VOL_SHIFT)); } else left_complete = 1; } else if (out1->ramp == WM8350_RAMP_DOWN) { /* ramp step down */ if (val > 0) { val--; reg &= ~WM8350_OUT1L_VOL_MASK; wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, reg | (val << WM8350_OUT1L_VOL_SHIFT)); } else left_complete = 1; } else return 1; /* right channel */ reg = wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME); val = (reg & WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; if (out1->ramp == WM8350_RAMP_UP) { /* ramp step up */ if (val < out1->right_vol) { val++; reg &= ~WM8350_OUT1R_VOL_MASK; wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, reg | (val << WM8350_OUT1R_VOL_SHIFT)); } else right_complete = 1; } else if (out1->ramp == WM8350_RAMP_DOWN) { /* ramp step down */ if (val > 0) { val--; reg &= ~WM8350_OUT1R_VOL_MASK; wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, reg | (val << WM8350_OUT1R_VOL_SHIFT)); } else right_complete = 1; } /* only hit the update bit if either volume has changed this step */ if (!left_complete || !right_complete) wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU); return left_complete & right_complete; } /* * Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown. */ static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec) { struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out2 = &wm8350_data->out2; struct wm8350 *wm8350 = wm8350_data->wm8350; int left_complete = 0, right_complete = 0; u16 reg, val; /* left channel */ reg = wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME); val = (reg & WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; if (out2->ramp == WM8350_RAMP_UP) { /* ramp step up */ if (val < out2->left_vol) { val++; reg &= ~WM8350_OUT2L_VOL_MASK; wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, reg | (val << WM8350_OUT1L_VOL_SHIFT)); } else left_complete = 1; } else if (out2->ramp == WM8350_RAMP_DOWN) { /* ramp step down */ if (val > 0) { val--; reg &= ~WM8350_OUT2L_VOL_MASK; wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, reg | (val << WM8350_OUT1L_VOL_SHIFT)); } else left_complete = 1; } else return 1; /* right channel */ reg = wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME); val = (reg & WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; if (out2->ramp == WM8350_RAMP_UP) { /* ramp step up */ if (val < out2->right_vol) { val++; reg &= ~WM8350_OUT2R_VOL_MASK; wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, reg | (val << WM8350_OUT1R_VOL_SHIFT)); } else right_complete = 1; } else if (out2->ramp == WM8350_RAMP_DOWN) { /* ramp step down */ if (val > 0) { val--; reg &= ~WM8350_OUT2R_VOL_MASK; wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, reg | (val << WM8350_OUT1R_VOL_SHIFT)); } else right_complete = 1; } /* only hit the update bit if either volume has changed this step */ if (!left_complete || !right_complete) wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU); return left_complete & right_complete; } /* * This work ramps both output PGAs at stream start/stop time to * minimise pop associated with DAPM power switching. * It's best to enable Zero Cross when ramping occurs to minimise any * zipper noises. */ static void wm8350_pga_work(struct work_struct *work) { struct snd_soc_dapm_context *dapm = container_of(work, struct snd_soc_dapm_context, delayed_work.work); struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1, *out2 = &wm8350_data->out2; int i, out1_complete, out2_complete; /* do we need to ramp at all ? */ if (out1->ramp == WM8350_RAMP_NONE && out2->ramp == WM8350_RAMP_NONE) return; /* PGA volumes have 6 bits of resolution to ramp */ for (i = 0; i <= 63; i++) { out1_complete = 1, out2_complete = 1; if (out1->ramp != WM8350_RAMP_NONE) out1_complete = wm8350_out1_ramp_step(codec); if (out2->ramp != WM8350_RAMP_NONE) out2_complete = wm8350_out2_ramp_step(codec); /* ramp finished ? */ if (out1_complete && out2_complete) break; /* we need to delay longer on the up ramp */ if (out1->ramp == WM8350_RAMP_UP || out2->ramp == WM8350_RAMP_UP) { /* delay is longer over 0dB as increases are larger */ if (i >= WM8350_OUTn_0dB) schedule_timeout_interruptible(msecs_to_jiffies (2)); else schedule_timeout_interruptible(msecs_to_jiffies (1)); } else udelay(50); /* doesn't matter if we delay longer */ } out1->ramp = WM8350_RAMP_NONE; out2->ramp = WM8350_RAMP_NONE; } /* * WM8350 Controls */ static int pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out; switch (w->shift) { case 0: case 1: out = &wm8350_data->out1; break; case 2: case 3: out = &wm8350_data->out2; break; default: WARN(1, "Invalid shift %d\n", w->shift); return -1; } switch (event) { case SND_SOC_DAPM_POST_PMU: out->ramp = WM8350_RAMP_UP; out->active = 1; schedule_delayed_work(&codec->dapm.delayed_work, msecs_to_jiffies(1)); break; case SND_SOC_DAPM_PRE_PMD: out->ramp = WM8350_RAMP_DOWN; out->active = 0; schedule_delayed_work(&codec->dapm.delayed_work, msecs_to_jiffies(1)); break; } return 0; } static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out = NULL; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; int ret; unsigned int reg = mc->reg; u16 val; /* For OUT1 and OUT2 we shadow the values and only actually write * them out when active in order to ensure the amplifier comes on * as quietly as possible. */ switch (reg) { case WM8350_LOUT1_VOLUME: out = &wm8350_priv->out1; break; case WM8350_LOUT2_VOLUME: out = &wm8350_priv->out2; break; default: break; } if (out) { out->left_vol = ucontrol->value.integer.value[0]; out->right_vol = ucontrol->value.integer.value[1]; if (!out->active) return 1; } ret = snd_soc_put_volsw(kcontrol, ucontrol); if (ret < 0) return ret; /* now hit the volume update bits (always bit 8) */ val = snd_soc_read(codec, reg); snd_soc_write(codec, reg, val | WM8350_OUT1_VU); return 1; } static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_priv->out1; struct wm8350_output *out2 = &wm8350_priv->out2; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int reg = mc->reg; /* If these are cached registers use the cache */ switch (reg) { case WM8350_LOUT1_VOLUME: ucontrol->value.integer.value[0] = out1->left_vol; ucontrol->value.integer.value[1] = out1->right_vol; return 0; case WM8350_LOUT2_VOLUME: ucontrol->value.integer.value[0] = out2->left_vol; ucontrol->value.integer.value[1] = out2->right_vol; return 0; default: break; } return snd_soc_get_volsw(kcontrol, ucontrol); } static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" }; static const char *wm8350_dacmutem[] = { "Normal", "Soft" }; static const char *wm8350_dacmutes[] = { "Fast", "Slow" }; static const char *wm8350_adcfilter[] = { "None", "High Pass" }; static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" }; static const char *wm8350_lr[] = { "Left", "Right" }; static const struct soc_enum wm8350_enum[] = { SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 4, 4, wm8350_deemp), SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol), SOC_ENUM_SINGLE(WM8350_INPUT_MIXER_VOLUME, 15, 2, wm8350_lr), }; static DECLARE_TLV_DB_SCALE(pre_amp_tlv, -1200, 3525, 0); static DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 600, 0); static DECLARE_TLV_DB_SCALE(dac_pcm_tlv, -7163, 36, 1); static DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -12700, 50, 1); static DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 1); static const unsigned int capture_sd_tlv[] = { TLV_DB_RANGE_HEAD(2), 0, 12, TLV_DB_SCALE_ITEM(-3600, 300, 1), 13, 15, TLV_DB_SCALE_ITEM(0, 0, 0), }; static const struct snd_kcontrol_new wm8350_snd_controls[] = { SOC_ENUM("Playback Deemphasis", wm8350_enum[0]), SOC_ENUM("Playback DAC Inversion", wm8350_enum[1]), SOC_DOUBLE_R_EXT_TLV("Playback PCM Volume", WM8350_DAC_DIGITAL_VOLUME_L, WM8350_DAC_DIGITAL_VOLUME_R, 0, 255, 0, wm8350_get_volsw_2r, wm8350_put_volsw_2r_vu, dac_pcm_tlv), SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]), SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]), SOC_ENUM("Capture PCM Filter", wm8350_enum[4]), SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]), SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]), SOC_DOUBLE_R_EXT_TLV("Capture PCM Volume", WM8350_ADC_DIGITAL_VOLUME_L, WM8350_ADC_DIGITAL_VOLUME_R, 0, 255, 0, wm8350_get_volsw_2r, wm8350_put_volsw_2r_vu, adc_pcm_tlv), SOC_DOUBLE_TLV("Capture Sidetone Volume", WM8350_ADC_DIVIDER, 8, 4, 15, 1, capture_sd_tlv), SOC_DOUBLE_R_EXT_TLV("Capture Volume", WM8350_LEFT_INPUT_VOLUME, WM8350_RIGHT_INPUT_VOLUME, 2, 63, 0, wm8350_get_volsw_2r, wm8350_put_volsw_2r_vu, pre_amp_tlv), SOC_DOUBLE_R("Capture ZC Switch", WM8350_LEFT_INPUT_VOLUME, WM8350_RIGHT_INPUT_VOLUME, 13, 1, 0), SOC_SINGLE_TLV("Left Input Left Sidetone Volume", WM8350_OUTPUT_LEFT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv), SOC_SINGLE_TLV("Left Input Right Sidetone Volume", WM8350_OUTPUT_LEFT_MIXER_VOLUME, 5, 7, 0, out_mix_tlv), SOC_SINGLE_TLV("Left Input Bypass Volume", WM8350_OUTPUT_LEFT_MIXER_VOLUME, 9, 7, 0, out_mix_tlv), SOC_SINGLE_TLV("Right Input Left Sidetone Volume", WM8350_OUTPUT_RIGHT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv), SOC_SINGLE_TLV("Right Input Right Sidetone Volume", WM8350_OUTPUT_RIGHT_MIXER_VOLUME, 5, 7, 0, out_mix_tlv), SOC_SINGLE_TLV("Right Input Bypass Volume", WM8350_OUTPUT_RIGHT_MIXER_VOLUME, 13, 7, 0, out_mix_tlv), SOC_SINGLE("Left Input Mixer +20dB Switch", WM8350_INPUT_MIXER_VOLUME_L, 0, 1, 0), SOC_SINGLE("Right Input Mixer +20dB Switch", WM8350_INPUT_MIXER_VOLUME_R, 0, 1, 0), SOC_SINGLE_TLV("Out4 Capture Volume", WM8350_INPUT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv), SOC_DOUBLE_R_EXT_TLV("Out1 Playback Volume", WM8350_LOUT1_VOLUME, WM8350_ROUT1_VOLUME, 2, 63, 0, wm8350_get_volsw_2r, wm8350_put_volsw_2r_vu, out_pga_tlv), SOC_DOUBLE_R("Out1 Playback ZC Switch", WM8350_LOUT1_VOLUME, WM8350_ROUT1_VOLUME, 13, 1, 0), SOC_DOUBLE_R_EXT_TLV("Out2 Playback Volume", WM8350_LOUT2_VOLUME, WM8350_ROUT2_VOLUME, 2, 63, 0, wm8350_get_volsw_2r, wm8350_put_volsw_2r_vu, out_pga_tlv), SOC_DOUBLE_R("Out2 Playback ZC Switch", WM8350_LOUT2_VOLUME, WM8350_ROUT2_VOLUME, 13, 1, 0), SOC_SINGLE("Out2 Right Invert Switch", WM8350_ROUT2_VOLUME, 10, 1, 0), SOC_SINGLE_TLV("Out2 Beep Volume", WM8350_BEEP_VOLUME, 5, 7, 0, out_mix_tlv), SOC_DOUBLE_R("Out1 Playback Switch", WM8350_LOUT1_VOLUME, WM8350_ROUT1_VOLUME, 14, 1, 1), SOC_DOUBLE_R("Out2 Playback Switch", WM8350_LOUT2_VOLUME, WM8350_ROUT2_VOLUME, 14, 1, 1), }; /* * DAPM Controls */ /* Left Playback Mixer */ static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = { SOC_DAPM_SINGLE("Playback Switch", WM8350_LEFT_MIXER_CONTROL, 11, 1, 0), SOC_DAPM_SINGLE("Left Bypass Switch", WM8350_LEFT_MIXER_CONTROL, 2, 1, 0), SOC_DAPM_SINGLE("Right Playback Switch", WM8350_LEFT_MIXER_CONTROL, 12, 1, 0), SOC_DAPM_SINGLE("Left Sidetone Switch", WM8350_LEFT_MIXER_CONTROL, 0, 1, 0), SOC_DAPM_SINGLE("Right Sidetone Switch", WM8350_LEFT_MIXER_CONTROL, 1, 1, 0), }; /* Right Playback Mixer */ static const struct snd_kcontrol_new wm8350_right_play_mixer_controls[] = { SOC_DAPM_SINGLE("Playback Switch", WM8350_RIGHT_MIXER_CONTROL, 12, 1, 0), SOC_DAPM_SINGLE("Right Bypass Switch", WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0), SOC_DAPM_SINGLE("Left Playback Switch", WM8350_RIGHT_MIXER_CONTROL, 11, 1, 0), SOC_DAPM_SINGLE("Left Sidetone Switch", WM8350_RIGHT_MIXER_CONTROL, 0, 1, 0), SOC_DAPM_SINGLE("Right Sidetone Switch", WM8350_RIGHT_MIXER_CONTROL, 1, 1, 0), }; /* Out4 Mixer */ static const struct snd_kcontrol_new wm8350_out4_mixer_controls[] = { SOC_DAPM_SINGLE("Right Playback Switch", WM8350_OUT4_MIXER_CONTROL, 12, 1, 0), SOC_DAPM_SINGLE("Left Playback Switch", WM8350_OUT4_MIXER_CONTROL, 11, 1, 0), SOC_DAPM_SINGLE("Right Capture Switch", WM8350_OUT4_MIXER_CONTROL, 9, 1, 0), SOC_DAPM_SINGLE("Out3 Playback Switch", WM8350_OUT4_MIXER_CONTROL, 2, 1, 0), SOC_DAPM_SINGLE("Right Mixer Switch", WM8350_OUT4_MIXER_CONTROL, 1, 1, 0), SOC_DAPM_SINGLE("Left Mixer Switch", WM8350_OUT4_MIXER_CONTROL, 0, 1, 0), }; /* Out3 Mixer */ static const struct snd_kcontrol_new wm8350_out3_mixer_controls[] = { SOC_DAPM_SINGLE("Left Playback Switch", WM8350_OUT3_MIXER_CONTROL, 11, 1, 0), SOC_DAPM_SINGLE("Left Capture Switch", WM8350_OUT3_MIXER_CONTROL, 8, 1, 0), SOC_DAPM_SINGLE("Out4 Playback Switch", WM8350_OUT3_MIXER_CONTROL, 3, 1, 0), SOC_DAPM_SINGLE("Left Mixer Switch", WM8350_OUT3_MIXER_CONTROL, 0, 1, 0), }; /* Left Input Mixer */ static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = { SOC_DAPM_SINGLE_TLV("L2 Capture Volume", WM8350_INPUT_MIXER_VOLUME_L, 1, 7, 0, out_mix_tlv), SOC_DAPM_SINGLE_TLV("L3 Capture Volume", WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv), SOC_DAPM_SINGLE("PGA Capture Switch", WM8350_LEFT_INPUT_VOLUME, 14, 1, 1), }; /* Right Input Mixer */ static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = { SOC_DAPM_SINGLE_TLV("L2 Capture Volume", WM8350_INPUT_MIXER_VOLUME_R, 5, 7, 0, out_mix_tlv), SOC_DAPM_SINGLE_TLV("L3 Capture Volume", WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv), SOC_DAPM_SINGLE("PGA Capture Switch", WM8350_RIGHT_INPUT_VOLUME, 14, 1, 1), }; /* Left Mic Mixer */ static const struct snd_kcontrol_new wm8350_left_mic_mixer_controls[] = { SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 1, 1, 0), SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 0, 1, 0), SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 2, 1, 0), }; /* Right Mic Mixer */ static const struct snd_kcontrol_new wm8350_right_mic_mixer_controls[] = { SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 9, 1, 0), SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 8, 1, 0), SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 10, 1, 0), }; /* Beep Switch */ static const struct snd_kcontrol_new wm8350_beep_switch_controls = SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1); /* Out4 Capture Mux */ static const struct snd_kcontrol_new wm8350_out4_capture_controls = SOC_DAPM_ENUM("Route", wm8350_enum[7]); static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = { SND_SOC_DAPM_PGA("IN3R PGA", WM8350_POWER_MGMT_2, 11, 0, NULL, 0), SND_SOC_DAPM_PGA("IN3L PGA", WM8350_POWER_MGMT_2, 10, 0, NULL, 0), SND_SOC_DAPM_PGA_E("Right Out2 PGA", WM8350_POWER_MGMT_3, 3, 0, NULL, 0, pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Left Out2 PGA", WM8350_POWER_MGMT_3, 2, 0, NULL, 0, pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Right Out1 PGA", WM8350_POWER_MGMT_3, 1, 0, NULL, 0, pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Left Out1 PGA", WM8350_POWER_MGMT_3, 0, 0, NULL, 0, pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_MIXER("Right Capture Mixer", WM8350_POWER_MGMT_2, 7, 0, &wm8350_right_capt_mixer_controls[0], ARRAY_SIZE(wm8350_right_capt_mixer_controls)), SND_SOC_DAPM_MIXER("Left Capture Mixer", WM8350_POWER_MGMT_2, 6, 0, &wm8350_left_capt_mixer_controls[0], ARRAY_SIZE(wm8350_left_capt_mixer_controls)), SND_SOC_DAPM_MIXER("Out4 Mixer", WM8350_POWER_MGMT_2, 5, 0, &wm8350_out4_mixer_controls[0], ARRAY_SIZE(wm8350_out4_mixer_controls)), SND_SOC_DAPM_MIXER("Out3 Mixer", WM8350_POWER_MGMT_2, 4, 0, &wm8350_out3_mixer_controls[0], ARRAY_SIZE(wm8350_out3_mixer_controls)), SND_SOC_DAPM_MIXER("Right Playback Mixer", WM8350_POWER_MGMT_2, 1, 0, &wm8350_right_play_mixer_controls[0], ARRAY_SIZE(wm8350_right_play_mixer_controls)), SND_SOC_DAPM_MIXER("Left Playback Mixer", WM8350_POWER_MGMT_2, 0, 0, &wm8350_left_play_mixer_controls[0], ARRAY_SIZE(wm8350_left_play_mixer_controls)), SND_SOC_DAPM_MIXER("Left Mic Mixer", WM8350_POWER_MGMT_2, 8, 0, &wm8350_left_mic_mixer_controls[0], ARRAY_SIZE(wm8350_left_mic_mixer_controls)), SND_SOC_DAPM_MIXER("Right Mic Mixer", WM8350_POWER_MGMT_2, 9, 0, &wm8350_right_mic_mixer_controls[0], ARRAY_SIZE(wm8350_right_mic_mixer_controls)), /* virtual mixer for Beep and Out2R */ SND_SOC_DAPM_MIXER("Out2 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_SWITCH("Beep", WM8350_POWER_MGMT_3, 7, 0, &wm8350_beep_switch_controls), SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8350_POWER_MGMT_4, 3, 0), SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8350_POWER_MGMT_4, 2, 0), SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8350_POWER_MGMT_4, 5, 0), SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8350_POWER_MGMT_4, 4, 0), SND_SOC_DAPM_MICBIAS("Mic Bias", WM8350_POWER_MGMT_1, 4, 0), SND_SOC_DAPM_MUX("Out4 Capture Channel", SND_SOC_NOPM, 0, 0, &wm8350_out4_capture_controls), SND_SOC_DAPM_OUTPUT("OUT1R"), SND_SOC_DAPM_OUTPUT("OUT1L"), SND_SOC_DAPM_OUTPUT("OUT2R"), SND_SOC_DAPM_OUTPUT("OUT2L"), SND_SOC_DAPM_OUTPUT("OUT3"), SND_SOC_DAPM_OUTPUT("OUT4"), SND_SOC_DAPM_INPUT("IN1RN"), SND_SOC_DAPM_INPUT("IN1R/* * drivers/base/node.c - basic Node class support */ #include <linux/sysdev.h> #include <linux/module.h> #include <linux/init.h> #include <linux/mm.h> #include <linux/node.h> #include <linux/hugetlb.h> #include <linux/cpumask.h> #include <linux/topology.h> #include <linux/nodemask.h> #include <linux/cpu.h> #include <linux/device.h> static struct sysdev_class node_class = { .name = "node", }; static ssize_t node_read_cpumap(struct sys_device *dev, int type, char *buf) { struct node *node_dev = to_node(dev); node_to_cpumask_ptr(mask, node_dev->sysdev.id); int len; /* 2008/04/07: buf currently PAGE_SIZE, need 9 chars per 32 bits. */ BUILD_BUG_ON((NR_CPUS/32 * 9) > (PAGE_SIZE-1)); len = type? cpulist_scnprintf(buf, PAGE_SIZE-2, *mask): cpumask_scnprintf(buf, PAGE_SIZE-2, *mask); buf[len++] = '\n'; buf[len] = '\0'; return len; } static inline ssize_t node_read_cpumask(struct sys_device *dev, char *buf) { return node_read_cpumap(dev, 0, buf); } static inline ssize_t node_read_cpulist(struct sys_device *dev, char *buf) { return node_read_cpumap(dev, 1, buf); } static SYSDEV_ATTR(cpumap, S_IRUGO, node_read_cpumask, NULL); static SYSDEV_ATTR(cpulist, S_IRUGO, node_read_cpulist, NULL); #define K(x) ((x) << (PAGE_SHIFT - 10)) static ssize_t node_read_meminfo(struct sys_device * dev, char * buf) { int n; int nid = dev->id; struct sysinfo i; si_meminfo_node(&i, nid); n = sprintf(buf, "\n" "Node %d MemTotal: %8lu kB\n" "Node %d MemFree: %8lu kB\n" "Node %d MemUsed: %8lu kB\n" "Node %d Active: %8lu kB\n" "Node %d Inactive: %8lu kB\n" #ifdef CONFIG_HIGHMEM "Node %d HighTotal: %8lu kB\n" "Node %d HighFree: %8lu kB\n" "Node %d LowTotal: %8lu kB\n" "Node %d LowFree: %8lu kB\n" #endif "Node %d Dirty: %8lu kB\n" "Node %d Writeback: %8lu kB\n" "Node %d FilePages: %8lu kB\n" "Node %d Mapped: %8lu kB\n" "Node %d AnonPages: %8lu kB\n" "Node %d PageTables: %8lu kB\n" "Node %d NFS_Unstable: %8lu kB\n" "Node %d Bounce: %8lu kB\n" "Node %d Slab: %8lu kB\n" "Node %d SReclaimable: %8lu kB\n" "Node %d SUnreclaim: %8lu kB\n", nid, K(i.totalram), nid, K(i.freeram), nid, K(i.totalram - i.freeram), nid, node_page_state(nid, NR_ACTIVE), nid, node_page_state(nid, NR_INACTIVE), #ifdef CONFIG_HIGHMEM nid, K(i.totalhigh), nid, K(i.freehigh), nid, K(i.totalram - i.totalhigh), nid, K(i.freeram - i.freehigh), #endif nid, K(node_page_state(nid, NR_FILE_DIRTY)), nid, K(node_page_state(nid, NR_WRITEBACK)), nid, K(node_page_state(nid, NR_FILE_PAGES)), nid, K(node_page_state(nid, NR_FILE_MAPPED)), nid, K(node_page_state(nid, NR_ANON_PAGES)), nid, K(node_page_state(nid, NR_PAGETABLE)), nid, K(node_page_state(nid, NR_UNSTABLE_NFS)), nid, K(node_page_state(nid, NR_BOUNCE)), nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE) + node_page_state(nid, NR_SLAB_UNRECLAIMABLE)), nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE)), nid, K(node_page_state(nid, NR_SLAB_UNRECLAIMABLE))); n += hugetlb_report_node_meminfo(nid, buf + n); return n; } #undef K static SYSDEV_ATTR(meminfo, S_IRUGO, node_read_meminfo, NULL); static ssize_t node_read_numastat(struct sys_device * dev, char * buf) { return sprintf(buf, "numa_hit %lu\n" "numa_miss %lu\n" "numa_foreign %lu\n" "interleave_hit %lu\n" "local_node %lu\n" "other_node %lu\n", node_page_state(dev->id, NUMA_HIT), node_page_state(dev->id, NUMA_MISS), node_page_state(dev->id, NUMA_FOREIGN), node_page_state(dev->id, NUMA_INTERLEAVE_HIT), node_page_state(dev->id, NUMA_LOCAL), node_page_state(dev->id, NUMA_OTHER)); } static SYSDEV_ATTR(numastat, S_IRUGO, node_read_numastat, NULL); static ssize_t node_read_distance(struct sys_device * dev, char * buf) { int nid = dev->id; int len = 0; int i; /* buf currently PAGE_SIZE, need ~4 chars per node */ BUILD_BUG_ON(MAX_NUMNODES*4 > PAGE_SIZE/2); for_each_online_node(i) len += sprintf(buf + len, "%s%d", i ? " " : "", node_distance(nid, i)); len += sprintf(buf + len, "\n"); return len; } static SYSDEV_ATTR(distance, S_IRUGO, node_read_distance, NULL); /* * register_node - Setup a sysfs device for a node. * @num - Node number to use when creating the device. * * Initialize and register the node device. */ int register_node(struct node *node, int num, struct node *parent) { int error; node->sysdev.id = num; node->sysdev.cls = &node_class; error = sysdev_register(&node->sysdev); if (!error){ sysdev_create_file(&node->sysdev, &attr_cpumap); sysdev_create_file(&node->sysdev, &attr_cpulist); sysdev_create_file(&node->sysdev, &attr_meminfo); sysdev_create_file(&node->sysdev, &attr_numastat); sysdev_create_file(&node->sysdev, &attr_distance); } return error; } /** * unregister_node - unregister a node device * @node: node going away * * Unregisters a node device @node. All the devices on the node must be * unregistered before calling this function. */ void unregister_node(struct node *node) { sysdev_remove_file(&node->sysdev, &attr_cpumap); sysdev_remove_file(&node->sysdev, &attr_cpulist); sysdev_remove_file(&node->sysdev, &attr_meminfo); sysdev_remove_file(&node->sysdev, &attr_numastat); sysdev_remove_file(&node->sysdev, &attr_distance); sysdev_unregister(&node->sysdev); } struct node node_devices[MAX_NUMNODES]; /* * register cpu under node */ int register_cpu_under_node(unsigned int cpu, unsigned int nid) { if (node_online(nid)) { struct sys_device *obj = get_cpu_sysdev(cpu); if (!obj) return 0; return sysfs_create_link(&node_devices[nid].sysdev.kobj, &obj->kobj, kobject_name(&obj->kobj)); } return 0; } int unregister_cpu_under_node(unsigned int cpu, unsigned int nid) { if (node_online(nid)) { struct sys_device *obj = get_cpu_sysdev(cpu); if (obj) sysfs_remove_link(&node_devices[nid].sysdev.kobj, kobject_name(&obj->kobj)); } return 0; } int register_one_node(int nid) { int error = 0; int cpu; if (node_online