ASoC Support For Moorestown
v2 cover ASoC Support For Moorestown
This series of patches provides the ASoC implementation of moorestown audio.
This series adds a new codec called upd9976 and a new machine driver called mrst_machine. The platform driver is mostly re-used.
With this patchset, it can do:
- playback via headphone
- playback via internal speaker
- capture via DMIC
- capture via headset microphone
- jack detection
v2:
- change pr_err to dev_err
- change DAPM widgets' name (adding correct suffix)
- change to data based init of controls, widgets and route in machine driver
- add Master Volume
- change original "HP & Spker Mixer Volume" to "PCM Volume"
- squash some patches into one, resulting in less number of patches
- move jack handling from machine driver to codec driver
- in codec probe, set the bias level to standby instead of off
Thanks for all the reviewers:
- Mark Brown
- Dimitris Papastamos
- Takashi Iwai
- Koul Vinod
Any more comments are welcome. :)
---
Lu Guanqun (8):
ASoC: upd9976: Add Renesas uPD9976 codec driver
ASoC: mrst_machine: add moorestown machine driver
ASoC: upd9976: add DMIC support
ASoC: upd9976: add Analog MIC support
ASoC: upd9976: add jack detection function
ASoC: mrst_machine: add capture functionality
ASoC: mrst_machine: add jack detection support
ASoC: mrst_machine: add initial config to machine driver
Wang Xingchao (2):
ASoC: sst_platform: modify current cpu dai driver to suit the needs for moorestown platform
ASoC: upd9976: add capture ability for dai driver
sound/soc/codecs/Kconfig | 4 sound/soc/codecs/Makefile | 2 sound/soc/codecs/upd9976.c | 697 ++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/upd9976.h | 87 +++++ sound/soc/mid-x86/Kconfig | 12 + sound/soc/mid-x86/Makefile | 2 sound/soc/mid-x86/mrst_machine.c | 228 ++++++++++++ sound/soc/mid-x86/sst_platform.c | 13 - 8 files changed, 1041 insertions(+), 4 deletions(-) create mode 100644 sound/soc/codecs/upd9976.c create mode 100644 sound/soc/codecs/upd9976.h create mode 100644 sound/soc/mid-x86/mrst_machine.c
Signature · 1 line
guanqun
PATCH v2 1/10 ASoC: upd9976: Add Renesas uPD9976 codec driver
uPD9976 is a complex codec, however this patch only provides basic playback functionality for headphone. More functionality will be added bit by bit in the following patches.
--- sound/soc/codecs/Kconfig | 4 sound/soc/codecs/Makefile | 2 sound/soc/codecs/upd9976.c | 407 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/upd9976.h | 83 +++++++++ 4 files changed, 496 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/upd9976.c create mode 100644 sound/soc/codecs/upd9976.h
sound/soc/codecs/Kconfig +4 -0
@@ -52,6 +52,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TWL6040 if TWL4030_CORE select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C + select SND_SOC_UPD9976 if INTEL_SCU_IPC select SND_SOC_WL1273 if MFD_WL1273_CORE select SND_SOC_WM1250_EV1 if I2C select SND_SOC_WM2000 if I2C
@@ -244,6 +245,9 @@ config SND_SOC_UDA134X config SND_SOC_UDA1380 tristate +config SND_SOC_UPD9976 + tristate + config SND_SOC_WL1273 tristate
sound/soc/codecs/Makefile +2 -0
@@ -37,6 +37,7 @@ snd-soc-twl4030-objs := twl4030.o snd-soc-twl6040-objs := twl6040.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o +snd-soc-upd9976-objs := upd9976.o snd-soc-wl1273-objs := wl1273.o snd-soc-wm1250-ev1-objs := wm1250-ev1.o snd-soc-wm8350-objs := wm8350.o
@@ -128,6 +129,7 @@ obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_UPD9976) += snd-soc-upd9976.o obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
sound/soc/codecs/upd9976.c new file +407 -0
407 lines not on this page · raw message
sound/soc/codecs/upd9976.h new file +83 -0
83 lines not on this page · raw message
Re: [PATCH v2 01/10] ASoC: upd9976: Add Renesas uPD9976 codec driver
sound/soc/codecs/upd9976.c line 51
Quoted 12 lines · Lu Guanqun
sound/soc/codecs/upd9976.c new file +12
-0+static inline unsigned int upd9976_read (struct snd_soc_codec *codec, +unsigned int reg) +{ +u8 value =0 ; +int ret; + + ret =intel_scu_ipc_ioread8 (reg, &value); +if (ret) +dev_err (codec->dev, +"upd9976 read of 0x%x failed, error: %d\n" , reg, ret); +return value; +}
Please factor this stuff out.
sound/soc/codecs/upd9976.c line 74
Quoted 10 lines
sound/soc/codecs/upd9976.c new file +10
-0+/* +* Mixing Volume: from -25 dB to 6 dB in 1 dB steps. +*/ +static DECLARE_TLV_DB_SCALE(mixer_tlv, -2500, 100, 0); + +/* +* Audio DAC Volume: From -84 dB to 10.5 dB in 0.75 steps. +*/ +static DECLARE_TLV_DB_SCALE(adac_tlv, -8400, 75, 0); +static const struct snd_kcontrol_new upd9976_snd_controls[] = {
Use of blank lines here is really odd.
sound/soc/codecs/upd9976.c line 80
Quoted 6 lines
sound/soc/codecs/upd9976.c new file +6
-0+SOC_DOUBLE_R_TLV ("Master Volume" , +UPD9976_AUDIOLVOL ,UPD9976_AUDIORVOL , +0 ,0x7f ,1 , adac_tlv), +SOC_DOUBLE_R_TLV ("PCM Volume" , +UPD9976_HPSPRLVOL ,UPD9976_HPSPRRVOL , + 0, 0x1f, 1, mixer_tlv),
PCM would usually be a digital audio stream but this is a control for a mixer.
sound/soc/codecs/upd9976.c line 294
Quoted 3 lines
sound/soc/codecs/upd9976.c new file +3
-0+static struct snd_soc_dai_driver upd9976_dais[] = { +{ + .name ="upd9976-audio" ,
Previous issue with naming still applies.
Quoted 8 lines
sound/soc/codecs/upd9976.c new file +8
-0+case SND_SOC_BIAS_PREPARE : +if (codec->dapm.bias_level ==SND_SOC_BIAS_STANDBY ) { +snd_soc_update_bits (codec,UPD9976_VAUDIOCNT , +0x27 ,0x27 ); +snd_soc_update_bits (codec,UPD9976_VREFPLL , +0x35 ,0x35 ); + } +break ;
sound/soc/codecs/upd9976.c line 334
Quoted 10 lines
sound/soc/codecs/upd9976.c new file +10
-0+ +case SND_SOC_BIAS_STANDBY : +snd_soc_write (codec,UPD9976_VAUDIOCNT ,0x25 ); +snd_soc_write (codec,UPD9976_VREFPLL ,0x10 ); +break ; + +case SND_SOC_BIAS_OFF : +snd_soc_write (codec,UPD9976_VREFPLL ,0 ); +snd_soc_write (codec,UPD9976_VAUDIOCNT ,0x24 ); +break ;
This is all very magic and lots of what's going on (especially with VAUDIOCNT) looks like it's actually trying to fiddle with bitmasks.
PATCH v2 2/10 ASoC: sst_platform: modify current cpu dai driver to suit the needs for moorestown platform
In reply to Lu GuanqunFrom: Wang Xingchao <[email protected]>
--- sound/soc/mid-x86/sst_platform.c | 13 +++++++++---- 1 files changed, 9 insertions(+), 4 deletions(-)
sound/soc/mid-x86/sst_platform.c +9 -4
@@ -68,16 +68,21 @@ struct snd_soc_dai_driver sst_platform_dai[] = { .name = "Headset-cpu-dai", .id = 0, .playback = { - .channels_min = SST_STEREO, + .channels_min = SST_MONO, .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE, + .rates = (SNDRV_PCM_RATE_48000 | +SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_8000), + .formats = (SNDRV_PCM_FMTBIT_S16 |SNDRV_PCM_FMTBIT_U16 | +SNDRV_PCM_FMTBIT_S24 |SNDRV_PCM_FMTBIT_U24 | + SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32), }, .capture = { .channels_min = 1, .channels_max = 5, .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE, + .formats = (SNDRV_PCM_FMTBIT_S16 |SNDRV_PCM_FMTBIT_U16 | + SNDRV_PCM_FMTBIT_S24_LE), }, }, {
PATCH v2 3/10 ASoC: mrst_machine: add moorestown machine driver
In reply to Lu GuanqunThis machine driver glues upd9976 codec driver and sst_platform driver.
Playback via headphone and internal speaker is supported. To note: This internal speaker needs an extra GPIO to control its power. And these two output devices can be enabled and disabled independently.
--- sound/soc/mid-x86/Kconfig | 12 ++ sound/soc/mid-x86/Makefile | 2 sound/soc/mid-x86/mrst_machine.c | 194 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 0 deletions(-) create mode 100644 sound/soc/mid-x86/mrst_machine.c
sound/soc/mid-x86/Kconfig +12 -0
@@ -10,5 +10,17 @@ config SND_MFLD_MACHINE Say Y if you have such a device If unsure select "N". +config SND_MRST_MACHINE + tristate "SoC Machine Audio Driver for Intel Moorestown Platform" + depends on INTEL_SCU_IPC + depends on SND_INTEL_SST + select SND_SOC_UPD9976 + select SND_SST_PLATFORM + help + This adds support for ASoC machine driver for Intel(R) MID Moorestown platform. + It is used as ALSA device in audio substream in Intel(R) MID devices. + Say Y if you have such a device. + If unsure, select 'N'. + config SND_SST_PLATFORM tristate
sound/soc/mid-x86/Makefile +2 -0
@@ -1,5 +1,7 @@ snd-soc-sst-platform-objs := sst_platform.o snd-soc-mfld-machine-objs := mfld_machine.o +snd-soc-mrst-machine-objs := mrst_machine.o obj-$(CONFIG_SND_SST_PLATFORM) += snd-soc-sst-platform.o obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o +obj-$(CONFIG_SND_MRST_MACHINE) += snd-soc-mrst-machine.o
sound/soc/mid-x86/mrst_machine.c new file +194 -0
194 lines not on this page · raw message
PATCH v2 4/10 ASoC: upd9976: add capture ability for dai driver
In reply to Lu GuanqunFrom: Wang Xingchao <[email protected]>
--- sound/soc/codecs/upd9976.c | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-)
sound/soc/codecs/upd9976.c +7 -0
@@ -303,6 +303,13 @@ static struct snd_soc_dai_driver upd9976_dais[] = {SNDRV_PCM_FMTBIT_S24 |SNDRV_PCM_FMTBIT_U24 | SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32), }, + .capture = { + .stream_name = "Audio Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_48000, + .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16), + }, .ops = &upd9976_audio_dai_ops, }, };
PATCH v2 5/10 ASoC: upd9976: add DMIC support
In reply to Lu Guanqun--- sound/soc/codecs/upd9976.c | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 76 insertions(+), 0 deletions(-)
sound/soc/codecs/upd9976.c +76 -0
@@ -71,6 +71,12 @@ static DECLARE_TLV_DB_SCALE(mixer_tlv, -2500, 100, 0);* Audio DAC Volume: From -84 dB to 10.5 dB in 0.75 steps. */ static DECLARE_TLV_DB_SCALE(adac_tlv, -8400, 75, 0); + +/* +* DMIC Volume: from -63 dB to 1 dB in 1 db steps. +*/ +static DECLARE_TLV_DB_SCALE(dmic_tlv, -6300, 100, 1); +static const struct snd_kcontrol_new upd9976_snd_controls[] = { SOC_DOUBLE_R_TLV("Master Volume", UPD9976_AUDIOLVOL, UPD9976_AUDIORVOL,
@@ -78,6 +84,9 @@ static const struct snd_kcontrol_new upd9976_snd_controls[] = {SOC_DOUBLE_R_TLV ("PCM Volume" ,UPD9976_HPSPRLVOL ,UPD9976_HPSPRRVOL ,0 ,0x1f ,1 , mixer_tlv), +SOC_SINGLE ("Internal Mic Switch" ,UPD9976_DMICCTRL1 ,6 ,1 ,0 ), +SOC_SINGLE_TLV ("Internal Mic Capture Volume" , + UPD9976_DMICCTRL1, 0, 0x3f, 1, dmic_tlv), }; static const struct snd_kcontrol_new upd9976_hp_spkr_mixer_left_controls[] = {
@@ -90,6 +99,29 @@ static const struct snd_kcontrol_new upd9976_hp_spkr_mixer_right_controls[] = { SOC_DAPM_SINGLE("Audio DAC Right Switch", UPD9976_HPRMIXSEL, 3, 1, 1), }; +/* PCM2 Left Mux */ +static const char *upd9976_pcm2_left_mux_texts[] = {"AADC Left" ,"DMIC" }; +static const struct soc_enum upd9976_pcm2_left_mux_enum = +SOC_ENUM_SINGLE (UPD9976_ADCSAMPLERATE ,5 ,2 , + upd9976_pcm2_left_mux_texts); +static const struct snd_kcontrol_new upd9976_pcm2_left_mux_control = +SOC_DAPM_ENUM ("PCM2 Left Mux" , upd9976_pcm2_left_mux_enum); + +/* PCM2 Right Mux */ +static const char *upd9976_pcm2_right_mux_texts[] = {"AADC Right" ,"DMIC" }; +static const struct soc_enum upd9976_pcm2_right_mux_enum = +SOC_ENUM_SINGLE (UPD9976_ADCSAMPLERATE ,4 ,2 , + upd9976_pcm2_right_mux_texts); +static const struct snd_kcontrol_new upd9976_pcm2_right_mux_control = +SOC_DAPM_ENUM ("PCM2 Right Mux" , upd9976_pcm2_right_mux_enum); + +/* PCM2 Mux */ +static const char *upd9976_pcm2_mux_texts[] = {"No Mix" ,"Mix" }; +static const struct soc_enum upd9976_pcm2_mux_enum = +SOC_ENUM_SINGLE (UPD9976_ADCSAMPLERATE ,3 ,2 , upd9976_pcm2_mux_texts); +static const struct snd_kcontrol_new upd9976_pcm2_mux_control = +SOC_DAPM_ENUM ("PCM2 Mux" , upd9976_pcm2_mux_enum); +static const struct snd_soc_dapm_widget upd9976_dapm_widgets[] = {/* Input */ SND_SOC_DAPM_INPUT ("LINEINL" ),
@@ -130,12 +162,34 @@ static const struct snd_soc_dapm_widget upd9976_dapm_widgets[] = {SND_SOC_DAPM_MICBIAS ("MIC1 Bias" ,UPD9976_MICCTRL ,6 ,0 ),SND_SOC_DAPM_MICBIAS ("MIC2 Bias" ,UPD9976_MICCTRL ,4 ,0 ), +SND_SOC_DAPM_MIXER ("PCM2 In Mixer" ,SND_SOC_NOPM ,0 ,0 ,NULL ,0 ), +SND_SOC_DAPM_MIXER ("PCM2 In No Mixer" ,SND_SOC_NOPM ,0 ,0 ,NULL ,0 ), + +/* Mux */ +SND_SOC_DAPM_MUX ("PCM2 Left Mux" ,SND_SOC_NOPM ,0 ,0 , + &upd9976_pcm2_left_mux_control), +SND_SOC_DAPM_MUX ("PCM2 Right Mux" ,SND_SOC_NOPM ,0 ,0 , + &upd9976_pcm2_right_mux_control), +SND_SOC_DAPM_MUX ("PCM2 Mux" ,SND_SOC_NOPM ,0 ,0 , + &upd9976_pcm2_mux_control), +/* PGA */ SND_SOC_DAPM_PGA ("HP Playback Left PGA" ,UPD9976_DRVPOWERCTRL ,2 ,0 ,NULL ,0 ),SND_SOC_DAPM_PGA ("HP Playback Right PGA" ,UPD9976_DRVPOWERCTRL ,1 ,0 ,NULL ,0 ), +SND_SOC_DAPM_PGA ("AADC Left PGA" ,SND_SOC_NOPM ,0 ,0 ,NULL ,0 ), +SND_SOC_DAPM_PGA ("AADC Right PGA" ,SND_SOC_NOPM ,0 ,0 ,NULL ,0 ), + +SND_SOC_DAPM_PGA ("DMIC Gain PGA" ,UPD9976_DMICCTRL1 ,7 ,0 ,NULL ,0 ), + +/* Audio Interface */ +SND_SOC_DAPM_AIF_OUT ("PCM2 Out" ,"Audio Capture" ,0 ,SND_SOC_NOPM ,0 , +0 ), + +/* Supply */ + SND_SOC_DAPM_SUPPLY("DMIC Supply", UPD9976_POWERCTRL2, 1, 0, NULL, 0), }; static const struct snd_soc_dapm_route upd9976_dapm_routes[] = {
@@ -154,6 +208,28 @@ static const struct snd_soc_dapm_route upd9976_dapm_routes[] = { {"HPOUTL", NULL, "HP Playback Left PGA"}, {"HPOUTR", NULL, "HP Playback Right PGA"}, + {"DMICDAT", NULL, "DMIC Supply"}, + {"DMIC Gain PGA", NULL, "DMICDAT"}, + + {"AADC Left PGA", NULL, "AADC"}, + {"AADC Right PGA", NULL, "AADC"}, + + {"PCM2 Left Mux", "DMIC", "DMIC Gain PGA"}, + {"PCM2 Left Mux", "AADC Left", "AADC Left PGA"}, + + {"PCM2 Right Mux", "DMIC", "DMIC Gain PGA"}, + {"PCM2 Right Mux", "AADC Right", "AADC Right PGA"}, + + {"PCM2 In Mixer", NULL, "PCM2 Left Mux"}, + {"PCM2 In No Mixer", NULL, "PCM2 Left Mux"}, + + {"PCM2 In Mixer", NULL, "PCM2 Right Mux"}, + {"PCM2 In No Mixer", NULL, "PCM2 Right Mux"}, + + {"PCM2 Mux", "Mix", "PCM2 In Mixer"}, + {"PCM2 Mux", "No Mix", "PCM2 In No Mixer"}, + + {"PCM2 Out", NULL, "PCM2 Mux"}, };static int upd9976_audio_digital_mute (struct snd_soc_dai *dai,int mute)
Re: [PATCH v2 05/10] ASoC: upd9976: add DMIC support
Quoted 1 line · Lu Guanqun
Signed-off-by Lu Guanqun <[email protected]>
As previously mentioned please merge these trivial feature patches into the original driver unless there's some purpose in splitting them out. In this case it looks like the split is the cause of one of the review issues in the earlier post.
PATCH v2 6/10 ASoC: upd9976: add Analog MIC support
In reply to Lu Guanqun--- sound/soc/codecs/upd9976.c | 29 +++++++++++++++++++++++++++++ 1 files changed, 29 insertions(+), 0 deletions(-)
sound/soc/codecs/upd9976.c +29 -0
@@ -77,6 +77,11 @@ static DECLARE_TLV_DB_SCALE(adac_tlv, -8400, 75, 0);*/ static DECLARE_TLV_DB_SCALE(dmic_tlv, -6300, 100, 1); +/* +* Analog Volume: from -25 dB to 6 dB in 1 dB steps. +*/ +static DECLARE_TLV_DB_SCALE(analog_tlv, -2500, 100, 0); +static const struct snd_kcontrol_new upd9976_snd_controls[] = { SOC_DOUBLE_R_TLV("Master Volume", UPD9976_AUDIOLVOL, UPD9976_AUDIORVOL,
@@ -87,6 +92,11 @@ static const struct snd_kcontrol_new upd9976_snd_controls[] = {SOC_SINGLE ("Internal Mic Switch" ,UPD9976_DMICCTRL1 ,6 ,1 ,0 ),SOC_SINGLE_TLV ("Internal Mic Capture Volume" ,UPD9976_DMICCTRL1 ,0 ,0x3f ,1 , dmic_tlv), +SOC_DOUBLE_R ("Analog Mic Switch" ,UPD9976_LILSEL ,UPD9976_LIRSEL , +6 ,1 ,0 ), +SOC_DOUBLE_R_TLV ("Analog Capture Volume" , +UPD9976_LILSEL ,UPD9976_LILSEL , + 0, 0x1f, 1, analog_tlv), }; static const struct snd_kcontrol_new upd9976_hp_spkr_mixer_left_controls[] = {
@@ -99,6 +109,13 @@ static const struct snd_kcontrol_new upd9976_hp_spkr_mixer_right_controls[] = { SOC_DAPM_SINGLE("Audio DAC Right Switch", UPD9976_HPRMIXSEL, 3, 1, 1), }; +/* Analog Right Mux */ +static const char *upd9976_analog_mux_texts[] = {"MIC" ,"LineIn" }; +static const struct soc_enum upd9976_ar_mux_enum = +SOC_ENUM_SINGLE (UPD9976_LIRSEL ,7 ,2 , upd9976_analog_mux_texts); +static const struct snd_kcontrol_new upd9976_ar_mux_control = +SOC_DAPM_ENUM ("Route" , upd9976_ar_mux_enum); +/* PCM2 Left Mux */ static const char *upd9976_pcm2_left_mux_texts[] = {"AADC Left" ,"DMIC" }; static const struct soc_enum upd9976_pcm2_left_mux_enum =
@@ -166,6 +183,9 @@ static const struct snd_soc_dapm_widget upd9976_dapm_widgets[] = {SND_SOC_DAPM_MIXER ("PCM2 In No Mixer" ,SND_SOC_NOPM ,0 ,0 ,NULL ,0 ),/* Mux */ +SND_SOC_DAPM_MUX ("Analog Right Mux" ,UPD9976_POWERCTRL1 ,4 ,0 , + &upd9976_ar_mux_control), +SND_SOC_DAPM_MUX ("PCM2 Left Mux" ,SND_SOC_NOPM ,0 ,0 , &upd9976_pcm2_left_mux_control), SND_SOC_DAPM_MUX("PCM2 Right Mux", SND_SOC_NOPM, 0, 0,
@@ -184,6 +204,8 @@ static const struct snd_soc_dapm_widget upd9976_dapm_widgets[] = {SND_SOC_DAPM_PGA ("DMIC Gain PGA" ,UPD9976_DMICCTRL1 ,7 ,0 ,NULL ,0 ), +SND_SOC_DAPM_PGA ("MIC2 In PGA" ,UPD9976_POWERCTRL1 ,2 ,0 ,NULL ,0 ), +/* Audio Interface */ SND_SOC_DAPM_AIF_OUT ("PCM2 Out" ,"Audio Capture" ,0 ,SND_SOC_NOPM ,0 , 0),
@@ -208,6 +230,13 @@ static const struct snd_soc_dapm_route upd9976_dapm_routes[] = { {"HPOUTL", NULL, "HP Playback Left PGA"}, {"HPOUTR", NULL, "HP Playback Right PGA"}, + {"MIC2 In PGA", NULL, "MIC2"}, + + {"Analog Right Mux", "MIC", "MIC2 In PGA"}, + {"Analog Right Mux", "LineIn", "LINEINR"}, + + {"AADC", NULL, "Analog Right Mux"}, + {"DMICDAT", NULL, "DMIC Supply"}, {"DMIC Gain PGA", NULL, "DMICDAT"},
PATCH v2 7/10 ASoC: upd9976: add jack detection function
In reply to Lu GuanqunAll the interrupt handling is done here in codec driver.
--- sound/soc/codecs/upd9976.c | 182 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/upd9976.h | 4 + 2 files changed, 184 insertions(+), 2 deletions(-)
sound/soc/codecs/upd9976.c +180 -2
@@ -27,6 +27,8 @@*/ #include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/io.h> +#include <linux/list.h> #include <asm/intel_scu_ipc.h> #include <sound/pcm.h> #include <sound/pcm_params.h>
@@ -37,6 +39,97 @@#include <sound/jack.h> #include "upd9976.h" +struct upd9976_priv { +int irq; +struct resource *irq_mem; +void __iomem *int_base; +struct snd_soc_jack *jack; +int jack_init; +struct list_head pending_status; +spinlock_t lock; +}; + +struct upd9976_jack_node { +struct list_head list; +u8 interrupt_status; +}; + +static irqreturn_t upd9976_jack_intr_handler (int irq,void *dev) +{ +struct upd9976_priv *upd9976 = dev; +u8 interrupt_status; +struct upd9976_jack_node *node; +unsigned long flags; + +memcpy_fromio (&interrupt_status, + upd9976->int_base, +sizeof (u8)); + +/* check if it's valid */ +if (!(interrupt_status &0xf )) +return IRQ_NONE ; + + node =kzalloc (sizeof (*node),GFP_ATOMIC ); +if (!node) +return IRQ_NONE ; + +INIT_LIST_HEAD (&node->list); + node->interrupt_status = interrupt_status; + +spin_lock_irqsave (&upd9976->lock, flags); +list_add_tail (&node->list, &upd9976->pending_status); +spin_unlock_irqrestore (&upd9976->lock, flags); + +return IRQ_WAKE_THREAD ; +} + +static irqreturn_t upd9976_jack_thread_handler (int irq,void *data) +{ +struct upd9976_priv *upd9976 = data; +struct snd_soc_jack *jack = upd9976->jack; +struct snd_soc_codec *codec = jack->codec; +struct upd9976_jack_node *node; +u8 interrupt_status; +unsigned long flags; +int status =0 ; +unsigned int value =0 ; +int mask =SND_JACK_BTN_0 |SND_JACK_BTN_1 |SND_JACK_HEADSET ; + +spin_lock_irqsave (&upd9976->lock, flags); +if (list_empty (&upd9976->pending_status)) { +spin_unlock_irqrestore (&upd9976->lock, flags); +return IRQ_HANDLED ; + } + node =list_first_entry (&upd9976->pending_status, + struct upd9976_jack_node, + list); + interrupt_status = node->interrupt_status; +list_del (&node->list); +kfree (node); +spin_unlock_irqrestore (&upd9976->lock, flags); + +if (interrupt_status &0x3 ) + value =snd_soc_read (codec,UPD9976_SAUXINT ); + +if (interrupt_status &0x1 && value ==0x1 ) + status |=SND_JACK_HEADSET ; + +if (interrupt_status &0x2 && value ==0x2 ) + status |=SND_JACK_HEADPHONE ; + +if (interrupt_status &0x4 ) + status |=SND_JACK_HEADSET |SND_JACK_BTN_0 ; + +if (interrupt_status &0x8 ) + status |=SND_JACK_HEADSET |SND_JACK_BTN_1 ; + +snd_soc_jack_report (jack, status, mask); +if (status & (SND_JACK_BTN_0 |SND_JACK_BTN_1 )) +snd_soc_jack_report (jack,SND_JACK_HEADSET , mask); + +return IRQ_HANDLED ; +} +static inline unsigned int upd9976_read (struct snd_soc_codec *codec,unsigned int reg) {
@@ -62,6 +155,24 @@ static inline int upd9976_write(struct snd_soc_codec *codec,return ret; } +int upd9976_jack_detect (struct snd_soc_codec *codec, +struct snd_soc_jack *jack, +unsigned int debounce) +{ +struct upd9976_priv *upd9976 =snd_soc_codec_get_drvdata (codec); + +if (!upd9976->jack_init) +return -ENODEV ; + + upd9976->jack = jack; + +/* set debounce time */ +snd_soc_write (codec,UPD9976_AUXDBNC , debounce); + +return 0 ; +} +EXPORT_SYMBOL_GPL (upd9976_jack_detect); +/* * Mixing Volume: from -25 dB to 6 dB in 1 dB steps. */
@@ -452,13 +563,50 @@ static int upd9976_set_bias_level(struct snd_soc_codec *codec,static int upd9976_codec_probe (struct snd_soc_codec *codec) { +struct upd9976_priv *upd9976 =snd_soc_codec_get_drvdata (codec); +upd9976_set_bias_level (codec,SND_SOC_BIAS_STANDBY ); +/* disable jack detection first, will be enabled later */ +snd_soc_update_bits (codec,UPD9976_MICCTRL ,BIT (4 ),0 ); + +spin_lock_init (&upd9976->lock); +INIT_LIST_HEAD (&upd9976->pending_status); + + upd9976->jack_init =0 ; + +if (upd9976->irq <0 || !upd9976->irq_mem) +return 0 ; + + upd9976->int_base =ioremap_nocache (upd9976->irq_mem->start, +resource_size (upd9976->irq_mem)); +if (!upd9976->int_base) +return 0 ; + +if (request_threaded_irq (upd9976->irq, + upd9976_jack_intr_handler, + upd9976_jack_thread_handler, +IRQF_SHARED , +"jack detection" , + upd9976)) { +iounmap (upd9976->int_base); +return 0 ; + } + + upd9976->jack_init =1 ; +return 0 ; }static int upd9976_codec_remove (struct snd_soc_codec *codec) { +struct upd9976_priv *upd9976 =snd_soc_codec_get_drvdata (codec); + +if (upd9976->jack_init) { +free_irq (upd9976->irq, upd9976); +iounmap (upd9976->int_base); + } +upd9976_set_bias_level (codec,SND_SOC_BIAS_OFF );return 0 ;
@@ -481,13 +629,43 @@ static struct snd_soc_codec_driver upd9976_codec = {static int __devinit upd9976_device_probe (struct platform_device *pdev) { -return snd_soc_register_codec (&pdev->dev, &upd9976_codec, - upd9976_dais,ARRAY_SIZE (upd9976_dais)); +struct upd9976_priv *upd9976; +int err; + + upd9976 =kzalloc (sizeof (*upd9976),GFP_KERNEL ); +if (!upd9976) { + err = -ENOMEM ; + goto fail; + } + + upd9976->irq =platform_get_irq (pdev,0 ); + upd9976->irq_mem =platform_get_resource_byname (pdev, +IORESOURCE_MEM , +"IRQ_BASE" ); +platform_set_drvdata (pdev, upd9976); + + err =snd_soc_register_codec (&pdev->dev, &upd9976_codec, + upd9976_dais,ARRAY_SIZE (upd9976_dais)); +if (err) + goto fail_register_codec; + +return 0 ; + +fail_register_codec: +platform_set_drvdata (pdev,NULL ); +kfree (upd9976); +fail: +return err; } static int __devexit upd9976_device_remove(struct platform_device *pdev) { +struct upd9976_priv *upd9976 =platform_get_drvdata (pdev); +snd_soc_unregister_codec (&pdev->dev); +platform_set_drvdata (pdev,NULL ); +kfree (upd9976); +return 0 ; }
sound/soc/codecs/upd9976.h +4 -0
@@ -80,4 +80,8 @@#define UPD9976_SAUXINT 0x132 +int upd9976_jack_detect (struct snd_soc_codec *codec, +struct snd_soc_jack *jack, +unsigned int debounce); +#endif
Re: [PATCH v2 07/10] ASoC: upd9976: add jack detection function
sound/soc/codecs/upd9976.c line 124
Quoted 11 lines · Lu Guanqun
sound/soc/codecs/upd9976.c +11
-0+if (interrupt_status &0x1 && value ==0x1 ) + status |=SND_JACK_HEADSET ; + +if (interrupt_status &0x2 && value ==0x2 ) + status |=SND_JACK_HEADPHONE ; + +if (interrupt_status &0x4 ) + status |=SND_JACK_HEADSET |SND_JACK_BTN_0 ; + +if (interrupt_status &0x8 ) + status |=SND_JACK_HEADSET |SND_JACK_BTN_1 ;
It's very strange that you do a mix of checks with and without the == - it doesn't matter either way but it'd be clearer to be consistent.
Review
Quoted 2 lines
(quoted diff) +2
-0+ if (upd9976->irq < 0 || !upd9976->irq_mem) + return 0;
It'd seem better to have this condition the other way around so if you need to add more conditional stuff things will be clearer.
PATCH v2 8/10 ASoC: mrst_machine: add capture functionality
In reply to Lu Guanqun--- sound/soc/mid-x86/mrst_machine.c | 12 ++++++++++++ 1 files changed, 12 insertions(+), 0 deletions(-)
sound/soc/mid-x86/mrst_machine.c +12 -0
@@ -39,6 +39,9 @@static const struct snd_kcontrol_new mrst_snd_controls[] = {SOC_DAPM_PIN_SWITCH ("Headphone" ),SOC_DAPM_PIN_SWITCH ("Speaker" ), + +SOC_DAPM_PIN_SWITCH ("Headset MIC" ), +SOC_DAPM_PIN_SWITCH ("Internal MIC" ), };/*
@@ -59,6 +62,9 @@ static int mrst_speaker_event(struct snd_soc_dapm_widget *widget,static const struct snd_soc_dapm_widget mrst_audio_widgets[] = {SND_SOC_DAPM_HP ("Headphone" ,NULL ),SND_SOC_DAPM_SPK ("Speaker" , mrst_speaker_event), + +SND_SOC_DAPM_MIC ("Headset MIC" ,NULL ), +SND_SOC_DAPM_MIC ("Internal MIC" ,NULL ), };static const struct snd_soc_dapm_route mrst_audio_map[] = {
@@ -70,6 +76,9 @@ static const struct snd_soc_dapm_route mrst_audio_map[] = { {"Speaker", NULL, "PREOUTL"}, {"Speaker", NULL, "PREOUTR"}, + + {"MIC2", NULL, "Headset MIC"}, + {"DMICDAT", NULL, "Internal MIC"}, };static int mrst_audio_init (struct snd_soc_pcm_runtime *runtime)
@@ -85,6 +94,9 @@ static int mrst_audio_init(struct snd_soc_pcm_runtime *runtime)snd_soc_dapm_disable_pin (dapm,"Headphone" );snd_soc_dapm_enable_pin (dapm,"Speaker" ); +snd_soc_dapm_disable_pin (dapm,"Headset MIC" ); +snd_soc_dapm_enable_pin (dapm,"Internal MIC" ); +snd_soc_dapm_sync (dapm);return 0 ;
PATCH v2 9/10 ASoC: mrst_machine: add jack detection support
In reply to Lu GuanqunThe jack processing logic only pulls the trigger, all the heavy work such as interrupt handling is done in codec driver.
We need to enable MIC2 Bias to be able to get the jack interrupts.
--- sound/soc/mid-x86/mrst_machine.c | 16 ++++++++++++++++ 1 files changed, 16 insertions(+), 0 deletions(-)
sound/soc/mid-x86/mrst_machine.c +16 -0
@@ -35,6 +35,10 @@#include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/jack.h> +#include <../codecs/upd9976.h> + +static struct snd_soc_jack mrst_jack;static const struct snd_kcontrol_new mrst_snd_controls[] = {SOC_DAPM_PIN_SWITCH ("Headphone" ),
@@ -85,6 +89,7 @@ static int mrst_audio_init(struct snd_soc_pcm_runtime *runtime) {struct snd_soc_codec *codec = runtime->codec;struct snd_soc_dapm_context *dapm = &codec->dapm; +int ret;snd_soc_dapm_nc_pin (dapm,"LINEINL" );snd_soc_dapm_nc_pin (dapm,"LINEINR" );
@@ -99,6 +104,17 @@ static int mrst_audio_init(struct snd_soc_pcm_runtime *runtime)snd_soc_dapm_sync (dapm); + ret =snd_soc_jack_new (codec,"Intel(R) MID Audio Jack" , + (SND_JACK_HEADSET | +SND_JACK_BTN_0 | +SND_JACK_BTN_1 ), + &mrst_jack); +if (ret) +return ret; + +if (!upd9976_jack_detect (codec, &mrst_jack,0xff )) +snd_soc_dapm_force_enable_pin (dapm,"MIC2 Bias" ); +return 0 ; }
PATCH v2 10/10 ASoC: mrst_machine: add initial config to machine driver
In reply to Lu GuanqunOn Moorestown, DMIC's output is set to PCM2 mono.
And the Moorestown uses MIC2 pseudo-differential configuration.
--- sound/soc/mid-x86/mrst_machine.c | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-)
sound/soc/mid-x86/mrst_machine.c +6 -0
@@ -91,6 +91,12 @@ static int mrst_audio_init(struct snd_soc_pcm_runtime *runtime)struct snd_soc_dapm_context *dapm = &codec->dapm;int ret; +/* DMIC configuration: mono output to PCM2 */ +snd_soc_update_bits (codec,UPD9976_MICCTRL ,BIT (1 )|BIT (0 ),0x1 ); + +/* Set MIC2 to pseudo-differential */ +snd_soc_update_bits (codec,UPD9976_MICSELVOL ,BIT (4 ),BIT (4 )); +snd_soc_dapm_nc_pin (dapm,"LINEINL" );snd_soc_dapm_nc_pin (dapm,"LINEINR" );snd_soc_dapm_nc_pin (dapm,"LINEOUTL" );
Re: [PATCH v2 10/10] ASoC: mrst_machine: add initial config to machine driver
sound/soc/mid-x86/mrst_machine.c static int mrst_audio_init(struct snd_soc_pcm_runtime *runtime) · line 99
Quoted 6 lines · Lu Guanqun
sound/soc/mid-x86/mrst_machine.c +6
-0+/* DMIC configuration: mono output to PCM2 */ +snd_soc_update_bits (codec,UPD9976_MICCTRL ,BIT (1 )|BIT (0 ),0x1 ); + +/* Set MIC2 to pseudo-differential */ +snd_soc_update_bits (codec,UPD9976_MICSELVOL ,BIT (4 ),BIT (4 )); +
These look like regular routing controls to me... If they do need to be set with magic writes they should be platform data but things like pseudo differential inputs are normally just represented as two single ended inputs for which userspace just happens to choose a path that looks differential.
Re: [PATCH v2 01/10] ASoC: upd9976: Add Renesas uPD9976 codec driver
Quoted 16 lines · Mark Brown
Review
Quoted 12 lines · Lu Guanqun
(quoted diff) +12
-0+static inline unsigned int upd9976_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 value = 0; + int ret; + + ret = intel_scu_ipc_ioread8(reg, &value); + if (ret) + dev_err(codec->dev, + "upd9976 read of 0x%x failed, error: %d\n", reg, ret); + return value; +}Please factor this stuff out.
OK. Patch sent in another thread. Please give some comments whether it's the right way to achieve this.
Quoted 13 lines
Review
Quoted 11 lines
(quoted diff) +10
-0+/* + * Mixing Volume: from -25 dB to 6 dB in 1 dB steps. + */ +static DECLARE_TLV_DB_SCALE(mixer_tlv, -2500, 100, 0); + +/* + * Audio DAC Volume: From -84 dB to 10.5 dB in 0.75 steps. + */ +static DECLARE_TLV_DB_SCALE(adac_tlv, -8400, 75, 0); +static const struct snd_kcontrol_new upd9976_snd_controls[] = {Use of blank lines here is really odd.
I don't see a blank line between upd9976_snd_controls and Master Volume. Do you mean the blank line between the above comment and DECLARE_TLV_DB_SCALE line? I'll remove these blank lines.
Quoted 9 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ SOC_DOUBLE_R_TLV("Master Volume", + UPD9976_AUDIOLVOL, UPD9976_AUDIORVOL, + 0, 0x7f, 1, adac_tlv), + SOC_DOUBLE_R_TLV("PCM Volume", + UPD9976_HPSPRLVOL, UPD9976_HPSPRRVOL, + 0, 0x1f, 1, mixer_tlv),PCM would usually be a digital audio stream but this is a control for a mixer.
OK. I'll change it back to "Headphone Speaker Mixer Volume".
Quoted 6 lines
Review
Quoted 4 lines
(quoted diff) +3
-0+static struct snd_soc_dai_driver upd9976_dais[] = { +{ + .name = "upd9976-audio",Previous issue with naming still applies.
Will change it to upd9976-hifi, thanks for your suggestion.
Quoted 14 lines
Quoted 14 lines
(quoted diff) +13
-0+ case SND_SOC_BIAS_PREPARE: + if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { + snd_soc_update_bits(codec, UPD9976_VAUDIOCNT, + 0x27, 0x27); + snd_soc_update_bits(codec, UPD9976_VREFPLL, + 0x35, 0x35); + } + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_write(codec, UPD9976_VAUDIOCNT, 0x25); + snd_soc_write(codec, UPD9976_VREFPLL, 0x10); + break;
Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ + case SND_SOC_BIAS_OFF: + snd_soc_write(codec, UPD9976_VREFPLL, 0); + snd_soc_write(codec, UPD9976_VAUDIOCNT, 0x24); + break;This is all very magic and lots of what's going on (especially with VAUDIOCNT) looks like it's actually trying to fiddle with bitmasks.
I'll define some bitmasks so that these magic number will go away, and others can grasp the idea what's going on...
Generally, it's used to disable/enable power save mode, power on/off audio rail... etc.
Quoted 7 lines · Mark Brown
Quoted 1 line · Lu Guanqun
Signed-off-by Lu Guanqun <[email protected]>As previously mentioned please merge these trivial feature patches into the original driver unless there's some purpose in splitting them out. In this case it looks like the split is the cause of one of the review issues in the earlier post.
No problem. I'll make them about three pathes, one for codec driver, one for platform driver, one for machine driver.
Quoted 16 lines · Mark Brown
Review
Quoted 11 lines · Lu Guanqun
(quoted diff) +11
-0+ if (interrupt_status & 0x1 && value == 0x1) + status |= SND_JACK_HEADSET; + + if (interrupt_status & 0x2 && value == 0x2) + status |= SND_JACK_HEADPHONE; + + if (interrupt_status & 0x4) + status |= SND_JACK_HEADSET | SND_JACK_BTN_0; + + if (interrupt_status & 0x8) + status |= SND_JACK_HEADSET | SND_JACK_BTN_1;It's very strange that you do a mix of checks with and without the == - it doesn't matter either way but it'd be clearer to be consistent.
OK. Let me make the code more clear.
how about this?
+ if (interrupt_status & 0x1) {
+ if (snd_soc_read(codec, UPD9976_SAUXINT) == 0x1)
+ status |= SND_JACK_HEADSET;
+ }
+
+ if (interrupt_status & 0x2) {
+ if (snd_soc_read(codec, UPD9976_SAUXINT) == 0x2)
+ status |= SND_JACK_HEADPHONE;
+ }
+
+ if (interrupt_status & 0x4)
+ status |= SND_JACK_HEADSET | SND_JACK_BTN_0;
+
+ if (interrupt_status & 0x8)
+ status |= SND_JACK_HEADSET | SND_JACK_BTN_1;
Quoted 6 lines
Review
Quoted 3 lines
(quoted diff) +2
-0+ if (upd9976->irq < 0 || !upd9976->irq_mem) + return 0;It'd seem better to have this condition the other way around so if you need to add more conditional stuff things will be clearer.
changing it to `if (upd9976->ira >= 0 && !upd9976->irq_mem) is logically ok, but it introduces more indentation for a large block of code...
Quoted 14 lines · Mark Brown
sound/soc/mid-x86/mrst_machine.c static int mrst_audio_init(struct snd_soc_pcm_runtime *runtime) · line 99
Quoted 6 lines · Lu Guanqun
sound/soc/mid-x86/mrst_machine.c +6
-0+/* DMIC configuration: mono output to PCM2 */ +snd_soc_update_bits (codec,UPD9976_MICCTRL ,BIT (1 )|BIT (0 ),0x1 ); + +/* Set MIC2 to pseudo-differential */ +snd_soc_update_bits (codec,UPD9976_MICSELVOL ,BIT (4 ),BIT (4 )); +These look like regular routing controls to me... If they do need to be set with magic writes they should be platform data but things like pseudo differential inputs are normally just represented as two single ended inputs for which userspace just happens to choose a path that looks differential.
The above DMIC output might fit routing control in some way, but I think the MIC2 configuration is hardware specific, this is not related to routing quite much. Let me check whether it's easy to be implemented as a routing control or platform data...
Signature · 1 line
guanqun
Re: [PATCH v2 01/10] ASoC: upd9976: Add Renesas uPD9976 codec driver
Quoted 9 lines · Lu Guanqun
Quoted 6 lines · Mark Brown
Review
Quoted 2 lines · Lu Guanqun
(quoted diff) +2
-0+static DECLARE_TLV_DB_SCALE(adac_tlv, -8400, 75, 0); +static const struct snd_kcontrol_new upd9976_snd_controls[] = {Use of blank lines here is really odd.
I don't see a blank line between
upd9976_snd_controlsand Master Volume.
Yes, exactly. There's randomly omitted vertical space which makes things hard to read.
Quoted 2 lines
Do you mean the blank line between the above comment and
DECLARE_TLV_DB_SCALEline? I'll remove these blank lines.
No, don't do that.
Quoted 2 lines
OK. Let me make the code more clear.
how about this?
That's fine.
Quoted 8 lines
Quoted 5 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ if (upd9976->irq < 0 || !upd9976->irq_mem) + return 0;It'd seem better to have this condition the other way around so if you need to add more conditional stuff things will be clearer.
changing it to `if (
upd9976->ira>= 0 && !upd9976->irq_mem) is logically ok, but it introduces more indentation for a large block of code...
Put it in a function if that's an issue.
Quoted 10 lines
Quoted 5 lines
These look like regular routing controls to me... If they do need to be set with magic writes they should be platform data but things like pseudo differential inputs are normally just represented as two single ended inputs for which userspace just happens to choose a path that looks differential.
The above DMIC output might fit routing control in some way, but I think the MIC2 configuration is hardware specific, this is not related to routing quite much. Let me check whether it's easy to be implemented as a routing control or platform data...
Really, this is *very* common - a pseudo differential input is just two single ended inputs that happen to be used to fake a differential input on a given board. It's likely that a board will use the same setup most of the time (though sometimes it does change) but they should still not be configuring this with magic register writes in the machine driver.
Re: [PATCH v2 01/10] ASoC: upd9976: Add Renesas uPD9976 codec driver
Quoted 4 lines · Mark Brown
Quoted 2 lines
changing it to `if (
upd9976->ira>= 0 && !upd9976->irq_mem) is logically ok, but it introduces more indentation for a large block of code...Put it in a function if that's an issue.
OK.
Quoted 12 lines
Quoted 11 lines
Quoted 6 lines
These look like regular routing controls to me... If they do need to be set with magic writes they should be platform data but things like pseudo differential inputs are normally just represented as two single ended inputs for which userspace just happens to choose a path that looks differential.
The above DMIC output might fit routing control in some way, but I think the MIC2 configuration is hardware specific, this is not related to routing quite much. Let me check whether it's easy to be implemented as a routing control or platform data...
Quoted 5 lines
Really, this is *very* common - a pseudo differential input is just two single ended inputs that happen to be used to fake a differential input on a given board. It's likely that a board will use the same setup most of the time (though sometimes it does change) but they should still not be configuring this with magic register writes in the machine driver.
As this should not be configured in machine driver, I'll make it a control so that user is about to change it (this should only be set once, and leave it there untouched).
Signature · 1 line
guanqun