84c2a316aac395ebe942735fddf1dbb25ccfc15b
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / bridge / dw-hdmi-i2s-audio.c
1 /*
2  * dw-hdmi-i2s-audio.c
3  *
4  * Copyright (c) 2016 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  */
10 #include <drm/bridge/dw_hdmi.h>
11
12 #include <sound/hdmi-codec.h>
13
14 #include "dw-hdmi.h"
15 #include "dw-hdmi-audio.h"
16
17 #define DRIVER_NAME "dw-hdmi-i2s-audio"
18
19 static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio, u8 val, int offset)
20 {
21         struct dw_hdmi *hdmi = audio->hdmi;
22
23         audio->write(hdmi, val, offset);
24 }
25
26 static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset)
27 {
28         struct dw_hdmi *hdmi = audio->hdmi;
29
30         return audio->read(hdmi, offset);
31 }
32
33 static int dw_hdmi_i2s_hw_params(struct device *dev, void *data,
34                                  struct hdmi_codec_daifmt *fmt,
35                                  struct hdmi_codec_params *hparms)
36 {
37         struct dw_hdmi_i2s_audio_data *audio = data;
38         struct dw_hdmi *hdmi = audio->hdmi;
39         u8 conf0 = 0;
40         u8 conf1 = 0;
41         u8 inputclkfs = 0;
42
43         /* it cares I2S only */
44         if ((fmt->fmt != HDMI_I2S) ||
45             (fmt->bit_clk_master | fmt->frame_clk_master)) {
46                 dev_err(dev, "unsupported format/settings\n");
47                 return -EINVAL;
48         }
49
50         inputclkfs      = HDMI_AUD_INPUTCLKFS_64FS;
51
52         switch (hparms->sample_width) {
53         case 16:
54                 conf1 = HDMI_AUD_CONF1_WIDTH_16;
55                 break;
56         case 24:
57         case 32:
58                 conf1 = HDMI_AUD_CONF1_WIDTH_24;
59                 break;
60         default:
61                 dev_err(dev, "unsupported sample width [%d]\n", hparms->sample_width);
62                 return -EINVAL;
63         }
64
65         switch (hparms->channels) {
66         case 2:
67                 conf0 = HDMI_AUD_CONF0_I2S_2CHANNEL_ENABLE;
68                 break;
69         case 4:
70                 conf0 = HDMI_AUD_CONF0_I2S_4CHANNEL_ENABLE;
71                 break;
72         case 6:
73                 conf0 = HDMI_AUD_CONF0_I2S_6CHANNEL_ENABLE;
74                 break;
75         case 8:
76                 conf0 = HDMI_AUD_CONF0_I2S_8CHANNEL_ENABLE;
77                 break;
78         default:
79                 dev_err(dev, "unsupported channels [%d]\n", hparms->channels);
80                 return -EINVAL;
81         }
82
83         dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate);
84
85         hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS);
86         hdmi_write(audio, conf0, HDMI_AUD_CONF0);
87         hdmi_write(audio, conf1, HDMI_AUD_CONF1);
88         /*
89          * dw-hdmi introduced insert_pcuv bit in version 2.10a.
90          * When set (1'b1), this bit enables the insertion of the PCUV
91          * (Parity, Channel Status, User bit and Validity) bits on the
92          * incoming audio stream (support limited to Linear PCM audio)
93          */
94         if (hdmi_read(audio, HDMI_DESIGN_ID) > 0x21)
95                 hdmi_write(audio, HDMI_AUD_CONF2_INSERT_PCUV, HDMI_AUD_CONF2);
96         dw_hdmi_audio_enable(hdmi);
97
98         return 0;
99 }
100
101 static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data)
102 {
103         struct dw_hdmi_i2s_audio_data *audio = data;
104         struct dw_hdmi *hdmi = audio->hdmi;
105
106         dw_hdmi_audio_disable(hdmi);
107
108         hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0);
109 }
110
111 static struct hdmi_codec_ops dw_hdmi_i2s_ops = {
112         .hw_params      = dw_hdmi_i2s_hw_params,
113         .audio_shutdown = dw_hdmi_i2s_audio_shutdown,
114 };
115
116 static int snd_dw_hdmi_probe(struct platform_device *pdev)
117 {
118         struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data;
119         struct platform_device_info pdevinfo;
120         struct hdmi_codec_pdata pdata;
121
122         pdata.ops               = &dw_hdmi_i2s_ops;
123         pdata.i2s               = 1;
124         pdata.max_i2s_channels  = 8;
125         pdata.data              = audio;
126
127         memset(&pdevinfo, 0, sizeof(pdevinfo));
128         pdevinfo.parent         = pdev->dev.parent;
129         pdevinfo.id             = PLATFORM_DEVID_AUTO;
130         pdevinfo.name           = HDMI_CODEC_DRV_NAME;
131         pdevinfo.data           = &pdata;
132         pdevinfo.size_data      = sizeof(pdata);
133         pdevinfo.dma_mask       = DMA_BIT_MASK(32);
134
135         return IS_ERR_OR_NULL(platform_device_register_full(&pdevinfo));
136 }
137
138 static struct platform_driver snd_dw_hdmi_driver = {
139         .probe  = snd_dw_hdmi_probe,
140         .driver = {
141                 .name = DRIVER_NAME,
142                 .owner = THIS_MODULE,
143         },
144 };
145 module_platform_driver(snd_dw_hdmi_driver);
146
147 MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
148 MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface");
149 MODULE_LICENSE("GPL v2");
150 MODULE_ALIAS("platform:" DRIVER_NAME);