diff options
Diffstat (limited to 'kernel/drivers/gpu/drm/bridge')
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/Kconfig | 36 | ||||
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/Makefile | 5 | ||||
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/dw_hdmi-ahb-audio.c | 653 | ||||
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/dw_hdmi-audio.h | 14 | ||||
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/dw_hdmi.c | 746 | ||||
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/dw_hdmi.h | 11 | ||||
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/nxp-ptn3460.c (renamed from kernel/drivers/gpu/drm/bridge/ptn3460.c) | 34 | ||||
-rw-r--r-- | kernel/drivers/gpu/drm/bridge/parade-ps8622.c (renamed from kernel/drivers/gpu/drm/bridge/ps8622.c) | 28 |
8 files changed, 1171 insertions, 356 deletions
diff --git a/kernel/drivers/gpu/drm/bridge/Kconfig b/kernel/drivers/gpu/drm/bridge/Kconfig index acef32237..6dddd392a 100644 --- a/kernel/drivers/gpu/drm/bridge/Kconfig +++ b/kernel/drivers/gpu/drm/bridge/Kconfig @@ -1,24 +1,44 @@ +config DRM_BRIDGE + def_bool y + depends on DRM + help + Bridge registration and lookup framework. + +menu "Display Interface Bridges" + depends on DRM && DRM_BRIDGE + config DRM_DW_HDMI tristate - depends on DRM select DRM_KMS_HELPER -config DRM_PTN3460 - tristate "PTN3460 DP/LVDS bridge" - depends on DRM +config DRM_DW_HDMI_AHB_AUDIO + tristate "Synopsis Designware AHB Audio interface" + depends on DRM_DW_HDMI && SND + select SND_PCM + select SND_PCM_ELD + select SND_PCM_IEC958 + help + Support the AHB Audio interface which is part of the Synopsis + Designware HDMI block. This is used in conjunction with + the i.MX6 HDMI driver. + + +config DRM_NXP_PTN3460 + tristate "NXP PTN3460 DP/LVDS bridge" depends on OF select DRM_KMS_HELPER select DRM_PANEL ---help--- - ptn3460 eDP-LVDS bridge chip driver. + NXP PTN3460 eDP-LVDS bridge chip driver. -config DRM_PS8622 +config DRM_PARADE_PS8622 tristate "Parade eDP/LVDS bridge" - depends on DRM depends on OF select DRM_PANEL select DRM_KMS_HELPER select BACKLIGHT_LCD_SUPPORT select BACKLIGHT_CLASS_DEVICE ---help--- - parade eDP-LVDS bridge chip driver. + Parade eDP-LVDS bridge chip driver. + +endmenu diff --git a/kernel/drivers/gpu/drm/bridge/Makefile b/kernel/drivers/gpu/drm/bridge/Makefile index 8dfebd984..d4e28beec 100644 --- a/kernel/drivers/gpu/drm/bridge/Makefile +++ b/kernel/drivers/gpu/drm/bridge/Makefile @@ -1,5 +1,6 @@ ccflags-y := -Iinclude/drm -obj-$(CONFIG_DRM_PS8622) += ps8622.o -obj-$(CONFIG_DRM_PTN3460) += ptn3460.o obj-$(CONFIG_DRM_DW_HDMI) += dw_hdmi.o +obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw_hdmi-ahb-audio.o +obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o +obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o diff --git a/kernel/drivers/gpu/drm/bridge/dw_hdmi-ahb-audio.c b/kernel/drivers/gpu/drm/bridge/dw_hdmi-ahb-audio.c new file mode 100644 index 000000000..59f630f1c --- /dev/null +++ b/kernel/drivers/gpu/drm/bridge/dw_hdmi-ahb-audio.c @@ -0,0 +1,653 @@ +/* + * DesignWare HDMI audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Written and tested against the Designware HDMI Tx found in iMX6. + */ +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <drm/bridge/dw_hdmi.h> +#include <drm/drm_edid.h> + +#include <sound/asoundef.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_drm_eld.h> +#include <sound/pcm_iec958.h> + +#include "dw_hdmi-audio.h" + +#define DRIVER_NAME "dw-hdmi-ahb-audio" + +/* Provide some bits rather than bit offsets */ +enum { + HDMI_AHB_DMA_CONF0_SW_FIFO_RST = BIT(7), + HDMI_AHB_DMA_CONF0_EN_HLOCK = BIT(3), + HDMI_AHB_DMA_START_START = BIT(0), + HDMI_AHB_DMA_STOP_STOP = BIT(0), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = BIT(5), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = BIT(4), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = BIT(3), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = BIT(2), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = BIT(1), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL = + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY, + HDMI_IH_AHBDMAAUD_STAT0_ERROR = BIT(5), + HDMI_IH_AHBDMAAUD_STAT0_LOST = BIT(4), + HDMI_IH_AHBDMAAUD_STAT0_RETRY = BIT(3), + HDMI_IH_AHBDMAAUD_STAT0_DONE = BIT(2), + HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = BIT(1), + HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0), + HDMI_IH_AHBDMAAUD_STAT0_ALL = + HDMI_IH_AHBDMAAUD_STAT0_ERROR | + HDMI_IH_AHBDMAAUD_STAT0_LOST | + HDMI_IH_AHBDMAAUD_STAT0_RETRY | + HDMI_IH_AHBDMAAUD_STAT0_DONE | + HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL | + HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY, + HDMI_AHB_DMA_CONF0_INCR16 = 2 << 1, + HDMI_AHB_DMA_CONF0_INCR8 = 1 << 1, + HDMI_AHB_DMA_CONF0_INCR4 = 0, + HDMI_AHB_DMA_CONF0_BURST_MODE = BIT(0), + HDMI_AHB_DMA_MASK_DONE = BIT(7), + + HDMI_REVISION_ID = 0x0001, + HDMI_IH_AHBDMAAUD_STAT0 = 0x0109, + HDMI_IH_MUTE_AHBDMAAUD_STAT0 = 0x0189, + HDMI_FC_AUDICONF2 = 0x1027, + HDMI_FC_AUDSCONF = 0x1063, + HDMI_FC_AUDSCONF_LAYOUT1 = 1 << 0, + HDMI_FC_AUDSCONF_LAYOUT0 = 0 << 0, + HDMI_AHB_DMA_CONF0 = 0x3600, + HDMI_AHB_DMA_START = 0x3601, + HDMI_AHB_DMA_STOP = 0x3602, + HDMI_AHB_DMA_THRSLD = 0x3603, + HDMI_AHB_DMA_STRADDR0 = 0x3604, + HDMI_AHB_DMA_STPADDR0 = 0x3608, + HDMI_AHB_DMA_MASK = 0x3614, + HDMI_AHB_DMA_POL = 0x3615, + HDMI_AHB_DMA_CONF1 = 0x3616, + HDMI_AHB_DMA_BUFFPOL = 0x361a, +}; + +struct dw_hdmi_channel_conf { + u8 conf1; + u8 ca; +}; + +/* + * The default mapping of ALSA channels to HDMI channels and speaker + * allocation bits. Note that we can't do channel remapping here - + * channels must be in the same order. + * + * Mappings for alsa-lib pcm/surround*.conf files: + * + * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1 + * Channels 2 4 6 6 6 8 + * + * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel: + * + * Number of ALSA channels + * ALSA Channel 2 3 4 5 6 7 8 + * 0 FL:0 = = = = = = + * 1 FR:1 = = = = = = + * 2 FC:3 RL:4 LFE:2 = = = + * 3 RR:5 RL:4 FC:3 = = + * 4 RR:5 RL:4 = = + * 5 RR:5 = = + * 6 RC:6 = + * 7 RLC/FRC RLC/FRC + */ +static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = { + { 0x03, 0x00 }, /* FL,FR */ + { 0x0b, 0x02 }, /* FL,FR,FC */ + { 0x33, 0x08 }, /* FL,FR,RL,RR */ + { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */ + { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */ + { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */ + { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */ +}; + +struct snd_dw_hdmi { + struct snd_card *card; + struct snd_pcm *pcm; + spinlock_t lock; + struct dw_hdmi_audio_data data; + struct snd_pcm_substream *substream; + void (*reformat)(struct snd_dw_hdmi *, size_t, size_t); + void *buf_src; + void *buf_dst; + dma_addr_t buf_addr; + unsigned buf_offset; + unsigned buf_period; + unsigned buf_size; + unsigned channels; + u8 revision; + u8 iec_offset; + u8 cs[192][8]; +}; + +static void dw_hdmi_writel(u32 val, void __iomem *ptr) +{ + writeb_relaxed(val, ptr); + writeb_relaxed(val >> 8, ptr + 1); + writeb_relaxed(val >> 16, ptr + 2); + writeb_relaxed(val >> 24, ptr + 3); +} + +/* + * Convert to hardware format: The userspace buffer contains IEC958 samples, + * with the PCUV bits in bits 31..28 and audio samples in bits 27..4. We + * need these to be in bits 27..24, with the IEC B bit in bit 28, and audio + * samples in 23..0. + * + * Default preamble in bits 3..0: 8 = block start, 4 = even 2 = odd + * + * Ideally, we could do with having the data properly formatted in userspace. + */ +static void dw_hdmi_reformat_iec958(struct snd_dw_hdmi *dw, + size_t offset, size_t bytes) +{ + u32 *src = dw->buf_src + offset; + u32 *dst = dw->buf_dst + offset; + u32 *end = dw->buf_src + offset + bytes; + + do { + u32 b, sample = *src++; + + b = (sample & 8) << (28 - 3); + + sample >>= 4; + + *dst++ = sample | b; + } while (src < end); +} + +static u32 parity(u32 sample) +{ + sample ^= sample >> 16; + sample ^= sample >> 8; + sample ^= sample >> 4; + sample ^= sample >> 2; + sample ^= sample >> 1; + return (sample & 1) << 27; +} + +static void dw_hdmi_reformat_s24(struct snd_dw_hdmi *dw, + size_t offset, size_t bytes) +{ + u32 *src = dw->buf_src + offset; + u32 *dst = dw->buf_dst + offset; + u32 *end = dw->buf_src + offset + bytes; + + do { + unsigned i; + u8 *cs; + + cs = dw->cs[dw->iec_offset++]; + if (dw->iec_offset >= 192) + dw->iec_offset = 0; + + i = dw->channels; + do { + u32 sample = *src++; + + sample &= ~0xff000000; + sample |= *cs++ << 24; + sample |= parity(sample & ~0xf8000000); + + *dst++ = sample; + } while (--i); + } while (src < end); +} + +static void dw_hdmi_create_cs(struct snd_dw_hdmi *dw, + struct snd_pcm_runtime *runtime) +{ + u8 cs[4]; + unsigned ch, i, j; + + snd_pcm_create_iec958_consumer(runtime, cs, sizeof(cs)); + + memset(dw->cs, 0, sizeof(dw->cs)); + + for (ch = 0; ch < 8; ch++) { + cs[2] &= ~IEC958_AES2_CON_CHANNEL; + cs[2] |= (ch + 1) << 4; + + for (i = 0; i < ARRAY_SIZE(cs); i++) { + unsigned c = cs[i]; + + for (j = 0; j < 8; j++, c >>= 1) + dw->cs[i * 8 + j][ch] = (c & 1) << 2; + } + } + dw->cs[0][0] |= BIT(4); +} + +static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw) +{ + void __iomem *base = dw->data.base; + unsigned offset = dw->buf_offset; + unsigned period = dw->buf_period; + u32 start, stop; + + dw->reformat(dw, offset, period); + + /* Clear all irqs before enabling irqs and starting DMA */ + writeb_relaxed(HDMI_IH_AHBDMAAUD_STAT0_ALL, + base + HDMI_IH_AHBDMAAUD_STAT0); + + start = dw->buf_addr + offset; + stop = start + period - 1; + + /* Setup the hardware start/stop addresses */ + dw_hdmi_writel(start, base + HDMI_AHB_DMA_STRADDR0); + dw_hdmi_writel(stop, base + HDMI_AHB_DMA_STPADDR0); + + writeb_relaxed((u8)~HDMI_AHB_DMA_MASK_DONE, base + HDMI_AHB_DMA_MASK); + writeb(HDMI_AHB_DMA_START_START, base + HDMI_AHB_DMA_START); + + offset += period; + if (offset >= dw->buf_size) + offset = 0; + dw->buf_offset = offset; +} + +static void dw_hdmi_stop_dma(struct snd_dw_hdmi *dw) +{ + /* Disable interrupts before disabling DMA */ + writeb_relaxed(~0, dw->data.base + HDMI_AHB_DMA_MASK); + writeb_relaxed(HDMI_AHB_DMA_STOP_STOP, dw->data.base + HDMI_AHB_DMA_STOP); +} + +static irqreturn_t snd_dw_hdmi_irq(int irq, void *data) +{ + struct snd_dw_hdmi *dw = data; + struct snd_pcm_substream *substream; + unsigned stat; + + stat = readb_relaxed(dw->data.base + HDMI_IH_AHBDMAAUD_STAT0); + if (!stat) + return IRQ_NONE; + + writeb_relaxed(stat, dw->data.base + HDMI_IH_AHBDMAAUD_STAT0); + + substream = dw->substream; + if (stat & HDMI_IH_AHBDMAAUD_STAT0_DONE && substream) { + snd_pcm_period_elapsed(substream); + + spin_lock(&dw->lock); + if (dw->substream) + dw_hdmi_start_dma(dw); + spin_unlock(&dw->lock); + } + + return IRQ_HANDLED; +} + +static struct snd_pcm_hardware dw_hdmi_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 8192, /* ERR004323: must limit to 8k */ + .periods_min = 2, + .periods_max = 16, + .fifo_size = 0, +}; + +static int dw_hdmi_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dw_hdmi *dw = substream->private_data; + void __iomem *base = dw->data.base; + int ret; + + runtime->hw = dw_hdmi_hw; + + ret = snd_pcm_hw_constraint_eld(runtime, dw->data.eld); + if (ret < 0) + return ret; + + ret = snd_pcm_limit_hw_rates(runtime); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + /* Limit the buffer size to the size of the preallocated buffer */ + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 0, substream->dma_buffer.bytes); + if (ret < 0) + return ret; + + /* Clear FIFO */ + writeb_relaxed(HDMI_AHB_DMA_CONF0_SW_FIFO_RST, + base + HDMI_AHB_DMA_CONF0); + + /* Configure interrupt polarities */ + writeb_relaxed(~0, base + HDMI_AHB_DMA_POL); + writeb_relaxed(~0, base + HDMI_AHB_DMA_BUFFPOL); + + /* Keep interrupts masked, and clear any pending */ + writeb_relaxed(~0, base + HDMI_AHB_DMA_MASK); + writeb_relaxed(~0, base + HDMI_IH_AHBDMAAUD_STAT0); + + ret = request_irq(dw->data.irq, snd_dw_hdmi_irq, IRQF_SHARED, + "dw-hdmi-audio", dw); + if (ret) + return ret; + + /* Un-mute done interrupt */ + writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL & + ~HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE, + base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); + + return 0; +} + +static int dw_hdmi_close(struct snd_pcm_substream *substream) +{ + struct snd_dw_hdmi *dw = substream->private_data; + + /* Mute all interrupts */ + writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL, + dw->data.base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); + + free_irq(dw->data.irq, dw); + + return 0; +} + +static int dw_hdmi_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int dw_hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + /* Allocate the PCM runtime buffer, which is exposed to userspace. */ + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int dw_hdmi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dw_hdmi *dw = substream->private_data; + u8 threshold, conf0, conf1, layout, ca; + + /* Setup as per 3.0.5 FSL 4.1.0 BSP */ + switch (dw->revision) { + case 0x0a: + conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR4; + if (runtime->channels == 2) + threshold = 126; + else + threshold = 124; + break; + case 0x1a: + conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR8; + threshold = 128; + break; + default: + /* NOTREACHED */ + return -EINVAL; + } + + dw_hdmi_set_sample_rate(dw->data.hdmi, runtime->rate); + + /* Minimum number of bytes in the fifo. */ + runtime->hw.fifo_size = threshold * 32; + + conf0 |= HDMI_AHB_DMA_CONF0_EN_HLOCK; + conf1 = default_hdmi_channel_config[runtime->channels - 2].conf1; + ca = default_hdmi_channel_config[runtime->channels - 2].ca; + + /* + * For >2 channel PCM audio, we need to select layout 1 + * and set an appropriate channel map. + */ + if (runtime->channels > 2) + layout = HDMI_FC_AUDSCONF_LAYOUT1; + else + layout = HDMI_FC_AUDSCONF_LAYOUT0; + + writeb_relaxed(threshold, dw->data.base + HDMI_AHB_DMA_THRSLD); + writeb_relaxed(conf0, dw->data.base + HDMI_AHB_DMA_CONF0); + writeb_relaxed(conf1, dw->data.base + HDMI_AHB_DMA_CONF1); + writeb_relaxed(layout, dw->data.base + HDMI_FC_AUDSCONF); + writeb_relaxed(ca, dw->data.base + HDMI_FC_AUDICONF2); + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + dw->reformat = dw_hdmi_reformat_iec958; + break; + case SNDRV_PCM_FORMAT_S24_LE: + dw_hdmi_create_cs(dw, runtime); + dw->reformat = dw_hdmi_reformat_s24; + break; + } + dw->iec_offset = 0; + dw->channels = runtime->channels; + dw->buf_src = runtime->dma_area; + dw->buf_dst = substream->dma_buffer.area; + dw->buf_addr = substream->dma_buffer.addr; + dw->buf_period = snd_pcm_lib_period_bytes(substream); + dw->buf_size = snd_pcm_lib_buffer_bytes(substream); + + return 0; +} + +static int dw_hdmi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dw_hdmi *dw = substream->private_data; + unsigned long flags; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + spin_lock_irqsave(&dw->lock, flags); + dw->buf_offset = 0; + dw->substream = substream; + dw_hdmi_start_dma(dw); + dw_hdmi_audio_enable(dw->data.hdmi); + spin_unlock_irqrestore(&dw->lock, flags); + substream->runtime->delay = substream->runtime->period_size; + break; + + case SNDRV_PCM_TRIGGER_STOP: + spin_lock_irqsave(&dw->lock, flags); + dw->substream = NULL; + dw_hdmi_stop_dma(dw); + dw_hdmi_audio_disable(dw->data.hdmi); + spin_unlock_irqrestore(&dw->lock, flags); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t dw_hdmi_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dw_hdmi *dw = substream->private_data; + + /* + * We are unable to report the exact hardware position as + * reading the 32-bit DMA position using 8-bit reads is racy. + */ + return bytes_to_frames(runtime, dw->buf_offset); +} + +static struct snd_pcm_ops snd_dw_hdmi_ops = { + .open = dw_hdmi_open, + .close = dw_hdmi_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dw_hdmi_hw_params, + .hw_free = dw_hdmi_hw_free, + .prepare = dw_hdmi_prepare, + .trigger = dw_hdmi_trigger, + .pointer = dw_hdmi_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static int snd_dw_hdmi_probe(struct platform_device *pdev) +{ + const struct dw_hdmi_audio_data *data = pdev->dev.platform_data; + struct device *dev = pdev->dev.parent; + struct snd_dw_hdmi *dw; + struct snd_card *card; + struct snd_pcm *pcm; + unsigned revision; + int ret; + + writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL, + data->base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); + revision = readb_relaxed(data->base + HDMI_REVISION_ID); + if (revision != 0x0a && revision != 0x1a) { + dev_err(dev, "dw-hdmi-audio: unknown revision 0x%02x\n", + revision); + return -ENXIO; + } + + ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(struct snd_dw_hdmi), &card); + if (ret < 0) + return ret; + + strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strlcpy(card->shortname, "DW-HDMI", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s rev 0x%02x, irq %d", card->shortname, revision, + data->irq); + + dw = card->private_data; + dw->card = card; + dw->data = *data; + dw->revision = revision; + + spin_lock_init(&dw->lock); + + ret = snd_pcm_new(card, "DW HDMI", 0, 1, 0, &pcm); + if (ret < 0) + goto err; + + dw->pcm = pcm; + pcm->private_data = dw; + strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name)); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dw_hdmi_ops); + + /* + * To support 8-channel 96kHz audio reliably, we need 512k + * to satisfy alsa with our restricted period (ERR004323). + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + dev, 128 * 1024, 1024 * 1024); + + ret = snd_card_register(card); + if (ret < 0) + goto err; + + platform_set_drvdata(pdev, dw); + + return 0; + +err: + snd_card_free(card); + return ret; +} + +static int snd_dw_hdmi_remove(struct platform_device *pdev) +{ + struct snd_dw_hdmi *dw = platform_get_drvdata(pdev); + + snd_card_free(dw->card); + + return 0; +} + +#if defined(CONFIG_PM_SLEEP) && defined(IS_NOT_BROKEN) +/* + * This code is fine, but requires implementation in the dw_hdmi_trigger() + * method which is currently missing as I have no way to test this. + */ +static int snd_dw_hdmi_suspend(struct device *dev) +{ + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); + + snd_power_change_state(dw->card, SNDRV_CTL_POWER_D3cold); + snd_pcm_suspend_all(dw->pcm); + + return 0; +} + +static int snd_dw_hdmi_resume(struct device *dev) +{ + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); + + snd_power_change_state(dw->card, SNDRV_CTL_POWER_D0); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_dw_hdmi_pm, snd_dw_hdmi_suspend, + snd_dw_hdmi_resume); +#define PM_OPS &snd_dw_hdmi_pm +#else +#define PM_OPS NULL +#endif + +static struct platform_driver snd_dw_hdmi_driver = { + .probe = snd_dw_hdmi_probe, + .remove = snd_dw_hdmi_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = PM_OPS, + }, +}; + +module_platform_driver(snd_dw_hdmi_driver); + +MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>"); +MODULE_DESCRIPTION("Synopsis Designware HDMI AHB ALSA interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/kernel/drivers/gpu/drm/bridge/dw_hdmi-audio.h b/kernel/drivers/gpu/drm/bridge/dw_hdmi-audio.h new file mode 100644 index 000000000..91f631bee --- /dev/null +++ b/kernel/drivers/gpu/drm/bridge/dw_hdmi-audio.h @@ -0,0 +1,14 @@ +#ifndef DW_HDMI_AUDIO_H +#define DW_HDMI_AUDIO_H + +struct dw_hdmi; + +struct dw_hdmi_audio_data { + phys_addr_t phys; + void __iomem *base; + int irq; + struct dw_hdmi *hdmi; + u8 *eld; +}; + +#endif diff --git a/kernel/drivers/gpu/drm/bridge/dw_hdmi.c b/kernel/drivers/gpu/drm/bridge/dw_hdmi.c index 49cafb61d..56de9f1c9 100644 --- a/kernel/drivers/gpu/drm/bridge/dw_hdmi.c +++ b/kernel/drivers/gpu/drm/bridge/dw_hdmi.c @@ -18,6 +18,7 @@ #include <linux/hdmi.h> #include <linux/mutex.h> #include <linux/of_device.h> +#include <linux/spinlock.h> #include <drm/drm_of.h> #include <drm/drmP.h> @@ -27,6 +28,7 @@ #include <drm/bridge/dw_hdmi.h> #include "dw_hdmi.h" +#include "dw_hdmi-audio.h" #define HDMI_EDID_LEN 512 @@ -81,10 +83,6 @@ static const u16 csc_coeff_rgb_in_eitu709[3][4] = { }; struct hdmi_vmode { - bool mdvi; - bool mhsyncpolarity; - bool mvsyncpolarity; - bool minterlaced; bool mdataenablepolarity; unsigned int mpixelclock; @@ -107,6 +105,7 @@ struct dw_hdmi { struct drm_encoder *encoder; struct drm_bridge *bridge; + struct platform_device *audio; enum dw_hdmi_devtype dev_type; struct device *dev; struct clk *isfr_clk; @@ -123,18 +122,37 @@ struct dw_hdmi { bool phy_enabled; struct drm_display_mode previous_mode; - struct regmap *regmap; struct i2c_adapter *ddc; void __iomem *regs; + bool sink_is_hdmi; + bool sink_has_audio; + struct mutex mutex; /* for state below and previous_mode */ + enum drm_connector_force force; /* mutex-protected force state */ + bool disabled; /* DRM has disabled our bridge */ + bool bridge_is_on; /* indicates the bridge is on */ + bool rxsense; /* rxsense state */ + u8 phy_mask; /* desired phy int mask settings */ + + spinlock_t audio_lock; struct mutex audio_mutex; unsigned int sample_rate; - int ratio; + unsigned int audio_cts; + unsigned int audio_n; + bool audio_enable; void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); u8 (*read)(struct dw_hdmi *hdmi, int offset); }; +#define HDMI_IH_PHY_STAT0_RX_SENSE \ + (HDMI_IH_PHY_STAT0_RX_SENSE0 | HDMI_IH_PHY_STAT0_RX_SENSE1 | \ + HDMI_IH_PHY_STAT0_RX_SENSE2 | HDMI_IH_PHY_STAT0_RX_SENSE3) + +#define HDMI_PHY_RX_SENSE \ + (HDMI_PHY_RX_SENSE0 | HDMI_PHY_RX_SENSE1 | \ + HDMI_PHY_RX_SENSE2 | HDMI_PHY_RX_SENSE3) + static void dw_hdmi_writel(struct dw_hdmi *hdmi, u8 val, int offset) { writel(val, hdmi->regs + (offset << 2)); @@ -198,61 +216,53 @@ static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts, hdmi_writeb(hdmi, n & 0xff, HDMI_AUD_N1); } -static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk, - unsigned int ratio) +static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) { unsigned int n = (128 * freq) / 1000; + unsigned int mult = 1; + + while (freq > 48000) { + mult *= 2; + freq /= 2; + } switch (freq) { case 32000: - if (pixel_clk == 25170000) - n = (ratio == 150) ? 9152 : 4576; - else if (pixel_clk == 27020000) - n = (ratio == 150) ? 8192 : 4096; - else if (pixel_clk == 74170000 || pixel_clk == 148350000) + if (pixel_clk == 25175000) + n = 4576; + else if (pixel_clk == 27027000) + n = 4096; + else if (pixel_clk == 74176000 || pixel_clk == 148352000) n = 11648; else n = 4096; + n *= mult; break; case 44100: - if (pixel_clk == 25170000) + if (pixel_clk == 25175000) n = 7007; - else if (pixel_clk == 74170000) + else if (pixel_clk == 74176000) n = 17836; - else if (pixel_clk == 148350000) - n = (ratio == 150) ? 17836 : 8918; + else if (pixel_clk == 148352000) + n = 8918; else n = 6272; + n *= mult; break; case 48000: - if (pixel_clk == 25170000) - n = (ratio == 150) ? 9152 : 6864; - else if (pixel_clk == 27020000) - n = (ratio == 150) ? 8192 : 6144; - else if (pixel_clk == 74170000) + if (pixel_clk == 25175000) + n = 6864; + else if (pixel_clk == 27027000) + n = 6144; + else if (pixel_clk == 74176000) n = 11648; - else if (pixel_clk == 148350000) - n = (ratio == 150) ? 11648 : 5824; + else if (pixel_clk == 148352000) + n = 5824; else n = 6144; - break; - - case 88200: - n = hdmi_compute_n(44100, pixel_clk, ratio) * 2; - break; - - case 96000: - n = hdmi_compute_n(48000, pixel_clk, ratio) * 2; - break; - - case 176400: - n = hdmi_compute_n(44100, pixel_clk, ratio) * 4; - break; - - case 192000: - n = hdmi_compute_n(48000, pixel_clk, ratio) * 4; + n *= mult; break; default: @@ -262,115 +272,84 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk, return n; } -static unsigned int hdmi_compute_cts(unsigned int freq, unsigned long pixel_clk, - unsigned int ratio) -{ - unsigned int cts = 0; - - pr_debug("%s: freq: %d pixel_clk: %ld ratio: %d\n", __func__, freq, - pixel_clk, ratio); - - switch (freq) { - case 32000: - if (pixel_clk == 297000000) { - cts = 222750; - break; - } - case 48000: - case 96000: - case 192000: - switch (pixel_clk) { - case 25200000: - case 27000000: - case 54000000: - case 74250000: - case 148500000: - cts = pixel_clk / 1000; - break; - case 297000000: - cts = 247500; - break; - /* - * All other TMDS clocks are not supported by - * DWC_hdmi_tx. The TMDS clocks divided or - * multiplied by 1,001 coefficients are not - * supported. - */ - default: - break; - } - break; - case 44100: - case 88200: - case 176400: - switch (pixel_clk) { - case 25200000: - cts = 28000; - break; - case 27000000: - cts = 30000; - break; - case 54000000: - cts = 60000; - break; - case 74250000: - cts = 82500; - break; - case 148500000: - cts = 165000; - break; - case 297000000: - cts = 247500; - break; - default: - break; - } - break; - default: - break; - } - if (ratio == 100) - return cts; - return (cts * ratio) / 100; -} - static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, - unsigned long pixel_clk) + unsigned long pixel_clk, unsigned int sample_rate) { - unsigned int clk_n, clk_cts; + unsigned long ftdms = pixel_clk; + unsigned int n, cts; + u64 tmp; - clk_n = hdmi_compute_n(hdmi->sample_rate, pixel_clk, - hdmi->ratio); - clk_cts = hdmi_compute_cts(hdmi->sample_rate, pixel_clk, - hdmi->ratio); + n = hdmi_compute_n(sample_rate, pixel_clk); - if (!clk_cts) { - dev_dbg(hdmi->dev, "%s: pixel clock not supported: %lu\n", - __func__, pixel_clk); - return; - } + /* + * Compute the CTS value from the N value. Note that CTS and N + * can be up to 20 bits in total, so we need 64-bit math. Also + * note that our TDMS clock is not fully accurate; it is accurate + * to kHz. This can introduce an unnecessary remainder in the + * calculation below, so we don't try to warn about that. + */ + tmp = (u64)ftdms * n; + do_div(tmp, 128 * sample_rate); + cts = tmp; - dev_dbg(hdmi->dev, "%s: samplerate=%d ratio=%d pixelclk=%lu N=%d cts=%d\n", - __func__, hdmi->sample_rate, hdmi->ratio, - pixel_clk, clk_n, clk_cts); + dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", + __func__, sample_rate, ftdms / 1000000, (ftdms / 1000) % 1000, + n, cts); - hdmi_set_cts_n(hdmi, clk_cts, clk_n); + spin_lock_irq(&hdmi->audio_lock); + hdmi->audio_n = n; + hdmi->audio_cts = cts; + hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0); + spin_unlock_irq(&hdmi->audio_lock); } static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi) { mutex_lock(&hdmi->audio_mutex); - hdmi_set_clk_regenerator(hdmi, 74250000); + hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate); mutex_unlock(&hdmi->audio_mutex); } static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) { mutex_lock(&hdmi->audio_mutex); - hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock); + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock, + hdmi->sample_rate); mutex_unlock(&hdmi->audio_mutex); } +void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_rate = rate; + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate); + +void dw_hdmi_audio_enable(struct dw_hdmi *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = true; + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_audio_enable); + +void dw_hdmi_audio_disable(struct dw_hdmi *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = false; + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_audio_disable); + /* * this submodule is responsible for the video data synchronization. * for example, for RGB 4:4:4 input, the data map is defined as @@ -701,9 +680,9 @@ static int hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, return 0; } -static void dw_hdmi_phy_enable_power(struct dw_hdmi *hdmi, u8 enable) +static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable) { - hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0, HDMI_PHY_CONF0_PDZ_OFFSET, HDMI_PHY_CONF0_PDZ_MASK); } @@ -753,12 +732,12 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable) static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep, unsigned char res, int cscon) { - unsigned res_idx, i; + unsigned res_idx; u8 val, msec; - const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data; - const struct dw_hdmi_mpll_config *mpll_config = plat_data->mpll_cfg; - const struct dw_hdmi_curr_ctrl *curr_ctrl = plat_data->cur_ctr; - const struct dw_hdmi_phy_config *phy_config = plat_data->phy_config; + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg; + const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr; + const struct dw_hdmi_phy_config *phy_config = pdata->phy_config; if (prep) return -EINVAL; @@ -778,6 +757,30 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep, return -EINVAL; } + /* PLL/MPLL Cfg - always match on final entry */ + for (; mpll_config->mpixelclock != ~0UL; mpll_config++) + if (hdmi->hdmi_data.video_mode.mpixelclock <= + mpll_config->mpixelclock) + break; + + for (; curr_ctrl->mpixelclock != ~0UL; curr_ctrl++) + if (hdmi->hdmi_data.video_mode.mpixelclock <= + curr_ctrl->mpixelclock) + break; + + for (; phy_config->mpixelclock != ~0UL; phy_config++) + if (hdmi->hdmi_data.video_mode.mpixelclock <= + phy_config->mpixelclock) + break; + + if (mpll_config->mpixelclock == ~0UL || + curr_ctrl->mpixelclock == ~0UL || + phy_config->mpixelclock == ~0UL) { + dev_err(hdmi->dev, "Pixel clock %d - unsupported by HDMI\n", + hdmi->hdmi_data.video_mode.mpixelclock); + return -EINVAL; + } + /* Enable csc path */ if (cscon) val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH; @@ -803,48 +806,23 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep, HDMI_PHY_I2CM_SLAVE_ADDR); hdmi_phy_test_clear(hdmi, 0); - /* PLL/MPLL Cfg - always match on final entry */ - for (i = 0; mpll_config[i].mpixelclock != (~0UL); i++) - if (hdmi->hdmi_data.video_mode.mpixelclock <= - mpll_config[i].mpixelclock) - break; - - hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].cpce, 0x06); - hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].gmp, 0x15); - - for (i = 0; curr_ctrl[i].mpixelclock != (~0UL); i++) - if (hdmi->hdmi_data.video_mode.mpixelclock <= - curr_ctrl[i].mpixelclock) - break; - - if (curr_ctrl[i].mpixelclock == (~0UL)) { - dev_err(hdmi->dev, "Pixel clock %d - unsupported by HDMI\n", - hdmi->hdmi_data.video_mode.mpixelclock); - return -EINVAL; - } + hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce, 0x06); + hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].gmp, 0x15); /* CURRCTRL */ - hdmi_phy_i2c_write(hdmi, curr_ctrl[i].curr[res_idx], 0x10); + hdmi_phy_i2c_write(hdmi, curr_ctrl->curr[res_idx], 0x10); hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */ hdmi_phy_i2c_write(hdmi, 0x0006, 0x17); - for (i = 0; phy_config[i].mpixelclock != (~0UL); i++) - if (hdmi->hdmi_data.video_mode.mpixelclock <= - phy_config[i].mpixelclock) - break; - - /* RESISTANCE TERM 133Ohm Cfg */ - hdmi_phy_i2c_write(hdmi, phy_config[i].term, 0x19); /* TXTERM */ - /* PREEMP Cgf 0.00 */ - hdmi_phy_i2c_write(hdmi, phy_config[i].sym_ctr, 0x09); /* CKSYMTXCTRL */ - /* TX/CK LVL 10 */ - hdmi_phy_i2c_write(hdmi, phy_config[i].vlev_ctr, 0x0E); /* VLEVCTRL */ + hdmi_phy_i2c_write(hdmi, phy_config->term, 0x19); /* TXTERM */ + hdmi_phy_i2c_write(hdmi, phy_config->sym_ctr, 0x09); /* CKSYMTXCTRL */ + hdmi_phy_i2c_write(hdmi, phy_config->vlev_ctr, 0x0E); /* VLEVCTRL */ /* REMOVE CLK TERM */ hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */ - dw_hdmi_phy_enable_power(hdmi, 1); + dw_hdmi_phy_enable_powerdown(hdmi, false); /* toggle TMDS enable */ dw_hdmi_phy_enable_tmds(hdmi, 0); @@ -879,18 +857,17 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep, static int dw_hdmi_phy_init(struct dw_hdmi *hdmi) { int i, ret; - bool cscon = false; + bool cscon; /*check csc whether needed activated in HDMI mode */ - cscon = (is_color_space_conversion(hdmi) && - !hdmi->hdmi_data.video_mode.mdvi); + cscon = hdmi->sink_is_hdmi && is_color_space_conversion(hdmi); /* HDMI Phy spec says to do the phy initialization sequence twice */ for (i = 0; i < 2; i++) { dw_hdmi_phy_sel_data_en_pol(hdmi, 1); dw_hdmi_phy_sel_interface_control(hdmi, 0); dw_hdmi_phy_enable_tmds(hdmi, 0); - dw_hdmi_phy_enable_power(hdmi, 0); + dw_hdmi_phy_enable_powerdown(hdmi, true); /* Enable CSC */ ret = hdmi_phy_configure(hdmi, 0, 8, cscon); @@ -921,74 +898,76 @@ static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi) HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); } -static void hdmi_config_AVI(struct dw_hdmi *hdmi) +static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) { - u8 val, pix_fmt, under_scan; - u8 act_ratio, coded_ratio, colorimetry, ext_colorimetry; - bool aspect_16_9; + struct hdmi_avi_infoframe frame; + u8 val; - aspect_16_9 = false; /* FIXME */ + /* Initialise info frame from DRM mode */ + drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); - /* AVI Data Byte 1 */ if (hdmi->hdmi_data.enc_out_format == YCBCR444) - pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR444; + frame.colorspace = HDMI_COLORSPACE_YUV444; else if (hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) - pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR422; + frame.colorspace = HDMI_COLORSPACE_YUV422; else - pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_RGB; - - under_scan = HDMI_FC_AVICONF0_SCAN_INFO_NODATA; - - /* - * Active format identification data is present in the AVI InfoFrame. - * Under scan info, no bar data - */ - val = pix_fmt | under_scan | - HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT | - HDMI_FC_AVICONF0_BAR_DATA_NO_DATA; - - hdmi_writeb(hdmi, val, HDMI_FC_AVICONF0); - - /* AVI Data Byte 2 -Set the Aspect Ratio */ - if (aspect_16_9) { - act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9; - coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9; - } else { - act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3; - coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3; - } + frame.colorspace = HDMI_COLORSPACE_RGB; /* Set up colorimetry */ if (hdmi->hdmi_data.enc_out_format == XVYCC444) { - colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO; + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601) - ext_colorimetry = - HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; else /*hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_709*/ - ext_colorimetry = - HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; } else if (hdmi->hdmi_data.enc_out_format != RGB) { - if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601) - colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_SMPTE; - else /*hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_709*/ - colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_ITUR; - ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; + frame.colorimetry = hdmi->hdmi_data.colorimetry; + frame.extended_colorimetry = HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; } else { /* Carries no data */ - colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA; - ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; + frame.colorimetry = HDMI_COLORIMETRY_NONE; + frame.extended_colorimetry = HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; } - val = colorimetry | coded_ratio | act_ratio; + frame.scan_mode = HDMI_SCAN_MODE_NONE; + + /* + * The Designware IP uses a different byte format from standard + * AVI info frames, though generally the bits are in the correct + * bytes. + */ + + /* + * AVI data byte 1 differences: Colorspace in bits 4,5 rather than 5,6, + * active aspect present in bit 6 rather than 4. + */ + val = (frame.colorspace & 3) << 4 | (frame.scan_mode & 0x3); + if (frame.active_aspect & 15) + val |= HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT; + if (frame.top_bar || frame.bottom_bar) + val |= HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR; + if (frame.left_bar || frame.right_bar) + val |= HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR; + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF0); + + /* AVI data byte 2 differences: none */ + val = ((frame.colorimetry & 0x3) << 6) | + ((frame.picture_aspect & 0x3) << 4) | + (frame.active_aspect & 0xf); hdmi_writeb(hdmi, val, HDMI_FC_AVICONF1); - /* AVI Data Byte 3 */ - val = HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA | ext_colorimetry | - HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT | - HDMI_FC_AVICONF2_SCALING_NONE; + /* AVI data byte 3 differences: none */ + val = ((frame.extended_colorimetry & 0x7) << 4) | + ((frame.quantization_range & 0x3) << 2) | + (frame.nups & 0x3); + if (frame.itc) + val |= HDMI_FC_AVICONF2_IT_CONTENT_VALID; hdmi_writeb(hdmi, val, HDMI_FC_AVICONF2); - /* AVI Data Byte 4 */ - hdmi_writeb(hdmi, hdmi->vic, HDMI_FC_AVIVID); + /* AVI data byte 4 differences: none */ + val = frame.video_code & 0x7f; + hdmi_writeb(hdmi, val, HDMI_FC_AVIVID); /* AVI Data Byte 5- set up input and output pixel repetition */ val = (((hdmi->hdmi_data.video_mode.mpixelrepetitioninput + 1) << @@ -999,20 +978,23 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi) HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK); hdmi_writeb(hdmi, val, HDMI_FC_PRCONF); - /* IT Content and quantization range = don't care */ - val = HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS | - HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED; + /* + * AVI data byte 5 differences: content type in 0,1 rather than 4,5, + * ycc range in bits 2,3 rather than 6,7 + */ + val = ((frame.ycc_quantization_range & 0x3) << 2) | + (frame.content_type & 0x3); hdmi_writeb(hdmi, val, HDMI_FC_AVICONF3); /* AVI Data Bytes 6-13 */ - hdmi_writeb(hdmi, 0, HDMI_FC_AVIETB0); - hdmi_writeb(hdmi, 0, HDMI_FC_AVIETB1); - hdmi_writeb(hdmi, 0, HDMI_FC_AVISBB0); - hdmi_writeb(hdmi, 0, HDMI_FC_AVISBB1); - hdmi_writeb(hdmi, 0, HDMI_FC_AVIELB0); - hdmi_writeb(hdmi, 0, HDMI_FC_AVIELB1); - hdmi_writeb(hdmi, 0, HDMI_FC_AVISRB0); - hdmi_writeb(hdmi, 0, HDMI_FC_AVISRB1); + hdmi_writeb(hdmi, frame.top_bar & 0xff, HDMI_FC_AVIETB0); + hdmi_writeb(hdmi, (frame.top_bar >> 8) & 0xff, HDMI_FC_AVIETB1); + hdmi_writeb(hdmi, frame.bottom_bar & 0xff, HDMI_FC_AVISBB0); + hdmi_writeb(hdmi, (frame.bottom_bar >> 8) & 0xff, HDMI_FC_AVISBB1); + hdmi_writeb(hdmi, frame.left_bar & 0xff, HDMI_FC_AVIELB0); + hdmi_writeb(hdmi, (frame.left_bar >> 8) & 0xff, HDMI_FC_AVIELB1); + hdmi_writeb(hdmi, frame.right_bar & 0xff, HDMI_FC_AVISRB0); + hdmi_writeb(hdmi, (frame.right_bar >> 8) & 0xff, HDMI_FC_AVISRB1); } static void hdmi_av_composer(struct dw_hdmi *hdmi, @@ -1021,10 +1003,8 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, u8 inv_val; struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; + unsigned int vdisplay; - vmode->mhsyncpolarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC); - vmode->mvsyncpolarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC); - vmode->minterlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); vmode->mpixelclock = mode->clock * 1000; dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock); @@ -1034,13 +1014,13 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); - inv_val |= (vmode->mvsyncpolarity ? + inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ? HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : - HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW); + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW; - inv_val |= (vmode->mhsyncpolarity ? + inv_val |= mode->flags & DRM_MODE_FLAG_PHSYNC ? HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH : - HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW); + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW; inv_val |= (vmode->mdataenablepolarity ? HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH : @@ -1049,27 +1029,43 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, if (hdmi->vic == 39) inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH; else - inv_val |= (vmode->minterlaced ? + inv_val |= mode->flags & DRM_MODE_FLAG_INTERLACE ? HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH : - HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW); + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW; - inv_val |= (vmode->minterlaced ? + inv_val |= mode->flags & DRM_MODE_FLAG_INTERLACE ? HDMI_FC_INVIDCONF_IN_I_P_INTERLACED : - HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE); + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE; - inv_val |= (vmode->mdvi ? - HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE : - HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE); + inv_val |= hdmi->sink_is_hdmi ? + HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE : + HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE; hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF); + vdisplay = mode->vdisplay; + vblank = mode->vtotal - mode->vdisplay; + v_de_vs = mode->vsync_start - mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + + /* + * When we're setting an interlaced mode, we need + * to adjust the vertical timing to suit. + */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + vdisplay /= 2; + vblank /= 2; + v_de_vs /= 2; + vsync_len /= 2; + } + /* Set up horizontal active pixel width */ hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1); hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0); /* Set up vertical active lines */ - hdmi_writeb(hdmi, mode->vdisplay >> 8, HDMI_FC_INVACTV1); - hdmi_writeb(hdmi, mode->vdisplay, HDMI_FC_INVACTV0); + hdmi_writeb(hdmi, vdisplay >> 8, HDMI_FC_INVACTV1); + hdmi_writeb(hdmi, vdisplay, HDMI_FC_INVACTV0); /* Set up horizontal blanking pixel region width */ hblank = mode->htotal - mode->hdisplay; @@ -1077,7 +1073,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, hdmi_writeb(hdmi, hblank, HDMI_FC_INHBLANK0); /* Set up vertical blanking pixel region width */ - vblank = mode->vtotal - mode->vdisplay; hdmi_writeb(hdmi, vblank, HDMI_FC_INVBLANK); /* Set up HSYNC active edge delay width (in pixel clks) */ @@ -1086,7 +1081,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, hdmi_writeb(hdmi, h_de_hs, HDMI_FC_HSYNCINDELAY0); /* Set up VSYNC active edge delay (in lines) */ - v_de_vs = mode->vsync_start - mode->vdisplay; hdmi_writeb(hdmi, v_de_vs, HDMI_FC_VSYNCINDELAY); /* Set up HSYNC active pulse width (in pixel clks) */ @@ -1095,7 +1089,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, hdmi_writeb(hdmi, hsync_len, HDMI_FC_HSYNCINWIDTH0); /* Set up VSYNC active edge delay (in lines) */ - vsync_len = mode->vsync_end - mode->vsync_start; hdmi_writeb(hdmi, vsync_len, HDMI_FC_VSYNCINWIDTH); } @@ -1105,7 +1098,7 @@ static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi) return; dw_hdmi_phy_enable_tmds(hdmi, 0); - dw_hdmi_phy_enable_power(hdmi, 0); + dw_hdmi_phy_enable_powerdown(hdmi, true); hdmi->phy_enabled = false; } @@ -1186,10 +1179,8 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) if (!hdmi->vic) { dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n"); - hdmi->hdmi_data.video_mode.mdvi = true; } else { dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic); - hdmi->hdmi_data.video_mode.mdvi = false; } if ((hdmi->vic == 6) || (hdmi->vic == 7) || @@ -1200,18 +1191,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) else hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709; - if ((hdmi->vic == 10) || (hdmi->vic == 11) || - (hdmi->vic == 12) || (hdmi->vic == 13) || - (hdmi->vic == 14) || (hdmi->vic == 15) || - (hdmi->vic == 25) || (hdmi->vic == 26) || - (hdmi->vic == 27) || (hdmi->vic == 28) || - (hdmi->vic == 29) || (hdmi->vic == 30) || - (hdmi->vic == 35) || (hdmi->vic == 36) || - (hdmi->vic == 37) || (hdmi->vic == 38)) - hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1; - else - hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; - + hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; /* TODO: Get input format from IPU (via FB driver interface) */ @@ -1235,18 +1215,22 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) /* HDMI Initialization Step B.3 */ dw_hdmi_enable_video_path(hdmi); - /* not for DVI mode */ - if (hdmi->hdmi_data.video_mode.mdvi) { - dev_dbg(hdmi->dev, "%s DVI mode\n", __func__); - } else { - dev_dbg(hdmi->dev, "%s CEA mode\n", __func__); + if (hdmi->sink_has_audio) { + dev_dbg(hdmi->dev, "sink has audio support\n"); /* HDMI Initialization Step E - Configure audio */ hdmi_clk_regenerator_update_pixel_clock(hdmi); hdmi_enable_audio_clk(hdmi); + } + + /* not for DVI mode */ + if (hdmi->sink_is_hdmi) { + dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__); /* HDMI Initialization Step F - Configure AVI InfoFrame */ - hdmi_config_AVI(hdmi); + hdmi_config_AVI(hdmi, mode); + } else { + dev_dbg(hdmi->dev, "%s DVI mode\n", __func__); } hdmi_video_packetize(hdmi); @@ -1255,7 +1239,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) hdmi_tx_hdcp_config(hdmi); dw_hdmi_clear_overflow(hdmi); - if (hdmi->cable_plugin && !hdmi->hdmi_data.video_mode.mdvi) + if (hdmi->cable_plugin && hdmi->sink_is_hdmi) hdmi_enable_overflow_interrupts(hdmi); return 0; @@ -1272,10 +1256,11 @@ static int dw_hdmi_fb_registered(struct dw_hdmi *hdmi) HDMI_PHY_I2CM_CTLINT_ADDR); /* enable cable hot plug irq */ - hdmi_writeb(hdmi, (u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0); + hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); /* Clear Hotplug interrupts */ - hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, + HDMI_IH_PHY_STAT0); return 0; } @@ -1334,12 +1319,61 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) static void dw_hdmi_poweron(struct dw_hdmi *hdmi) { + hdmi->bridge_is_on = true; dw_hdmi_setup(hdmi, &hdmi->previous_mode); } static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) { dw_hdmi_phy_disable(hdmi); + hdmi->bridge_is_on = false; +} + +static void dw_hdmi_update_power(struct dw_hdmi *hdmi) +{ + int force = hdmi->force; + + if (hdmi->disabled) { + force = DRM_FORCE_OFF; + } else if (force == DRM_FORCE_UNSPECIFIED) { + if (hdmi->rxsense) + force = DRM_FORCE_ON; + else + force = DRM_FORCE_OFF; + } + + if (force == DRM_FORCE_OFF) { + if (hdmi->bridge_is_on) + dw_hdmi_poweroff(hdmi); + } else { + if (!hdmi->bridge_is_on) + dw_hdmi_poweron(hdmi); + } +} + +/* + * Adjust the detection of RXSENSE according to whether we have a forced + * connection mode enabled, or whether we have been disabled. There is + * no point processing RXSENSE interrupts if we have a forced connection + * state, or DRM has us disabled. + * + * We also disable rxsense interrupts when we think we're disconnected + * to avoid floating TDMS signals giving false rxsense interrupts. + * + * Note: we still need to listen for HPD interrupts even when DRM has us + * disabled so that we can detect a connect event. + */ +static void dw_hdmi_update_phy_mask(struct dw_hdmi *hdmi) +{ + u8 old_mask = hdmi->phy_mask; + + if (hdmi->force || hdmi->disabled || !hdmi->rxsense) + hdmi->phy_mask |= HDMI_PHY_RX_SENSE; + else + hdmi->phy_mask &= ~HDMI_PHY_RX_SENSE; + + if (old_mask != hdmi->phy_mask) + hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); } static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, @@ -1348,10 +1382,12 @@ static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, { struct dw_hdmi *hdmi = bridge->driver_private; - dw_hdmi_setup(hdmi, mode); + mutex_lock(&hdmi->mutex); /* Store the display mode for plugin/DKMS poweron events */ memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); + + mutex_unlock(&hdmi->mutex); } static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, @@ -1365,14 +1401,22 @@ static void dw_hdmi_bridge_disable(struct drm_bridge *bridge) { struct dw_hdmi *hdmi = bridge->driver_private; - dw_hdmi_poweroff(hdmi); + mutex_lock(&hdmi->mutex); + hdmi->disabled = true; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); } static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) { struct dw_hdmi *hdmi = bridge->driver_private; - dw_hdmi_poweron(hdmi); + mutex_lock(&hdmi->mutex); + hdmi->disabled = false; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); } static void dw_hdmi_bridge_nop(struct drm_bridge *bridge) @@ -1386,6 +1430,12 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); + mutex_lock(&hdmi->mutex); + hdmi->force = DRM_FORCE_UNSPECIFIED; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); + return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ? connector_status_connected : connector_status_disconnected; } @@ -1395,7 +1445,7 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector) struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); struct edid *edid; - int ret; + int ret = 0; if (!hdmi->ddc) return 0; @@ -1405,14 +1455,18 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector) dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n", edid->width_cm, edid->height_cm); + hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + hdmi->sink_has_audio = drm_detect_monitor_audio(edid); drm_mode_connector_update_edid_property(connector, edid); ret = drm_add_edid_modes(connector, edid); + /* Store the ELD */ + drm_edid_to_eld(connector, edid); kfree(edid); } else { dev_dbg(hdmi->dev, "failed to get edid\n"); } - return 0; + return ret; } static enum drm_mode_status @@ -1423,6 +1477,10 @@ dw_hdmi_connector_mode_valid(struct drm_connector *connector, struct dw_hdmi, connector); enum drm_mode_status mode_status = MODE_OK; + /* We don't support double-clocked modes */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return MODE_BAD; + if (hdmi->plat_data->mode_valid) mode_status = hdmi->plat_data->mode_valid(connector, mode); @@ -1444,11 +1502,24 @@ static void dw_hdmi_connector_destroy(struct drm_connector *connector) drm_connector_cleanup(connector); } +static void dw_hdmi_connector_force(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, + connector); + + mutex_lock(&hdmi->mutex); + hdmi->force = connector->force; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); +} + static struct drm_connector_funcs dw_hdmi_connector_funcs = { .dpms = drm_helper_connector_dpms, .fill_modes = drm_helper_probe_single_connector_modes, .detect = dw_hdmi_connector_detect, .destroy = dw_hdmi_connector_destroy, + .force = dw_hdmi_connector_force, }; static struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { @@ -1457,7 +1528,7 @@ static struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { .best_encoder = dw_hdmi_connector_best_encoder, }; -struct drm_bridge_funcs dw_hdmi_bridge_funcs = { +static struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .enable = dw_hdmi_bridge_enable, .disable = dw_hdmi_bridge_disable, .pre_enable = dw_hdmi_bridge_nop, @@ -1481,33 +1552,69 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) { struct dw_hdmi *hdmi = dev_id; - u8 intr_stat; - u8 phy_int_pol; + u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat; intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); - phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); + phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0); + + phy_pol_mask = 0; + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) + phy_pol_mask |= HDMI_PHY_HPD; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE0) + phy_pol_mask |= HDMI_PHY_RX_SENSE0; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE1) + phy_pol_mask |= HDMI_PHY_RX_SENSE1; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE2) + phy_pol_mask |= HDMI_PHY_RX_SENSE2; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE3) + phy_pol_mask |= HDMI_PHY_RX_SENSE3; + + if (phy_pol_mask) + hdmi_modb(hdmi, ~phy_int_pol, phy_pol_mask, HDMI_PHY_POL0); - if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { - if (phy_int_pol & HDMI_PHY_HPD) { - dev_dbg(hdmi->dev, "EVENT=plugin\n"); - - hdmi_modb(hdmi, 0, HDMI_PHY_HPD, HDMI_PHY_POL0); - - dw_hdmi_poweron(hdmi); - } else { - dev_dbg(hdmi->dev, "EVENT=plugout\n"); - - hdmi_modb(hdmi, HDMI_PHY_HPD, HDMI_PHY_HPD, - HDMI_PHY_POL0); - - dw_hdmi_poweroff(hdmi); + /* + * RX sense tells us whether the TDMS transmitters are detecting + * load - in other words, there's something listening on the + * other end of the link. Use this to decide whether we should + * power on the phy as HPD may be toggled by the sink to merely + * ask the source to re-read the EDID. + */ + if (intr_stat & + (HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) { + mutex_lock(&hdmi->mutex); + if (!hdmi->disabled && !hdmi->force) { + /* + * If the RX sense status indicates we're disconnected, + * clear the software rxsense status. + */ + if (!(phy_stat & HDMI_PHY_RX_SENSE)) + hdmi->rxsense = false; + + /* + * Only set the software rxsense status when both + * rxsense and hpd indicates we're connected. + * This avoids what seems to be bad behaviour in + * at least iMX6S versions of the phy. + */ + if (phy_stat & HDMI_PHY_HPD) + hdmi->rxsense = true; + + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); } - drm_helper_hpd_irq_event(hdmi->connector.dev); + mutex_unlock(&hdmi->mutex); + } + + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { + dev_dbg(hdmi->dev, "EVENT=%s\n", + phy_int_pol & HDMI_PHY_HPD ? "plugin" : "plugout"); + drm_helper_hpd_irq_event(hdmi->bridge->dev); } hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); - hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), + HDMI_IH_MUTE_PHY_STAT0); return IRQ_HANDLED; } @@ -1555,7 +1662,9 @@ int dw_hdmi_bind(struct device *dev, struct device *master, { struct drm_device *drm = data; struct device_node *np = dev->of_node; + struct platform_device_info pdevinfo; struct device_node *ddc_node; + struct dw_hdmi_audio_data audio; struct dw_hdmi *hdmi; int ret; u32 val = 1; @@ -1564,14 +1673,20 @@ int dw_hdmi_bind(struct device *dev, struct device *master, if (!hdmi) return -ENOMEM; + hdmi->connector.interlace_allowed = 1; + hdmi->plat_data = plat_data; hdmi->dev = dev; hdmi->dev_type = plat_data->dev_type; hdmi->sample_rate = 48000; - hdmi->ratio = 100; hdmi->encoder = encoder; + hdmi->disabled = true; + hdmi->rxsense = true; + hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE); + mutex_init(&hdmi->mutex); mutex_init(&hdmi->audio_mutex); + spin_lock_init(&hdmi->audio_lock); of_property_read_u32(np, "reg-io-width", &val); @@ -1658,10 +1773,11 @@ int dw_hdmi_bind(struct device *dev, struct device *master, * Configure registers related to HDMI interrupt * generation before registering IRQ. */ - hdmi_writeb(hdmi, HDMI_PHY_HPD, HDMI_PHY_POL0); + hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0); /* Clear Hotplug interrupts */ - hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, + HDMI_IH_PHY_STAT0); ret = dw_hdmi_fb_registered(hdmi); if (ret) @@ -1672,7 +1788,26 @@ int dw_hdmi_bind(struct device *dev, struct device *master, goto err_iahb; /* Unmute interrupts */ - hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), + HDMI_IH_MUTE_PHY_STAT0); + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; + + if (hdmi_readb(hdmi, HDMI_CONFIG1_ID) & HDMI_CONFIG1_AHB) { + audio.phys = iores->start; + audio.base = hdmi->regs; + audio.irq = irq; + audio.hdmi = hdmi; + audio.eld = hdmi->connector.eld; + + pdevinfo.name = "dw-hdmi-ahb-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); + } dev_set_drvdata(dev, hdmi); @@ -1691,6 +1826,9 @@ void dw_hdmi_unbind(struct device *dev, struct device *master, void *data) { struct dw_hdmi *hdmi = dev_get_drvdata(dev); + if (hdmi->audio && !IS_ERR(hdmi->audio)) + platform_device_unregister(hdmi->audio); + /* Disable all interrupts */ hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); diff --git a/kernel/drivers/gpu/drm/bridge/dw_hdmi.h b/kernel/drivers/gpu/drm/bridge/dw_hdmi.h index 175dbc89a..fc9a56042 100644 --- a/kernel/drivers/gpu/drm/bridge/dw_hdmi.h +++ b/kernel/drivers/gpu/drm/bridge/dw_hdmi.h @@ -7,8 +7,8 @@ * (at your option) any later version. */ -#ifndef __IMX_HDMI_H__ -#define __IMX_HDMI_H__ +#ifndef __DW_HDMI_H__ +#define __DW_HDMI_H__ /* Identification Registers */ #define HDMI_DESIGN_ID 0x0000 @@ -525,7 +525,7 @@ /* I2C Master Registers (E-DDC) */ #define HDMI_I2CM_SLAVE 0x7E00 -#define HDMI_I2CMESS 0x7E01 +#define HDMI_I2CM_ADDRESS 0x7E01 #define HDMI_I2CM_DATAO 0x7E02 #define HDMI_I2CM_DATAI 0x7E03 #define HDMI_I2CM_OPERATION 0x7E04 @@ -545,6 +545,9 @@ #define HDMI_I2CM_FS_SCL_LCNT_0_ADDR 0x7E12 enum { +/* CONFIG1_ID field values */ + HDMI_CONFIG1_AHB = 0x01, + /* IH_FC_INT2 field values */ HDMI_IH_FC_INT2_OVERFLOW_MASK = 0x03, HDMI_IH_FC_INT2_LOW_PRIORITY_OVERFLOW = 0x02, @@ -1031,4 +1034,4 @@ enum { HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW = 0x0, }; -#endif /* __IMX_HDMI_H__ */ +#endif /* __DW_HDMI_H__ */ diff --git a/kernel/drivers/gpu/drm/bridge/ptn3460.c b/kernel/drivers/gpu/drm/bridge/nxp-ptn3460.c index 63a09e407..0ffa3a6a2 100644 --- a/kernel/drivers/gpu/drm/bridge/ptn3460.c +++ b/kernel/drivers/gpu/drm/bridge/nxp-ptn3460.c @@ -24,10 +24,9 @@ #include <drm/drm_panel.h> -#include "bridge/ptn3460.h" - #include "drm_crtc.h" #include "drm_crtc_helper.h" +#include "drm_atomic_helper.h" #include "drm_edid.h" #include "drmP.h" @@ -260,10 +259,13 @@ static void ptn3460_connector_destroy(struct drm_connector *connector) } static struct drm_connector_funcs ptn3460_connector_funcs = { - .dpms = drm_helper_connector_dpms, + .dpms = drm_atomic_helper_connector_dpms, .fill_modes = drm_helper_probe_single_connector_modes, .detect = ptn3460_detect, .destroy = ptn3460_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static int ptn3460_bridge_attach(struct drm_bridge *bridge) @@ -331,32 +333,23 @@ static int ptn3460_probe(struct i2c_client *client, ptn_bridge->client = client; - ptn_bridge->gpio_pd_n = devm_gpiod_get(&client->dev, "powerdown"); + ptn_bridge->gpio_pd_n = devm_gpiod_get(&client->dev, "powerdown", + GPIOD_OUT_HIGH); if (IS_ERR(ptn_bridge->gpio_pd_n)) { ret = PTR_ERR(ptn_bridge->gpio_pd_n); dev_err(dev, "cannot get gpio_pd_n %d\n", ret); return ret; } - ret = gpiod_direction_output(ptn_bridge->gpio_pd_n, 1); - if (ret) { - DRM_ERROR("cannot configure gpio_pd_n\n"); - return ret; - } - - ptn_bridge->gpio_rst_n = devm_gpiod_get(&client->dev, "reset"); - if (IS_ERR(ptn_bridge->gpio_rst_n)) { - ret = PTR_ERR(ptn_bridge->gpio_rst_n); - DRM_ERROR("cannot get gpio_rst_n %d\n", ret); - return ret; - } /* * Request the reset pin low to avoid the bridge being * initialized prematurely */ - ret = gpiod_direction_output(ptn_bridge->gpio_rst_n, 0); - if (ret) { - DRM_ERROR("cannot configure gpio_rst_n\n"); + ptn_bridge->gpio_rst_n = devm_gpiod_get(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ptn_bridge->gpio_rst_n)) { + ret = PTR_ERR(ptn_bridge->gpio_rst_n); + DRM_ERROR("cannot get gpio_rst_n %d\n", ret); return ret; } @@ -390,7 +383,7 @@ static int ptn3460_remove(struct i2c_client *client) } static const struct i2c_device_id ptn3460_i2c_table[] = { - {"nxp,ptn3460", 0}, + {"ptn3460", 0}, {}, }; MODULE_DEVICE_TABLE(i2c, ptn3460_i2c_table); @@ -407,7 +400,6 @@ static struct i2c_driver ptn3460_driver = { .remove = ptn3460_remove, .driver = { .name = "nxp,ptn3460", - .owner = THIS_MODULE, .of_match_table = ptn3460_match, }, }; diff --git a/kernel/drivers/gpu/drm/bridge/ps8622.c b/kernel/drivers/gpu/drm/bridge/parade-ps8622.c index e895aa7ea..be881e9fe 100644 --- a/kernel/drivers/gpu/drm/bridge/ps8622.c +++ b/kernel/drivers/gpu/drm/bridge/parade-ps8622.c @@ -18,6 +18,7 @@ #include <linux/err.h> #include <linux/fb.h> #include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/module.h> #include <linux/of.h> @@ -31,6 +32,7 @@ #include "drmP.h" #include "drm_crtc.h" #include "drm_crtc_helper.h" +#include "drm_atomic_helper.h" /* Brightness scale on the Parade chip */ #define PS8622_MAX_BRIGHTNESS 0xff @@ -498,10 +500,13 @@ static void ps8622_connector_destroy(struct drm_connector *connector) } static const struct drm_connector_funcs ps8622_connector_funcs = { - .dpms = drm_helper_connector_dpms, + .dpms = drm_atomic_helper_connector_dpms, .fill_modes = drm_helper_probe_single_connector_modes, .detect = ps8622_detect, .destroy = ps8622_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static int ps8622_attach(struct drm_bridge *bridge) @@ -581,31 +586,21 @@ static int ps8622_probe(struct i2c_client *client, ps8622->v12 = NULL; } - ps8622->gpio_slp = devm_gpiod_get(dev, "sleep"); + ps8622->gpio_slp = devm_gpiod_get(dev, "sleep", GPIOD_OUT_HIGH); if (IS_ERR(ps8622->gpio_slp)) { ret = PTR_ERR(ps8622->gpio_slp); dev_err(dev, "cannot get gpio_slp %d\n", ret); return ret; } - ret = gpiod_direction_output(ps8622->gpio_slp, 1); - if (ret) { - dev_err(dev, "cannot configure gpio_slp\n"); - return ret; - } - ps8622->gpio_rst = devm_gpiod_get(dev, "reset"); - if (IS_ERR(ps8622->gpio_rst)) { - ret = PTR_ERR(ps8622->gpio_rst); - dev_err(dev, "cannot get gpio_rst %d\n", ret); - return ret; - } /* * Assert the reset pin high to avoid the bridge being * initialized prematurely */ - ret = gpiod_direction_output(ps8622->gpio_rst, 1); - if (ret) { - dev_err(dev, "cannot configure gpio_rst\n"); + ps8622->gpio_rst = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ps8622->gpio_rst)) { + ret = PTR_ERR(ps8622->gpio_rst); + dev_err(dev, "cannot get gpio_rst %d\n", ret); return ret; } @@ -673,7 +668,6 @@ static struct i2c_driver ps8622_driver = { .remove = ps8622_remove, .driver = { .name = "ps8622", - .owner = THIS_MODULE, .of_match_table = ps8622_devices, }, }; |