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