19952f691SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
286059653Szhengxing /*
386059653Szhengxing  * Rockchip machine ASoC driver for boards using a RT5645/RT5650 CODEC.
486059653Szhengxing  *
586059653Szhengxing  * Copyright (c) 2015, ROCKCHIP CORPORATION.  All rights reserved.
686059653Szhengxing  */
786059653Szhengxing 
886059653Szhengxing #include <linux/module.h>
986059653Szhengxing #include <linux/platform_device.h>
1086059653Szhengxing #include <linux/slab.h>
1186059653Szhengxing #include <linux/gpio.h>
1286059653Szhengxing #include <linux/of_gpio.h>
1386059653Szhengxing #include <linux/delay.h>
1486059653Szhengxing #include <sound/core.h>
1586059653Szhengxing #include <sound/jack.h>
1686059653Szhengxing #include <sound/pcm.h>
1786059653Szhengxing #include <sound/pcm_params.h>
1886059653Szhengxing #include <sound/soc.h>
1986059653Szhengxing #include "rockchip_i2s.h"
2079223bf1SKuninori Morimoto #include "../codecs/rt5645.h"
2186059653Szhengxing 
2286059653Szhengxing #define DRV_NAME "rockchip-snd-rt5645"
2386059653Szhengxing 
2486059653Szhengxing static struct snd_soc_jack headset_jack;
2586059653Szhengxing 
2686059653Szhengxing static const struct snd_soc_dapm_widget rk_dapm_widgets[] = {
2786059653Szhengxing 	SND_SOC_DAPM_HP("Headphones", NULL),
2886059653Szhengxing 	SND_SOC_DAPM_SPK("Speakers", NULL),
2986059653Szhengxing 	SND_SOC_DAPM_MIC("Headset Mic", NULL),
3086059653Szhengxing 	SND_SOC_DAPM_MIC("Int Mic", NULL),
3186059653Szhengxing };
3286059653Szhengxing 
3386059653Szhengxing static const struct snd_soc_dapm_route rk_audio_map[] = {
3486059653Szhengxing 	/* Input Lines */
3586059653Szhengxing 	{"DMIC L2", NULL, "Int Mic"},
3686059653Szhengxing 	{"DMIC R2", NULL, "Int Mic"},
3786059653Szhengxing 	{"RECMIXL", NULL, "Headset Mic"},
3886059653Szhengxing 	{"RECMIXR", NULL, "Headset Mic"},
3986059653Szhengxing 
4086059653Szhengxing 	/* Output Lines */
4186059653Szhengxing 	{"Headphones", NULL, "HPOR"},
4286059653Szhengxing 	{"Headphones", NULL, "HPOL"},
4386059653Szhengxing 	{"Speakers", NULL, "SPOL"},
4486059653Szhengxing 	{"Speakers", NULL, "SPOR"},
4586059653Szhengxing };
4686059653Szhengxing 
4786059653Szhengxing static const struct snd_kcontrol_new rk_mc_controls[] = {
4886059653Szhengxing 	SOC_DAPM_PIN_SWITCH("Headphones"),
4986059653Szhengxing 	SOC_DAPM_PIN_SWITCH("Speakers"),
5086059653Szhengxing 	SOC_DAPM_PIN_SWITCH("Headset Mic"),
5186059653Szhengxing 	SOC_DAPM_PIN_SWITCH("Int Mic"),
5286059653Szhengxing };
5386059653Szhengxing 
5486059653Szhengxing static int rk_aif1_hw_params(struct snd_pcm_substream *substream,
5586059653Szhengxing 			     struct snd_pcm_hw_params *params)
5686059653Szhengxing {
5786059653Szhengxing 	int ret = 0;
5886059653Szhengxing 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
5986059653Szhengxing 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
6086059653Szhengxing 	struct snd_soc_dai *codec_dai = rtd->codec_dai;
6186059653Szhengxing 	int mclk;
6286059653Szhengxing 
6386059653Szhengxing 	switch (params_rate(params)) {
6486059653Szhengxing 	case 8000:
6586059653Szhengxing 	case 16000:
663a3b070dSCaesar Wang 	case 24000:
673a3b070dSCaesar Wang 	case 32000:
6886059653Szhengxing 	case 48000:
693a3b070dSCaesar Wang 	case 64000:
7086059653Szhengxing 	case 96000:
7186059653Szhengxing 		mclk = 12288000;
7286059653Szhengxing 		break;
733a3b070dSCaesar Wang 	case 11025:
743a3b070dSCaesar Wang 	case 22050:
7586059653Szhengxing 	case 44100:
763a3b070dSCaesar Wang 	case 88200:
7786059653Szhengxing 		mclk = 11289600;
7886059653Szhengxing 		break;
7986059653Szhengxing 	default:
8086059653Szhengxing 		return -EINVAL;
8186059653Szhengxing 	}
8286059653Szhengxing 
8386059653Szhengxing 	ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
8486059653Szhengxing 				     SND_SOC_CLOCK_OUT);
8586059653Szhengxing 	if (ret < 0) {
8686059653Szhengxing 		dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret);
8786059653Szhengxing 		return ret;
8886059653Szhengxing 	}
8986059653Szhengxing 
9086059653Szhengxing 	ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
9186059653Szhengxing 				     SND_SOC_CLOCK_IN);
9286059653Szhengxing 	if (ret < 0) {
9386059653Szhengxing 		dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret);
9486059653Szhengxing 		return ret;
9586059653Szhengxing 	}
9686059653Szhengxing 
9786059653Szhengxing 	return ret;
9886059653Szhengxing }
9986059653Szhengxing 
10086059653Szhengxing static int rk_init(struct snd_soc_pcm_runtime *runtime)
10186059653Szhengxing {
10286059653Szhengxing 	struct snd_soc_card *card = runtime->card;
10386059653Szhengxing 	int ret;
10486059653Szhengxing 
10586059653Szhengxing 	/* Enable Headset and 4 Buttons Jack detection */
10686059653Szhengxing 	ret = snd_soc_card_jack_new(card, "Headset Jack",
10786059653Szhengxing 				    SND_JACK_HEADPHONE | SND_JACK_MICROPHONE |
10886059653Szhengxing 				    SND_JACK_BTN_0 | SND_JACK_BTN_1 |
10986059653Szhengxing 				    SND_JACK_BTN_2 | SND_JACK_BTN_3,
11086059653Szhengxing 				    &headset_jack, NULL, 0);
111f8ce2000SXing Zheng 	if (ret) {
11286059653Szhengxing 		dev_err(card->dev, "New Headset Jack failed! (%d)\n", ret);
11386059653Szhengxing 		return ret;
11486059653Szhengxing 	}
11586059653Szhengxing 
11679223bf1SKuninori Morimoto 	return rt5645_set_jack_detect(runtime->codec_dai->component,
11786059653Szhengxing 				     &headset_jack,
11886059653Szhengxing 				     &headset_jack,
11986059653Szhengxing 				     &headset_jack);
12086059653Szhengxing }
12186059653Szhengxing 
122705e9994SJulia Lawall static const struct snd_soc_ops rk_aif1_ops = {
12386059653Szhengxing 	.hw_params = rk_aif1_hw_params,
12486059653Szhengxing };
12586059653Szhengxing 
12686059653Szhengxing static struct snd_soc_dai_link rk_dailink = {
12786059653Szhengxing 	.name = "rt5645",
12886059653Szhengxing 	.stream_name = "rt5645 PCM",
12986059653Szhengxing 	.codec_dai_name = "rt5645-aif1",
13086059653Szhengxing 	.init = rk_init,
13186059653Szhengxing 	.ops = &rk_aif1_ops,
13286059653Szhengxing 	/* set rt5645 as slave */
13386059653Szhengxing 	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
13486059653Szhengxing 		SND_SOC_DAIFMT_CBS_CFS,
13586059653Szhengxing };
13686059653Szhengxing 
13786059653Szhengxing static struct snd_soc_card snd_soc_card_rk = {
13886059653Szhengxing 	.name = "I2S-RT5650",
13954d8697fSAxel Lin 	.owner = THIS_MODULE,
14086059653Szhengxing 	.dai_link = &rk_dailink,
14186059653Szhengxing 	.num_links = 1,
14286059653Szhengxing 	.dapm_widgets = rk_dapm_widgets,
14386059653Szhengxing 	.num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets),
14486059653Szhengxing 	.dapm_routes = rk_audio_map,
14586059653Szhengxing 	.num_dapm_routes = ARRAY_SIZE(rk_audio_map),
14686059653Szhengxing 	.controls = rk_mc_controls,
14786059653Szhengxing 	.num_controls = ARRAY_SIZE(rk_mc_controls),
14886059653Szhengxing };
14986059653Szhengxing 
15086059653Szhengxing static int snd_rk_mc_probe(struct platform_device *pdev)
15186059653Szhengxing {
15286059653Szhengxing 	int ret = 0;
15386059653Szhengxing 	struct snd_soc_card *card = &snd_soc_card_rk;
15486059653Szhengxing 	struct device_node *np = pdev->dev.of_node;
15586059653Szhengxing 
15686059653Szhengxing 	/* register the soc card */
15786059653Szhengxing 	card->dev = &pdev->dev;
15886059653Szhengxing 
15986059653Szhengxing 	rk_dailink.codec_of_node = of_parse_phandle(np,
16086059653Szhengxing 			"rockchip,audio-codec", 0);
16186059653Szhengxing 	if (!rk_dailink.codec_of_node) {
16286059653Szhengxing 		dev_err(&pdev->dev,
16386059653Szhengxing 			"Property 'rockchip,audio-codec' missing or invalid\n");
16486059653Szhengxing 		return -EINVAL;
16586059653Szhengxing 	}
16686059653Szhengxing 
16786059653Szhengxing 	rk_dailink.cpu_of_node = of_parse_phandle(np,
16886059653Szhengxing 			"rockchip,i2s-controller", 0);
16986059653Szhengxing 	if (!rk_dailink.cpu_of_node) {
17086059653Szhengxing 		dev_err(&pdev->dev,
17186059653Szhengxing 			"Property 'rockchip,i2s-controller' missing or invalid\n");
172a56df73bSAlexey Khoroshilov 		ret = -EINVAL;
173a56df73bSAlexey Khoroshilov 		goto put_codec_of_node;
17486059653Szhengxing 	}
17586059653Szhengxing 
17686059653Szhengxing 	rk_dailink.platform_of_node = rk_dailink.cpu_of_node;
17786059653Szhengxing 
17886059653Szhengxing 	ret = snd_soc_of_parse_card_name(card, "rockchip,model");
17986059653Szhengxing 	if (ret) {
18086059653Szhengxing 		dev_err(&pdev->dev,
18186059653Szhengxing 			"Soc parse card name failed %d\n", ret);
182a56df73bSAlexey Khoroshilov 		goto put_cpu_of_node;
18386059653Szhengxing 	}
18486059653Szhengxing 
18586059653Szhengxing 	ret = devm_snd_soc_register_card(&pdev->dev, card);
18686059653Szhengxing 	if (ret) {
18786059653Szhengxing 		dev_err(&pdev->dev,
18886059653Szhengxing 			"Soc register card failed %d\n", ret);
189a56df73bSAlexey Khoroshilov 		goto put_cpu_of_node;
19086059653Szhengxing 	}
19186059653Szhengxing 
19286059653Szhengxing 	return ret;
193a56df73bSAlexey Khoroshilov 
194a56df73bSAlexey Khoroshilov put_cpu_of_node:
195a56df73bSAlexey Khoroshilov 	of_node_put(rk_dailink.cpu_of_node);
196a56df73bSAlexey Khoroshilov 	rk_dailink.cpu_of_node = NULL;
197a56df73bSAlexey Khoroshilov put_codec_of_node:
198a56df73bSAlexey Khoroshilov 	of_node_put(rk_dailink.codec_of_node);
199a56df73bSAlexey Khoroshilov 	rk_dailink.codec_of_node = NULL;
200a56df73bSAlexey Khoroshilov 
201a56df73bSAlexey Khoroshilov 	return ret;
202a56df73bSAlexey Khoroshilov }
203a56df73bSAlexey Khoroshilov 
204a56df73bSAlexey Khoroshilov static int snd_rk_mc_remove(struct platform_device *pdev)
205a56df73bSAlexey Khoroshilov {
206a56df73bSAlexey Khoroshilov 	of_node_put(rk_dailink.cpu_of_node);
207a56df73bSAlexey Khoroshilov 	rk_dailink.cpu_of_node = NULL;
208a56df73bSAlexey Khoroshilov 	of_node_put(rk_dailink.codec_of_node);
209a56df73bSAlexey Khoroshilov 	rk_dailink.codec_of_node = NULL;
210a56df73bSAlexey Khoroshilov 
211a56df73bSAlexey Khoroshilov 	return 0;
21286059653Szhengxing }
21386059653Szhengxing 
21486059653Szhengxing static const struct of_device_id rockchip_rt5645_of_match[] = {
21586059653Szhengxing 	{ .compatible = "rockchip,rockchip-audio-rt5645", },
21686059653Szhengxing 	{},
21786059653Szhengxing };
21886059653Szhengxing 
21986059653Szhengxing MODULE_DEVICE_TABLE(of, rockchip_rt5645_of_match);
22086059653Szhengxing 
22186059653Szhengxing static struct platform_driver snd_rk_mc_driver = {
22286059653Szhengxing 	.probe = snd_rk_mc_probe,
223a56df73bSAlexey Khoroshilov 	.remove = snd_rk_mc_remove,
22486059653Szhengxing 	.driver = {
22586059653Szhengxing 		.name = DRV_NAME,
22686059653Szhengxing 		.pm = &snd_soc_pm_ops,
22786059653Szhengxing 		.of_match_table = rockchip_rt5645_of_match,
22886059653Szhengxing 	},
22986059653Szhengxing };
23086059653Szhengxing 
23186059653Szhengxing module_platform_driver(snd_rk_mc_driver);
23286059653Szhengxing 
23386059653Szhengxing MODULE_AUTHOR("Xing Zheng <zhengxing@rock-chips.com>");
23486059653Szhengxing MODULE_DESCRIPTION("Rockchip rt5645 machine ASoC driver");
23586059653Szhengxing MODULE_LICENSE("GPL v2");
23686059653Szhengxing MODULE_ALIAS("platform:" DRV_NAME);
237