Commit 9ace42b1 authored by Dave Airlie's avatar Dave Airlie

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
parents 48f87dd1 dfbdaf50
......@@ -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
......
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
/*
* 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);
#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);
/*
* 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=%ukHz ratio=%d pixelclk=%luMHz N=%d cts=%d\n",
__func__, sample_rate, ratio, pixel_clk, n, 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);
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);
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) {
hdmi_modb(hdmi, ~phy_int_pol, HDMI_PHY_HPD, 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)
dw_hdmi_poweron(hdmi);
} else {
dev_dbg(hdmi->dev, "EVENT=plugout\n");
if (!hdmi->disabled)
dw_hdmi_poweroff(hdmi);
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);
}
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,
/* 1: internal VSYNC for each frame */
.run_count = v_total * 2 - 1,
.run_src = 3, /* == counter 7 */
}, {
.run_count = h_total - 11,
/* PIN2: HSYNC waveform */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.cnt_down = 4,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->mode.hsync_len * 2,
}, {
.run_count = v_total * 2 - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1,
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_down = 4,
/* 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,
}, {
.run_count = v_total / 2 - 1,
/* 4: Field */
.run_count = v_total / 2,
.run_src = DI_SYNC_HSYNC,
.offset_count = sig->mode.vback_porch,
.offset_src = DI_SYNC_HSYNC,
.offset_count = h_total / 2,
.offset_src = DI_SYNC_CLK,
.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,
.cnt_clr_src = DI_SYNC_CNT1,
}, {
.run_count = v_total / 2 - 1,
/* 5: Active lines */
.run_src = DI_SYNC_HSYNC,
.offset_count = 9,
.offset_count = (sig->mode.vsync_len +
sig->mode.vback_porch) / 2,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = 2,
.cnt_clr_src = DI_SYNC_VSYNC,
.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.hback_porch,
.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,25 +615,13 @@ 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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment