xref: /linux/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c (revision 946661e3bef8efa11ba8079d4ebafe6fc3b0aaad)
1199d035bSKuninori Morimoto // SPDX-License-Identifier: GPL-2.0
22761ba6cSKuninori Morimoto /*
32761ba6cSKuninori Morimoto  * dw-hdmi-i2s-audio.c
42761ba6cSKuninori Morimoto  *
581aa368cSKuninori Morimoto  * Copyright (c) 2017 Renesas Solutions Corp.
681aa368cSKuninori Morimoto  * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
72761ba6cSKuninori Morimoto  */
8428747aeSSam Ravnborg 
9428747aeSSam Ravnborg #include <linux/dma-mapping.h>
10428747aeSSam Ravnborg #include <linux/module.h>
11428747aeSSam Ravnborg 
122761ba6cSKuninori Morimoto #include <drm/bridge/dw_hdmi.h>
13fc1ca6e0SJerome Brunet #include <drm/drm_crtc.h>
142761ba6cSKuninori Morimoto 
152761ba6cSKuninori Morimoto #include <sound/hdmi-codec.h>
162761ba6cSKuninori Morimoto 
172761ba6cSKuninori Morimoto #include "dw-hdmi.h"
182761ba6cSKuninori Morimoto #include "dw-hdmi-audio.h"
192761ba6cSKuninori Morimoto 
202761ba6cSKuninori Morimoto #define DRIVER_NAME "dw-hdmi-i2s-audio"
212761ba6cSKuninori Morimoto 
hdmi_write(struct dw_hdmi_i2s_audio_data * audio,u8 val,int offset)222761ba6cSKuninori Morimoto static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio,
232761ba6cSKuninori Morimoto 			      u8 val, int offset)
242761ba6cSKuninori Morimoto {
252761ba6cSKuninori Morimoto 	struct dw_hdmi *hdmi = audio->hdmi;
262761ba6cSKuninori Morimoto 
272761ba6cSKuninori Morimoto 	audio->write(hdmi, val, offset);
282761ba6cSKuninori Morimoto }
292761ba6cSKuninori Morimoto 
hdmi_read(struct dw_hdmi_i2s_audio_data * audio,int offset)302761ba6cSKuninori Morimoto static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset)
312761ba6cSKuninori Morimoto {
322761ba6cSKuninori Morimoto 	struct dw_hdmi *hdmi = audio->hdmi;
332761ba6cSKuninori Morimoto 
342761ba6cSKuninori Morimoto 	return audio->read(hdmi, offset);
352761ba6cSKuninori Morimoto }
362761ba6cSKuninori Morimoto 
dw_hdmi_i2s_hw_params(struct device * dev,void * data,struct hdmi_codec_daifmt * fmt,struct hdmi_codec_params * hparms)372761ba6cSKuninori Morimoto static int dw_hdmi_i2s_hw_params(struct device *dev, void *data,
382761ba6cSKuninori Morimoto 				 struct hdmi_codec_daifmt *fmt,
392761ba6cSKuninori Morimoto 				 struct hdmi_codec_params *hparms)
402761ba6cSKuninori Morimoto {
412761ba6cSKuninori Morimoto 	struct dw_hdmi_i2s_audio_data *audio = data;
422761ba6cSKuninori Morimoto 	struct dw_hdmi *hdmi = audio->hdmi;
432761ba6cSKuninori Morimoto 	u8 conf0 = 0;
442761ba6cSKuninori Morimoto 	u8 conf1 = 0;
452761ba6cSKuninori Morimoto 	u8 inputclkfs = 0;
462761ba6cSKuninori Morimoto 
472761ba6cSKuninori Morimoto 	/* it cares I2S only */
489f1c8677SMark Brown 	if (fmt->bit_clk_provider | fmt->frame_clk_provider) {
498067f62bSJerome Brunet 		dev_err(dev, "unsupported clock settings\n");
502761ba6cSKuninori Morimoto 		return -EINVAL;
512761ba6cSKuninori Morimoto 	}
522761ba6cSKuninori Morimoto 
5346cecde3SJerome Brunet 	/* Reset the FIFOs before applying new params */
5446cecde3SJerome Brunet 	hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0);
5546cecde3SJerome Brunet 	hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ);
5646cecde3SJerome Brunet 
572761ba6cSKuninori Morimoto 	inputclkfs	= HDMI_AUD_INPUTCLKFS_64FS;
5843e88f67SJerome Brunet 	conf0		= (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0);
5943e88f67SJerome Brunet 
6043e88f67SJerome Brunet 	/* Enable the required i2s lanes */
6143e88f67SJerome Brunet 	switch (hparms->channels) {
6243e88f67SJerome Brunet 	case 7 ... 8:
6343e88f67SJerome Brunet 		conf0 |= HDMI_AUD_CONF0_I2S_EN3;
64df561f66SGustavo A. R. Silva 		fallthrough;
6543e88f67SJerome Brunet 	case 5 ... 6:
6643e88f67SJerome Brunet 		conf0 |= HDMI_AUD_CONF0_I2S_EN2;
67df561f66SGustavo A. R. Silva 		fallthrough;
6843e88f67SJerome Brunet 	case 3 ... 4:
6943e88f67SJerome Brunet 		conf0 |= HDMI_AUD_CONF0_I2S_EN1;
7043e88f67SJerome Brunet 		/* Fall-thru */
7143e88f67SJerome Brunet 	}
722761ba6cSKuninori Morimoto 
732761ba6cSKuninori Morimoto 	switch (hparms->sample_width) {
742761ba6cSKuninori Morimoto 	case 16:
752761ba6cSKuninori Morimoto 		conf1 = HDMI_AUD_CONF1_WIDTH_16;
762761ba6cSKuninori Morimoto 		break;
772761ba6cSKuninori Morimoto 	case 24:
782761ba6cSKuninori Morimoto 	case 32:
792761ba6cSKuninori Morimoto 		conf1 = HDMI_AUD_CONF1_WIDTH_24;
802761ba6cSKuninori Morimoto 		break;
812761ba6cSKuninori Morimoto 	}
822761ba6cSKuninori Morimoto 
838067f62bSJerome Brunet 	switch (fmt->fmt) {
848067f62bSJerome Brunet 	case HDMI_I2S:
858067f62bSJerome Brunet 		conf1 |= HDMI_AUD_CONF1_MODE_I2S;
868067f62bSJerome Brunet 		break;
878067f62bSJerome Brunet 	case HDMI_RIGHT_J:
888067f62bSJerome Brunet 		conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J;
898067f62bSJerome Brunet 		break;
908067f62bSJerome Brunet 	case HDMI_LEFT_J:
918067f62bSJerome Brunet 		conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J;
928067f62bSJerome Brunet 		break;
938067f62bSJerome Brunet 	case HDMI_DSP_A:
948067f62bSJerome Brunet 		conf1 |= HDMI_AUD_CONF1_MODE_BURST_1;
958067f62bSJerome Brunet 		break;
968067f62bSJerome Brunet 	case HDMI_DSP_B:
978067f62bSJerome Brunet 		conf1 |= HDMI_AUD_CONF1_MODE_BURST_2;
988067f62bSJerome Brunet 		break;
998067f62bSJerome Brunet 	default:
1008067f62bSJerome Brunet 		dev_err(dev, "unsupported format\n");
1018067f62bSJerome Brunet 		return -EINVAL;
1028067f62bSJerome Brunet 	}
1038067f62bSJerome Brunet 
1042761ba6cSKuninori Morimoto 	dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate);
1053250cdf9SYakir Yang 	dw_hdmi_set_channel_status(hdmi, hparms->iec.status);
10617a1e555SJerome Brunet 	dw_hdmi_set_channel_count(hdmi, hparms->channels);
1070c609885SJerome Brunet 	dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation);
1082761ba6cSKuninori Morimoto 
1092761ba6cSKuninori Morimoto 	hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS);
1102761ba6cSKuninori Morimoto 	hdmi_write(audio, conf0, HDMI_AUD_CONF0);
1112761ba6cSKuninori Morimoto 	hdmi_write(audio, conf1, HDMI_AUD_CONF1);
1122761ba6cSKuninori Morimoto 
113c41784b0SCheng-Yi Chiang 	return 0;
114c41784b0SCheng-Yi Chiang }
115c41784b0SCheng-Yi Chiang 
dw_hdmi_i2s_audio_startup(struct device * dev,void * data)116c41784b0SCheng-Yi Chiang static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data)
117c41784b0SCheng-Yi Chiang {
118c41784b0SCheng-Yi Chiang 	struct dw_hdmi_i2s_audio_data *audio = data;
119c41784b0SCheng-Yi Chiang 	struct dw_hdmi *hdmi = audio->hdmi;
120c41784b0SCheng-Yi Chiang 
1212761ba6cSKuninori Morimoto 	dw_hdmi_audio_enable(hdmi);
1222761ba6cSKuninori Morimoto 
1232761ba6cSKuninori Morimoto 	return 0;
1242761ba6cSKuninori Morimoto }
1252761ba6cSKuninori Morimoto 
dw_hdmi_i2s_audio_shutdown(struct device * dev,void * data)1262761ba6cSKuninori Morimoto static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data)
1272761ba6cSKuninori Morimoto {
1282761ba6cSKuninori Morimoto 	struct dw_hdmi_i2s_audio_data *audio = data;
1292761ba6cSKuninori Morimoto 	struct dw_hdmi *hdmi = audio->hdmi;
1302761ba6cSKuninori Morimoto 
1312761ba6cSKuninori Morimoto 	dw_hdmi_audio_disable(hdmi);
1322761ba6cSKuninori Morimoto }
1332761ba6cSKuninori Morimoto 
dw_hdmi_i2s_get_eld(struct device * dev,void * data,uint8_t * buf,size_t len)134fc1ca6e0SJerome Brunet static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
135fc1ca6e0SJerome Brunet 			       size_t len)
136fc1ca6e0SJerome Brunet {
137fc1ca6e0SJerome Brunet 	struct dw_hdmi_i2s_audio_data *audio = data;
1383f2532d6SNeil Armstrong 	u8 *eld;
139fc1ca6e0SJerome Brunet 
1403f2532d6SNeil Armstrong 	eld = audio->get_eld(audio->hdmi);
1413f2532d6SNeil Armstrong 	if (eld)
1423f2532d6SNeil Armstrong 		memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
1433f2532d6SNeil Armstrong 	else
1443f2532d6SNeil Armstrong 		/* Pass en empty ELD if connector not available */
1453f2532d6SNeil Armstrong 		memset(buf, 0, len);
1463f2532d6SNeil Armstrong 
147fc1ca6e0SJerome Brunet 	return 0;
148fc1ca6e0SJerome Brunet }
149fc1ca6e0SJerome Brunet 
dw_hdmi_i2s_get_dai_id(struct snd_soc_component * component,struct device_node * endpoint,void * data)150e3839bd6SKuninori Morimoto static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
151*6af45d7dSDmitry Baryshkov 				  struct device_node *endpoint,
152*6af45d7dSDmitry Baryshkov 				  void *data)
153e3839bd6SKuninori Morimoto {
154e3839bd6SKuninori Morimoto 	struct of_endpoint of_ep;
155e3839bd6SKuninori Morimoto 	int ret;
156e3839bd6SKuninori Morimoto 
157e3839bd6SKuninori Morimoto 	ret = of_graph_parse_endpoint(endpoint, &of_ep);
158e3839bd6SKuninori Morimoto 	if (ret < 0)
159e3839bd6SKuninori Morimoto 		return ret;
160e3839bd6SKuninori Morimoto 
161e3839bd6SKuninori Morimoto 	/*
162e3839bd6SKuninori Morimoto 	 * HDMI sound should be located as reg = <2>
163e3839bd6SKuninori Morimoto 	 * Then, it is sound port 0
164e3839bd6SKuninori Morimoto 	 */
165e3839bd6SKuninori Morimoto 	if (of_ep.port == 2)
166e3839bd6SKuninori Morimoto 		return 0;
167e3839bd6SKuninori Morimoto 
168e3839bd6SKuninori Morimoto 	return -EINVAL;
169e3839bd6SKuninori Morimoto }
170e3839bd6SKuninori Morimoto 
dw_hdmi_i2s_hook_plugged_cb(struct device * dev,void * data,hdmi_codec_plugged_cb fn,struct device * codec_dev)171a9c82d63SCheng-Yi Chiang static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data,
172a9c82d63SCheng-Yi Chiang 				       hdmi_codec_plugged_cb fn,
173a9c82d63SCheng-Yi Chiang 				       struct device *codec_dev)
174a9c82d63SCheng-Yi Chiang {
175a9c82d63SCheng-Yi Chiang 	struct dw_hdmi_i2s_audio_data *audio = data;
176a9c82d63SCheng-Yi Chiang 	struct dw_hdmi *hdmi = audio->hdmi;
177a9c82d63SCheng-Yi Chiang 
178a9c82d63SCheng-Yi Chiang 	return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev);
179a9c82d63SCheng-Yi Chiang }
180a9c82d63SCheng-Yi Chiang 
181f3d52908SRikard Falkeborn static const struct hdmi_codec_ops dw_hdmi_i2s_ops = {
1822761ba6cSKuninori Morimoto 	.hw_params	= dw_hdmi_i2s_hw_params,
183c41784b0SCheng-Yi Chiang 	.audio_startup  = dw_hdmi_i2s_audio_startup,
1842761ba6cSKuninori Morimoto 	.audio_shutdown	= dw_hdmi_i2s_audio_shutdown,
185fc1ca6e0SJerome Brunet 	.get_eld	= dw_hdmi_i2s_get_eld,
186e3839bd6SKuninori Morimoto 	.get_dai_id	= dw_hdmi_i2s_get_dai_id,
187a9c82d63SCheng-Yi Chiang 	.hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb,
1882761ba6cSKuninori Morimoto };
1892761ba6cSKuninori Morimoto 
snd_dw_hdmi_probe(struct platform_device * pdev)1902761ba6cSKuninori Morimoto static int snd_dw_hdmi_probe(struct platform_device *pdev)
1912761ba6cSKuninori Morimoto {
1922761ba6cSKuninori Morimoto 	struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data;
1932761ba6cSKuninori Morimoto 	struct platform_device_info pdevinfo;
1942761ba6cSKuninori Morimoto 	struct hdmi_codec_pdata pdata;
1952761ba6cSKuninori Morimoto 	struct platform_device *platform;
1962761ba6cSKuninori Morimoto 
19754650eb1SKuninori Morimoto 	memset(&pdata, 0, sizeof(pdata));
1982761ba6cSKuninori Morimoto 	pdata.ops		= &dw_hdmi_i2s_ops;
1992761ba6cSKuninori Morimoto 	pdata.i2s		= 1;
20017a1e555SJerome Brunet 	pdata.max_i2s_channels	= 8;
2012761ba6cSKuninori Morimoto 	pdata.data		= audio;
2022761ba6cSKuninori Morimoto 
2032761ba6cSKuninori Morimoto 	memset(&pdevinfo, 0, sizeof(pdevinfo));
2042761ba6cSKuninori Morimoto 	pdevinfo.parent		= pdev->dev.parent;
2052761ba6cSKuninori Morimoto 	pdevinfo.id		= PLATFORM_DEVID_AUTO;
2062761ba6cSKuninori Morimoto 	pdevinfo.name		= HDMI_CODEC_DRV_NAME;
2072761ba6cSKuninori Morimoto 	pdevinfo.data		= &pdata;
2082761ba6cSKuninori Morimoto 	pdevinfo.size_data	= sizeof(pdata);
2092761ba6cSKuninori Morimoto 	pdevinfo.dma_mask	= DMA_BIT_MASK(32);
2102761ba6cSKuninori Morimoto 
2112761ba6cSKuninori Morimoto 	platform = platform_device_register_full(&pdevinfo);
2122761ba6cSKuninori Morimoto 	if (IS_ERR(platform))
2132761ba6cSKuninori Morimoto 		return PTR_ERR(platform);
2142761ba6cSKuninori Morimoto 
2152761ba6cSKuninori Morimoto 	dev_set_drvdata(&pdev->dev, platform);
2162761ba6cSKuninori Morimoto 
2172761ba6cSKuninori Morimoto 	return 0;
2182761ba6cSKuninori Morimoto }
2192761ba6cSKuninori Morimoto 
snd_dw_hdmi_remove(struct platform_device * pdev)220ed58ee12SUwe Kleine-König static void snd_dw_hdmi_remove(struct platform_device *pdev)
2212761ba6cSKuninori Morimoto {
2222761ba6cSKuninori Morimoto 	struct platform_device *platform = dev_get_drvdata(&pdev->dev);
2232761ba6cSKuninori Morimoto 
2242761ba6cSKuninori Morimoto 	platform_device_unregister(platform);
2252761ba6cSKuninori Morimoto }
2262761ba6cSKuninori Morimoto 
2272761ba6cSKuninori Morimoto static struct platform_driver snd_dw_hdmi_driver = {
2282761ba6cSKuninori Morimoto 	.probe	= snd_dw_hdmi_probe,
229e70140baSLinus Torvalds 	.remove = snd_dw_hdmi_remove,
2302761ba6cSKuninori Morimoto 	.driver	= {
2312761ba6cSKuninori Morimoto 		.name = DRIVER_NAME,
2322761ba6cSKuninori Morimoto 	},
2332761ba6cSKuninori Morimoto };
2342761ba6cSKuninori Morimoto module_platform_driver(snd_dw_hdmi_driver);
2352761ba6cSKuninori Morimoto 
2362761ba6cSKuninori Morimoto MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
2372761ba6cSKuninori Morimoto MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface");
2382761ba6cSKuninori Morimoto MODULE_LICENSE("GPL v2");
2392761ba6cSKuninori Morimoto MODULE_ALIAS("platform:" DRIVER_NAME);
240