Merge branch 'drm-dwhdmi-devel' of git://ftp.arm.linux.org.uk/~rmk/linux-arm into drm-next
This series: * adds support for interlaced video modes to the ipu-v3 driver and dw_hdmi bridge. * reworks the dw_hdmi connector enable/disable support, to ensure that when DRM disables the output, it stays disabled irrespective of the hotplug state. * adds support for connector forcing, so we can force the hotplug state for this connector. * adds the ALSA AHB audio driver to the bridge: Iwai has acked the audio driver. * a few fixes to the ACR calculations to allow more modes to work with audio on iMX6. Fabio has independently tested this series, so all patches here carry his tested-by tag. * 'drm-dwhdmi-devel' of git://ftp.arm.linux.org.uk/~rmk/linux-arm: drm: bridge/dw_hdmi: replace CTS calculation for the ACR drm: bridge/dw_hdmi: remove ratio support from ACR code drm: bridge/dw_hdmi: adjust pixel clock values in N calculation drm: bridge/dw_hdmi: avoid being recursive in N calculation drm: bridge/dw_hdmi-ahb-audio: allow larger buffer sizes drm: bridge/dw_hdmi-ahb-audio: basic support for multi-channel PCM audio drm: bridge/dw_hdmi-ahb-audio: parse ELD from HDMI driver drm: bridge/dw_hdmi-ahb-audio: add audio driver drm: bridge/dw_hdmi: improve HDMI enable/disable handling drm: bridge/dw_hdmi: add connector mode forcing drm: bridge/dw_hdmi: add support for interlaced video modes gpu: imx: fix support for interlaced modes gpu: imx: simplify sync polarity setting
This commit is contained in:
commit
9ace42b1d1
|
@ -11,6 +11,18 @@ config DRM_DW_HDMI
|
|||
tristate
|
||||
select DRM_KMS_HELPER
|
||||
|
||||
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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
ccflags-y := -Iinclude/drm
|
||||
|
||||
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
|
||||
|
|
|
@ -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);
|
|
@ -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
|
|
@ -28,6 +28,7 @@
|
|||
#include <drm/bridge/dw_hdmi.h>
|
||||
|
||||
#include "dw_hdmi.h"
|
||||
#include "dw_hdmi-audio.h"
|
||||
|
||||
#define HDMI_EDID_LEN 512
|
||||
|
||||
|
@ -104,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;
|
||||
|
@ -126,7 +128,11 @@ struct dw_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;
|
||||
|
@ -134,12 +140,19 @@ struct dw_hdmi {
|
|||
unsigned int audio_cts;
|
||||
unsigned int audio_n;
|
||||
bool audio_enable;
|
||||
int ratio;
|
||||
|
||||
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));
|
||||
|
@ -203,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:
|
||||
|
@ -267,93 +272,29 @@ 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 int sample_rate, unsigned int ratio)
|
||||
unsigned long pixel_clk, unsigned int sample_rate)
|
||||
{
|
||||
unsigned long ftdms = pixel_clk;
|
||||
unsigned int n, cts;
|
||||
u64 tmp;
|
||||
|
||||
n = hdmi_compute_n(sample_rate, pixel_clk, ratio);
|
||||
cts = hdmi_compute_cts(sample_rate, pixel_clk, ratio);
|
||||
if (!cts) {
|
||||
dev_err(hdmi->dev,
|
||||
"%s: pixel clock/sample rate not supported: %luMHz / %ukHz\n",
|
||||
__func__, pixel_clk, sample_rate);
|
||||
}
|
||||
n = hdmi_compute_n(sample_rate, pixel_clk);
|
||||
|
||||
dev_dbg(hdmi->dev, "%s: samplerate=%ukHz ratio=%d pixelclk=%luMHz N=%d cts=%d\n",
|
||||
__func__, sample_rate, ratio, pixel_clk, n, cts);
|
||||
/*
|
||||
* 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: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n",
|
||||
__func__, sample_rate, ftdms / 1000000, (ftdms / 1000) % 1000,
|
||||
n, cts);
|
||||
|
||||
spin_lock_irq(&hdmi->audio_lock);
|
||||
hdmi->audio_n = n;
|
||||
|
@ -365,8 +306,7 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
|
|||
static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi)
|
||||
{
|
||||
mutex_lock(&hdmi->audio_mutex);
|
||||
hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate,
|
||||
hdmi->ratio);
|
||||
hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate);
|
||||
mutex_unlock(&hdmi->audio_mutex);
|
||||
}
|
||||
|
||||
|
@ -374,7 +314,7 @@ 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->sample_rate, hdmi->ratio);
|
||||
hdmi->sample_rate);
|
||||
mutex_unlock(&hdmi->audio_mutex);
|
||||
}
|
||||
|
||||
|
@ -383,7 +323,7 @@ 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, hdmi->ratio);
|
||||
hdmi->sample_rate);
|
||||
mutex_unlock(&hdmi->audio_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate);
|
||||
|
@ -1063,6 +1003,7 @@ 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->mpixelclock = mode->clock * 1000;
|
||||
|
||||
|
@ -1102,13 +1043,29 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
|
|||
|
||||
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;
|
||||
|
@ -1116,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) */
|
||||
|
@ -1125,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) */
|
||||
|
@ -1134,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);
|
||||
}
|
||||
|
||||
|
@ -1302,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;
|
||||
}
|
||||
|
@ -1364,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,
|
||||
|
@ -1399,7 +1403,8 @@ static void dw_hdmi_bridge_disable(struct drm_bridge *bridge)
|
|||
|
||||
mutex_lock(&hdmi->mutex);
|
||||
hdmi->disabled = true;
|
||||
dw_hdmi_poweroff(hdmi);
|
||||
dw_hdmi_update_power(hdmi);
|
||||
dw_hdmi_update_phy_mask(hdmi);
|
||||
mutex_unlock(&hdmi->mutex);
|
||||
}
|
||||
|
||||
|
@ -1408,8 +1413,9 @@ static void dw_hdmi_bridge_enable(struct drm_bridge *bridge)
|
|||
struct dw_hdmi *hdmi = bridge->driver_private;
|
||||
|
||||
mutex_lock(&hdmi->mutex);
|
||||
dw_hdmi_poweron(hdmi);
|
||||
hdmi->disabled = false;
|
||||
dw_hdmi_update_power(hdmi);
|
||||
dw_hdmi_update_phy_mask(hdmi);
|
||||
mutex_unlock(&hdmi->mutex);
|
||||
}
|
||||
|
||||
|
@ -1424,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;
|
||||
}
|
||||
|
@ -1447,6 +1459,8 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
|
|||
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");
|
||||
|
@ -1488,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 = {
|
||||
|
@ -1525,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);
|
||||
|
||||
if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
|
||||
hdmi_modb(hdmi, ~phy_int_pol, HDMI_PHY_HPD, HDMI_PHY_POL0);
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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 (phy_int_pol & HDMI_PHY_HPD) {
|
||||
dev_dbg(hdmi->dev, "EVENT=plugin\n");
|
||||
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;
|
||||
|
||||
if (!hdmi->disabled)
|
||||
dw_hdmi_poweron(hdmi);
|
||||
} else {
|
||||
dev_dbg(hdmi->dev, "EVENT=plugout\n");
|
||||
/*
|
||||
* 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;
|
||||
|
||||
if (!hdmi->disabled)
|
||||
dw_hdmi_poweroff(hdmi);
|
||||
dw_hdmi_update_power(hdmi);
|
||||
dw_hdmi_update_phy_mask(hdmi);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -1599,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;
|
||||
|
@ -1608,13 +1673,16 @@ 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);
|
||||
|
@ -1705,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)
|
||||
|
@ -1719,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);
|
||||
|
||||
|
@ -1738,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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -183,12 +183,19 @@ int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced,
|
|||
}
|
||||
|
||||
if (interlaced) {
|
||||
dc_link_event(dc, DC_EVT_NL, 0, 3);
|
||||
dc_link_event(dc, DC_EVT_EOL, 0, 2);
|
||||
dc_link_event(dc, DC_EVT_NEW_DATA, 0, 1);
|
||||
int addr;
|
||||
|
||||
if (dc->di)
|
||||
addr = 1;
|
||||
else
|
||||
addr = 0;
|
||||
|
||||
dc_link_event(dc, DC_EVT_NL, addr, 3);
|
||||
dc_link_event(dc, DC_EVT_EOL, addr, 2);
|
||||
dc_link_event(dc, DC_EVT_NEW_DATA, addr, 1);
|
||||
|
||||
/* Init template microcode */
|
||||
dc_write_tmpl(dc, 0, WROD(0), 0, map, SYNC_WAVE, 0, 8, 1);
|
||||
dc_write_tmpl(dc, addr, WROD(0), 0, map, SYNC_WAVE, 0, 6, 1);
|
||||
} else {
|
||||
if (dc->di) {
|
||||
dc_link_event(dc, DC_EVT_NL, 2, 3);
|
||||
|
|
|
@ -71,6 +71,10 @@ enum di_sync_wave {
|
|||
DI_SYNC_HSYNC = 3,
|
||||
DI_SYNC_VSYNC = 4,
|
||||
DI_SYNC_DE = 6,
|
||||
|
||||
DI_SYNC_CNT1 = 2, /* counter >= 2 only */
|
||||
DI_SYNC_CNT4 = 5, /* counter >= 5 only */
|
||||
DI_SYNC_CNT5 = 6, /* counter >= 6 only */
|
||||
};
|
||||
|
||||
#define SYNC_WAVE 0
|
||||
|
@ -211,66 +215,59 @@ static void ipu_di_sync_config_interlaced(struct ipu_di *di,
|
|||
sig->mode.hback_porch + sig->mode.hfront_porch;
|
||||
u32 v_total = sig->mode.vactive + sig->mode.vsync_len +
|
||||
sig->mode.vback_porch + sig->mode.vfront_porch;
|
||||
u32 reg;
|
||||
struct di_sync_config cfg[] = {
|
||||
{
|
||||
.run_count = h_total / 2 - 1,
|
||||
.run_src = DI_SYNC_CLK,
|
||||
}, {
|
||||
.run_count = h_total - 11,
|
||||
.run_src = DI_SYNC_CLK,
|
||||
.cnt_down = 4,
|
||||
}, {
|
||||
/* 1: internal VSYNC for each frame */
|
||||
.run_count = v_total * 2 - 1,
|
||||
.run_src = DI_SYNC_INT_HSYNC,
|
||||
.offset_count = 1,
|
||||
.offset_src = DI_SYNC_INT_HSYNC,
|
||||
.cnt_down = 4,
|
||||
}, {
|
||||
.run_count = v_total / 2 - 1,
|
||||
.run_src = DI_SYNC_HSYNC,
|
||||
.offset_count = sig->mode.vback_porch,
|
||||
.offset_src = DI_SYNC_HSYNC,
|
||||
.repeat_count = 2,
|
||||
.cnt_clr_src = DI_SYNC_VSYNC,
|
||||
}, {
|
||||
.run_src = DI_SYNC_HSYNC,
|
||||
.repeat_count = sig->mode.vactive / 2,
|
||||
.cnt_clr_src = 4,
|
||||
}, {
|
||||
.run_count = v_total - 1,
|
||||
.run_src = DI_SYNC_HSYNC,
|
||||
}, {
|
||||
.run_count = v_total / 2 - 1,
|
||||
.run_src = DI_SYNC_HSYNC,
|
||||
.offset_count = 9,
|
||||
.offset_src = DI_SYNC_HSYNC,
|
||||
.repeat_count = 2,
|
||||
.cnt_clr_src = DI_SYNC_VSYNC,
|
||||
.run_src = 3, /* == counter 7 */
|
||||
}, {
|
||||
/* PIN2: HSYNC waveform */
|
||||
.run_count = h_total - 1,
|
||||
.run_src = DI_SYNC_CLK,
|
||||
.offset_count = sig->mode.hback_porch,
|
||||
.cnt_polarity_gen_en = 1,
|
||||
.cnt_polarity_trigger_src = DI_SYNC_CLK,
|
||||
.cnt_down = sig->mode.hsync_len * 2,
|
||||
}, {
|
||||
/* PIN3: VSYNC waveform */
|
||||
.run_count = v_total - 1,
|
||||
.run_src = 4, /* == counter 7 */
|
||||
.cnt_polarity_gen_en = 1,
|
||||
.cnt_polarity_trigger_src = 4, /* == counter 7 */
|
||||
.cnt_down = sig->mode.vsync_len * 2,
|
||||
.cnt_clr_src = DI_SYNC_CNT1,
|
||||
}, {
|
||||
/* 4: Field */
|
||||
.run_count = v_total / 2,
|
||||
.run_src = DI_SYNC_HSYNC,
|
||||
.offset_count = h_total / 2,
|
||||
.offset_src = DI_SYNC_CLK,
|
||||
.repeat_count = 2,
|
||||
.cnt_clr_src = DI_SYNC_CNT1,
|
||||
}, {
|
||||
/* 5: Active lines */
|
||||
.run_src = DI_SYNC_HSYNC,
|
||||
.offset_count = (sig->mode.vsync_len +
|
||||
sig->mode.vback_porch) / 2,
|
||||
.offset_src = DI_SYNC_HSYNC,
|
||||
.repeat_count = sig->mode.vactive / 2,
|
||||
.cnt_clr_src = DI_SYNC_CNT4,
|
||||
}, {
|
||||
/* 6: Active pixel, referenced by DC */
|
||||
.run_src = DI_SYNC_CLK,
|
||||
.offset_count = sig->mode.hsync_len +
|
||||
sig->mode.hback_porch,
|
||||
.offset_src = DI_SYNC_CLK,
|
||||
.repeat_count = sig->mode.hactive,
|
||||
.cnt_clr_src = 5,
|
||||
.cnt_clr_src = DI_SYNC_CNT5,
|
||||
}, {
|
||||
.run_count = v_total - 1,
|
||||
.run_src = DI_SYNC_INT_HSYNC,
|
||||
.offset_count = v_total / 2,
|
||||
.offset_src = DI_SYNC_INT_HSYNC,
|
||||
.cnt_clr_src = DI_SYNC_HSYNC,
|
||||
.cnt_down = 4,
|
||||
/* 7: Half line HSYNC */
|
||||
.run_count = h_total / 2 - 1,
|
||||
.run_src = DI_SYNC_CLK,
|
||||
}
|
||||
};
|
||||
|
||||
ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg));
|
||||
|
||||
/* set gentime select and tag sel */
|
||||
reg = ipu_di_read(di, DI_SW_GEN1(9));
|
||||
reg &= 0x1FFFFFFF;
|
||||
reg |= (3 - 1) << 29 | 0x00008000;
|
||||
ipu_di_write(di, reg, DI_SW_GEN1(9));
|
||||
|
||||
ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF);
|
||||
}
|
||||
|
||||
|
@ -543,6 +540,29 @@ int ipu_di_adjust_videomode(struct ipu_di *di, struct videomode *mode)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(ipu_di_adjust_videomode);
|
||||
|
||||
static u32 ipu_di_gen_polarity(int pin)
|
||||
{
|
||||
switch (pin) {
|
||||
case 1:
|
||||
return DI_GEN_POLARITY_1;
|
||||
case 2:
|
||||
return DI_GEN_POLARITY_2;
|
||||
case 3:
|
||||
return DI_GEN_POLARITY_3;
|
||||
case 4:
|
||||
return DI_GEN_POLARITY_4;
|
||||
case 5:
|
||||
return DI_GEN_POLARITY_5;
|
||||
case 6:
|
||||
return DI_GEN_POLARITY_6;
|
||||
case 7:
|
||||
return DI_GEN_POLARITY_7;
|
||||
case 8:
|
||||
return DI_GEN_POLARITY_8;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
|
||||
{
|
||||
u32 reg;
|
||||
|
@ -582,15 +602,8 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
|
|||
|
||||
/* set y_sel = 1 */
|
||||
di_gen |= 0x10000000;
|
||||
di_gen |= DI_GEN_POLARITY_5;
|
||||
di_gen |= DI_GEN_POLARITY_8;
|
||||
|
||||
vsync_cnt = 7;
|
||||
|
||||
if (sig->mode.flags & DISPLAY_FLAGS_HSYNC_HIGH)
|
||||
di_gen |= DI_GEN_POLARITY_3;
|
||||
if (sig->mode.flags & DISPLAY_FLAGS_VSYNC_HIGH)
|
||||
di_gen |= DI_GEN_POLARITY_2;
|
||||
vsync_cnt = 3;
|
||||
} else {
|
||||
ipu_di_sync_config_noninterlaced(di, sig, div);
|
||||
|
||||
|
@ -602,24 +615,12 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
|
|||
*/
|
||||
if (!(sig->hsync_pin == 2 && sig->vsync_pin == 3))
|
||||
vsync_cnt = 6;
|
||||
}
|
||||
|
||||
if (sig->mode.flags & DISPLAY_FLAGS_HSYNC_HIGH) {
|
||||
if (sig->hsync_pin == 2)
|
||||
di_gen |= DI_GEN_POLARITY_2;
|
||||
else if (sig->hsync_pin == 4)
|
||||
di_gen |= DI_GEN_POLARITY_4;
|
||||
else if (sig->hsync_pin == 7)
|
||||
di_gen |= DI_GEN_POLARITY_7;
|
||||
}
|
||||
if (sig->mode.flags & DISPLAY_FLAGS_VSYNC_HIGH) {
|
||||
if (sig->vsync_pin == 3)
|
||||
di_gen |= DI_GEN_POLARITY_3;
|
||||
else if (sig->vsync_pin == 6)
|
||||
di_gen |= DI_GEN_POLARITY_6;
|
||||
else if (sig->vsync_pin == 8)
|
||||
di_gen |= DI_GEN_POLARITY_8;
|
||||
}
|
||||
}
|
||||
if (sig->mode.flags & DISPLAY_FLAGS_HSYNC_HIGH)
|
||||
di_gen |= ipu_di_gen_polarity(sig->hsync_pin);
|
||||
if (sig->mode.flags & DISPLAY_FLAGS_VSYNC_HIGH)
|
||||
di_gen |= ipu_di_gen_polarity(sig->vsync_pin);
|
||||
|
||||
if (sig->clk_pol)
|
||||
di_gen |= DI_GEN_POLARITY_DISP_CLK;
|
||||
|
|
Loading…
Reference in New Issue