18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// ALSA SoC driver for Migo-R 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clkdev.h> 88c2ecf20Sopenharmony_ci#include <linux/device.h> 98c2ecf20Sopenharmony_ci#include <linux/firmware.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <asm/clock.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <cpu/sh7722.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <sound/core.h> 178c2ecf20Sopenharmony_ci#include <sound/pcm.h> 188c2ecf20Sopenharmony_ci#include <sound/soc.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "../codecs/wm8978.h" 218c2ecf20Sopenharmony_ci#include "siu.h" 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* Default 8000Hz sampling frequency */ 248c2ecf20Sopenharmony_cistatic unsigned long codec_freq = 8000 * 512; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic unsigned int use_count; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* External clock, sourced from the codec at the SIUMCKB pin */ 298c2ecf20Sopenharmony_cistatic unsigned long siumckb_recalc(struct clk *clk) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci return codec_freq; 328c2ecf20Sopenharmony_ci} 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic struct sh_clk_ops siumckb_clk_ops = { 358c2ecf20Sopenharmony_ci .recalc = siumckb_recalc, 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic struct clk siumckb_clk = { 398c2ecf20Sopenharmony_ci .ops = &siumckb_clk_ops, 408c2ecf20Sopenharmony_ci .rate = 0, /* initialised at run-time */ 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic struct clk_lookup *siumckb_lookup; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic int migor_hw_params(struct snd_pcm_substream *substream, 468c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *params) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 498c2ecf20Sopenharmony_ci struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); 508c2ecf20Sopenharmony_ci int ret; 518c2ecf20Sopenharmony_ci unsigned int rate = params_rate(params); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000, 548c2ecf20Sopenharmony_ci SND_SOC_CLOCK_IN); 558c2ecf20Sopenharmony_ci if (ret < 0) 568c2ecf20Sopenharmony_ci return ret; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512); 598c2ecf20Sopenharmony_ci if (ret < 0) 608c2ecf20Sopenharmony_ci return ret; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci codec_freq = rate * 512; 638c2ecf20Sopenharmony_ci /* 648c2ecf20Sopenharmony_ci * This propagates the parent frequency change to children and 658c2ecf20Sopenharmony_ci * recalculates the frequency table 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_ci clk_set_rate(&siumckb_clk, codec_freq); 688c2ecf20Sopenharmony_ci dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), SIU_CLKB_EXT, 718c2ecf20Sopenharmony_ci codec_freq / 2, SND_SOC_CLOCK_IN); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (!ret) 748c2ecf20Sopenharmony_ci use_count++; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return ret; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int migor_hw_free(struct snd_pcm_substream *substream) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 828c2ecf20Sopenharmony_ci struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (use_count) { 858c2ecf20Sopenharmony_ci use_count--; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (!use_count) 888c2ecf20Sopenharmony_ci snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0, 898c2ecf20Sopenharmony_ci SND_SOC_CLOCK_IN); 908c2ecf20Sopenharmony_ci } else { 918c2ecf20Sopenharmony_ci dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n"); 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci return 0; 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic const struct snd_soc_ops migor_dai_ops = { 988c2ecf20Sopenharmony_ci .hw_params = migor_hw_params, 998c2ecf20Sopenharmony_ci .hw_free = migor_hw_free, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget migor_dapm_widgets[] = { 1038c2ecf20Sopenharmony_ci SND_SOC_DAPM_HP("Headphone", NULL), 1048c2ecf20Sopenharmony_ci SND_SOC_DAPM_MIC("Onboard Microphone", NULL), 1058c2ecf20Sopenharmony_ci SND_SOC_DAPM_MIC("External Microphone", NULL), 1068c2ecf20Sopenharmony_ci}; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route audio_map[] = { 1098c2ecf20Sopenharmony_ci /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */ 1108c2ecf20Sopenharmony_ci { "Headphone", NULL, "OUT4 VMID" }, 1118c2ecf20Sopenharmony_ci { "OUT4 VMID", NULL, "LHP" }, 1128c2ecf20Sopenharmony_ci { "OUT4 VMID", NULL, "RHP" }, 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* On-board microphone */ 1158c2ecf20Sopenharmony_ci { "RMICN", NULL, "Mic Bias" }, 1168c2ecf20Sopenharmony_ci { "RMICP", NULL, "Mic Bias" }, 1178c2ecf20Sopenharmony_ci { "Mic Bias", NULL, "Onboard Microphone" }, 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci /* External microphone */ 1208c2ecf20Sopenharmony_ci { "LMICN", NULL, "Mic Bias" }, 1218c2ecf20Sopenharmony_ci { "LMICP", NULL, "Mic Bias" }, 1228c2ecf20Sopenharmony_ci { "Mic Bias", NULL, "External Microphone" }, 1238c2ecf20Sopenharmony_ci}; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci/* migor digital audio interface glue - connects codec <--> CPU */ 1268c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEFS(wm8978, 1278c2ecf20Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CPU("siu-pcm-audio")), 1288c2ecf20Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CODEC("wm8978.0-001a", "wm8978-hifi")), 1298c2ecf20Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_PLATFORM("siu-pcm-audio"))); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic struct snd_soc_dai_link migor_dai = { 1328c2ecf20Sopenharmony_ci .name = "wm8978", 1338c2ecf20Sopenharmony_ci .stream_name = "WM8978", 1348c2ecf20Sopenharmony_ci .dai_fmt = SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_I2S | 1358c2ecf20Sopenharmony_ci SND_SOC_DAIFMT_CBS_CFS, 1368c2ecf20Sopenharmony_ci .ops = &migor_dai_ops, 1378c2ecf20Sopenharmony_ci SND_SOC_DAILINK_REG(wm8978), 1388c2ecf20Sopenharmony_ci}; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci/* migor audio machine driver */ 1418c2ecf20Sopenharmony_cistatic struct snd_soc_card snd_soc_migor = { 1428c2ecf20Sopenharmony_ci .name = "Migo-R", 1438c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1448c2ecf20Sopenharmony_ci .dai_link = &migor_dai, 1458c2ecf20Sopenharmony_ci .num_links = 1, 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci .dapm_widgets = migor_dapm_widgets, 1488c2ecf20Sopenharmony_ci .num_dapm_widgets = ARRAY_SIZE(migor_dapm_widgets), 1498c2ecf20Sopenharmony_ci .dapm_routes = audio_map, 1508c2ecf20Sopenharmony_ci .num_dapm_routes = ARRAY_SIZE(audio_map), 1518c2ecf20Sopenharmony_ci}; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic struct platform_device *migor_snd_device; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic int __init migor_init(void) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci int ret; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci ret = clk_register(&siumckb_clk); 1608c2ecf20Sopenharmony_ci if (ret < 0) 1618c2ecf20Sopenharmony_ci return ret; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci siumckb_lookup = clkdev_create(&siumckb_clk, "siumckb_clk", NULL); 1648c2ecf20Sopenharmony_ci if (!siumckb_lookup) { 1658c2ecf20Sopenharmony_ci ret = -ENOMEM; 1668c2ecf20Sopenharmony_ci goto eclkdevalloc; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci /* Port number used on this machine: port B */ 1708c2ecf20Sopenharmony_ci migor_snd_device = platform_device_alloc("soc-audio", 1); 1718c2ecf20Sopenharmony_ci if (!migor_snd_device) { 1728c2ecf20Sopenharmony_ci ret = -ENOMEM; 1738c2ecf20Sopenharmony_ci goto epdevalloc; 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci platform_set_drvdata(migor_snd_device, &snd_soc_migor); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci ret = platform_device_add(migor_snd_device); 1798c2ecf20Sopenharmony_ci if (ret) 1808c2ecf20Sopenharmony_ci goto epdevadd; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci return 0; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ciepdevadd: 1858c2ecf20Sopenharmony_ci platform_device_put(migor_snd_device); 1868c2ecf20Sopenharmony_ciepdevalloc: 1878c2ecf20Sopenharmony_ci clkdev_drop(siumckb_lookup); 1888c2ecf20Sopenharmony_cieclkdevalloc: 1898c2ecf20Sopenharmony_ci clk_unregister(&siumckb_clk); 1908c2ecf20Sopenharmony_ci return ret; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic void __exit migor_exit(void) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci clkdev_drop(siumckb_lookup); 1968c2ecf20Sopenharmony_ci clk_unregister(&siumckb_clk); 1978c2ecf20Sopenharmony_ci platform_device_unregister(migor_snd_device); 1988c2ecf20Sopenharmony_ci} 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cimodule_init(migor_init); 2018c2ecf20Sopenharmony_cimodule_exit(migor_exit); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ciMODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); 2048c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA SoC Migor"); 2058c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 206