18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * sam9g20_wm8731  --  SoC audio for AT91SAM9G20-based
48c2ecf20Sopenharmony_ci * 			ATMEL AT91SAM9G20ek board.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci *  Copyright (C) 2005 SAN People
78c2ecf20Sopenharmony_ci *  Copyright (C) 2008 Atmel
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * Based on ati_b1_wm8731.c by:
128c2ecf20Sopenharmony_ci * Frank Mandarino <fmandarino@endrelia.com>
138c2ecf20Sopenharmony_ci * Copyright 2006 Endrelia Technologies Inc.
148c2ecf20Sopenharmony_ci * Based on corgi.c by:
158c2ecf20Sopenharmony_ci * Copyright 2005 Wolfson Microelectronics PLC.
168c2ecf20Sopenharmony_ci * Copyright 2005 Openedhand Ltd.
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/module.h>
208c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
218c2ecf20Sopenharmony_ci#include <linux/kernel.h>
228c2ecf20Sopenharmony_ci#include <linux/clk.h>
238c2ecf20Sopenharmony_ci#include <linux/timer.h>
248c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
258c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
268c2ecf20Sopenharmony_ci#include <linux/i2c.h>
278c2ecf20Sopenharmony_ci#include <linux/of.h>
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#include <linux/atmel-ssc.h>
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#include <sound/core.h>
328c2ecf20Sopenharmony_ci#include <sound/pcm.h>
338c2ecf20Sopenharmony_ci#include <sound/pcm_params.h>
348c2ecf20Sopenharmony_ci#include <sound/soc.h>
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#include "../codecs/wm8731.h"
378c2ecf20Sopenharmony_ci#include "atmel-pcm.h"
388c2ecf20Sopenharmony_ci#include "atmel_ssc_dai.h"
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define MCLK_RATE 12000000
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/*
438c2ecf20Sopenharmony_ci * As shipped the board does not have inputs.  However, it is relatively
448c2ecf20Sopenharmony_ci * straightforward to modify the board to hook them up so support is left
458c2ecf20Sopenharmony_ci * in the driver.
468c2ecf20Sopenharmony_ci */
478c2ecf20Sopenharmony_ci#undef ENABLE_MIC_INPUT
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic struct clk *mclk;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
528c2ecf20Sopenharmony_ci					struct snd_soc_dapm_context *dapm,
538c2ecf20Sopenharmony_ci					enum snd_soc_bias_level level)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	static int mclk_on;
568c2ecf20Sopenharmony_ci	int ret = 0;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	switch (level) {
598c2ecf20Sopenharmony_ci	case SND_SOC_BIAS_ON:
608c2ecf20Sopenharmony_ci	case SND_SOC_BIAS_PREPARE:
618c2ecf20Sopenharmony_ci		if (!mclk_on)
628c2ecf20Sopenharmony_ci			ret = clk_enable(mclk);
638c2ecf20Sopenharmony_ci		if (ret == 0)
648c2ecf20Sopenharmony_ci			mclk_on = 1;
658c2ecf20Sopenharmony_ci		break;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	case SND_SOC_BIAS_OFF:
688c2ecf20Sopenharmony_ci	case SND_SOC_BIAS_STANDBY:
698c2ecf20Sopenharmony_ci		if (mclk_on)
708c2ecf20Sopenharmony_ci			clk_disable(mclk);
718c2ecf20Sopenharmony_ci		mclk_on = 0;
728c2ecf20Sopenharmony_ci		break;
738c2ecf20Sopenharmony_ci	}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	return ret;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
798c2ecf20Sopenharmony_ci	SND_SOC_DAPM_MIC("Int Mic", NULL),
808c2ecf20Sopenharmony_ci	SND_SOC_DAPM_SPK("Ext Spk", NULL),
818c2ecf20Sopenharmony_ci};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route intercon[] = {
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	/* speaker connected to LHPOUT/RHPOUT */
868c2ecf20Sopenharmony_ci	{"Ext Spk", NULL, "LHPOUT"},
878c2ecf20Sopenharmony_ci	{"Ext Spk", NULL, "RHPOUT"},
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	/* mic is connected to Mic Jack, with WM8731 Mic Bias */
908c2ecf20Sopenharmony_ci	{"MICIN", NULL, "Mic Bias"},
918c2ecf20Sopenharmony_ci	{"Mic Bias", NULL, "Int Mic"},
928c2ecf20Sopenharmony_ci};
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci/*
958c2ecf20Sopenharmony_ci * Logic for a wm8731 as connected on a at91sam9g20ek board.
968c2ecf20Sopenharmony_ci */
978c2ecf20Sopenharmony_cistatic int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
1008c2ecf20Sopenharmony_ci	struct device *dev = rtd->dev;
1018c2ecf20Sopenharmony_ci	int ret;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s called\n", __func__);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK,
1068c2ecf20Sopenharmony_ci				     MCLK_RATE, SND_SOC_CLOCK_IN);
1078c2ecf20Sopenharmony_ci	if (ret < 0) {
1088c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret);
1098c2ecf20Sopenharmony_ci		return ret;
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci#ifndef ENABLE_MIC_INPUT
1138c2ecf20Sopenharmony_ci	snd_soc_dapm_nc_pin(&rtd->card->dapm, "Int Mic");
1148c2ecf20Sopenharmony_ci#endif
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	return 0;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEFS(pcm,
1208c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CPU("at91rm9200_ssc.0")),
1218c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.0-001b", "wm8731-hifi")),
1228c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_PLATFORM("at91rm9200_ssc.0")));
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic struct snd_soc_dai_link at91sam9g20ek_dai = {
1258c2ecf20Sopenharmony_ci	.name = "WM8731",
1268c2ecf20Sopenharmony_ci	.stream_name = "WM8731 PCM",
1278c2ecf20Sopenharmony_ci	.init = at91sam9g20ek_wm8731_init,
1288c2ecf20Sopenharmony_ci	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
1298c2ecf20Sopenharmony_ci		   SND_SOC_DAIFMT_CBM_CFM,
1308c2ecf20Sopenharmony_ci	SND_SOC_DAILINK_REG(pcm),
1318c2ecf20Sopenharmony_ci};
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic struct snd_soc_card snd_soc_at91sam9g20ek = {
1348c2ecf20Sopenharmony_ci	.name = "AT91SAMG20-EK",
1358c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
1368c2ecf20Sopenharmony_ci	.dai_link = &at91sam9g20ek_dai,
1378c2ecf20Sopenharmony_ci	.num_links = 1,
1388c2ecf20Sopenharmony_ci	.set_bias_level = at91sam9g20ek_set_bias_level,
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	.dapm_widgets = at91sam9g20ek_dapm_widgets,
1418c2ecf20Sopenharmony_ci	.num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets),
1428c2ecf20Sopenharmony_ci	.dapm_routes = intercon,
1438c2ecf20Sopenharmony_ci	.num_dapm_routes = ARRAY_SIZE(intercon),
1448c2ecf20Sopenharmony_ci	.fully_routed = true,
1458c2ecf20Sopenharmony_ci};
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic int at91sam9g20ek_audio_probe(struct platform_device *pdev)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
1508c2ecf20Sopenharmony_ci	struct device_node *codec_np, *cpu_np;
1518c2ecf20Sopenharmony_ci	struct clk *pllb;
1528c2ecf20Sopenharmony_ci	struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
1538c2ecf20Sopenharmony_ci	int ret;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	if (!np) {
1568c2ecf20Sopenharmony_ci		return -ENODEV;
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	ret = atmel_ssc_set_audio(0);
1608c2ecf20Sopenharmony_ci	if (ret) {
1618c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "ssc channel is not valid\n");
1628c2ecf20Sopenharmony_ci		return -EINVAL;
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	/*
1668c2ecf20Sopenharmony_ci	 * Codec MCLK is supplied by PCK0 - set it up.
1678c2ecf20Sopenharmony_ci	 */
1688c2ecf20Sopenharmony_ci	mclk = clk_get(NULL, "pck0");
1698c2ecf20Sopenharmony_ci	if (IS_ERR(mclk)) {
1708c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to get MCLK\n");
1718c2ecf20Sopenharmony_ci		ret = PTR_ERR(mclk);
1728c2ecf20Sopenharmony_ci		goto err;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	pllb = clk_get(NULL, "pllb");
1768c2ecf20Sopenharmony_ci	if (IS_ERR(pllb)) {
1778c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to get PLLB\n");
1788c2ecf20Sopenharmony_ci		ret = PTR_ERR(pllb);
1798c2ecf20Sopenharmony_ci		goto err_mclk;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci	ret = clk_set_parent(mclk, pllb);
1828c2ecf20Sopenharmony_ci	clk_put(pllb);
1838c2ecf20Sopenharmony_ci	if (ret != 0) {
1848c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to set MCLK parent\n");
1858c2ecf20Sopenharmony_ci		goto err_mclk;
1868c2ecf20Sopenharmony_ci	}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	clk_set_rate(mclk, MCLK_RATE);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	card->dev = &pdev->dev;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	/* Parse device node info */
1938c2ecf20Sopenharmony_ci	ret = snd_soc_of_parse_card_name(card, "atmel,model");
1948c2ecf20Sopenharmony_ci	if (ret)
1958c2ecf20Sopenharmony_ci		goto err;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	ret = snd_soc_of_parse_audio_routing(card,
1988c2ecf20Sopenharmony_ci		"atmel,audio-routing");
1998c2ecf20Sopenharmony_ci	if (ret)
2008c2ecf20Sopenharmony_ci		goto err;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	/* Parse codec info */
2038c2ecf20Sopenharmony_ci	at91sam9g20ek_dai.codecs->name = NULL;
2048c2ecf20Sopenharmony_ci	codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
2058c2ecf20Sopenharmony_ci	if (!codec_np) {
2068c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "codec info missing\n");
2078c2ecf20Sopenharmony_ci		return -EINVAL;
2088c2ecf20Sopenharmony_ci	}
2098c2ecf20Sopenharmony_ci	at91sam9g20ek_dai.codecs->of_node = codec_np;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	/* Parse dai and platform info */
2128c2ecf20Sopenharmony_ci	at91sam9g20ek_dai.cpus->dai_name = NULL;
2138c2ecf20Sopenharmony_ci	at91sam9g20ek_dai.platforms->name = NULL;
2148c2ecf20Sopenharmony_ci	cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
2158c2ecf20Sopenharmony_ci	if (!cpu_np) {
2168c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "dai and pcm info missing\n");
2178c2ecf20Sopenharmony_ci		of_node_put(codec_np);
2188c2ecf20Sopenharmony_ci		return -EINVAL;
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci	at91sam9g20ek_dai.cpus->of_node = cpu_np;
2218c2ecf20Sopenharmony_ci	at91sam9g20ek_dai.platforms->of_node = cpu_np;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	of_node_put(codec_np);
2248c2ecf20Sopenharmony_ci	of_node_put(cpu_np);
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	ret = snd_soc_register_card(card);
2278c2ecf20Sopenharmony_ci	if (ret) {
2288c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "snd_soc_register_card() failed\n");
2298c2ecf20Sopenharmony_ci	}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	return ret;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_cierr_mclk:
2348c2ecf20Sopenharmony_ci	clk_put(mclk);
2358c2ecf20Sopenharmony_ci	mclk = NULL;
2368c2ecf20Sopenharmony_cierr:
2378c2ecf20Sopenharmony_ci	atmel_ssc_put_audio(0);
2388c2ecf20Sopenharmony_ci	return ret;
2398c2ecf20Sopenharmony_ci}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_cistatic int at91sam9g20ek_audio_remove(struct platform_device *pdev)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	struct snd_soc_card *card = platform_get_drvdata(pdev);
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	clk_disable(mclk);
2468c2ecf20Sopenharmony_ci	mclk = NULL;
2478c2ecf20Sopenharmony_ci	snd_soc_unregister_card(card);
2488c2ecf20Sopenharmony_ci	atmel_ssc_put_audio(0);
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	return 0;
2518c2ecf20Sopenharmony_ci}
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
2548c2ecf20Sopenharmony_cistatic const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = {
2558c2ecf20Sopenharmony_ci	{ .compatible = "atmel,at91sam9g20ek-wm8731-audio", },
2568c2ecf20Sopenharmony_ci	{ }
2578c2ecf20Sopenharmony_ci};
2588c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids);
2598c2ecf20Sopenharmony_ci#endif
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic struct platform_driver at91sam9g20ek_audio_driver = {
2628c2ecf20Sopenharmony_ci	.driver = {
2638c2ecf20Sopenharmony_ci		.name	= "at91sam9g20ek-audio",
2648c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
2658c2ecf20Sopenharmony_ci	},
2668c2ecf20Sopenharmony_ci	.probe	= at91sam9g20ek_audio_probe,
2678c2ecf20Sopenharmony_ci	.remove	= at91sam9g20ek_audio_remove,
2688c2ecf20Sopenharmony_ci};
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cimodule_platform_driver(at91sam9g20ek_audio_driver);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci/* Module information */
2738c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
2748c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
2758c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:at91sam9g20ek-audio");
2768c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
277