xref: /OK3568_Linux_fs/kernel/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * dw-hdmi-i2s-audio.c
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright (c) 2017 Renesas Solutions Corp.
6*4882a593Smuzhiyun  * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
7*4882a593Smuzhiyun  */
8*4882a593Smuzhiyun 
9*4882a593Smuzhiyun #include <linux/dma-mapping.h>
10*4882a593Smuzhiyun #include <linux/module.h>
11*4882a593Smuzhiyun 
12*4882a593Smuzhiyun #include <drm/bridge/dw_hdmi.h>
13*4882a593Smuzhiyun #include <drm/drm_crtc.h>
14*4882a593Smuzhiyun 
15*4882a593Smuzhiyun #include <sound/hdmi-codec.h>
16*4882a593Smuzhiyun 
17*4882a593Smuzhiyun #include "dw-hdmi.h"
18*4882a593Smuzhiyun #include "dw-hdmi-audio.h"
19*4882a593Smuzhiyun 
20*4882a593Smuzhiyun #define DRIVER_NAME "dw-hdmi-i2s-audio"
21*4882a593Smuzhiyun 
hdmi_write(struct dw_hdmi_i2s_audio_data * audio,u8 val,int offset)22*4882a593Smuzhiyun static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio,
23*4882a593Smuzhiyun 			      u8 val, int offset)
24*4882a593Smuzhiyun {
25*4882a593Smuzhiyun 	struct dw_hdmi *hdmi = audio->hdmi;
26*4882a593Smuzhiyun 
27*4882a593Smuzhiyun 	audio->write(hdmi, val, offset);
28*4882a593Smuzhiyun }
29*4882a593Smuzhiyun 
hdmi_read(struct dw_hdmi_i2s_audio_data * audio,int offset)30*4882a593Smuzhiyun static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset)
31*4882a593Smuzhiyun {
32*4882a593Smuzhiyun 	struct dw_hdmi *hdmi = audio->hdmi;
33*4882a593Smuzhiyun 
34*4882a593Smuzhiyun 	return audio->read(hdmi, offset);
35*4882a593Smuzhiyun }
36*4882a593Smuzhiyun 
dw_hdmi_i2s_hw_params(struct device * dev,void * data,struct hdmi_codec_daifmt * fmt,struct hdmi_codec_params * hparms)37*4882a593Smuzhiyun static int dw_hdmi_i2s_hw_params(struct device *dev, void *data,
38*4882a593Smuzhiyun 				 struct hdmi_codec_daifmt *fmt,
39*4882a593Smuzhiyun 				 struct hdmi_codec_params *hparms)
40*4882a593Smuzhiyun {
41*4882a593Smuzhiyun 	struct dw_hdmi_i2s_audio_data *audio = data;
42*4882a593Smuzhiyun 	struct dw_hdmi *hdmi = audio->hdmi;
43*4882a593Smuzhiyun 	u8 conf0 = 0;
44*4882a593Smuzhiyun 	u8 conf1 = 0;
45*4882a593Smuzhiyun 	u8 conf2 = 0;
46*4882a593Smuzhiyun 	u8 inputclkfs = 0;
47*4882a593Smuzhiyun 
48*4882a593Smuzhiyun 	/* it cares I2S only */
49*4882a593Smuzhiyun 	if (fmt->bit_clk_master | fmt->frame_clk_master) {
50*4882a593Smuzhiyun 		dev_err(dev, "unsupported clock settings\n");
51*4882a593Smuzhiyun 		return -EINVAL;
52*4882a593Smuzhiyun 	}
53*4882a593Smuzhiyun 
54*4882a593Smuzhiyun 	/* Reset the FIFOs before applying new params */
55*4882a593Smuzhiyun 	hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0);
56*4882a593Smuzhiyun 	hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ);
57*4882a593Smuzhiyun 
58*4882a593Smuzhiyun 	inputclkfs	= HDMI_AUD_INPUTCLKFS_64FS;
59*4882a593Smuzhiyun 	conf0		= (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0);
60*4882a593Smuzhiyun 
61*4882a593Smuzhiyun 	/* Enable the required i2s lanes */
62*4882a593Smuzhiyun 	switch (hparms->channels) {
63*4882a593Smuzhiyun 	case 7 ... 8:
64*4882a593Smuzhiyun 		conf0 |= HDMI_AUD_CONF0_I2S_EN3;
65*4882a593Smuzhiyun 		fallthrough;
66*4882a593Smuzhiyun 	case 5 ... 6:
67*4882a593Smuzhiyun 		conf0 |= HDMI_AUD_CONF0_I2S_EN2;
68*4882a593Smuzhiyun 		fallthrough;
69*4882a593Smuzhiyun 	case 3 ... 4:
70*4882a593Smuzhiyun 		conf0 |= HDMI_AUD_CONF0_I2S_EN1;
71*4882a593Smuzhiyun 		/* Fall-thru */
72*4882a593Smuzhiyun 	}
73*4882a593Smuzhiyun 
74*4882a593Smuzhiyun 	switch (hparms->sample_width) {
75*4882a593Smuzhiyun 	case 16:
76*4882a593Smuzhiyun 		conf1 = HDMI_AUD_CONF1_WIDTH_16;
77*4882a593Smuzhiyun 		break;
78*4882a593Smuzhiyun 	case 24:
79*4882a593Smuzhiyun 	case 32:
80*4882a593Smuzhiyun 		conf1 = HDMI_AUD_CONF1_WIDTH_24;
81*4882a593Smuzhiyun 		break;
82*4882a593Smuzhiyun 	}
83*4882a593Smuzhiyun 
84*4882a593Smuzhiyun 	switch (fmt->fmt) {
85*4882a593Smuzhiyun 	case HDMI_I2S:
86*4882a593Smuzhiyun 		conf1 |= HDMI_AUD_CONF1_MODE_I2S;
87*4882a593Smuzhiyun 		break;
88*4882a593Smuzhiyun 	case HDMI_RIGHT_J:
89*4882a593Smuzhiyun 		conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J;
90*4882a593Smuzhiyun 		break;
91*4882a593Smuzhiyun 	case HDMI_LEFT_J:
92*4882a593Smuzhiyun 		conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J;
93*4882a593Smuzhiyun 		break;
94*4882a593Smuzhiyun 	case HDMI_DSP_A:
95*4882a593Smuzhiyun 		conf1 |= HDMI_AUD_CONF1_MODE_BURST_1;
96*4882a593Smuzhiyun 		break;
97*4882a593Smuzhiyun 	case HDMI_DSP_B:
98*4882a593Smuzhiyun 		conf1 |= HDMI_AUD_CONF1_MODE_BURST_2;
99*4882a593Smuzhiyun 		break;
100*4882a593Smuzhiyun 	default:
101*4882a593Smuzhiyun 		dev_err(dev, "unsupported format\n");
102*4882a593Smuzhiyun 		return -EINVAL;
103*4882a593Smuzhiyun 	}
104*4882a593Smuzhiyun 
105*4882a593Smuzhiyun 	switch (fmt->bit_fmt) {
106*4882a593Smuzhiyun 	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
107*4882a593Smuzhiyun 		conf1 = HDMI_AUD_CONF1_WIDTH_21;
108*4882a593Smuzhiyun 		conf2 = (hparms->channels == 8) ? HDMI_AUD_CONF2_HBR : HDMI_AUD_CONF2_NLPCM;
109*4882a593Smuzhiyun 		break;
110*4882a593Smuzhiyun 	default:
111*4882a593Smuzhiyun 		/*
112*4882a593Smuzhiyun 		 * dw-hdmi introduced insert_pcuv bit in version 2.10a.
113*4882a593Smuzhiyun 		 * When set (1'b1), this bit enables the insertion of the PCUV
114*4882a593Smuzhiyun 		 * (Parity, Channel Status, User bit and Validity) bits on the
115*4882a593Smuzhiyun 		 * incoming audio stream (support limited to Linear PCM audio)
116*4882a593Smuzhiyun 		 */
117*4882a593Smuzhiyun 		if (hdmi_read(audio, HDMI_DESIGN_ID) >= 0x21)
118*4882a593Smuzhiyun 			conf2 = HDMI_AUD_CONF2_INSERT_PCUV;
119*4882a593Smuzhiyun 		break;
120*4882a593Smuzhiyun 	}
121*4882a593Smuzhiyun 
122*4882a593Smuzhiyun 	dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate);
123*4882a593Smuzhiyun 	dw_hdmi_set_channel_status(hdmi, hparms->iec.status);
124*4882a593Smuzhiyun 	dw_hdmi_set_channel_count(hdmi, hparms->channels);
125*4882a593Smuzhiyun 	dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation);
126*4882a593Smuzhiyun 
127*4882a593Smuzhiyun 	hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS);
128*4882a593Smuzhiyun 	hdmi_write(audio, conf0, HDMI_AUD_CONF0);
129*4882a593Smuzhiyun 	hdmi_write(audio, conf1, HDMI_AUD_CONF1);
130*4882a593Smuzhiyun 	hdmi_write(audio, conf2, HDMI_AUD_CONF2);
131*4882a593Smuzhiyun 
132*4882a593Smuzhiyun 	return 0;
133*4882a593Smuzhiyun }
134*4882a593Smuzhiyun 
dw_hdmi_i2s_audio_startup(struct device * dev,void * data)135*4882a593Smuzhiyun static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data)
136*4882a593Smuzhiyun {
137*4882a593Smuzhiyun 	struct dw_hdmi_i2s_audio_data *audio = data;
138*4882a593Smuzhiyun 	struct dw_hdmi *hdmi = audio->hdmi;
139*4882a593Smuzhiyun 
140*4882a593Smuzhiyun 	dw_hdmi_audio_enable(hdmi);
141*4882a593Smuzhiyun 
142*4882a593Smuzhiyun 	return 0;
143*4882a593Smuzhiyun }
144*4882a593Smuzhiyun 
dw_hdmi_i2s_audio_shutdown(struct device * dev,void * data)145*4882a593Smuzhiyun static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data)
146*4882a593Smuzhiyun {
147*4882a593Smuzhiyun 	struct dw_hdmi_i2s_audio_data *audio = data;
148*4882a593Smuzhiyun 	struct dw_hdmi *hdmi = audio->hdmi;
149*4882a593Smuzhiyun 
150*4882a593Smuzhiyun 	dw_hdmi_audio_disable(hdmi);
151*4882a593Smuzhiyun }
152*4882a593Smuzhiyun 
dw_hdmi_i2s_get_eld(struct device * dev,void * data,uint8_t * buf,size_t len)153*4882a593Smuzhiyun static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
154*4882a593Smuzhiyun 			       size_t len)
155*4882a593Smuzhiyun {
156*4882a593Smuzhiyun 	struct dw_hdmi_i2s_audio_data *audio = data;
157*4882a593Smuzhiyun 	u8 *eld;
158*4882a593Smuzhiyun 
159*4882a593Smuzhiyun 	eld = audio->get_eld(audio->hdmi);
160*4882a593Smuzhiyun 	if (eld)
161*4882a593Smuzhiyun 		memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
162*4882a593Smuzhiyun 	else
163*4882a593Smuzhiyun 		/* Pass en empty ELD if connector not available */
164*4882a593Smuzhiyun 		memset(buf, 0, len);
165*4882a593Smuzhiyun 
166*4882a593Smuzhiyun 	return 0;
167*4882a593Smuzhiyun }
168*4882a593Smuzhiyun 
dw_hdmi_i2s_get_dai_id(struct snd_soc_component * component,struct device_node * endpoint)169*4882a593Smuzhiyun static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
170*4882a593Smuzhiyun 				  struct device_node *endpoint)
171*4882a593Smuzhiyun {
172*4882a593Smuzhiyun 	struct of_endpoint of_ep;
173*4882a593Smuzhiyun 	int ret;
174*4882a593Smuzhiyun 
175*4882a593Smuzhiyun 	ret = of_graph_parse_endpoint(endpoint, &of_ep);
176*4882a593Smuzhiyun 	if (ret < 0)
177*4882a593Smuzhiyun 		return ret;
178*4882a593Smuzhiyun 
179*4882a593Smuzhiyun 	/*
180*4882a593Smuzhiyun 	 * HDMI sound should be located as reg = <2>
181*4882a593Smuzhiyun 	 * Then, it is sound port 0
182*4882a593Smuzhiyun 	 */
183*4882a593Smuzhiyun 	if (of_ep.port == 2)
184*4882a593Smuzhiyun 		return 0;
185*4882a593Smuzhiyun 
186*4882a593Smuzhiyun 	return -EINVAL;
187*4882a593Smuzhiyun }
188*4882a593Smuzhiyun 
dw_hdmi_i2s_hook_plugged_cb(struct device * dev,void * data,hdmi_codec_plugged_cb fn,struct device * codec_dev)189*4882a593Smuzhiyun static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data,
190*4882a593Smuzhiyun 				       hdmi_codec_plugged_cb fn,
191*4882a593Smuzhiyun 				       struct device *codec_dev)
192*4882a593Smuzhiyun {
193*4882a593Smuzhiyun 	struct dw_hdmi_i2s_audio_data *audio = data;
194*4882a593Smuzhiyun 	struct dw_hdmi *hdmi = audio->hdmi;
195*4882a593Smuzhiyun 
196*4882a593Smuzhiyun 	return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev);
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun 
199*4882a593Smuzhiyun static struct hdmi_codec_ops dw_hdmi_i2s_ops = {
200*4882a593Smuzhiyun 	.hw_params	= dw_hdmi_i2s_hw_params,
201*4882a593Smuzhiyun 	.audio_startup  = dw_hdmi_i2s_audio_startup,
202*4882a593Smuzhiyun 	.audio_shutdown	= dw_hdmi_i2s_audio_shutdown,
203*4882a593Smuzhiyun 	.get_eld	= dw_hdmi_i2s_get_eld,
204*4882a593Smuzhiyun 	.get_dai_id	= dw_hdmi_i2s_get_dai_id,
205*4882a593Smuzhiyun 	.hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb,
206*4882a593Smuzhiyun };
207*4882a593Smuzhiyun 
snd_dw_hdmi_probe(struct platform_device * pdev)208*4882a593Smuzhiyun static int snd_dw_hdmi_probe(struct platform_device *pdev)
209*4882a593Smuzhiyun {
210*4882a593Smuzhiyun 	struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data;
211*4882a593Smuzhiyun 	struct platform_device_info pdevinfo;
212*4882a593Smuzhiyun 	struct hdmi_codec_pdata pdata;
213*4882a593Smuzhiyun 	struct platform_device *platform;
214*4882a593Smuzhiyun 
215*4882a593Smuzhiyun 	pdata.ops		= &dw_hdmi_i2s_ops;
216*4882a593Smuzhiyun 	pdata.i2s		= 1;
217*4882a593Smuzhiyun 	pdata.max_i2s_channels	= 8;
218*4882a593Smuzhiyun 	pdata.data		= audio;
219*4882a593Smuzhiyun 
220*4882a593Smuzhiyun 	memset(&pdevinfo, 0, sizeof(pdevinfo));
221*4882a593Smuzhiyun 	pdevinfo.parent		= pdev->dev.parent;
222*4882a593Smuzhiyun 	pdevinfo.id		= PLATFORM_DEVID_AUTO;
223*4882a593Smuzhiyun 	pdevinfo.name		= HDMI_CODEC_DRV_NAME;
224*4882a593Smuzhiyun 	pdevinfo.data		= &pdata;
225*4882a593Smuzhiyun 	pdevinfo.size_data	= sizeof(pdata);
226*4882a593Smuzhiyun 	pdevinfo.dma_mask	= DMA_BIT_MASK(32);
227*4882a593Smuzhiyun 
228*4882a593Smuzhiyun 	platform = platform_device_register_full(&pdevinfo);
229*4882a593Smuzhiyun 	if (IS_ERR(platform))
230*4882a593Smuzhiyun 		return PTR_ERR(platform);
231*4882a593Smuzhiyun 
232*4882a593Smuzhiyun 	dev_set_drvdata(&pdev->dev, platform);
233*4882a593Smuzhiyun 
234*4882a593Smuzhiyun 	return 0;
235*4882a593Smuzhiyun }
236*4882a593Smuzhiyun 
snd_dw_hdmi_remove(struct platform_device * pdev)237*4882a593Smuzhiyun static int snd_dw_hdmi_remove(struct platform_device *pdev)
238*4882a593Smuzhiyun {
239*4882a593Smuzhiyun 	struct platform_device *platform = dev_get_drvdata(&pdev->dev);
240*4882a593Smuzhiyun 
241*4882a593Smuzhiyun 	platform_device_unregister(platform);
242*4882a593Smuzhiyun 
243*4882a593Smuzhiyun 	return 0;
244*4882a593Smuzhiyun }
245*4882a593Smuzhiyun 
246*4882a593Smuzhiyun static struct platform_driver snd_dw_hdmi_driver = {
247*4882a593Smuzhiyun 	.probe	= snd_dw_hdmi_probe,
248*4882a593Smuzhiyun 	.remove	= snd_dw_hdmi_remove,
249*4882a593Smuzhiyun 	.driver	= {
250*4882a593Smuzhiyun 		.name = DRIVER_NAME,
251*4882a593Smuzhiyun 	},
252*4882a593Smuzhiyun };
253*4882a593Smuzhiyun module_platform_driver(snd_dw_hdmi_driver);
254*4882a593Smuzhiyun 
255*4882a593Smuzhiyun MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
256*4882a593Smuzhiyun MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface");
257*4882a593Smuzhiyun MODULE_LICENSE("GPL v2");
258*4882a593Smuzhiyun MODULE_ALIAS("platform:" DRIVER_NAME);
259