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