xref: /openbmc/linux/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
1199d035bSKuninori Morimoto // SPDX-License-Identifier: GPL-2.0
235dc8aabSLaurent Pinchart /*
335dc8aabSLaurent Pinchart  * dw-hdmi-i2s-audio.c
435dc8aabSLaurent Pinchart  *
581aa368cSKuninori Morimoto  * Copyright (c) 2017 Renesas Solutions Corp.
681aa368cSKuninori Morimoto  * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
735dc8aabSLaurent Pinchart  */
8428747aeSSam Ravnborg 
9428747aeSSam Ravnborg #include <linux/dma-mapping.h>
10428747aeSSam Ravnborg #include <linux/module.h>
11428747aeSSam Ravnborg 
1235dc8aabSLaurent Pinchart #include <drm/bridge/dw_hdmi.h>
13fc1ca6e0SJerome Brunet #include <drm/drm_crtc.h>
1435dc8aabSLaurent Pinchart 
1535dc8aabSLaurent Pinchart #include <sound/hdmi-codec.h>
1635dc8aabSLaurent Pinchart 
1735dc8aabSLaurent Pinchart #include "dw-hdmi.h"
1835dc8aabSLaurent Pinchart #include "dw-hdmi-audio.h"
1935dc8aabSLaurent Pinchart 
2035dc8aabSLaurent Pinchart #define DRIVER_NAME "dw-hdmi-i2s-audio"
2135dc8aabSLaurent Pinchart 
hdmi_write(struct dw_hdmi_i2s_audio_data * audio,u8 val,int offset)2235dc8aabSLaurent Pinchart static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio,
2335dc8aabSLaurent Pinchart 			      u8 val, int offset)
2435dc8aabSLaurent Pinchart {
2535dc8aabSLaurent Pinchart 	struct dw_hdmi *hdmi = audio->hdmi;
2635dc8aabSLaurent Pinchart 
2735dc8aabSLaurent Pinchart 	audio->write(hdmi, val, offset);
2835dc8aabSLaurent Pinchart }
2935dc8aabSLaurent Pinchart 
hdmi_read(struct dw_hdmi_i2s_audio_data * audio,int offset)3035dc8aabSLaurent Pinchart static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset)
3135dc8aabSLaurent Pinchart {
3235dc8aabSLaurent Pinchart 	struct dw_hdmi *hdmi = audio->hdmi;
3335dc8aabSLaurent Pinchart 
3435dc8aabSLaurent Pinchart 	return audio->read(hdmi, offset);
3535dc8aabSLaurent Pinchart }
3635dc8aabSLaurent Pinchart 
dw_hdmi_i2s_hw_params(struct device * dev,void * data,struct hdmi_codec_daifmt * fmt,struct hdmi_codec_params * hparms)3735dc8aabSLaurent Pinchart static int dw_hdmi_i2s_hw_params(struct device *dev, void *data,
3835dc8aabSLaurent Pinchart 				 struct hdmi_codec_daifmt *fmt,
3935dc8aabSLaurent Pinchart 				 struct hdmi_codec_params *hparms)
4035dc8aabSLaurent Pinchart {
4135dc8aabSLaurent Pinchart 	struct dw_hdmi_i2s_audio_data *audio = data;
4235dc8aabSLaurent Pinchart 	struct dw_hdmi *hdmi = audio->hdmi;
4335dc8aabSLaurent Pinchart 	u8 conf0 = 0;
4435dc8aabSLaurent Pinchart 	u8 conf1 = 0;
4535dc8aabSLaurent Pinchart 	u8 inputclkfs = 0;
4635dc8aabSLaurent Pinchart 
4735dc8aabSLaurent Pinchart 	/* it cares I2S only */
489f1c8677SMark Brown 	if (fmt->bit_clk_provider | fmt->frame_clk_provider) {
498067f62bSJerome Brunet 		dev_err(dev, "unsupported clock settings\n");
5035dc8aabSLaurent Pinchart 		return -EINVAL;
5135dc8aabSLaurent Pinchart 	}
5235dc8aabSLaurent Pinchart 
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 
5735dc8aabSLaurent Pinchart 	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 	}
7235dc8aabSLaurent Pinchart 
7335dc8aabSLaurent Pinchart 	switch (hparms->sample_width) {
7435dc8aabSLaurent Pinchart 	case 16:
7535dc8aabSLaurent Pinchart 		conf1 = HDMI_AUD_CONF1_WIDTH_16;
7635dc8aabSLaurent Pinchart 		break;
7735dc8aabSLaurent Pinchart 	case 24:
7835dc8aabSLaurent Pinchart 	case 32:
7935dc8aabSLaurent Pinchart 		conf1 = HDMI_AUD_CONF1_WIDTH_24;
8035dc8aabSLaurent Pinchart 		break;
8135dc8aabSLaurent Pinchart 	}
8235dc8aabSLaurent Pinchart 
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 
10435dc8aabSLaurent Pinchart 	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);
10835dc8aabSLaurent Pinchart 
10935dc8aabSLaurent Pinchart 	hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS);
11035dc8aabSLaurent Pinchart 	hdmi_write(audio, conf0, HDMI_AUD_CONF0);
11135dc8aabSLaurent Pinchart 	hdmi_write(audio, conf1, HDMI_AUD_CONF1);
11235dc8aabSLaurent Pinchart 
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 
12135dc8aabSLaurent Pinchart 	dw_hdmi_audio_enable(hdmi);
12235dc8aabSLaurent Pinchart 
12335dc8aabSLaurent Pinchart 	return 0;
12435dc8aabSLaurent Pinchart }
12535dc8aabSLaurent Pinchart 
dw_hdmi_i2s_audio_shutdown(struct device * dev,void * data)12635dc8aabSLaurent Pinchart static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data)
12735dc8aabSLaurent Pinchart {
12835dc8aabSLaurent Pinchart 	struct dw_hdmi_i2s_audio_data *audio = data;
12935dc8aabSLaurent Pinchart 	struct dw_hdmi *hdmi = audio->hdmi;
13035dc8aabSLaurent Pinchart 
13135dc8aabSLaurent Pinchart 	dw_hdmi_audio_disable(hdmi);
13235dc8aabSLaurent Pinchart }
13335dc8aabSLaurent Pinchart 
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)150e3839bd6SKuninori Morimoto static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
151e3839bd6SKuninori Morimoto 				  struct device_node *endpoint)
152e3839bd6SKuninori Morimoto {
153e3839bd6SKuninori Morimoto 	struct of_endpoint of_ep;
154e3839bd6SKuninori Morimoto 	int ret;
155e3839bd6SKuninori Morimoto 
156e3839bd6SKuninori Morimoto 	ret = of_graph_parse_endpoint(endpoint, &of_ep);
157e3839bd6SKuninori Morimoto 	if (ret < 0)
158e3839bd6SKuninori Morimoto 		return ret;
159e3839bd6SKuninori Morimoto 
160e3839bd6SKuninori Morimoto 	/*
161e3839bd6SKuninori Morimoto 	 * HDMI sound should be located as reg = <2>
162e3839bd6SKuninori Morimoto 	 * Then, it is sound port 0
163e3839bd6SKuninori Morimoto 	 */
164e3839bd6SKuninori Morimoto 	if (of_ep.port == 2)
165e3839bd6SKuninori Morimoto 		return 0;
166e3839bd6SKuninori Morimoto 
167e3839bd6SKuninori Morimoto 	return -EINVAL;
168e3839bd6SKuninori Morimoto }
169e3839bd6SKuninori Morimoto 
dw_hdmi_i2s_hook_plugged_cb(struct device * dev,void * data,hdmi_codec_plugged_cb fn,struct device * codec_dev)170a9c82d63SCheng-Yi Chiang static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data,
171a9c82d63SCheng-Yi Chiang 				       hdmi_codec_plugged_cb fn,
172a9c82d63SCheng-Yi Chiang 				       struct device *codec_dev)
173a9c82d63SCheng-Yi Chiang {
174a9c82d63SCheng-Yi Chiang 	struct dw_hdmi_i2s_audio_data *audio = data;
175a9c82d63SCheng-Yi Chiang 	struct dw_hdmi *hdmi = audio->hdmi;
176a9c82d63SCheng-Yi Chiang 
177a9c82d63SCheng-Yi Chiang 	return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev);
178a9c82d63SCheng-Yi Chiang }
179a9c82d63SCheng-Yi Chiang 
180f3d52908SRikard Falkeborn static const struct hdmi_codec_ops dw_hdmi_i2s_ops = {
18135dc8aabSLaurent Pinchart 	.hw_params	= dw_hdmi_i2s_hw_params,
182c41784b0SCheng-Yi Chiang 	.audio_startup  = dw_hdmi_i2s_audio_startup,
18335dc8aabSLaurent Pinchart 	.audio_shutdown	= dw_hdmi_i2s_audio_shutdown,
184fc1ca6e0SJerome Brunet 	.get_eld	= dw_hdmi_i2s_get_eld,
185e3839bd6SKuninori Morimoto 	.get_dai_id	= dw_hdmi_i2s_get_dai_id,
186a9c82d63SCheng-Yi Chiang 	.hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb,
18735dc8aabSLaurent Pinchart };
18835dc8aabSLaurent Pinchart 
snd_dw_hdmi_probe(struct platform_device * pdev)18935dc8aabSLaurent Pinchart static int snd_dw_hdmi_probe(struct platform_device *pdev)
19035dc8aabSLaurent Pinchart {
19135dc8aabSLaurent Pinchart 	struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data;
19235dc8aabSLaurent Pinchart 	struct platform_device_info pdevinfo;
19335dc8aabSLaurent Pinchart 	struct hdmi_codec_pdata pdata;
19435dc8aabSLaurent Pinchart 	struct platform_device *platform;
19535dc8aabSLaurent Pinchart 
19654650eb1SKuninori Morimoto 	memset(&pdata, 0, sizeof(pdata));
19735dc8aabSLaurent Pinchart 	pdata.ops		= &dw_hdmi_i2s_ops;
19835dc8aabSLaurent Pinchart 	pdata.i2s		= 1;
19917a1e555SJerome Brunet 	pdata.max_i2s_channels	= 8;
20035dc8aabSLaurent Pinchart 	pdata.data		= audio;
20135dc8aabSLaurent Pinchart 
20235dc8aabSLaurent Pinchart 	memset(&pdevinfo, 0, sizeof(pdevinfo));
20335dc8aabSLaurent Pinchart 	pdevinfo.parent		= pdev->dev.parent;
20435dc8aabSLaurent Pinchart 	pdevinfo.id		= PLATFORM_DEVID_AUTO;
20535dc8aabSLaurent Pinchart 	pdevinfo.name		= HDMI_CODEC_DRV_NAME;
20635dc8aabSLaurent Pinchart 	pdevinfo.data		= &pdata;
20735dc8aabSLaurent Pinchart 	pdevinfo.size_data	= sizeof(pdata);
20835dc8aabSLaurent Pinchart 	pdevinfo.dma_mask	= DMA_BIT_MASK(32);
20935dc8aabSLaurent Pinchart 
21035dc8aabSLaurent Pinchart 	platform = platform_device_register_full(&pdevinfo);
21135dc8aabSLaurent Pinchart 	if (IS_ERR(platform))
21235dc8aabSLaurent Pinchart 		return PTR_ERR(platform);
21335dc8aabSLaurent Pinchart 
21435dc8aabSLaurent Pinchart 	dev_set_drvdata(&pdev->dev, platform);
21535dc8aabSLaurent Pinchart 
21635dc8aabSLaurent Pinchart 	return 0;
21735dc8aabSLaurent Pinchart }
21835dc8aabSLaurent Pinchart 
snd_dw_hdmi_remove(struct platform_device * pdev)219*ed58ee12SUwe Kleine-König static void snd_dw_hdmi_remove(struct platform_device *pdev)
22035dc8aabSLaurent Pinchart {
22135dc8aabSLaurent Pinchart 	struct platform_device *platform = dev_get_drvdata(&pdev->dev);
22235dc8aabSLaurent Pinchart 
22335dc8aabSLaurent Pinchart 	platform_device_unregister(platform);
22435dc8aabSLaurent Pinchart }
22535dc8aabSLaurent Pinchart 
22635dc8aabSLaurent Pinchart static struct platform_driver snd_dw_hdmi_driver = {
22735dc8aabSLaurent Pinchart 	.probe	= snd_dw_hdmi_probe,
228*ed58ee12SUwe Kleine-König 	.remove_new = snd_dw_hdmi_remove,
22935dc8aabSLaurent Pinchart 	.driver	= {
23035dc8aabSLaurent Pinchart 		.name = DRIVER_NAME,
23135dc8aabSLaurent Pinchart 	},
23235dc8aabSLaurent Pinchart };
23335dc8aabSLaurent Pinchart module_platform_driver(snd_dw_hdmi_driver);
23435dc8aabSLaurent Pinchart 
23535dc8aabSLaurent Pinchart MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
23635dc8aabSLaurent Pinchart MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface");
23735dc8aabSLaurent Pinchart MODULE_LICENSE("GPL v2");
23835dc8aabSLaurent Pinchart MODULE_ALIAS("platform:" DRIVER_NAME);
239