1c087a94bSLad Prabhakar // SPDX-License-Identifier: GPL-2.0 2c087a94bSLad Prabhakar // 3c087a94bSLad Prabhakar // ALSA SoC driver for Migo-R 4c087a94bSLad Prabhakar // 5c087a94bSLad Prabhakar // Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> 6c087a94bSLad Prabhakar 7c087a94bSLad Prabhakar #include <linux/clkdev.h> 8c087a94bSLad Prabhakar #include <linux/device.h> 9c087a94bSLad Prabhakar #include <linux/firmware.h> 10c087a94bSLad Prabhakar #include <linux/module.h> 11c087a94bSLad Prabhakar 12c087a94bSLad Prabhakar #include <asm/clock.h> 13c087a94bSLad Prabhakar 14c087a94bSLad Prabhakar #include <cpu/sh7722.h> 15c087a94bSLad Prabhakar 16c087a94bSLad Prabhakar #include <sound/core.h> 17c087a94bSLad Prabhakar #include <sound/pcm.h> 18c087a94bSLad Prabhakar #include <sound/soc.h> 19c087a94bSLad Prabhakar 20c087a94bSLad Prabhakar #include "../codecs/wm8978.h" 21c087a94bSLad Prabhakar #include "siu.h" 22c087a94bSLad Prabhakar 23c087a94bSLad Prabhakar /* Default 8000Hz sampling frequency */ 24c087a94bSLad Prabhakar static unsigned long codec_freq = 8000 * 512; 25c087a94bSLad Prabhakar 26c087a94bSLad Prabhakar static unsigned int use_count; 27c087a94bSLad Prabhakar 28c087a94bSLad Prabhakar /* External clock, sourced from the codec at the SIUMCKB pin */ 29c087a94bSLad Prabhakar static unsigned long siumckb_recalc(struct clk *clk) 30c087a94bSLad Prabhakar { 31c087a94bSLad Prabhakar return codec_freq; 32c087a94bSLad Prabhakar } 33c087a94bSLad Prabhakar 34c087a94bSLad Prabhakar static struct sh_clk_ops siumckb_clk_ops = { 35c087a94bSLad Prabhakar .recalc = siumckb_recalc, 36c087a94bSLad Prabhakar }; 37c087a94bSLad Prabhakar 38c087a94bSLad Prabhakar static struct clk siumckb_clk = { 39c087a94bSLad Prabhakar .ops = &siumckb_clk_ops, 40c087a94bSLad Prabhakar .rate = 0, /* initialised at run-time */ 41c087a94bSLad Prabhakar }; 42c087a94bSLad Prabhakar 43c087a94bSLad Prabhakar static struct clk_lookup *siumckb_lookup; 44c087a94bSLad Prabhakar 45c087a94bSLad Prabhakar static int migor_hw_params(struct snd_pcm_substream *substream, 46c087a94bSLad Prabhakar struct snd_pcm_hw_params *params) 47c087a94bSLad Prabhakar { 48c087a94bSLad Prabhakar struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 49c087a94bSLad Prabhakar struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); 50c087a94bSLad Prabhakar int ret; 51c087a94bSLad Prabhakar unsigned int rate = params_rate(params); 52c087a94bSLad Prabhakar 53c087a94bSLad Prabhakar ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000, 54c087a94bSLad Prabhakar SND_SOC_CLOCK_IN); 55c087a94bSLad Prabhakar if (ret < 0) 56c087a94bSLad Prabhakar return ret; 57c087a94bSLad Prabhakar 58c087a94bSLad Prabhakar ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512); 59c087a94bSLad Prabhakar if (ret < 0) 60c087a94bSLad Prabhakar return ret; 61c087a94bSLad Prabhakar 62c087a94bSLad Prabhakar codec_freq = rate * 512; 63c087a94bSLad Prabhakar /* 64c087a94bSLad Prabhakar * This propagates the parent frequency change to children and 65c087a94bSLad Prabhakar * recalculates the frequency table 66c087a94bSLad Prabhakar */ 67c087a94bSLad Prabhakar clk_set_rate(&siumckb_clk, codec_freq); 68c087a94bSLad Prabhakar dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq); 69c087a94bSLad Prabhakar 70c087a94bSLad Prabhakar ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(rtd, 0), SIU_CLKB_EXT, 71c087a94bSLad Prabhakar codec_freq / 2, SND_SOC_CLOCK_IN); 72c087a94bSLad Prabhakar 73c087a94bSLad Prabhakar if (!ret) 74c087a94bSLad Prabhakar use_count++; 75c087a94bSLad Prabhakar 76c087a94bSLad Prabhakar return ret; 77c087a94bSLad Prabhakar } 78c087a94bSLad Prabhakar 79c087a94bSLad Prabhakar static int migor_hw_free(struct snd_pcm_substream *substream) 80c087a94bSLad Prabhakar { 81c087a94bSLad Prabhakar struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 82c087a94bSLad Prabhakar struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); 83c087a94bSLad Prabhakar 84c087a94bSLad Prabhakar if (use_count) { 85c087a94bSLad Prabhakar use_count--; 86c087a94bSLad Prabhakar 87c087a94bSLad Prabhakar if (!use_count) 88c087a94bSLad Prabhakar snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0, 89c087a94bSLad Prabhakar SND_SOC_CLOCK_IN); 90c087a94bSLad Prabhakar } else { 91c087a94bSLad Prabhakar dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n"); 92c087a94bSLad Prabhakar } 93c087a94bSLad Prabhakar 94c087a94bSLad Prabhakar return 0; 95c087a94bSLad Prabhakar } 96c087a94bSLad Prabhakar 97c087a94bSLad Prabhakar static const struct snd_soc_ops migor_dai_ops = { 98c087a94bSLad Prabhakar .hw_params = migor_hw_params, 99c087a94bSLad Prabhakar .hw_free = migor_hw_free, 100c087a94bSLad Prabhakar }; 101c087a94bSLad Prabhakar 102c087a94bSLad Prabhakar static const struct snd_soc_dapm_widget migor_dapm_widgets[] = { 103c087a94bSLad Prabhakar SND_SOC_DAPM_HP("Headphone", NULL), 104c087a94bSLad Prabhakar SND_SOC_DAPM_MIC("Onboard Microphone", NULL), 105c087a94bSLad Prabhakar SND_SOC_DAPM_MIC("External Microphone", NULL), 106c087a94bSLad Prabhakar }; 107c087a94bSLad Prabhakar 108c087a94bSLad Prabhakar static const struct snd_soc_dapm_route audio_map[] = { 109c087a94bSLad Prabhakar /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */ 110c087a94bSLad Prabhakar { "Headphone", NULL, "OUT4 VMID" }, 111c087a94bSLad Prabhakar { "OUT4 VMID", NULL, "LHP" }, 112c087a94bSLad Prabhakar { "OUT4 VMID", NULL, "RHP" }, 113c087a94bSLad Prabhakar 114c087a94bSLad Prabhakar /* On-board microphone */ 115c087a94bSLad Prabhakar { "RMICN", NULL, "Mic Bias" }, 116c087a94bSLad Prabhakar { "RMICP", NULL, "Mic Bias" }, 117c087a94bSLad Prabhakar { "Mic Bias", NULL, "Onboard Microphone" }, 118c087a94bSLad Prabhakar 119c087a94bSLad Prabhakar /* External microphone */ 120c087a94bSLad Prabhakar { "LMICN", NULL, "Mic Bias" }, 121c087a94bSLad Prabhakar { "LMICP", NULL, "Mic Bias" }, 122c087a94bSLad Prabhakar { "Mic Bias", NULL, "External Microphone" }, 123c087a94bSLad Prabhakar }; 124c087a94bSLad Prabhakar 125c087a94bSLad Prabhakar /* migor digital audio interface glue - connects codec <--> CPU */ 126c087a94bSLad Prabhakar SND_SOC_DAILINK_DEFS(wm8978, 127c087a94bSLad Prabhakar DAILINK_COMP_ARRAY(COMP_CPU("siu-pcm-audio")), 128c087a94bSLad Prabhakar DAILINK_COMP_ARRAY(COMP_CODEC("wm8978.0-001a", "wm8978-hifi")), 129c087a94bSLad Prabhakar DAILINK_COMP_ARRAY(COMP_PLATFORM("siu-pcm-audio"))); 130c087a94bSLad Prabhakar 131c087a94bSLad Prabhakar static struct snd_soc_dai_link migor_dai = { 132c087a94bSLad Prabhakar .name = "wm8978", 133c087a94bSLad Prabhakar .stream_name = "WM8978", 134c087a94bSLad Prabhakar .dai_fmt = SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_I2S | 135*a5a3de89SKuninori Morimoto SND_SOC_DAIFMT_CBC_CFC, 136c087a94bSLad Prabhakar .ops = &migor_dai_ops, 137c087a94bSLad Prabhakar SND_SOC_DAILINK_REG(wm8978), 138c087a94bSLad Prabhakar }; 139c087a94bSLad Prabhakar 140c087a94bSLad Prabhakar /* migor audio machine driver */ 141c087a94bSLad Prabhakar static struct snd_soc_card snd_soc_migor = { 142c087a94bSLad Prabhakar .name = "Migo-R", 143c087a94bSLad Prabhakar .owner = THIS_MODULE, 144c087a94bSLad Prabhakar .dai_link = &migor_dai, 145c087a94bSLad Prabhakar .num_links = 1, 146c087a94bSLad Prabhakar 147c087a94bSLad Prabhakar .dapm_widgets = migor_dapm_widgets, 148c087a94bSLad Prabhakar .num_dapm_widgets = ARRAY_SIZE(migor_dapm_widgets), 149c087a94bSLad Prabhakar .dapm_routes = audio_map, 150c087a94bSLad Prabhakar .num_dapm_routes = ARRAY_SIZE(audio_map), 151c087a94bSLad Prabhakar }; 152c087a94bSLad Prabhakar 153c087a94bSLad Prabhakar static struct platform_device *migor_snd_device; 154c087a94bSLad Prabhakar 155c087a94bSLad Prabhakar static int __init migor_init(void) 156c087a94bSLad Prabhakar { 157c087a94bSLad Prabhakar int ret; 158c087a94bSLad Prabhakar 159c087a94bSLad Prabhakar ret = clk_register(&siumckb_clk); 160c087a94bSLad Prabhakar if (ret < 0) 161c087a94bSLad Prabhakar return ret; 162c087a94bSLad Prabhakar 163c087a94bSLad Prabhakar siumckb_lookup = clkdev_create(&siumckb_clk, "siumckb_clk", NULL); 164c087a94bSLad Prabhakar if (!siumckb_lookup) { 165c087a94bSLad Prabhakar ret = -ENOMEM; 166c087a94bSLad Prabhakar goto eclkdevalloc; 167c087a94bSLad Prabhakar } 168c087a94bSLad Prabhakar 169c087a94bSLad Prabhakar /* Port number used on this machine: port B */ 170c087a94bSLad Prabhakar migor_snd_device = platform_device_alloc("soc-audio", 1); 171c087a94bSLad Prabhakar if (!migor_snd_device) { 172c087a94bSLad Prabhakar ret = -ENOMEM; 173c087a94bSLad Prabhakar goto epdevalloc; 174c087a94bSLad Prabhakar } 175c087a94bSLad Prabhakar 176c087a94bSLad Prabhakar platform_set_drvdata(migor_snd_device, &snd_soc_migor); 177c087a94bSLad Prabhakar 178c087a94bSLad Prabhakar ret = platform_device_add(migor_snd_device); 179c087a94bSLad Prabhakar if (ret) 180c087a94bSLad Prabhakar goto epdevadd; 181c087a94bSLad Prabhakar 182c087a94bSLad Prabhakar return 0; 183c087a94bSLad Prabhakar 184c087a94bSLad Prabhakar epdevadd: 185c087a94bSLad Prabhakar platform_device_put(migor_snd_device); 186c087a94bSLad Prabhakar epdevalloc: 187c087a94bSLad Prabhakar clkdev_drop(siumckb_lookup); 188c087a94bSLad Prabhakar eclkdevalloc: 189c087a94bSLad Prabhakar clk_unregister(&siumckb_clk); 190c087a94bSLad Prabhakar return ret; 191c087a94bSLad Prabhakar } 192c087a94bSLad Prabhakar 193c087a94bSLad Prabhakar static void __exit migor_exit(void) 194c087a94bSLad Prabhakar { 195c087a94bSLad Prabhakar clkdev_drop(siumckb_lookup); 196c087a94bSLad Prabhakar clk_unregister(&siumckb_clk); 197c087a94bSLad Prabhakar platform_device_unregister(migor_snd_device); 198c087a94bSLad Prabhakar } 199c087a94bSLad Prabhakar 200c087a94bSLad Prabhakar module_init(migor_init); 201c087a94bSLad Prabhakar module_exit(migor_exit); 202c087a94bSLad Prabhakar 203c087a94bSLad Prabhakar MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); 204c087a94bSLad Prabhakar MODULE_DESCRIPTION("ALSA SoC Migor"); 205c087a94bSLad Prabhakar MODULE_LICENSE("GPL v2"); 206