Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 1 | /* |
| 2 | * DesignWare HDMI audio driver |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License version 2 as |
| 6 | * published by the Free Software Foundation. |
| 7 | * |
| 8 | * Written and tested against the Designware HDMI Tx found in iMX6. |
| 9 | */ |
| 10 | #include <linux/io.h> |
| 11 | #include <linux/interrupt.h> |
| 12 | #include <linux/module.h> |
| 13 | #include <linux/platform_device.h> |
| 14 | #include <drm/bridge/dw_hdmi.h> |
Russell King | f5ce405 | 2013-11-07 16:06:01 +0000 | [diff] [blame] | 15 | #include <drm/drm_edid.h> |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 16 | |
| 17 | #include <sound/asoundef.h> |
| 18 | #include <sound/core.h> |
| 19 | #include <sound/initval.h> |
| 20 | #include <sound/pcm.h> |
Russell King | f5ce405 | 2013-11-07 16:06:01 +0000 | [diff] [blame] | 21 | #include <sound/pcm_drm_eld.h> |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 22 | #include <sound/pcm_iec958.h> |
| 23 | |
Thierry Reding | 248a86f | 2015-11-24 17:52:58 +0100 | [diff] [blame] | 24 | #include "dw-hdmi-audio.h" |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 25 | |
| 26 | #define DRIVER_NAME "dw-hdmi-ahb-audio" |
| 27 | |
| 28 | /* Provide some bits rather than bit offsets */ |
| 29 | enum { |
| 30 | HDMI_AHB_DMA_CONF0_SW_FIFO_RST = BIT(7), |
| 31 | HDMI_AHB_DMA_CONF0_EN_HLOCK = BIT(3), |
| 32 | HDMI_AHB_DMA_START_START = BIT(0), |
| 33 | HDMI_AHB_DMA_STOP_STOP = BIT(0), |
| 34 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = BIT(5), |
| 35 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = BIT(4), |
| 36 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = BIT(3), |
| 37 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = BIT(2), |
| 38 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = BIT(1), |
| 39 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0), |
| 40 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL = |
| 41 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR | |
| 42 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST | |
| 43 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY | |
| 44 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE | |
| 45 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL | |
| 46 | HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY, |
| 47 | HDMI_IH_AHBDMAAUD_STAT0_ERROR = BIT(5), |
| 48 | HDMI_IH_AHBDMAAUD_STAT0_LOST = BIT(4), |
| 49 | HDMI_IH_AHBDMAAUD_STAT0_RETRY = BIT(3), |
| 50 | HDMI_IH_AHBDMAAUD_STAT0_DONE = BIT(2), |
| 51 | HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = BIT(1), |
| 52 | HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0), |
| 53 | HDMI_IH_AHBDMAAUD_STAT0_ALL = |
| 54 | HDMI_IH_AHBDMAAUD_STAT0_ERROR | |
| 55 | HDMI_IH_AHBDMAAUD_STAT0_LOST | |
| 56 | HDMI_IH_AHBDMAAUD_STAT0_RETRY | |
| 57 | HDMI_IH_AHBDMAAUD_STAT0_DONE | |
| 58 | HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL | |
| 59 | HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY, |
| 60 | HDMI_AHB_DMA_CONF0_INCR16 = 2 << 1, |
| 61 | HDMI_AHB_DMA_CONF0_INCR8 = 1 << 1, |
| 62 | HDMI_AHB_DMA_CONF0_INCR4 = 0, |
| 63 | HDMI_AHB_DMA_CONF0_BURST_MODE = BIT(0), |
| 64 | HDMI_AHB_DMA_MASK_DONE = BIT(7), |
Russell King | 9dc515f | 2015-06-04 10:13:28 +0100 | [diff] [blame] | 65 | |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 66 | HDMI_REVISION_ID = 0x0001, |
| 67 | HDMI_IH_AHBDMAAUD_STAT0 = 0x0109, |
| 68 | HDMI_IH_MUTE_AHBDMAAUD_STAT0 = 0x0189, |
Russell King | 9dc515f | 2015-06-04 10:13:28 +0100 | [diff] [blame] | 69 | HDMI_FC_AUDICONF2 = 0x1027, |
| 70 | HDMI_FC_AUDSCONF = 0x1063, |
| 71 | HDMI_FC_AUDSCONF_LAYOUT1 = 1 << 0, |
| 72 | HDMI_FC_AUDSCONF_LAYOUT0 = 0 << 0, |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 73 | HDMI_AHB_DMA_CONF0 = 0x3600, |
| 74 | HDMI_AHB_DMA_START = 0x3601, |
| 75 | HDMI_AHB_DMA_STOP = 0x3602, |
| 76 | HDMI_AHB_DMA_THRSLD = 0x3603, |
| 77 | HDMI_AHB_DMA_STRADDR0 = 0x3604, |
| 78 | HDMI_AHB_DMA_STPADDR0 = 0x3608, |
| 79 | HDMI_AHB_DMA_MASK = 0x3614, |
| 80 | HDMI_AHB_DMA_POL = 0x3615, |
| 81 | HDMI_AHB_DMA_CONF1 = 0x3616, |
| 82 | HDMI_AHB_DMA_BUFFPOL = 0x361a, |
| 83 | }; |
| 84 | |
Russell King | 9dc515f | 2015-06-04 10:13:28 +0100 | [diff] [blame] | 85 | struct dw_hdmi_channel_conf { |
| 86 | u8 conf1; |
| 87 | u8 ca; |
| 88 | }; |
| 89 | |
| 90 | /* |
| 91 | * The default mapping of ALSA channels to HDMI channels and speaker |
| 92 | * allocation bits. Note that we can't do channel remapping here - |
| 93 | * channels must be in the same order. |
| 94 | * |
| 95 | * Mappings for alsa-lib pcm/surround*.conf files: |
| 96 | * |
| 97 | * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1 |
| 98 | * Channels 2 4 6 6 6 8 |
| 99 | * |
| 100 | * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel: |
| 101 | * |
| 102 | * Number of ALSA channels |
| 103 | * ALSA Channel 2 3 4 5 6 7 8 |
| 104 | * 0 FL:0 = = = = = = |
| 105 | * 1 FR:1 = = = = = = |
| 106 | * 2 FC:3 RL:4 LFE:2 = = = |
| 107 | * 3 RR:5 RL:4 FC:3 = = |
| 108 | * 4 RR:5 RL:4 = = |
| 109 | * 5 RR:5 = = |
| 110 | * 6 RC:6 = |
| 111 | * 7 RLC/FRC RLC/FRC |
| 112 | */ |
| 113 | static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = { |
| 114 | { 0x03, 0x00 }, /* FL,FR */ |
| 115 | { 0x0b, 0x02 }, /* FL,FR,FC */ |
| 116 | { 0x33, 0x08 }, /* FL,FR,RL,RR */ |
| 117 | { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */ |
| 118 | { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */ |
| 119 | { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */ |
| 120 | { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */ |
| 121 | }; |
| 122 | |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 123 | struct snd_dw_hdmi { |
| 124 | struct snd_card *card; |
| 125 | struct snd_pcm *pcm; |
| 126 | spinlock_t lock; |
| 127 | struct dw_hdmi_audio_data data; |
| 128 | struct snd_pcm_substream *substream; |
| 129 | void (*reformat)(struct snd_dw_hdmi *, size_t, size_t); |
| 130 | void *buf_src; |
| 131 | void *buf_dst; |
| 132 | dma_addr_t buf_addr; |
| 133 | unsigned buf_offset; |
| 134 | unsigned buf_period; |
| 135 | unsigned buf_size; |
| 136 | unsigned channels; |
| 137 | u8 revision; |
| 138 | u8 iec_offset; |
| 139 | u8 cs[192][8]; |
| 140 | }; |
| 141 | |
| 142 | static void dw_hdmi_writel(u32 val, void __iomem *ptr) |
| 143 | { |
| 144 | writeb_relaxed(val, ptr); |
| 145 | writeb_relaxed(val >> 8, ptr + 1); |
| 146 | writeb_relaxed(val >> 16, ptr + 2); |
| 147 | writeb_relaxed(val >> 24, ptr + 3); |
| 148 | } |
| 149 | |
| 150 | /* |
| 151 | * Convert to hardware format: The userspace buffer contains IEC958 samples, |
| 152 | * with the PCUV bits in bits 31..28 and audio samples in bits 27..4. We |
| 153 | * need these to be in bits 27..24, with the IEC B bit in bit 28, and audio |
| 154 | * samples in 23..0. |
| 155 | * |
| 156 | * Default preamble in bits 3..0: 8 = block start, 4 = even 2 = odd |
| 157 | * |
| 158 | * Ideally, we could do with having the data properly formatted in userspace. |
| 159 | */ |
| 160 | static void dw_hdmi_reformat_iec958(struct snd_dw_hdmi *dw, |
| 161 | size_t offset, size_t bytes) |
| 162 | { |
| 163 | u32 *src = dw->buf_src + offset; |
| 164 | u32 *dst = dw->buf_dst + offset; |
| 165 | u32 *end = dw->buf_src + offset + bytes; |
| 166 | |
| 167 | do { |
| 168 | u32 b, sample = *src++; |
| 169 | |
| 170 | b = (sample & 8) << (28 - 3); |
| 171 | |
| 172 | sample >>= 4; |
| 173 | |
| 174 | *dst++ = sample | b; |
| 175 | } while (src < end); |
| 176 | } |
| 177 | |
| 178 | static u32 parity(u32 sample) |
| 179 | { |
| 180 | sample ^= sample >> 16; |
| 181 | sample ^= sample >> 8; |
| 182 | sample ^= sample >> 4; |
| 183 | sample ^= sample >> 2; |
| 184 | sample ^= sample >> 1; |
| 185 | return (sample & 1) << 27; |
| 186 | } |
| 187 | |
| 188 | static void dw_hdmi_reformat_s24(struct snd_dw_hdmi *dw, |
| 189 | size_t offset, size_t bytes) |
| 190 | { |
| 191 | u32 *src = dw->buf_src + offset; |
| 192 | u32 *dst = dw->buf_dst + offset; |
| 193 | u32 *end = dw->buf_src + offset + bytes; |
| 194 | |
| 195 | do { |
| 196 | unsigned i; |
| 197 | u8 *cs; |
| 198 | |
| 199 | cs = dw->cs[dw->iec_offset++]; |
| 200 | if (dw->iec_offset >= 192) |
| 201 | dw->iec_offset = 0; |
| 202 | |
| 203 | i = dw->channels; |
| 204 | do { |
| 205 | u32 sample = *src++; |
| 206 | |
| 207 | sample &= ~0xff000000; |
| 208 | sample |= *cs++ << 24; |
| 209 | sample |= parity(sample & ~0xf8000000); |
| 210 | |
| 211 | *dst++ = sample; |
| 212 | } while (--i); |
| 213 | } while (src < end); |
| 214 | } |
| 215 | |
| 216 | static void dw_hdmi_create_cs(struct snd_dw_hdmi *dw, |
| 217 | struct snd_pcm_runtime *runtime) |
| 218 | { |
| 219 | u8 cs[4]; |
| 220 | unsigned ch, i, j; |
| 221 | |
| 222 | snd_pcm_create_iec958_consumer(runtime, cs, sizeof(cs)); |
| 223 | |
| 224 | memset(dw->cs, 0, sizeof(dw->cs)); |
| 225 | |
| 226 | for (ch = 0; ch < 8; ch++) { |
| 227 | cs[2] &= ~IEC958_AES2_CON_CHANNEL; |
| 228 | cs[2] |= (ch + 1) << 4; |
| 229 | |
| 230 | for (i = 0; i < ARRAY_SIZE(cs); i++) { |
| 231 | unsigned c = cs[i]; |
| 232 | |
| 233 | for (j = 0; j < 8; j++, c >>= 1) |
| 234 | dw->cs[i * 8 + j][ch] = (c & 1) << 2; |
| 235 | } |
| 236 | } |
| 237 | dw->cs[0][0] |= BIT(4); |
| 238 | } |
| 239 | |
| 240 | static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw) |
| 241 | { |
| 242 | void __iomem *base = dw->data.base; |
| 243 | unsigned offset = dw->buf_offset; |
| 244 | unsigned period = dw->buf_period; |
| 245 | u32 start, stop; |
| 246 | |
| 247 | dw->reformat(dw, offset, period); |
| 248 | |
| 249 | /* Clear all irqs before enabling irqs and starting DMA */ |
| 250 | writeb_relaxed(HDMI_IH_AHBDMAAUD_STAT0_ALL, |
| 251 | base + HDMI_IH_AHBDMAAUD_STAT0); |
| 252 | |
| 253 | start = dw->buf_addr + offset; |
| 254 | stop = start + period - 1; |
| 255 | |
| 256 | /* Setup the hardware start/stop addresses */ |
| 257 | dw_hdmi_writel(start, base + HDMI_AHB_DMA_STRADDR0); |
| 258 | dw_hdmi_writel(stop, base + HDMI_AHB_DMA_STPADDR0); |
| 259 | |
| 260 | writeb_relaxed((u8)~HDMI_AHB_DMA_MASK_DONE, base + HDMI_AHB_DMA_MASK); |
| 261 | writeb(HDMI_AHB_DMA_START_START, base + HDMI_AHB_DMA_START); |
| 262 | |
| 263 | offset += period; |
| 264 | if (offset >= dw->buf_size) |
| 265 | offset = 0; |
| 266 | dw->buf_offset = offset; |
| 267 | } |
| 268 | |
| 269 | static void dw_hdmi_stop_dma(struct snd_dw_hdmi *dw) |
| 270 | { |
| 271 | /* Disable interrupts before disabling DMA */ |
| 272 | writeb_relaxed(~0, dw->data.base + HDMI_AHB_DMA_MASK); |
| 273 | writeb_relaxed(HDMI_AHB_DMA_STOP_STOP, dw->data.base + HDMI_AHB_DMA_STOP); |
| 274 | } |
| 275 | |
| 276 | static irqreturn_t snd_dw_hdmi_irq(int irq, void *data) |
| 277 | { |
| 278 | struct snd_dw_hdmi *dw = data; |
| 279 | struct snd_pcm_substream *substream; |
| 280 | unsigned stat; |
| 281 | |
| 282 | stat = readb_relaxed(dw->data.base + HDMI_IH_AHBDMAAUD_STAT0); |
| 283 | if (!stat) |
| 284 | return IRQ_NONE; |
| 285 | |
| 286 | writeb_relaxed(stat, dw->data.base + HDMI_IH_AHBDMAAUD_STAT0); |
| 287 | |
| 288 | substream = dw->substream; |
| 289 | if (stat & HDMI_IH_AHBDMAAUD_STAT0_DONE && substream) { |
| 290 | snd_pcm_period_elapsed(substream); |
| 291 | |
| 292 | spin_lock(&dw->lock); |
| 293 | if (dw->substream) |
| 294 | dw_hdmi_start_dma(dw); |
| 295 | spin_unlock(&dw->lock); |
| 296 | } |
| 297 | |
| 298 | return IRQ_HANDLED; |
| 299 | } |
| 300 | |
| 301 | static struct snd_pcm_hardware dw_hdmi_hw = { |
| 302 | .info = SNDRV_PCM_INFO_INTERLEAVED | |
| 303 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| 304 | SNDRV_PCM_INFO_MMAP | |
| 305 | SNDRV_PCM_INFO_MMAP_VALID, |
| 306 | .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | |
| 307 | SNDRV_PCM_FMTBIT_S24_LE, |
| 308 | .rates = SNDRV_PCM_RATE_32000 | |
| 309 | SNDRV_PCM_RATE_44100 | |
| 310 | SNDRV_PCM_RATE_48000 | |
| 311 | SNDRV_PCM_RATE_88200 | |
| 312 | SNDRV_PCM_RATE_96000 | |
| 313 | SNDRV_PCM_RATE_176400 | |
| 314 | SNDRV_PCM_RATE_192000, |
| 315 | .channels_min = 2, |
| 316 | .channels_max = 8, |
Russell King | 91cd690 | 2015-06-04 10:24:33 +0100 | [diff] [blame] | 317 | .buffer_bytes_max = 1024 * 1024, |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 318 | .period_bytes_min = 256, |
| 319 | .period_bytes_max = 8192, /* ERR004323: must limit to 8k */ |
| 320 | .periods_min = 2, |
| 321 | .periods_max = 16, |
| 322 | .fifo_size = 0, |
| 323 | }; |
| 324 | |
| 325 | static int dw_hdmi_open(struct snd_pcm_substream *substream) |
| 326 | { |
| 327 | struct snd_pcm_runtime *runtime = substream->runtime; |
| 328 | struct snd_dw_hdmi *dw = substream->private_data; |
| 329 | void __iomem *base = dw->data.base; |
| 330 | int ret; |
| 331 | |
| 332 | runtime->hw = dw_hdmi_hw; |
| 333 | |
Russell King | f5ce405 | 2013-11-07 16:06:01 +0000 | [diff] [blame] | 334 | ret = snd_pcm_hw_constraint_eld(runtime, dw->data.eld); |
| 335 | if (ret < 0) |
| 336 | return ret; |
| 337 | |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 338 | ret = snd_pcm_limit_hw_rates(runtime); |
| 339 | if (ret < 0) |
| 340 | return ret; |
| 341 | |
Russell King | 91cd690 | 2015-06-04 10:24:33 +0100 | [diff] [blame] | 342 | ret = snd_pcm_hw_constraint_integer(runtime, |
| 343 | SNDRV_PCM_HW_PARAM_PERIODS); |
| 344 | if (ret < 0) |
| 345 | return ret; |
| 346 | |
| 347 | /* Limit the buffer size to the size of the preallocated buffer */ |
| 348 | ret = snd_pcm_hw_constraint_minmax(runtime, |
| 349 | SNDRV_PCM_HW_PARAM_BUFFER_SIZE, |
| 350 | 0, substream->dma_buffer.bytes); |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 351 | if (ret < 0) |
| 352 | return ret; |
| 353 | |
| 354 | /* Clear FIFO */ |
| 355 | writeb_relaxed(HDMI_AHB_DMA_CONF0_SW_FIFO_RST, |
| 356 | base + HDMI_AHB_DMA_CONF0); |
| 357 | |
| 358 | /* Configure interrupt polarities */ |
| 359 | writeb_relaxed(~0, base + HDMI_AHB_DMA_POL); |
| 360 | writeb_relaxed(~0, base + HDMI_AHB_DMA_BUFFPOL); |
| 361 | |
| 362 | /* Keep interrupts masked, and clear any pending */ |
| 363 | writeb_relaxed(~0, base + HDMI_AHB_DMA_MASK); |
| 364 | writeb_relaxed(~0, base + HDMI_IH_AHBDMAAUD_STAT0); |
| 365 | |
| 366 | ret = request_irq(dw->data.irq, snd_dw_hdmi_irq, IRQF_SHARED, |
| 367 | "dw-hdmi-audio", dw); |
| 368 | if (ret) |
| 369 | return ret; |
| 370 | |
| 371 | /* Un-mute done interrupt */ |
| 372 | writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL & |
| 373 | ~HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE, |
| 374 | base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); |
| 375 | |
| 376 | return 0; |
| 377 | } |
| 378 | |
| 379 | static int dw_hdmi_close(struct snd_pcm_substream *substream) |
| 380 | { |
| 381 | struct snd_dw_hdmi *dw = substream->private_data; |
| 382 | |
| 383 | /* Mute all interrupts */ |
| 384 | writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL, |
| 385 | dw->data.base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); |
| 386 | |
| 387 | free_irq(dw->data.irq, dw); |
| 388 | |
| 389 | return 0; |
| 390 | } |
| 391 | |
| 392 | static int dw_hdmi_hw_free(struct snd_pcm_substream *substream) |
| 393 | { |
| 394 | return snd_pcm_lib_free_vmalloc_buffer(substream); |
| 395 | } |
| 396 | |
| 397 | static int dw_hdmi_hw_params(struct snd_pcm_substream *substream, |
| 398 | struct snd_pcm_hw_params *params) |
| 399 | { |
Russell King | 91cd690 | 2015-06-04 10:24:33 +0100 | [diff] [blame] | 400 | /* Allocate the PCM runtime buffer, which is exposed to userspace. */ |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 401 | return snd_pcm_lib_alloc_vmalloc_buffer(substream, |
| 402 | params_buffer_bytes(params)); |
| 403 | } |
| 404 | |
| 405 | static int dw_hdmi_prepare(struct snd_pcm_substream *substream) |
| 406 | { |
| 407 | struct snd_pcm_runtime *runtime = substream->runtime; |
| 408 | struct snd_dw_hdmi *dw = substream->private_data; |
Russell King | 9dc515f | 2015-06-04 10:13:28 +0100 | [diff] [blame] | 409 | u8 threshold, conf0, conf1, layout, ca; |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 410 | |
| 411 | /* Setup as per 3.0.5 FSL 4.1.0 BSP */ |
| 412 | switch (dw->revision) { |
| 413 | case 0x0a: |
| 414 | conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE | |
| 415 | HDMI_AHB_DMA_CONF0_INCR4; |
| 416 | if (runtime->channels == 2) |
| 417 | threshold = 126; |
| 418 | else |
| 419 | threshold = 124; |
| 420 | break; |
| 421 | case 0x1a: |
| 422 | conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE | |
| 423 | HDMI_AHB_DMA_CONF0_INCR8; |
| 424 | threshold = 128; |
| 425 | break; |
| 426 | default: |
| 427 | /* NOTREACHED */ |
| 428 | return -EINVAL; |
| 429 | } |
| 430 | |
| 431 | dw_hdmi_set_sample_rate(dw->data.hdmi, runtime->rate); |
| 432 | |
| 433 | /* Minimum number of bytes in the fifo. */ |
| 434 | runtime->hw.fifo_size = threshold * 32; |
| 435 | |
| 436 | conf0 |= HDMI_AHB_DMA_CONF0_EN_HLOCK; |
Russell King | 9dc515f | 2015-06-04 10:13:28 +0100 | [diff] [blame] | 437 | conf1 = default_hdmi_channel_config[runtime->channels - 2].conf1; |
| 438 | ca = default_hdmi_channel_config[runtime->channels - 2].ca; |
| 439 | |
| 440 | /* |
| 441 | * For >2 channel PCM audio, we need to select layout 1 |
| 442 | * and set an appropriate channel map. |
| 443 | */ |
| 444 | if (runtime->channels > 2) |
| 445 | layout = HDMI_FC_AUDSCONF_LAYOUT1; |
| 446 | else |
| 447 | layout = HDMI_FC_AUDSCONF_LAYOUT0; |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 448 | |
| 449 | writeb_relaxed(threshold, dw->data.base + HDMI_AHB_DMA_THRSLD); |
| 450 | writeb_relaxed(conf0, dw->data.base + HDMI_AHB_DMA_CONF0); |
| 451 | writeb_relaxed(conf1, dw->data.base + HDMI_AHB_DMA_CONF1); |
Russell King | 9dc515f | 2015-06-04 10:13:28 +0100 | [diff] [blame] | 452 | writeb_relaxed(layout, dw->data.base + HDMI_FC_AUDSCONF); |
| 453 | writeb_relaxed(ca, dw->data.base + HDMI_FC_AUDICONF2); |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 454 | |
| 455 | switch (runtime->format) { |
| 456 | case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: |
| 457 | dw->reformat = dw_hdmi_reformat_iec958; |
| 458 | break; |
| 459 | case SNDRV_PCM_FORMAT_S24_LE: |
| 460 | dw_hdmi_create_cs(dw, runtime); |
| 461 | dw->reformat = dw_hdmi_reformat_s24; |
| 462 | break; |
| 463 | } |
| 464 | dw->iec_offset = 0; |
| 465 | dw->channels = runtime->channels; |
| 466 | dw->buf_src = runtime->dma_area; |
| 467 | dw->buf_dst = substream->dma_buffer.area; |
| 468 | dw->buf_addr = substream->dma_buffer.addr; |
| 469 | dw->buf_period = snd_pcm_lib_period_bytes(substream); |
| 470 | dw->buf_size = snd_pcm_lib_buffer_bytes(substream); |
| 471 | |
| 472 | return 0; |
| 473 | } |
| 474 | |
| 475 | static int dw_hdmi_trigger(struct snd_pcm_substream *substream, int cmd) |
| 476 | { |
| 477 | struct snd_dw_hdmi *dw = substream->private_data; |
| 478 | unsigned long flags; |
| 479 | int ret = 0; |
| 480 | |
| 481 | switch (cmd) { |
| 482 | case SNDRV_PCM_TRIGGER_START: |
| 483 | spin_lock_irqsave(&dw->lock, flags); |
| 484 | dw->buf_offset = 0; |
| 485 | dw->substream = substream; |
| 486 | dw_hdmi_start_dma(dw); |
| 487 | dw_hdmi_audio_enable(dw->data.hdmi); |
| 488 | spin_unlock_irqrestore(&dw->lock, flags); |
| 489 | substream->runtime->delay = substream->runtime->period_size; |
| 490 | break; |
| 491 | |
| 492 | case SNDRV_PCM_TRIGGER_STOP: |
| 493 | spin_lock_irqsave(&dw->lock, flags); |
| 494 | dw->substream = NULL; |
| 495 | dw_hdmi_stop_dma(dw); |
| 496 | dw_hdmi_audio_disable(dw->data.hdmi); |
| 497 | spin_unlock_irqrestore(&dw->lock, flags); |
| 498 | break; |
| 499 | |
| 500 | default: |
| 501 | ret = -EINVAL; |
| 502 | break; |
| 503 | } |
| 504 | |
| 505 | return ret; |
| 506 | } |
| 507 | |
| 508 | static snd_pcm_uframes_t dw_hdmi_pointer(struct snd_pcm_substream *substream) |
| 509 | { |
| 510 | struct snd_pcm_runtime *runtime = substream->runtime; |
| 511 | struct snd_dw_hdmi *dw = substream->private_data; |
| 512 | |
| 513 | /* |
| 514 | * We are unable to report the exact hardware position as |
| 515 | * reading the 32-bit DMA position using 8-bit reads is racy. |
| 516 | */ |
| 517 | return bytes_to_frames(runtime, dw->buf_offset); |
| 518 | } |
| 519 | |
| 520 | static struct snd_pcm_ops snd_dw_hdmi_ops = { |
| 521 | .open = dw_hdmi_open, |
| 522 | .close = dw_hdmi_close, |
| 523 | .ioctl = snd_pcm_lib_ioctl, |
| 524 | .hw_params = dw_hdmi_hw_params, |
| 525 | .hw_free = dw_hdmi_hw_free, |
| 526 | .prepare = dw_hdmi_prepare, |
| 527 | .trigger = dw_hdmi_trigger, |
| 528 | .pointer = dw_hdmi_pointer, |
| 529 | .page = snd_pcm_lib_get_vmalloc_page, |
| 530 | }; |
| 531 | |
| 532 | static int snd_dw_hdmi_probe(struct platform_device *pdev) |
| 533 | { |
| 534 | const struct dw_hdmi_audio_data *data = pdev->dev.platform_data; |
| 535 | struct device *dev = pdev->dev.parent; |
| 536 | struct snd_dw_hdmi *dw; |
| 537 | struct snd_card *card; |
| 538 | struct snd_pcm *pcm; |
| 539 | unsigned revision; |
| 540 | int ret; |
| 541 | |
| 542 | writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL, |
| 543 | data->base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); |
| 544 | revision = readb_relaxed(data->base + HDMI_REVISION_ID); |
| 545 | if (revision != 0x0a && revision != 0x1a) { |
| 546 | dev_err(dev, "dw-hdmi-audio: unknown revision 0x%02x\n", |
| 547 | revision); |
| 548 | return -ENXIO; |
| 549 | } |
| 550 | |
| 551 | ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, |
| 552 | THIS_MODULE, sizeof(struct snd_dw_hdmi), &card); |
| 553 | if (ret < 0) |
| 554 | return ret; |
| 555 | |
| 556 | strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver)); |
| 557 | strlcpy(card->shortname, "DW-HDMI", sizeof(card->shortname)); |
| 558 | snprintf(card->longname, sizeof(card->longname), |
| 559 | "%s rev 0x%02x, irq %d", card->shortname, revision, |
| 560 | data->irq); |
| 561 | |
| 562 | dw = card->private_data; |
| 563 | dw->card = card; |
| 564 | dw->data = *data; |
| 565 | dw->revision = revision; |
| 566 | |
| 567 | spin_lock_init(&dw->lock); |
| 568 | |
| 569 | ret = snd_pcm_new(card, "DW HDMI", 0, 1, 0, &pcm); |
| 570 | if (ret < 0) |
| 571 | goto err; |
| 572 | |
| 573 | dw->pcm = pcm; |
| 574 | pcm->private_data = dw; |
| 575 | strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name)); |
| 576 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dw_hdmi_ops); |
| 577 | |
Russell King | 91cd690 | 2015-06-04 10:24:33 +0100 | [diff] [blame] | 578 | /* |
| 579 | * To support 8-channel 96kHz audio reliably, we need 512k |
| 580 | * to satisfy alsa with our restricted period (ERR004323). |
| 581 | */ |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 582 | snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, |
Russell King | 91cd690 | 2015-06-04 10:24:33 +0100 | [diff] [blame] | 583 | dev, 128 * 1024, 1024 * 1024); |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 584 | |
| 585 | ret = snd_card_register(card); |
| 586 | if (ret < 0) |
| 587 | goto err; |
| 588 | |
| 589 | platform_set_drvdata(pdev, dw); |
| 590 | |
| 591 | return 0; |
| 592 | |
| 593 | err: |
| 594 | snd_card_free(card); |
| 595 | return ret; |
| 596 | } |
| 597 | |
| 598 | static int snd_dw_hdmi_remove(struct platform_device *pdev) |
| 599 | { |
| 600 | struct snd_dw_hdmi *dw = platform_get_drvdata(pdev); |
| 601 | |
| 602 | snd_card_free(dw->card); |
| 603 | |
| 604 | return 0; |
| 605 | } |
| 606 | |
| 607 | #if defined(CONFIG_PM_SLEEP) && defined(IS_NOT_BROKEN) |
| 608 | /* |
| 609 | * This code is fine, but requires implementation in the dw_hdmi_trigger() |
| 610 | * method which is currently missing as I have no way to test this. |
| 611 | */ |
| 612 | static int snd_dw_hdmi_suspend(struct device *dev) |
| 613 | { |
| 614 | struct snd_dw_hdmi *dw = dev_get_drvdata(dev); |
| 615 | |
| 616 | snd_power_change_state(dw->card, SNDRV_CTL_POWER_D3cold); |
| 617 | snd_pcm_suspend_all(dw->pcm); |
| 618 | |
| 619 | return 0; |
| 620 | } |
| 621 | |
| 622 | static int snd_dw_hdmi_resume(struct device *dev) |
| 623 | { |
| 624 | struct snd_dw_hdmi *dw = dev_get_drvdata(dev); |
| 625 | |
| 626 | snd_power_change_state(dw->card, SNDRV_CTL_POWER_D0); |
| 627 | |
| 628 | return 0; |
| 629 | } |
| 630 | |
| 631 | static SIMPLE_DEV_PM_OPS(snd_dw_hdmi_pm, snd_dw_hdmi_suspend, |
| 632 | snd_dw_hdmi_resume); |
| 633 | #define PM_OPS &snd_dw_hdmi_pm |
| 634 | #else |
| 635 | #define PM_OPS NULL |
| 636 | #endif |
| 637 | |
| 638 | static struct platform_driver snd_dw_hdmi_driver = { |
| 639 | .probe = snd_dw_hdmi_probe, |
| 640 | .remove = snd_dw_hdmi_remove, |
| 641 | .driver = { |
| 642 | .name = DRIVER_NAME, |
Russell King | 7ed6c66 | 2013-11-07 16:01:45 +0000 | [diff] [blame] | 643 | .pm = PM_OPS, |
| 644 | }, |
| 645 | }; |
| 646 | |
| 647 | module_platform_driver(snd_dw_hdmi_driver); |
| 648 | |
| 649 | MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>"); |
| 650 | MODULE_DESCRIPTION("Synopsis Designware HDMI AHB ALSA interface"); |
| 651 | MODULE_LICENSE("GPL v2"); |
| 652 | MODULE_ALIAS("platform:" DRIVER_NAME); |