Path: blob/master/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
26516 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* DesignWare HDMI audio driver3*4* Written and tested against the Designware HDMI Tx found in iMX6.5*/6#include <linux/io.h>7#include <linux/interrupt.h>8#include <linux/module.h>9#include <linux/platform_device.h>10#include <linux/vmalloc.h>11#include <drm/bridge/dw_hdmi.h>12#include <drm/drm_edid.h>1314#include <sound/asoundef.h>15#include <sound/core.h>16#include <sound/initval.h>17#include <sound/pcm.h>18#include <sound/pcm_drm_eld.h>19#include <sound/pcm_iec958.h>2021#include "dw-hdmi-audio.h"2223#define DRIVER_NAME "dw-hdmi-ahb-audio"2425/* Provide some bits rather than bit offsets */26enum {27HDMI_AHB_DMA_CONF0_SW_FIFO_RST = BIT(7),28HDMI_AHB_DMA_CONF0_EN_HLOCK = BIT(3),29HDMI_AHB_DMA_START_START = BIT(0),30HDMI_AHB_DMA_STOP_STOP = BIT(0),31HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = BIT(5),32HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = BIT(4),33HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = BIT(3),34HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = BIT(2),35HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = BIT(1),36HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0),37HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL =38HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR |39HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST |40HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY |41HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE |42HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL |43HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY,44HDMI_IH_AHBDMAAUD_STAT0_ERROR = BIT(5),45HDMI_IH_AHBDMAAUD_STAT0_LOST = BIT(4),46HDMI_IH_AHBDMAAUD_STAT0_RETRY = BIT(3),47HDMI_IH_AHBDMAAUD_STAT0_DONE = BIT(2),48HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = BIT(1),49HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0),50HDMI_IH_AHBDMAAUD_STAT0_ALL =51HDMI_IH_AHBDMAAUD_STAT0_ERROR |52HDMI_IH_AHBDMAAUD_STAT0_LOST |53HDMI_IH_AHBDMAAUD_STAT0_RETRY |54HDMI_IH_AHBDMAAUD_STAT0_DONE |55HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL |56HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY,57HDMI_AHB_DMA_CONF0_INCR16 = 2 << 1,58HDMI_AHB_DMA_CONF0_INCR8 = 1 << 1,59HDMI_AHB_DMA_CONF0_INCR4 = 0,60HDMI_AHB_DMA_CONF0_BURST_MODE = BIT(0),61HDMI_AHB_DMA_MASK_DONE = BIT(7),6263HDMI_REVISION_ID = 0x0001,64HDMI_IH_AHBDMAAUD_STAT0 = 0x0109,65HDMI_IH_MUTE_AHBDMAAUD_STAT0 = 0x0189,66HDMI_AHB_DMA_CONF0 = 0x3600,67HDMI_AHB_DMA_START = 0x3601,68HDMI_AHB_DMA_STOP = 0x3602,69HDMI_AHB_DMA_THRSLD = 0x3603,70HDMI_AHB_DMA_STRADDR0 = 0x3604,71HDMI_AHB_DMA_STPADDR0 = 0x3608,72HDMI_AHB_DMA_MASK = 0x3614,73HDMI_AHB_DMA_POL = 0x3615,74HDMI_AHB_DMA_CONF1 = 0x3616,75HDMI_AHB_DMA_BUFFPOL = 0x361a,76};7778struct dw_hdmi_channel_conf {79u8 conf1;80u8 ca;81};8283/*84* The default mapping of ALSA channels to HDMI channels and speaker85* allocation bits. Note that we can't do channel remapping here -86* channels must be in the same order.87*88* Mappings for alsa-lib pcm/surround*.conf files:89*90* Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.191* Channels 2 4 6 6 6 892*93* Our mapping from ALSA channel to CEA686D speaker name and HDMI channel:94*95* Number of ALSA channels96* ALSA Channel 2 3 4 5 6 7 897* 0 FL:0 = = = = = =98* 1 FR:1 = = = = = =99* 2 FC:3 RL:4 LFE:2 = = =100* 3 RR:5 RL:4 FC:3 = =101* 4 RR:5 RL:4 = =102* 5 RR:5 = =103* 6 RC:6 =104* 7 RLC/FRC RLC/FRC105*/106static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = {107{ 0x03, 0x00 }, /* FL,FR */108{ 0x0b, 0x02 }, /* FL,FR,FC */109{ 0x33, 0x08 }, /* FL,FR,RL,RR */110{ 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */111{ 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */112{ 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */113{ 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */114};115116struct snd_dw_hdmi {117struct snd_card *card;118struct snd_pcm *pcm;119spinlock_t lock;120struct dw_hdmi_audio_data data;121struct snd_pcm_substream *substream;122void (*reformat)(struct snd_dw_hdmi *, size_t, size_t);123void *buf_src;124void *buf_dst;125dma_addr_t buf_addr;126unsigned buf_offset;127unsigned buf_period;128unsigned buf_size;129unsigned channels;130u8 revision;131u8 iec_offset;132u8 cs[192][8];133};134135static void dw_hdmi_writel(u32 val, void __iomem *ptr)136{137writeb_relaxed(val, ptr);138writeb_relaxed(val >> 8, ptr + 1);139writeb_relaxed(val >> 16, ptr + 2);140writeb_relaxed(val >> 24, ptr + 3);141}142143/*144* Convert to hardware format: The userspace buffer contains IEC958 samples,145* with the PCUV bits in bits 31..28 and audio samples in bits 27..4. We146* need these to be in bits 27..24, with the IEC B bit in bit 28, and audio147* samples in 23..0.148*149* Default preamble in bits 3..0: 8 = block start, 4 = even 2 = odd150*151* Ideally, we could do with having the data properly formatted in userspace.152*/153static void dw_hdmi_reformat_iec958(struct snd_dw_hdmi *dw,154size_t offset, size_t bytes)155{156u32 *src = dw->buf_src + offset;157u32 *dst = dw->buf_dst + offset;158u32 *end = dw->buf_src + offset + bytes;159160do {161u32 b, sample = *src++;162163b = (sample & 8) << (28 - 3);164165sample >>= 4;166167*dst++ = sample | b;168} while (src < end);169}170171static u32 parity(u32 sample)172{173sample ^= sample >> 16;174sample ^= sample >> 8;175sample ^= sample >> 4;176sample ^= sample >> 2;177sample ^= sample >> 1;178return (sample & 1) << 27;179}180181static void dw_hdmi_reformat_s24(struct snd_dw_hdmi *dw,182size_t offset, size_t bytes)183{184u32 *src = dw->buf_src + offset;185u32 *dst = dw->buf_dst + offset;186u32 *end = dw->buf_src + offset + bytes;187188do {189unsigned i;190u8 *cs;191192cs = dw->cs[dw->iec_offset++];193if (dw->iec_offset >= 192)194dw->iec_offset = 0;195196i = dw->channels;197do {198u32 sample = *src++;199200sample &= ~0xff000000;201sample |= *cs++ << 24;202sample |= parity(sample & ~0xf8000000);203204*dst++ = sample;205} while (--i);206} while (src < end);207}208209static void dw_hdmi_create_cs(struct snd_dw_hdmi *dw,210struct snd_pcm_runtime *runtime)211{212u8 cs[4];213unsigned ch, i, j;214215snd_pcm_create_iec958_consumer(runtime, cs, sizeof(cs));216217memset(dw->cs, 0, sizeof(dw->cs));218219for (ch = 0; ch < 8; ch++) {220cs[2] &= ~IEC958_AES2_CON_CHANNEL;221cs[2] |= (ch + 1) << 4;222223for (i = 0; i < ARRAY_SIZE(cs); i++) {224unsigned c = cs[i];225226for (j = 0; j < 8; j++, c >>= 1)227dw->cs[i * 8 + j][ch] = (c & 1) << 2;228}229}230dw->cs[0][0] |= BIT(4);231}232233static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw)234{235void __iomem *base = dw->data.base;236unsigned offset = dw->buf_offset;237unsigned period = dw->buf_period;238u32 start, stop;239240dw->reformat(dw, offset, period);241242/* Clear all irqs before enabling irqs and starting DMA */243writeb_relaxed(HDMI_IH_AHBDMAAUD_STAT0_ALL,244base + HDMI_IH_AHBDMAAUD_STAT0);245246start = dw->buf_addr + offset;247stop = start + period - 1;248249/* Setup the hardware start/stop addresses */250dw_hdmi_writel(start, base + HDMI_AHB_DMA_STRADDR0);251dw_hdmi_writel(stop, base + HDMI_AHB_DMA_STPADDR0);252253writeb_relaxed((u8)~HDMI_AHB_DMA_MASK_DONE, base + HDMI_AHB_DMA_MASK);254writeb(HDMI_AHB_DMA_START_START, base + HDMI_AHB_DMA_START);255256offset += period;257if (offset >= dw->buf_size)258offset = 0;259dw->buf_offset = offset;260}261262static void dw_hdmi_stop_dma(struct snd_dw_hdmi *dw)263{264/* Disable interrupts before disabling DMA */265writeb_relaxed(~0, dw->data.base + HDMI_AHB_DMA_MASK);266writeb_relaxed(HDMI_AHB_DMA_STOP_STOP, dw->data.base + HDMI_AHB_DMA_STOP);267}268269static irqreturn_t snd_dw_hdmi_irq(int irq, void *data)270{271struct snd_dw_hdmi *dw = data;272struct snd_pcm_substream *substream;273unsigned stat;274275stat = readb_relaxed(dw->data.base + HDMI_IH_AHBDMAAUD_STAT0);276if (!stat)277return IRQ_NONE;278279writeb_relaxed(stat, dw->data.base + HDMI_IH_AHBDMAAUD_STAT0);280281substream = dw->substream;282if (stat & HDMI_IH_AHBDMAAUD_STAT0_DONE && substream) {283snd_pcm_period_elapsed(substream);284285spin_lock(&dw->lock);286if (dw->substream)287dw_hdmi_start_dma(dw);288spin_unlock(&dw->lock);289}290291return IRQ_HANDLED;292}293294static const struct snd_pcm_hardware dw_hdmi_hw = {295.info = SNDRV_PCM_INFO_INTERLEAVED |296SNDRV_PCM_INFO_BLOCK_TRANSFER |297SNDRV_PCM_INFO_MMAP |298SNDRV_PCM_INFO_MMAP_VALID,299.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE |300SNDRV_PCM_FMTBIT_S24_LE,301.rates = SNDRV_PCM_RATE_32000 |302SNDRV_PCM_RATE_44100 |303SNDRV_PCM_RATE_48000 |304SNDRV_PCM_RATE_88200 |305SNDRV_PCM_RATE_96000 |306SNDRV_PCM_RATE_176400 |307SNDRV_PCM_RATE_192000,308.channels_min = 2,309.channels_max = 8,310.buffer_bytes_max = 1024 * 1024,311.period_bytes_min = 256,312.period_bytes_max = 8192, /* ERR004323: must limit to 8k */313.periods_min = 2,314.periods_max = 16,315.fifo_size = 0,316};317318static int dw_hdmi_open(struct snd_pcm_substream *substream)319{320struct snd_pcm_runtime *runtime = substream->runtime;321struct snd_dw_hdmi *dw = substream->private_data;322void __iomem *base = dw->data.base;323u8 *eld;324int ret;325326runtime->hw = dw_hdmi_hw;327328eld = dw->data.get_eld(dw->data.hdmi);329if (eld) {330ret = snd_pcm_hw_constraint_eld(runtime, eld);331if (ret < 0)332return ret;333}334335ret = snd_pcm_limit_hw_rates(runtime);336if (ret < 0)337return ret;338339ret = snd_pcm_hw_constraint_integer(runtime,340SNDRV_PCM_HW_PARAM_PERIODS);341if (ret < 0)342return ret;343344/* Limit the buffer size to the size of the preallocated buffer */345ret = snd_pcm_hw_constraint_minmax(runtime,346SNDRV_PCM_HW_PARAM_BUFFER_SIZE,3470, substream->dma_buffer.bytes);348if (ret < 0)349return ret;350351/* Clear FIFO */352writeb_relaxed(HDMI_AHB_DMA_CONF0_SW_FIFO_RST,353base + HDMI_AHB_DMA_CONF0);354355/* Configure interrupt polarities */356writeb_relaxed(~0, base + HDMI_AHB_DMA_POL);357writeb_relaxed(~0, base + HDMI_AHB_DMA_BUFFPOL);358359/* Keep interrupts masked, and clear any pending */360writeb_relaxed(~0, base + HDMI_AHB_DMA_MASK);361writeb_relaxed(~0, base + HDMI_IH_AHBDMAAUD_STAT0);362363ret = request_irq(dw->data.irq, snd_dw_hdmi_irq, IRQF_SHARED,364"dw-hdmi-audio", dw);365if (ret)366return ret;367368/* Un-mute done interrupt */369writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL &370~HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE,371base + HDMI_IH_MUTE_AHBDMAAUD_STAT0);372373return 0;374}375376static int dw_hdmi_close(struct snd_pcm_substream *substream)377{378struct snd_dw_hdmi *dw = substream->private_data;379380/* Mute all interrupts */381writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,382dw->data.base + HDMI_IH_MUTE_AHBDMAAUD_STAT0);383384free_irq(dw->data.irq, dw);385386return 0;387}388389static int dw_hdmi_hw_free(struct snd_pcm_substream *substream)390{391struct snd_pcm_runtime *runtime = substream->runtime;392393vfree(runtime->dma_area);394runtime->dma_area = NULL;395return 0;396}397398static int dw_hdmi_hw_params(struct snd_pcm_substream *substream,399struct snd_pcm_hw_params *params)400{401struct snd_pcm_runtime *runtime = substream->runtime;402size_t size = params_buffer_bytes(params);403404/* Allocate the PCM runtime buffer, which is exposed to userspace. */405if (runtime->dma_area) {406if (runtime->dma_bytes >= size)407return 0; /* already large enough */408vfree(runtime->dma_area);409}410runtime->dma_area = vzalloc(size);411if (!runtime->dma_area)412return -ENOMEM;413runtime->dma_bytes = size;414return 1;415}416417static struct page *dw_hdmi_get_page(struct snd_pcm_substream *substream,418unsigned long offset)419{420return vmalloc_to_page(substream->runtime->dma_area + offset);421}422423static int dw_hdmi_prepare(struct snd_pcm_substream *substream)424{425struct snd_pcm_runtime *runtime = substream->runtime;426struct snd_dw_hdmi *dw = substream->private_data;427u8 threshold, conf0, conf1, ca;428429/* Setup as per 3.0.5 FSL 4.1.0 BSP */430switch (dw->revision) {431case 0x0a:432conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE |433HDMI_AHB_DMA_CONF0_INCR4;434if (runtime->channels == 2)435threshold = 126;436else437threshold = 124;438break;439case 0x1a:440conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE |441HDMI_AHB_DMA_CONF0_INCR8;442threshold = 128;443break;444default:445/* NOTREACHED */446return -EINVAL;447}448449dw_hdmi_set_sample_rate(dw->data.hdmi, runtime->rate);450451/* Minimum number of bytes in the fifo. */452runtime->hw.fifo_size = threshold * 32;453454conf0 |= HDMI_AHB_DMA_CONF0_EN_HLOCK;455conf1 = default_hdmi_channel_config[runtime->channels - 2].conf1;456ca = default_hdmi_channel_config[runtime->channels - 2].ca;457458writeb_relaxed(threshold, dw->data.base + HDMI_AHB_DMA_THRSLD);459writeb_relaxed(conf0, dw->data.base + HDMI_AHB_DMA_CONF0);460writeb_relaxed(conf1, dw->data.base + HDMI_AHB_DMA_CONF1);461462dw_hdmi_set_channel_count(dw->data.hdmi, runtime->channels);463dw_hdmi_set_channel_allocation(dw->data.hdmi, ca);464465switch (runtime->format) {466case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:467dw->reformat = dw_hdmi_reformat_iec958;468break;469case SNDRV_PCM_FORMAT_S24_LE:470dw_hdmi_create_cs(dw, runtime);471dw->reformat = dw_hdmi_reformat_s24;472break;473}474dw->iec_offset = 0;475dw->channels = runtime->channels;476dw->buf_src = runtime->dma_area;477dw->buf_dst = substream->dma_buffer.area;478dw->buf_addr = substream->dma_buffer.addr;479dw->buf_period = snd_pcm_lib_period_bytes(substream);480dw->buf_size = snd_pcm_lib_buffer_bytes(substream);481482return 0;483}484485static int dw_hdmi_trigger(struct snd_pcm_substream *substream, int cmd)486{487struct snd_dw_hdmi *dw = substream->private_data;488unsigned long flags;489int ret = 0;490491switch (cmd) {492case SNDRV_PCM_TRIGGER_START:493spin_lock_irqsave(&dw->lock, flags);494dw->buf_offset = 0;495dw->substream = substream;496dw_hdmi_start_dma(dw);497dw_hdmi_audio_enable(dw->data.hdmi);498spin_unlock_irqrestore(&dw->lock, flags);499substream->runtime->delay = substream->runtime->period_size;500break;501502case SNDRV_PCM_TRIGGER_STOP:503spin_lock_irqsave(&dw->lock, flags);504dw->substream = NULL;505dw_hdmi_stop_dma(dw);506dw_hdmi_audio_disable(dw->data.hdmi);507spin_unlock_irqrestore(&dw->lock, flags);508break;509510default:511ret = -EINVAL;512break;513}514515return ret;516}517518static snd_pcm_uframes_t dw_hdmi_pointer(struct snd_pcm_substream *substream)519{520struct snd_pcm_runtime *runtime = substream->runtime;521struct snd_dw_hdmi *dw = substream->private_data;522523/*524* We are unable to report the exact hardware position as525* reading the 32-bit DMA position using 8-bit reads is racy.526*/527return bytes_to_frames(runtime, dw->buf_offset);528}529530static const struct snd_pcm_ops snd_dw_hdmi_ops = {531.open = dw_hdmi_open,532.close = dw_hdmi_close,533.ioctl = snd_pcm_lib_ioctl,534.hw_params = dw_hdmi_hw_params,535.hw_free = dw_hdmi_hw_free,536.prepare = dw_hdmi_prepare,537.trigger = dw_hdmi_trigger,538.pointer = dw_hdmi_pointer,539.page = dw_hdmi_get_page,540};541542static int snd_dw_hdmi_probe(struct platform_device *pdev)543{544const struct dw_hdmi_audio_data *data = pdev->dev.platform_data;545struct device *dev = pdev->dev.parent;546struct snd_dw_hdmi *dw;547struct snd_card *card;548struct snd_pcm *pcm;549unsigned revision;550int ret;551552writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,553data->base + HDMI_IH_MUTE_AHBDMAAUD_STAT0);554revision = readb_relaxed(data->base + HDMI_REVISION_ID);555if (revision != 0x0a && revision != 0x1a) {556dev_err(dev, "dw-hdmi-audio: unknown revision 0x%02x\n",557revision);558return -ENXIO;559}560561ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,562THIS_MODULE, sizeof(struct snd_dw_hdmi), &card);563if (ret < 0)564return ret;565566strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));567strscpy(card->shortname, "DW-HDMI", sizeof(card->shortname));568snprintf(card->longname, sizeof(card->longname),569"%s rev 0x%02x, irq %d", card->shortname, revision,570data->irq);571572dw = card->private_data;573dw->card = card;574dw->data = *data;575dw->revision = revision;576577spin_lock_init(&dw->lock);578579ret = snd_pcm_new(card, "DW HDMI", 0, 1, 0, &pcm);580if (ret < 0)581goto err;582583dw->pcm = pcm;584pcm->private_data = dw;585strscpy(pcm->name, DRIVER_NAME, sizeof(pcm->name));586snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dw_hdmi_ops);587588/*589* To support 8-channel 96kHz audio reliably, we need 512k590* to satisfy alsa with our restricted period (ERR004323).591*/592snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,593dev, 128 * 1024, 1024 * 1024);594595ret = snd_card_register(card);596if (ret < 0)597goto err;598599platform_set_drvdata(pdev, dw);600601return 0;602603err:604snd_card_free(card);605return ret;606}607608static void snd_dw_hdmi_remove(struct platform_device *pdev)609{610struct snd_dw_hdmi *dw = platform_get_drvdata(pdev);611612snd_card_free(dw->card);613}614615#if defined(CONFIG_PM_SLEEP) && defined(IS_NOT_BROKEN)616/*617* This code is fine, but requires implementation in the dw_hdmi_trigger()618* method which is currently missing as I have no way to test this.619*/620static int snd_dw_hdmi_suspend(struct device *dev)621{622struct snd_dw_hdmi *dw = dev_get_drvdata(dev);623624snd_power_change_state(dw->card, SNDRV_CTL_POWER_D3cold);625626return 0;627}628629static int snd_dw_hdmi_resume(struct device *dev)630{631struct snd_dw_hdmi *dw = dev_get_drvdata(dev);632633snd_power_change_state(dw->card, SNDRV_CTL_POWER_D0);634635return 0;636}637638static SIMPLE_DEV_PM_OPS(snd_dw_hdmi_pm, snd_dw_hdmi_suspend,639snd_dw_hdmi_resume);640#define PM_OPS &snd_dw_hdmi_pm641#else642#define PM_OPS NULL643#endif644645static struct platform_driver snd_dw_hdmi_driver = {646.probe = snd_dw_hdmi_probe,647.remove = snd_dw_hdmi_remove,648.driver = {649.name = DRIVER_NAME,650.pm = PM_OPS,651},652};653654module_platform_driver(snd_dw_hdmi_driver);655656MODULE_AUTHOR("Russell King <[email protected]>");657MODULE_DESCRIPTION("Synopsis Designware HDMI AHB ALSA interface");658MODULE_LICENSE("GPL v2");659MODULE_ALIAS("platform:" DRIVER_NAME);660661662