ASoC: WM9712: debugfs for mixer registers From: John Bonesio debugfs file added to display mixer registers. --- sound/soc/codecs/wm9712.c | 309 +++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 309 insertions(+), 0 deletions(-) diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index b57c817..8d13ece 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -154,6 +155,50 @@ SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0), }; +typedef struct reg_map_type { + unsigned int reg; + char *name; +} reg_map_type; + +reg_map_type reg_map[] = { + { 0x02, "spkr out (1/2)" }, + { 0x04, "hp out (l/r)" }, + { 0x06, "phone out (mono)" }, + { 0x0a, "pcbeep" }, + { 0x0c, "phone" }, + { 0x0e, "mic to phone" }, + { 0x10, "linein" }, + { 0x12, "AUXDAC" }, + { 0x14, "mic to hp (sidetone)" }, + { 0x16, "OUT3" }, + { 0x18, "DAC" }, + { 0x20, "ADC" }, + { 0x24, "PowMgmnt #1" }, + { 0x26, "PowMgmnt #2" }, + { HPL_MIXER, "HPL_MIXER" }, + { HPR_MIXER, "HPR_MIXER" }, + { 0x00, NULL } +}; + +char *reg_name(unsigned int reg) { + unsigned int idx; + + for (idx=0; reg_map[idx].name != NULL; idx++) { + if (reg_map[idx].reg == reg) + break; + } + + if (reg_map[idx].name == NULL) { + return "(unknown register)"; + } + return reg_map[idx].name; +} + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_root; +#endif + + /* We have to create a fake left and right HP mixers because * the codec only has a single control that is shared by both channels. * This makes it impossible to determine the audio path. @@ -429,6 +474,254 @@ static const struct snd_soc_dapm_route audio_map[] = { {"ROUT2", NULL, "Speaker PGA"}, }; +#ifdef CONFIG_DEBUG_FS +static int wm9712_reg_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static char tmp_str[120]; +static char *decode_bits(unsigned int reg, unsigned int value) { + switch (reg) { + case 0x2: + /* lout2/rout2 */ + snprintf(tmp_str, sizeof(tmp_str), + "%smute; lout2 vol: %d; zc: %s inv: %s; rout2 vol: %d", + (value & 0x8000) ? "" : "un", + (value >> 8) & 0x3F, + (value & 0x80) ? "on" : "off", + (value & 0x40) ? "on" : "off", + value & 0x3F); + break; + case 0x4: + /* hpout l/r */ + snprintf(tmp_str, sizeof(tmp_str), + "%smute; hpout2 vol: %d; zc: %s hpout2 vol: %d", + (value & 0x8000) ? "" : "un", + (value >> 8) & 0x3F, + (value & 0x80) ? "on" : "off", + value & 0x3F); + break; + case 0x6: + /* monoout */ + snprintf(tmp_str, sizeof(tmp_str), + "%smute; zc: %s monoout vol: %d", + (value & 0x8000) ? "" : "un", + (value & 0x80) ? "on" : "off", + value & 0x3F); + break; + case 0xa: + /* pcbeep */ + snprintf(tmp_str, sizeof(tmp_str), + "b-hph %smute; b-hph vol: %d; b-spk %smute; b-spk vol: %d; b-ph %smute; b-ph vol: %d;", + (value & 0x8000) ? "" : "un", + (value >> 12) & 0x7, + (value & 0x800) ? "" : "un", + (value >> 8) & 0x7, + (value & 0x80) ? "" : "un", + (value >> 4) & 0x7); + break; + case 0xc: + /* phone */ + snprintf(tmp_str, sizeof(tmp_str), + "p-hph %smute; p-spk %smute; phone vol: %d", + (value & 0x8000) ? "" : "un", + (value & 0x4000) ? "" : "un", + value & 0x1F); + break; + case 0xe: + /* mic1/mic2 */ + snprintf(tmp_str, sizeof(tmp_str), + "m1 %smute; m2 %smute; lmic(1) vol: %d; 20db: %s; ms: %x; mic(2) vol: %d", + (value & 0x4000) ? "" : "un", + (value & 0x2000) ? "" : "un", + (value >> 8) & 0x1f, + (value & 0x80) ? "on" : "off", + ((value >> 5) & 0x3), + value & 0x1F); + break; + case 0x10: + /* linein */ + snprintf(tmp_str, sizeof(tmp_str), + "l-hph %smute; l-spk %smute; l-ph %smute; lineinl vol: %d; linein2 vol: %d", + (value & 0x8000) ? "" : "un", + (value & 0x4000) ? "" : "un", + (value & 0x2000) ? "" : "un", + (value >> 8) & 0x1f, + value & 0x1F); + break; + case 0x12: + /* auxadc */ + snprintf(tmp_str, sizeof(tmp_str), + "a-hph %smute; a-hph vol: %d; a-spk %smute; a-spk vol: %d; a-ph %smute; a-ph vol: %d; axe: %s", + (value & 0x8000) ? "" : "un", + (value >> 12) & 0x7, + (value & 0x800) ? "" : "un", + (value >> 8) & 0x7, + (value & 0x80) ? "" : "un", + (value >> 4) & 0x7, + (value & 0x1) ? "on" : "off"); + break; + case 0x14: + /* side tone */ + snprintf(tmp_str, sizeof(tmp_str), + "st %smute; st vol: %d; alc-hph mute: %d; alc vol: %d", + (value & 0x8000) ? "" : "un", + (value >> 12) & 0x7, + (value >> 10) & 0x3, + (value >> 7) & 0x7); + break; + case 0x16: + /* out3 */ + snprintf(tmp_str, sizeof(tmp_str), + "%smute; out3 src: %d; lout2/rout2 src: %s; zc: %s; alc vol: %d", + (value & 0x8000) ? "" : "un", + (value >> 9) & 0x3, + (value & 0x100) ? "spk" : "hph", + (value & 0x80) ? "on" : "off", + value & 0x3f); + break; + case 0x18: + /* dac */ + snprintf(tmp_str, sizeof(tmp_str), + "d-hph %smute; d-spk %smute; d-ph %smute; ldac vol: %d; rdac vol: %d", + (value & 0x8000) ? "" : "un", + (value & 0x4000) ? "" : "un", + (value & 0x2000) ? "" : "un", + (value >> 8) & 0x1f, + value & 0x1F); + break; + default: + snprintf(tmp_str, sizeof(tmp_str), "(not decoded)"); + }; + return tmp_str; +} + +extern unsigned *mixer_addr; +static ssize_t wm9712_mixer_reg_show(struct snd_soc_codec *codec, char *buf) +{ + int idx, count = 0; + unsigned int value, cached_value; + + if (!codec->reg_cache_size) + return 0; + + count += sprintf(buf, "%s Mixer registers:\n", codec->name); + for (idx = 0; reg_map[idx].name != NULL; idx++) { + cached_value = ac97_read(codec, reg_map[idx].reg); + if ((reg_map[idx].reg != HPL_MIXER) && (reg_map[idx].reg != HPR_MIXER)) { + value = soc_ac97_ops.read(codec->ac97, reg_map[idx].reg); + } else { + value = cached_value; + } + + count += snprintf(buf + count, PAGE_SIZE - count, + "%12s: 0x%0x: %s", reg_map[idx].name, + value, decode_bits(reg_map[idx].reg, value)); + if (cached_value != value) { + count += snprintf(buf + count, PAGE_SIZE - count, + "(cached WRONG!!: 0x%0x)\n", cached_value); + } else { + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + } + + if (count >= PAGE_SIZE - 1) + break; + } + count += snprintf(buf + count, PAGE_SIZE - count, + "global mixer_addr: %p\n", mixer_addr); + + /* Truncate count; min() would cause a warning */ + if (count >= PAGE_SIZE) + count = PAGE_SIZE - 1; + + return count; +} + + +static ssize_t wm9712_reg_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + struct snd_soc_codec *codec = file->private_data; + char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = wm9712_mixer_reg_show(codec, buf); + if (ret >= 0) + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t wm9712_reg_write_file(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + char *start = buf; + unsigned long reg, value; + int step = 1; + struct snd_soc_codec *codec = file->private_data; + +return -EINVAL; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + if (codec->reg_cache_step) + step = codec->reg_cache_step; + + while (*start == ' ') + start++; + reg = simple_strtoul(start, &start, 16); + if ((reg >= codec->reg_cache_size) || (reg % step)) + return -EINVAL; + while (*start == ' ') + start++; + if (strict_strtoul(start, 16, &value)) + return -EINVAL; + codec->write(codec, reg, value); + return buf_size; +} + +static const struct file_operations codec_reg_fops = { + .open = wm9712_reg_open_file, + .read = wm9712_reg_read_file, + .write = wm9712_reg_write_file, +}; + +static void wm9712_init_debugfs(struct snd_soc_codec *codec) +{ + codec->debugfs_reg = debugfs_create_file("mixer_reg", 0644, + debugfs_root, codec, + &codec_reg_fops); + if (!codec->debugfs_reg) + printk(KERN_WARNING + "ASoC: Failed to create codec register debugfs file\n"); +} + +static void wm9712_cleanup_debugfs(struct snd_soc_codec *codec) +{ + debugfs_remove(codec->debugfs_pop_time); + debugfs_remove(codec->debugfs_reg); +} + +#else + +static inline void wm9712_init_debugfs(struct snd_soc_codec *codec) +{ +} + +static inline void wm9712_cleanup_debugfs(struct snd_soc_codec *codec) +{ +} +#endif + static int wm9712_add_widgets(struct snd_soc_codec *codec) { snd_soc_dapm_new_controls(codec, wm9712_dapm_widgets, @@ -699,6 +992,8 @@ static int wm9712_soc_probe(struct platform_device *pdev) goto reset_err; } + wm9712_init_debugfs(codec); + return 0; reset_err: @@ -724,6 +1019,8 @@ static int wm9712_soc_remove(struct platform_device *pdev) if (codec == NULL) return 0; + wm9712_cleanup_debugfs(codec); + snd_soc_dapm_free(socdev); snd_soc_free_pcms(socdev); snd_soc_free_ac97_codec(codec); @@ -742,12 +1039,24 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_wm9712); static int __init wm9712_modinit(void) { +#ifdef CONFIG_DEBUG_FS + debugfs_root = debugfs_create_dir("wm9712", NULL); + if (IS_ERR(debugfs_root) || !debugfs_root) { + printk(KERN_WARNING + "WM9712: Failed to create debugfs directory\n"); + debugfs_root = NULL; + } +#endif return snd_soc_register_dais(wm9712_dai, ARRAY_SIZE(wm9712_dai)); } module_init(wm9712_modinit); static void __exit wm9712_exit(void) { +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(debugfs_root); +#endif + snd_soc_unregister_dais(wm9712_dai, ARRAY_SIZE(wm9712_dai)); } module_exit(wm9712_exit);