18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * zylonite.c  --  SoC audio for Zylonite
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2008 Wolfson Microelectronics PLC.
68c2ecf20Sopenharmony_ci * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
118c2ecf20Sopenharmony_ci#include <linux/device.h>
128c2ecf20Sopenharmony_ci#include <linux/clk.h>
138c2ecf20Sopenharmony_ci#include <linux/i2c.h>
148c2ecf20Sopenharmony_ci#include <sound/core.h>
158c2ecf20Sopenharmony_ci#include <sound/pcm.h>
168c2ecf20Sopenharmony_ci#include <sound/pcm_params.h>
178c2ecf20Sopenharmony_ci#include <sound/soc.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include "../codecs/wm9713.h"
208c2ecf20Sopenharmony_ci#include "pxa-ssp.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/*
238c2ecf20Sopenharmony_ci * There is a physical switch SW15 on the board which changes the MCLK
248c2ecf20Sopenharmony_ci * for the WM9713 between the standard AC97 master clock and the
258c2ecf20Sopenharmony_ci * output of the CLK_POUT signal from the PXA.
268c2ecf20Sopenharmony_ci */
278c2ecf20Sopenharmony_cistatic int clk_pout;
288c2ecf20Sopenharmony_cimodule_param(clk_pout, int, 0);
298c2ecf20Sopenharmony_ciMODULE_PARM_DESC(clk_pout, "Use CLK_POUT as WM9713 MCLK (SW15 on board).");
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic struct clk *pout;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic struct snd_soc_card zylonite;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget zylonite_dapm_widgets[] = {
368c2ecf20Sopenharmony_ci	SND_SOC_DAPM_HP("Headphone", NULL),
378c2ecf20Sopenharmony_ci	SND_SOC_DAPM_MIC("Headset Microphone", NULL),
388c2ecf20Sopenharmony_ci	SND_SOC_DAPM_MIC("Handset Microphone", NULL),
398c2ecf20Sopenharmony_ci	SND_SOC_DAPM_SPK("Multiactor", NULL),
408c2ecf20Sopenharmony_ci	SND_SOC_DAPM_SPK("Headset Earpiece", NULL),
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci/* Currently supported audio map */
448c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route audio_map[] = {
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	/* Headphone output connected to HPL/HPR */
478c2ecf20Sopenharmony_ci	{ "Headphone", NULL,  "HPL" },
488c2ecf20Sopenharmony_ci	{ "Headphone", NULL,  "HPR" },
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	/* On-board earpiece */
518c2ecf20Sopenharmony_ci	{ "Headset Earpiece", NULL, "OUT3" },
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	/* Headphone mic */
548c2ecf20Sopenharmony_ci	{ "MIC2A", NULL, "Mic Bias" },
558c2ecf20Sopenharmony_ci	{ "Mic Bias", NULL, "Headset Microphone" },
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	/* On-board mic */
588c2ecf20Sopenharmony_ci	{ "MIC1", NULL, "Mic Bias" },
598c2ecf20Sopenharmony_ci	{ "Mic Bias", NULL, "Handset Microphone" },
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	/* Multiactor differentially connected over SPKL/SPKR */
628c2ecf20Sopenharmony_ci	{ "Multiactor", NULL, "SPKL" },
638c2ecf20Sopenharmony_ci	{ "Multiactor", NULL, "SPKR" },
648c2ecf20Sopenharmony_ci};
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int zylonite_wm9713_init(struct snd_soc_pcm_runtime *rtd)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	if (clk_pout)
698c2ecf20Sopenharmony_ci		snd_soc_dai_set_pll(asoc_rtd_to_codec(rtd, 0), 0, 0,
708c2ecf20Sopenharmony_ci				    clk_get_rate(pout), 0);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	return 0;
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic int zylonite_voice_hw_params(struct snd_pcm_substream *substream,
768c2ecf20Sopenharmony_ci				    struct snd_pcm_hw_params *params)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
798c2ecf20Sopenharmony_ci	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
808c2ecf20Sopenharmony_ci	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
818c2ecf20Sopenharmony_ci	unsigned int wm9713_div = 0;
828c2ecf20Sopenharmony_ci	int ret = 0;
838c2ecf20Sopenharmony_ci	int rate = params_rate(params);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	/* Only support ratios that we can generate neatly from the AC97
868c2ecf20Sopenharmony_ci	 * based master clock - in particular, this excludes 44.1kHz.
878c2ecf20Sopenharmony_ci	 * In most applications the voice DAC will be used for telephony
888c2ecf20Sopenharmony_ci	 * data so multiples of 8kHz will be the common case.
898c2ecf20Sopenharmony_ci	 */
908c2ecf20Sopenharmony_ci	switch (rate) {
918c2ecf20Sopenharmony_ci	case 8000:
928c2ecf20Sopenharmony_ci		wm9713_div = 12;
938c2ecf20Sopenharmony_ci		break;
948c2ecf20Sopenharmony_ci	case 16000:
958c2ecf20Sopenharmony_ci		wm9713_div = 6;
968c2ecf20Sopenharmony_ci		break;
978c2ecf20Sopenharmony_ci	case 48000:
988c2ecf20Sopenharmony_ci		wm9713_div = 2;
998c2ecf20Sopenharmony_ci		break;
1008c2ecf20Sopenharmony_ci	default:
1018c2ecf20Sopenharmony_ci		/* Don't support OSS emulation */
1028c2ecf20Sopenharmony_ci		return -EINVAL;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0, 1);
1068c2ecf20Sopenharmony_ci	if (ret < 0)
1078c2ecf20Sopenharmony_ci		return ret;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	if (clk_pout)
1108c2ecf20Sopenharmony_ci		ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_PLL_DIV,
1118c2ecf20Sopenharmony_ci					     WM9713_PCMDIV(wm9713_div));
1128c2ecf20Sopenharmony_ci	else
1138c2ecf20Sopenharmony_ci		ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_DIV,
1148c2ecf20Sopenharmony_ci					     WM9713_PCMDIV(wm9713_div));
1158c2ecf20Sopenharmony_ci	if (ret < 0)
1168c2ecf20Sopenharmony_ci		return ret;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	return 0;
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic const struct snd_soc_ops zylonite_voice_ops = {
1228c2ecf20Sopenharmony_ci	.hw_params = zylonite_voice_hw_params,
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEFS(ac97,
1268c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")),
1278c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-hifi")),
1288c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEFS(ac97_aux,
1318c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")),
1328c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-aux")),
1338c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEFS(voice,
1368c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CPU("pxa-ssp-dai.2")),
1378c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-voice")),
1388c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic struct snd_soc_dai_link zylonite_dai[] = {
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	.name = "AC97",
1438c2ecf20Sopenharmony_ci	.stream_name = "AC97 HiFi",
1448c2ecf20Sopenharmony_ci	.init = zylonite_wm9713_init,
1458c2ecf20Sopenharmony_ci	SND_SOC_DAILINK_REG(ac97),
1468c2ecf20Sopenharmony_ci},
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	.name = "AC97 Aux",
1498c2ecf20Sopenharmony_ci	.stream_name = "AC97 Aux",
1508c2ecf20Sopenharmony_ci	SND_SOC_DAILINK_REG(ac97_aux),
1518c2ecf20Sopenharmony_ci},
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	.name = "WM9713 Voice",
1548c2ecf20Sopenharmony_ci	.stream_name = "WM9713 Voice",
1558c2ecf20Sopenharmony_ci	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
1568c2ecf20Sopenharmony_ci		   SND_SOC_DAIFMT_CBS_CFS,
1578c2ecf20Sopenharmony_ci	.ops = &zylonite_voice_ops,
1588c2ecf20Sopenharmony_ci	SND_SOC_DAILINK_REG(voice),
1598c2ecf20Sopenharmony_ci},
1608c2ecf20Sopenharmony_ci};
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic int zylonite_probe(struct snd_soc_card *card)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	int ret;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	if (clk_pout) {
1678c2ecf20Sopenharmony_ci		pout = clk_get(NULL, "CLK_POUT");
1688c2ecf20Sopenharmony_ci		if (IS_ERR(pout)) {
1698c2ecf20Sopenharmony_ci			dev_err(card->dev, "Unable to obtain CLK_POUT: %ld\n",
1708c2ecf20Sopenharmony_ci				PTR_ERR(pout));
1718c2ecf20Sopenharmony_ci			return PTR_ERR(pout);
1728c2ecf20Sopenharmony_ci		}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		ret = clk_enable(pout);
1758c2ecf20Sopenharmony_ci		if (ret != 0) {
1768c2ecf20Sopenharmony_ci			dev_err(card->dev, "Unable to enable CLK_POUT: %d\n",
1778c2ecf20Sopenharmony_ci				ret);
1788c2ecf20Sopenharmony_ci			clk_put(pout);
1798c2ecf20Sopenharmony_ci			return ret;
1808c2ecf20Sopenharmony_ci		}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci		dev_dbg(card->dev, "MCLK enabled at %luHz\n",
1838c2ecf20Sopenharmony_ci			clk_get_rate(pout));
1848c2ecf20Sopenharmony_ci	}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	return 0;
1878c2ecf20Sopenharmony_ci}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cistatic int zylonite_remove(struct snd_soc_card *card)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	if (clk_pout) {
1928c2ecf20Sopenharmony_ci		clk_disable(pout);
1938c2ecf20Sopenharmony_ci		clk_put(pout);
1948c2ecf20Sopenharmony_ci	}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	return 0;
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic int zylonite_suspend_post(struct snd_soc_card *card)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	if (clk_pout)
2028c2ecf20Sopenharmony_ci		clk_disable(pout);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	return 0;
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_cistatic int zylonite_resume_pre(struct snd_soc_card *card)
2088c2ecf20Sopenharmony_ci{
2098c2ecf20Sopenharmony_ci	int ret = 0;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	if (clk_pout) {
2128c2ecf20Sopenharmony_ci		ret = clk_enable(pout);
2138c2ecf20Sopenharmony_ci		if (ret != 0)
2148c2ecf20Sopenharmony_ci			dev_err(card->dev, "Unable to enable CLK_POUT: %d\n",
2158c2ecf20Sopenharmony_ci				ret);
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	return ret;
2198c2ecf20Sopenharmony_ci}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_cistatic struct snd_soc_card zylonite = {
2228c2ecf20Sopenharmony_ci	.name = "Zylonite",
2238c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
2248c2ecf20Sopenharmony_ci	.probe = &zylonite_probe,
2258c2ecf20Sopenharmony_ci	.remove = &zylonite_remove,
2268c2ecf20Sopenharmony_ci	.suspend_post = &zylonite_suspend_post,
2278c2ecf20Sopenharmony_ci	.resume_pre = &zylonite_resume_pre,
2288c2ecf20Sopenharmony_ci	.dai_link = zylonite_dai,
2298c2ecf20Sopenharmony_ci	.num_links = ARRAY_SIZE(zylonite_dai),
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	.dapm_widgets = zylonite_dapm_widgets,
2328c2ecf20Sopenharmony_ci	.num_dapm_widgets = ARRAY_SIZE(zylonite_dapm_widgets),
2338c2ecf20Sopenharmony_ci	.dapm_routes = audio_map,
2348c2ecf20Sopenharmony_ci	.num_dapm_routes = ARRAY_SIZE(audio_map),
2358c2ecf20Sopenharmony_ci};
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_cistatic struct platform_device *zylonite_snd_ac97_device;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_cistatic int __init zylonite_init(void)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	int ret;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	zylonite_snd_ac97_device = platform_device_alloc("soc-audio", -1);
2448c2ecf20Sopenharmony_ci	if (!zylonite_snd_ac97_device)
2458c2ecf20Sopenharmony_ci		return -ENOMEM;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	platform_set_drvdata(zylonite_snd_ac97_device, &zylonite);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	ret = platform_device_add(zylonite_snd_ac97_device);
2508c2ecf20Sopenharmony_ci	if (ret != 0)
2518c2ecf20Sopenharmony_ci		platform_device_put(zylonite_snd_ac97_device);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	return ret;
2548c2ecf20Sopenharmony_ci}
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_cistatic void __exit zylonite_exit(void)
2578c2ecf20Sopenharmony_ci{
2588c2ecf20Sopenharmony_ci	platform_device_unregister(zylonite_snd_ac97_device);
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cimodule_init(zylonite_init);
2628c2ecf20Sopenharmony_cimodule_exit(zylonite_exit);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
2658c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA SoC WM9713 Zylonite");
2668c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
267