This driver provides no built-in acceleration; acceleration is
performed by other IP found on the SoC. This driver provides
kernel mode setting and buffer management to userspace.
-
-config DRM_ARMADA_TDA1998X
- bool "Support TDA1998X HDMI output"
- depends on DRM_ARMADA != n
- depends on I2C && DRM_I2C_NXP_TDA998X = y
- default y
- help
- Support the TDA1998x HDMI output device found on the Solid-Run
- CuBox.
armada-y := armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \
- armada_gem.o armada_output.o armada_overlay.o \
- armada_slave.o
+ armada_gem.o armada_overlay.o
armada-y += armada_510.o
armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o
#include "armada_hw.h"
struct armada_frame_work {
+ struct armada_plane_work work;
struct drm_pending_vblank_event *event;
struct armada_regs regs[4];
struct drm_framebuffer *old_fb;
CSC_RGB_STUDIO = 2,
};
+static const uint32_t armada_primary_formats[] = {
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_VYUY,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+};
+
/*
* A note about interlacing. Let's consider HDMI 1920x1080i.
* The timing parameters we have from X are:
return i;
}
-static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
- struct armada_frame_work *work)
+static void armada_drm_plane_work_run(struct armada_crtc *dcrtc,
+ struct armada_plane *plane)
+{
+ struct armada_plane_work *work = xchg(&plane->work, NULL);
+
+ /* Handle any pending frame work. */
+ if (work) {
+ work->fn(dcrtc, plane, work);
+ drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+ }
+
+ wake_up(&plane->frame_wait);
+}
+
+int armada_drm_plane_work_queue(struct armada_crtc *dcrtc,
+ struct armada_plane *plane, struct armada_plane_work *work)
{
- struct drm_device *dev = dcrtc->crtc.dev;
- unsigned long flags;
int ret;
- ret = drm_vblank_get(dev, dcrtc->num);
+ ret = drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
if (ret) {
DRM_ERROR("failed to acquire vblank counter\n");
return ret;
}
- spin_lock_irqsave(&dev->event_lock, flags);
- if (!dcrtc->frame_work)
- dcrtc->frame_work = work;
- else
- ret = -EBUSY;
- spin_unlock_irqrestore(&dev->event_lock, flags);
-
+ ret = cmpxchg(&plane->work, NULL, work) ? -EBUSY : 0;
if (ret)
- drm_vblank_put(dev, dcrtc->num);
+ drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
return ret;
}
-static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc)
+int armada_drm_plane_work_wait(struct armada_plane *plane, long timeout)
{
- struct drm_device *dev = dcrtc->crtc.dev;
- struct armada_frame_work *work = dcrtc->frame_work;
+ return wait_event_timeout(plane->frame_wait, !plane->work, timeout);
+}
- dcrtc->frame_work = NULL;
+struct armada_plane_work *armada_drm_plane_work_cancel(
+ struct armada_crtc *dcrtc, struct armada_plane *plane)
+{
+ struct armada_plane_work *work = xchg(&plane->work, NULL);
- armada_drm_crtc_update_regs(dcrtc, work->regs);
+ if (work)
+ drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
- if (work->event)
- drm_send_vblank_event(dev, dcrtc->num, work->event);
+ return work;
+}
- drm_vblank_put(dev, dcrtc->num);
+static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
+ struct armada_frame_work *work)
+{
+ struct armada_plane *plane = drm_to_armada_plane(dcrtc->crtc.primary);
+
+ return armada_drm_plane_work_queue(dcrtc, plane, &work->work);
+}
+
+static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc,
+ struct armada_plane *plane, struct armada_plane_work *work)
+{
+ struct armada_frame_work *fwork = container_of(work, struct armada_frame_work, work);
+ struct drm_device *dev = dcrtc->crtc.dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dcrtc->irq_lock, flags);
+ armada_drm_crtc_update_regs(dcrtc, fwork->regs);
+ spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+
+ if (fwork->event) {
+ spin_lock_irqsave(&dev->event_lock, flags);
+ drm_send_vblank_event(dev, dcrtc->num, fwork->event);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+ }
/* Finally, queue the process-half of the cleanup. */
- __armada_drm_queue_unref_work(dcrtc->crtc.dev, work->old_fb);
- kfree(work);
+ __armada_drm_queue_unref_work(dcrtc->crtc.dev, fwork->old_fb);
+ kfree(fwork);
}
static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
work = kmalloc(sizeof(*work), GFP_KERNEL);
if (work) {
int i = 0;
+ work->work.fn = armada_drm_crtc_complete_frame_work;
work->event = NULL;
work->old_fb = fb;
armada_reg_queue_end(work->regs, i);
static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
{
- struct drm_device *dev = dcrtc->crtc.dev;
+ struct armada_plane *plane = drm_to_armada_plane(dcrtc->crtc.primary);
/*
* Tell the DRM core that vblank IRQs aren't going to happen for
* a while. This cleans up any pending vblank events for us.
*/
drm_crtc_vblank_off(&dcrtc->crtc);
-
- /* Handle any pending flip event. */
- spin_lock_irq(&dev->event_lock);
- if (dcrtc->frame_work)
- armada_drm_crtc_complete_frame_work(dcrtc);
- spin_unlock_irq(&dev->event_lock);
+ armada_drm_plane_work_run(dcrtc, plane);
}
void armada_drm_crtc_gamma_set(struct drm_crtc *crtc, u16 r, u16 g, u16 b,
if (dcrtc->dpms != dpms) {
dcrtc->dpms = dpms;
+ if (!IS_ERR(dcrtc->clk) && !dpms_blanked(dpms))
+ WARN_ON(clk_prepare_enable(dcrtc->clk));
armada_drm_crtc_update(dcrtc);
+ if (!IS_ERR(dcrtc->clk) && dpms_blanked(dpms))
+ clk_disable_unprepare(dcrtc->clk);
if (dpms_blanked(dpms))
armada_drm_vblank_off(dcrtc);
else
/*
* If we have an overlay plane associated with this CRTC, disable
* it before the modeset to avoid its coordinates being outside
- * the new mode parameters. DRM doesn't provide help with this.
+ * the new mode parameters.
*/
plane = dcrtc->plane;
- if (plane) {
- struct drm_framebuffer *fb = plane->fb;
-
- plane->funcs->disable_plane(plane);
- plane->fb = NULL;
- plane->crtc = NULL;
- drm_framebuffer_unreference(fb);
- }
+ if (plane)
+ drm_plane_force_disable(plane);
}
/* The mode_config.mutex will be held for this call */
static void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
{
- struct armada_vbl_event *e, *n;
void __iomem *base = dcrtc->base;
+ struct drm_plane *ovl_plane;
if (stat & DMA_FF_UNDERFLOW)
DRM_ERROR("video underflow on crtc %u\n", dcrtc->num);
drm_handle_vblank(dcrtc->crtc.dev, dcrtc->num);
spin_lock(&dcrtc->irq_lock);
-
- list_for_each_entry_safe(e, n, &dcrtc->vbl_list, node) {
- list_del_init(&e->node);
- drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
- e->fn(dcrtc, e->data);
+ ovl_plane = dcrtc->plane;
+ if (ovl_plane) {
+ struct armada_plane *plane = drm_to_armada_plane(ovl_plane);
+ armada_drm_plane_work_run(dcrtc, plane);
}
if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) {
spin_unlock(&dcrtc->irq_lock);
if (stat & GRA_FRAME_IRQ) {
- struct drm_device *dev = dcrtc->crtc.dev;
-
- spin_lock(&dev->event_lock);
- if (dcrtc->frame_work)
- armada_drm_crtc_complete_frame_work(dcrtc);
- spin_unlock(&dev->event_lock);
-
- wake_up(&dcrtc->frame_wait);
+ struct armada_plane *plane = drm_to_armada_plane(dcrtc->crtc.primary);
+ armada_drm_plane_work_run(dcrtc, plane);
}
}
adj->crtc_vtotal, tm, bm);
/* Wait for pending flips to complete */
- wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+ armada_drm_plane_work_wait(drm_to_armada_plane(dcrtc->crtc.primary),
+ MAX_SCHEDULE_TIMEOUT);
drm_crtc_vblank_off(crtc);
writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
}
+ /*
+ * If we are blanked, we would have disabled the clock. Re-enable
+ * it so that compute_clock() does the right thing.
+ */
+ if (!IS_ERR(dcrtc->clk) && dpms_blanked(dcrtc->dpms))
+ WARN_ON(clk_prepare_enable(dcrtc->clk));
+
/* Now compute the divider for real */
dcrtc->variant->compute_clock(dcrtc, adj, &sclk);
armada_reg_queue_end(regs, i);
/* Wait for pending flips to complete */
- wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+ armada_drm_plane_work_wait(drm_to_armada_plane(dcrtc->crtc.primary),
+ MAX_SCHEDULE_TIMEOUT);
/* Take a reference to the new fb as we're using it */
drm_framebuffer_reference(crtc->primary->fb);
return 0;
}
+void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc,
+ struct drm_plane *plane)
+{
+ u32 sram_para1, dma_ctrl0_mask;
+
+ /*
+ * Drop our reference on any framebuffer attached to this plane.
+ * We don't need to NULL this out as drm_plane_force_disable(),
+ * and __setplane_internal() will do so for an overlay plane, and
+ * __drm_helper_disable_unused_functions() will do so for the
+ * primary plane.
+ */
+ if (plane->fb)
+ drm_framebuffer_unreference(plane->fb);
+
+ /* Power down the Y/U/V FIFOs */
+ sram_para1 = CFG_PDWN16x66 | CFG_PDWN32x66;
+
+ /* Power down most RAMs and FIFOs if this is the primary plane */
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
+ sram_para1 |= CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 |
+ CFG_PDWN32x32 | CFG_PDWN64x66;
+ dma_ctrl0_mask = CFG_GRA_ENA;
+ } else {
+ dma_ctrl0_mask = CFG_DMA_ENA;
+ }
+
+ spin_lock_irq(&dcrtc->irq_lock);
+ armada_updatel(0, dma_ctrl0_mask, dcrtc->base + LCD_SPU_DMA_CTRL0);
+ spin_unlock_irq(&dcrtc->irq_lock);
+
+ armada_updatel(sram_para1, 0, dcrtc->base + LCD_SPU_SRAM_PARA1);
+}
+
/* The mode_config.mutex will be held for this call */
static void armada_drm_crtc_disable(struct drm_crtc *crtc)
{
struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
armada_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
- armada_drm_crtc_finish_fb(dcrtc, crtc->primary->fb, true);
-
- /* Power down most RAMs and FIFOs */
- writel_relaxed(CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 |
- CFG_PDWN32x32 | CFG_PDWN16x66 | CFG_PDWN32x66 |
- CFG_PDWN64x66, dcrtc->base + LCD_SPU_SRAM_PARA1);
+ armada_drm_crtc_plane_disable(dcrtc, crtc->primary);
}
static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
{
struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
struct armada_frame_work *work;
- struct drm_device *dev = crtc->dev;
- unsigned long flags;
unsigned i;
int ret;
if (!work)
return -ENOMEM;
+ work->work.fn = armada_drm_crtc_complete_frame_work;
work->event = event;
work->old_fb = dcrtc->crtc.primary->fb;
* Finally, if the display is blanked, we won't receive an
* interrupt, so complete it now.
*/
- if (dpms_blanked(dcrtc->dpms)) {
- spin_lock_irqsave(&dev->event_lock, flags);
- if (dcrtc->frame_work)
- armada_drm_crtc_complete_frame_work(dcrtc);
- spin_unlock_irqrestore(&dev->event_lock, flags);
- }
+ if (dpms_blanked(dcrtc->dpms))
+ armada_drm_plane_work_run(dcrtc, drm_to_armada_plane(dcrtc->crtc.primary));
return 0;
}
.set_property = armada_drm_crtc_set_property,
};
+static const struct drm_plane_funcs armada_primary_plane_funcs = {
+ .update_plane = drm_primary_helper_update,
+ .disable_plane = drm_primary_helper_disable,
+ .destroy = drm_primary_helper_destroy,
+};
+
+int armada_drm_plane_init(struct armada_plane *plane)
+{
+ init_waitqueue_head(&plane->frame_wait);
+
+ return 0;
+}
+
static struct drm_prop_enum_list armada_drm_csc_yuv_enum_list[] = {
{ CSC_AUTO, "Auto" },
{ CSC_YUV_CCIR601, "CCIR601" },
return 0;
}
-int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
+static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
struct resource *res, int irq, const struct armada_variant *variant,
struct device_node *port)
{
struct armada_private *priv = drm->dev_private;
struct armada_crtc *dcrtc;
+ struct armada_plane *primary;
void __iomem *base;
int ret;
dcrtc->spu_iopad_ctrl = CFG_VSCALE_LN_EN | CFG_IOPAD_DUMB24;
spin_lock_init(&dcrtc->irq_lock);
dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR;
- INIT_LIST_HEAD(&dcrtc->vbl_list);
- init_waitqueue_head(&dcrtc->frame_wait);
/* Initialize some registers which we don't otherwise set */
writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV);
priv->dcrtc[dcrtc->num] = dcrtc;
dcrtc->crtc.port = port;
- drm_crtc_init(drm, &dcrtc->crtc, &armada_crtc_funcs);
+
+ primary = kzalloc(sizeof(*primary), GFP_KERNEL);
+ if (!primary)
+ return -ENOMEM;
+
+ ret = armada_drm_plane_init(primary);
+ if (ret) {
+ kfree(primary);
+ return ret;
+ }
+
+ ret = drm_universal_plane_init(drm, &primary->base, 0,
+ &armada_primary_plane_funcs,
+ armada_primary_formats,
+ ARRAY_SIZE(armada_primary_formats),
+ DRM_PLANE_TYPE_PRIMARY);
+ if (ret) {
+ kfree(primary);
+ return ret;
+ }
+
+ ret = drm_crtc_init_with_planes(drm, &dcrtc->crtc, &primary->base, NULL,
+ &armada_crtc_funcs);
+ if (ret)
+ goto err_crtc_init;
+
drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs);
drm_object_attach_property(&dcrtc->crtc.base, priv->csc_yuv_prop,
dcrtc->csc_rgb_mode);
return armada_overlay_plane_create(drm, 1 << dcrtc->num);
+
+err_crtc_init:
+ primary->base.funcs->destroy(&primary->base);
+ return ret;
}
static int
#define armada_reg_queue_end(_r, _i) \
armada_reg_queue_mod(_r, _i, 0, 0, ~0)
-struct armada_frame_work;
+struct armada_crtc;
+struct armada_plane;
struct armada_variant;
+struct armada_plane_work {
+ void (*fn)(struct armada_crtc *,
+ struct armada_plane *,
+ struct armada_plane_work *);
+};
+
+struct armada_plane {
+ struct drm_plane base;
+ wait_queue_head_t frame_wait;
+ struct armada_plane_work *work;
+};
+#define drm_to_armada_plane(p) container_of(p, struct armada_plane, base)
+
+int armada_drm_plane_init(struct armada_plane *plane);
+int armada_drm_plane_work_queue(struct armada_crtc *dcrtc,
+ struct armada_plane *plane, struct armada_plane_work *work);
+int armada_drm_plane_work_wait(struct armada_plane *plane, long timeout);
+struct armada_plane_work *armada_drm_plane_work_cancel(
+ struct armada_crtc *dcrtc, struct armada_plane *plane);
+
struct armada_crtc {
struct drm_crtc crtc;
const struct armada_variant *variant;
uint32_t dumb_ctrl;
uint32_t spu_iopad_ctrl;
- wait_queue_head_t frame_wait;
- struct armada_frame_work *frame_work;
-
spinlock_t irq_lock;
uint32_t irq_ena;
- struct list_head vbl_list;
};
#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc)
-struct device_node;
-int armada_drm_crtc_create(struct drm_device *, struct device *,
- struct resource *, int, const struct armada_variant *,
- struct device_node *);
void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int);
void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int);
void armada_drm_crtc_disable_irq(struct armada_crtc *, u32);
void armada_drm_crtc_enable_irq(struct armada_crtc *, u32);
void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *);
+void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc,
+ struct drm_plane *plane);
+
extern struct platform_driver armada_lcd_platform_driver;
#endif
return ALIGN(pitch, 128);
}
-struct armada_vbl_event {
- struct list_head node;
- void *data;
- void (*fn)(struct armada_crtc *, void *);
-};
-void armada_drm_vbl_event_add(struct armada_crtc *,
- struct armada_vbl_event *);
-void armada_drm_vbl_event_remove(struct armada_crtc *,
- struct armada_vbl_event *);
-#define armada_drm_vbl_event_init(_e, _f, _d) do { \
- struct armada_vbl_event *__e = _e; \
- INIT_LIST_HEAD(&__e->node); \
- __e->data = _d; \
- __e->fn = _f; \
-} while (0)
-
struct armada_private;
#include <drm/armada_drm.h>
#include "armada_ioctlP.h"
-#ifdef CONFIG_DRM_ARMADA_TDA1998X
-#include <drm/i2c/tda998x.h>
-#include "armada_slave.h"
-
-static struct tda998x_encoder_params params = {
- /* With 0x24, there is no translation between vp_out and int_vp
- FB LCD out Pins VIP Int Vp
- R:23:16 R:7:0 VPC7:0 7:0 7:0[R]
- G:15:8 G:15:8 VPB7:0 23:16 23:16[G]
- B:7:0 B:23:16 VPA7:0 15:8 15:8[B]
- */
- .swap_a = 2,
- .swap_b = 3,
- .swap_c = 4,
- .swap_d = 5,
- .swap_e = 0,
- .swap_f = 1,
- .audio_cfg = BIT(2),
- .audio_frame[1] = 1,
- .audio_format = AFMT_SPDIF,
- .audio_sample_rate = 44100,
-};
-
-static const struct armada_drm_slave_config tda19988_config = {
- .i2c_adapter_id = 0,
- .crtcs = 1 << 0, /* Only LCD0 at the moment */
- .polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT,
- .interlace_allowed = true,
- .info = {
- .type = "tda998x",
- .addr = 0x70,
- .platform_data = ¶ms,
- },
-};
-#endif
-
-static bool is_componentized(struct device *dev)
-{
- return dev->of_node || dev->platform_data;
-}
-
static void armada_drm_unref_work(struct work_struct *work)
{
struct armada_private *priv =
static int armada_drm_load(struct drm_device *dev, unsigned long flags)
{
- const struct platform_device_id *id;
- const struct armada_variant *variant;
struct armada_private *priv;
- struct resource *res[ARRAY_SIZE(priv->dcrtc)];
struct resource *mem = NULL;
- int ret, n, i;
-
- memset(res, 0, sizeof(res));
+ int ret, n;
- for (n = i = 0; ; n++) {
+ for (n = 0; ; n++) {
struct resource *r = platform_get_resource(dev->platformdev,
IORESOURCE_MEM, n);
if (!r)
/* Resources above 64K are graphics memory */
if (resource_size(r) > SZ_64K)
mem = r;
- else if (i < ARRAY_SIZE(priv->dcrtc))
- res[i++] = r;
else
return -EINVAL;
}
platform_set_drvdata(dev->platformdev, dev);
dev->dev_private = priv;
- /* Get the implementation specific driver data. */
- id = platform_get_device_id(dev->platformdev);
- if (!id)
- return -ENXIO;
-
- variant = (const struct armada_variant *)id->driver_data;
-
INIT_WORK(&priv->fb_unref_work, armada_drm_unref_work);
INIT_KFIFO(priv->fb_unref);
dev->mode_config.funcs = &armada_drm_mode_config_funcs;
drm_mm_init(&priv->linear, mem->start, resource_size(mem));
- /* Create all LCD controllers */
- for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
- int irq;
-
- if (!res[n])
- break;
-
- irq = platform_get_irq(dev->platformdev, n);
- if (irq < 0)
- goto err_kms;
-
- ret = armada_drm_crtc_create(dev, dev->dev, res[n], irq,
- variant, NULL);
- if (ret)
- goto err_kms;
- }
-
- if (is_componentized(dev->dev)) {
- ret = component_bind_all(dev->dev, dev);
- if (ret)
- goto err_kms;
- } else {
-#ifdef CONFIG_DRM_ARMADA_TDA1998X
- ret = armada_drm_connector_slave_create(dev, &tda19988_config);
- if (ret)
- goto err_kms;
-#endif
- }
+ ret = component_bind_all(dev->dev, dev);
+ if (ret)
+ goto err_kms;
ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
if (ret)
return 0;
err_comp:
- if (is_componentized(dev->dev))
- component_unbind_all(dev->dev, dev);
+ component_unbind_all(dev->dev, dev);
err_kms:
drm_mode_config_cleanup(dev);
drm_mm_takedown(&priv->linear);
drm_kms_helper_poll_fini(dev);
armada_fbdev_fini(dev);
- if (is_componentized(dev->dev))
- component_unbind_all(dev->dev, dev);
+ component_unbind_all(dev->dev, dev);
drm_mode_config_cleanup(dev);
drm_mm_takedown(&priv->linear);
return 0;
}
-void armada_drm_vbl_event_add(struct armada_crtc *dcrtc,
- struct armada_vbl_event *evt)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&dcrtc->irq_lock, flags);
- if (list_empty(&evt->node)) {
- list_add_tail(&evt->node, &dcrtc->vbl_list);
-
- drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
- }
- spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
-}
-
-void armada_drm_vbl_event_remove(struct armada_crtc *dcrtc,
- struct armada_vbl_event *evt)
-{
- if (!list_empty(&evt->node)) {
- list_del_init(&evt->node);
- drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
- }
-}
-
/* These are called under the vbl_lock. */
static int armada_drm_enable_vblank(struct drm_device *dev, int crtc)
{
static int armada_drm_probe(struct platform_device *pdev)
{
- if (is_componentized(&pdev->dev)) {
- struct component_match *match = NULL;
- int ret;
-
- ret = armada_drm_find_components(&pdev->dev, &match);
- if (ret < 0)
- return ret;
-
- return component_master_add_with_match(&pdev->dev,
- &armada_master_ops, match);
- } else {
- return drm_platform_init(&armada_drm_driver, pdev);
- }
+ struct component_match *match = NULL;
+ int ret;
+
+ ret = armada_drm_find_components(&pdev->dev, &match);
+ if (ret < 0)
+ return ret;
+
+ return component_master_add_with_match(&pdev->dev, &armada_master_ops,
+ match);
}
static int armada_drm_remove(struct platform_device *pdev)
{
- if (is_componentized(&pdev->dev))
- component_master_del(&pdev->dev, &armada_master_ops);
- else
- drm_put_dev(platform_get_drvdata(pdev));
+ component_master_del(&pdev->dev, &armada_master_ops);
return 0;
}
static const struct platform_device_id armada_drm_platform_ids[] = {
{
.name = "armada-drm",
- .driver_data = (unsigned long)&armada510_ops,
}, {
.name = "armada-510-drm",
- .driver_data = (unsigned long)&armada510_ops,
},
{ },
};
+++ /dev/null
-/*
- * Copyright (C) 2012 Russell King
- *
- * 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.
- */
-#include <drm/drmP.h>
-#include <drm/drm_crtc_helper.h>
-#include <drm/drm_edid.h>
-#include <drm/drm_encoder_slave.h>
-#include "armada_output.h"
-#include "armada_drm.h"
-
-struct armada_connector {
- struct drm_connector conn;
- const struct armada_output_type *type;
-};
-
-#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
-
-struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn)
-{
- struct drm_encoder *enc = conn->encoder;
-
- return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]);
-}
-
-static enum drm_connector_status armada_drm_connector_detect(
- struct drm_connector *conn, bool force)
-{
- struct armada_connector *dconn = drm_to_armada_conn(conn);
- enum drm_connector_status status = connector_status_disconnected;
-
- if (dconn->type->detect) {
- status = dconn->type->detect(conn, force);
- } else {
- struct drm_encoder *enc = armada_drm_connector_encoder(conn);
-
- if (enc)
- status = encoder_helper_funcs(enc)->detect(enc, conn);
- }
-
- return status;
-}
-
-static void armada_drm_connector_destroy(struct drm_connector *conn)
-{
- struct armada_connector *dconn = drm_to_armada_conn(conn);
-
- drm_connector_unregister(conn);
- drm_connector_cleanup(conn);
- kfree(dconn);
-}
-
-static int armada_drm_connector_set_property(struct drm_connector *conn,
- struct drm_property *property, uint64_t value)
-{
- struct armada_connector *dconn = drm_to_armada_conn(conn);
-
- if (!dconn->type->set_property)
- return -EINVAL;
-
- return dconn->type->set_property(conn, property, value);
-}
-
-static const struct drm_connector_funcs armada_drm_conn_funcs = {
- .dpms = drm_helper_connector_dpms,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .detect = armada_drm_connector_detect,
- .destroy = armada_drm_connector_destroy,
- .set_property = armada_drm_connector_set_property,
-};
-
-/* Shouldn't this be a generic helper function? */
-int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
- struct drm_display_mode *mode)
-{
- struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
- int valid = MODE_BAD;
-
- if (encoder) {
- struct drm_encoder_slave *slave = to_encoder_slave(encoder);
-
- valid = slave->slave_funcs->mode_valid(encoder, mode);
- }
- return valid;
-}
-
-int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
- struct drm_property *property, uint64_t value)
-{
- struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
- int rc = -EINVAL;
-
- if (encoder) {
- struct drm_encoder_slave *slave = to_encoder_slave(encoder);
-
- rc = slave->slave_funcs->set_property(encoder, conn, property,
- value);
- }
- return rc;
-}
-
-int armada_output_create(struct drm_device *dev,
- const struct armada_output_type *type, const void *data)
-{
- struct armada_connector *dconn;
- int ret;
-
- dconn = kzalloc(sizeof(*dconn), GFP_KERNEL);
- if (!dconn)
- return -ENOMEM;
-
- dconn->type = type;
-
- ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs,
- type->connector_type);
- if (ret) {
- DRM_ERROR("unable to init connector\n");
- goto err_destroy_dconn;
- }
-
- ret = type->create(&dconn->conn, data);
- if (ret)
- goto err_conn;
-
- ret = drm_connector_register(&dconn->conn);
- if (ret)
- goto err_sysfs;
-
- return 0;
-
- err_sysfs:
- if (dconn->conn.encoder)
- dconn->conn.encoder->funcs->destroy(dconn->conn.encoder);
- err_conn:
- drm_connector_cleanup(&dconn->conn);
- err_destroy_dconn:
- kfree(dconn);
- return ret;
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 Russell King
- *
- * 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.
- */
-#ifndef ARMADA_CONNETOR_H
-#define ARMADA_CONNETOR_H
-
-#define encoder_helper_funcs(encoder) \
- ((const struct drm_encoder_helper_funcs *)encoder->helper_private)
-
-struct armada_output_type {
- int connector_type;
- enum drm_connector_status (*detect)(struct drm_connector *, bool);
- int (*create)(struct drm_connector *, const void *);
- int (*set_property)(struct drm_connector *, struct drm_property *,
- uint64_t);
-};
-
-struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn);
-
-int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
- struct drm_display_mode *mode);
-
-int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
- struct drm_property *property, uint64_t value);
-
-int armada_output_create(struct drm_device *dev,
- const struct armada_output_type *type, const void *data);
-
-#endif
#include <drm/armada_drm.h>
#include "armada_ioctlP.h"
-struct armada_plane_properties {
+struct armada_ovl_plane_properties {
uint32_t colorkey_yr;
uint32_t colorkey_ug;
uint32_t colorkey_vb;
uint32_t colorkey_mode;
};
-struct armada_plane {
- struct drm_plane base;
- spinlock_t lock;
+struct armada_ovl_plane {
+ struct armada_plane base;
struct drm_framebuffer *old_fb;
uint32_t src_hw;
uint32_t dst_hw;
uint32_t dst_yx;
uint32_t ctrl0;
struct {
- struct armada_vbl_event update;
+ struct armada_plane_work work;
struct armada_regs regs[13];
- wait_queue_head_t wait;
} vbl;
- struct armada_plane_properties prop;
+ struct armada_ovl_plane_properties prop;
};
-#define drm_to_armada_plane(p) container_of(p, struct armada_plane, base)
+#define drm_to_armada_ovl_plane(p) \
+ container_of(p, struct armada_ovl_plane, base.base)
static void
-armada_ovl_update_attr(struct armada_plane_properties *prop,
+armada_ovl_update_attr(struct armada_ovl_plane_properties *prop,
struct armada_crtc *dcrtc)
{
writel_relaxed(prop->colorkey_yr, dcrtc->base + LCD_SPU_COLORKEY_Y);
spin_unlock_irq(&dcrtc->irq_lock);
}
-/* === Plane support === */
-static void armada_plane_vbl(struct armada_crtc *dcrtc, void *data)
+static void armada_ovl_retire_fb(struct armada_ovl_plane *dplane,
+ struct drm_framebuffer *fb)
{
- struct armada_plane *dplane = data;
- struct drm_framebuffer *fb;
+ struct drm_framebuffer *old_fb;
- armada_drm_crtc_update_regs(dcrtc, dplane->vbl.regs);
+ old_fb = xchg(&dplane->old_fb, fb);
- spin_lock(&dplane->lock);
- fb = dplane->old_fb;
- dplane->old_fb = NULL;
- spin_unlock(&dplane->lock);
+ if (old_fb)
+ armada_drm_queue_unref_work(dplane->base.base.dev, old_fb);
+}
- if (fb)
- armada_drm_queue_unref_work(dcrtc->crtc.dev, fb);
+/* === Plane support === */
+static void armada_ovl_plane_work(struct armada_crtc *dcrtc,
+ struct armada_plane *plane, struct armada_plane_work *work)
+{
+ struct armada_ovl_plane *dplane = container_of(plane, struct armada_ovl_plane, base);
- wake_up(&dplane->vbl.wait);
+ armada_drm_crtc_update_regs(dcrtc, dplane->vbl.regs);
+ armada_ovl_retire_fb(dplane, NULL);
}
static int
-armada_plane_update(struct drm_plane *plane, struct drm_crtc *crtc,
+armada_ovl_plane_update(struct drm_plane *plane, struct drm_crtc *crtc,
struct drm_framebuffer *fb,
int crtc_x, int crtc_y, unsigned crtc_w, unsigned crtc_h,
uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h)
{
- struct armada_plane *dplane = drm_to_armada_plane(plane);
+ struct armada_ovl_plane *dplane = drm_to_armada_ovl_plane(plane);
struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
struct drm_rect src = {
.x1 = src_x,
dcrtc->base + LCD_SPU_SRAM_PARA1);
}
- wait_event_timeout(dplane->vbl.wait,
- list_empty(&dplane->vbl.update.node),
- HZ/25);
+ if (armada_drm_plane_work_wait(&dplane->base, HZ / 25) == 0)
+ armada_drm_plane_work_cancel(dcrtc, &dplane->base);
if (plane->fb != fb) {
struct armada_gem_object *obj = drm_fb_obj(fb);
*/
drm_framebuffer_reference(fb);
- if (plane->fb) {
- struct drm_framebuffer *older_fb;
-
- spin_lock_irq(&dplane->lock);
- older_fb = dplane->old_fb;
- dplane->old_fb = plane->fb;
- spin_unlock_irq(&dplane->lock);
- if (older_fb)
- armada_drm_queue_unref_work(dcrtc->crtc.dev,
- older_fb);
- }
+ if (plane->fb)
+ armada_ovl_retire_fb(dplane, plane->fb);
src_y = src.y1 >> 16;
src_x = src.x1 >> 16;
}
if (idx) {
armada_reg_queue_end(dplane->vbl.regs, idx);
- armada_drm_vbl_event_add(dcrtc, &dplane->vbl.update);
+ armada_drm_plane_work_queue(dcrtc, &dplane->base,
+ &dplane->vbl.work);
}
return 0;
}
-static int armada_plane_disable(struct drm_plane *plane)
+static int armada_ovl_plane_disable(struct drm_plane *plane)
{
- struct armada_plane *dplane = drm_to_armada_plane(plane);
+ struct armada_ovl_plane *dplane = drm_to_armada_ovl_plane(plane);
struct drm_framebuffer *fb;
struct armada_crtc *dcrtc;
- if (!dplane->base.crtc)
+ if (!dplane->base.base.crtc)
return 0;
- dcrtc = drm_to_armada_crtc(dplane->base.crtc);
- dcrtc->plane = NULL;
-
- spin_lock_irq(&dcrtc->irq_lock);
- armada_drm_vbl_event_remove(dcrtc, &dplane->vbl.update);
- armada_updatel(0, CFG_DMA_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
- dplane->ctrl0 = 0;
- spin_unlock_irq(&dcrtc->irq_lock);
+ dcrtc = drm_to_armada_crtc(dplane->base.base.crtc);
- /* Power down the Y/U/V FIFOs */
- armada_updatel(CFG_PDWN16x66 | CFG_PDWN32x66, 0,
- dcrtc->base + LCD_SPU_SRAM_PARA1);
+ armada_drm_plane_work_cancel(dcrtc, &dplane->base);
+ armada_drm_crtc_plane_disable(dcrtc, plane);
- if (plane->fb)
- drm_framebuffer_unreference(plane->fb);
+ dcrtc->plane = NULL;
+ dplane->ctrl0 = 0;
- spin_lock_irq(&dplane->lock);
- fb = dplane->old_fb;
- dplane->old_fb = NULL;
- spin_unlock_irq(&dplane->lock);
+ fb = xchg(&dplane->old_fb, NULL);
if (fb)
drm_framebuffer_unreference(fb);
return 0;
}
-static void armada_plane_destroy(struct drm_plane *plane)
+static void armada_ovl_plane_destroy(struct drm_plane *plane)
{
- struct armada_plane *dplane = drm_to_armada_plane(plane);
+ struct armada_ovl_plane *dplane = drm_to_armada_ovl_plane(plane);
drm_plane_cleanup(plane);
kfree(dplane);
}
-static int armada_plane_set_property(struct drm_plane *plane,
+static int armada_ovl_plane_set_property(struct drm_plane *plane,
struct drm_property *property, uint64_t val)
{
struct armada_private *priv = plane->dev->dev_private;
- struct armada_plane *dplane = drm_to_armada_plane(plane);
+ struct armada_ovl_plane *dplane = drm_to_armada_ovl_plane(plane);
bool update_attr = false;
if (property == priv->colorkey_prop) {
update_attr = true;
}
- if (update_attr && dplane->base.crtc)
+ if (update_attr && dplane->base.base.crtc)
armada_ovl_update_attr(&dplane->prop,
- drm_to_armada_crtc(dplane->base.crtc));
+ drm_to_armada_crtc(dplane->base.base.crtc));
return 0;
}
-static const struct drm_plane_funcs armada_plane_funcs = {
- .update_plane = armada_plane_update,
- .disable_plane = armada_plane_disable,
- .destroy = armada_plane_destroy,
- .set_property = armada_plane_set_property,
+static const struct drm_plane_funcs armada_ovl_plane_funcs = {
+ .update_plane = armada_ovl_plane_update,
+ .disable_plane = armada_ovl_plane_disable,
+ .destroy = armada_ovl_plane_destroy,
+ .set_property = armada_ovl_plane_set_property,
};
-static const uint32_t armada_formats[] = {
+static const uint32_t armada_ovl_formats[] = {
DRM_FORMAT_UYVY,
DRM_FORMAT_YUYV,
DRM_FORMAT_YUV420,
{
struct armada_private *priv = dev->dev_private;
struct drm_mode_object *mobj;
- struct armada_plane *dplane;
+ struct armada_ovl_plane *dplane;
int ret;
ret = armada_overlay_create_properties(dev);
if (!dplane)
return -ENOMEM;
- spin_lock_init(&dplane->lock);
- init_waitqueue_head(&dplane->vbl.wait);
- armada_drm_vbl_event_init(&dplane->vbl.update, armada_plane_vbl,
- dplane);
+ ret = armada_drm_plane_init(&dplane->base);
+ if (ret) {
+ kfree(dplane);
+ return ret;
+ }
+
+ dplane->vbl.work.fn = armada_ovl_plane_work;
- drm_plane_init(dev, &dplane->base, crtcs, &armada_plane_funcs,
- armada_formats, ARRAY_SIZE(armada_formats), false);
+ ret = drm_universal_plane_init(dev, &dplane->base.base, crtcs,
+ &armada_ovl_plane_funcs,
+ armada_ovl_formats,
+ ARRAY_SIZE(armada_ovl_formats),
+ DRM_PLANE_TYPE_OVERLAY);
+ if (ret) {
+ kfree(dplane);
+ return ret;
+ }
dplane->prop.colorkey_yr = 0xfefefe00;
dplane->prop.colorkey_ug = 0x01010100;
dplane->prop.contrast = 0x4000;
dplane->prop.saturation = 0x4000;
- mobj = &dplane->base.base;
+ mobj = &dplane->base.base.base;
drm_object_attach_property(mobj, priv->colorkey_prop,
0x0101fe);
drm_object_attach_property(mobj, priv->colorkey_min_prop,
+++ /dev/null
-/*
- * Copyright (C) 2012 Russell King
- * Rewritten from the dovefb driver, and Armada510 manuals.
- *
- * 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.
- */
-#include <drm/drmP.h>
-#include <drm/drm_crtc_helper.h>
-#include <drm/drm_edid.h>
-#include <drm/drm_encoder_slave.h>
-#include "armada_drm.h"
-#include "armada_output.h"
-#include "armada_slave.h"
-
-static int armada_drm_slave_get_modes(struct drm_connector *conn)
-{
- struct drm_encoder *enc = armada_drm_connector_encoder(conn);
- int count = 0;
-
- if (enc) {
- struct drm_encoder_slave *slave = to_encoder_slave(enc);
-
- count = slave->slave_funcs->get_modes(enc, conn);
- }
-
- return count;
-}
-
-static void armada_drm_slave_destroy(struct drm_encoder *enc)
-{
- struct drm_encoder_slave *slave = to_encoder_slave(enc);
- struct i2c_client *client = drm_i2c_encoder_get_client(enc);
-
- if (slave->slave_funcs)
- slave->slave_funcs->destroy(enc);
- if (client)
- i2c_put_adapter(client->adapter);
-
- drm_encoder_cleanup(&slave->base);
- kfree(slave);
-}
-
-static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = {
- .destroy = armada_drm_slave_destroy,
-};
-
-static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = {
- .get_modes = armada_drm_slave_get_modes,
- .mode_valid = armada_drm_slave_encoder_mode_valid,
- .best_encoder = armada_drm_connector_encoder,
-};
-
-static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = {
- .dpms = drm_i2c_encoder_dpms,
- .save = drm_i2c_encoder_save,
- .restore = drm_i2c_encoder_restore,
- .mode_fixup = drm_i2c_encoder_mode_fixup,
- .prepare = drm_i2c_encoder_prepare,
- .commit = drm_i2c_encoder_commit,
- .mode_set = drm_i2c_encoder_mode_set,
- .detect = drm_i2c_encoder_detect,
-};
-
-static int
-armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
-{
- const struct armada_drm_slave_config *config = data;
- struct drm_encoder_slave *slave;
- struct i2c_adapter *adap;
- int ret;
-
- conn->interlace_allowed = config->interlace_allowed;
- conn->doublescan_allowed = config->doublescan_allowed;
- conn->polled = config->polled;
-
- drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
-
- slave = kzalloc(sizeof(*slave), GFP_KERNEL);
- if (!slave)
- return -ENOMEM;
-
- slave->base.possible_crtcs = config->crtcs;
-
- adap = i2c_get_adapter(config->i2c_adapter_id);
- if (!adap) {
- kfree(slave);
- return -EPROBE_DEFER;
- }
-
- ret = drm_encoder_init(conn->dev, &slave->base,
- &armada_drm_slave_encoder_funcs,
- DRM_MODE_ENCODER_TMDS);
- if (ret) {
- DRM_ERROR("unable to init encoder\n");
- i2c_put_adapter(adap);
- kfree(slave);
- return ret;
- }
-
- ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
- i2c_put_adapter(adap);
- if (ret) {
- DRM_ERROR("unable to init encoder slave\n");
- armada_drm_slave_destroy(&slave->base);
- return ret;
- }
-
- drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
-
- ret = slave->slave_funcs->create_resources(&slave->base, conn);
- if (ret) {
- armada_drm_slave_destroy(&slave->base);
- return ret;
- }
-
- ret = drm_mode_connector_attach_encoder(conn, &slave->base);
- if (ret) {
- armada_drm_slave_destroy(&slave->base);
- return ret;
- }
-
- conn->encoder = &slave->base;
-
- return ret;
-}
-
-static const struct armada_output_type armada_drm_conn_slave = {
- .connector_type = DRM_MODE_CONNECTOR_HDMIA,
- .create = armada_drm_conn_slave_create,
- .set_property = armada_drm_slave_encoder_set_property,
-};
-
-int armada_drm_connector_slave_create(struct drm_device *dev,
- const struct armada_drm_slave_config *config)
-{
- return armada_output_create(dev, &armada_drm_conn_slave, config);
-}
+++ /dev/null
-/*
- * Copyright (C) 2012 Russell King
- *
- * 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.
- */
-#ifndef ARMADA_SLAVE_H
-#define ARMADA_SLAVE_H
-
-#include <linux/i2c.h>
-#include <drm/drmP.h>
-
-struct armada_drm_slave_config {
- int i2c_adapter_id;
- uint32_t crtcs;
- uint8_t polled;
- bool interlace_allowed;
- bool doublescan_allowed;
- struct i2c_board_info info;
-};
-
-int armada_drm_connector_slave_create(struct drm_device *dev,
- const struct armada_drm_slave_config *);
-
-#endif
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
--- /dev/null
+/*
+ * 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);
--- /dev/null
+#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
#include <drm/bridge/dw_hdmi.h>
#include "dw_hdmi.h"
+#include "dw_hdmi-audio.h"
#define HDMI_EDID_LEN 512
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;
bool sink_has_audio;
struct mutex mutex; /* for state below and previous_mode */
+ enum drm_connector_force force; /* mutex-protected force state */
bool disabled; /* DRM has disabled our bridge */
+ bool bridge_is_on; /* indicates the bridge is on */
+ bool rxsense; /* rxsense state */
+ u8 phy_mask; /* desired phy int mask settings */
spinlock_t audio_lock;
struct mutex audio_mutex;
unsigned int 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));
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:
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;
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);
}
{
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);
}
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);
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;
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;
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) */
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) */
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);
}
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;
}
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,
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);
}
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);
}
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;
}
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");
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 = {
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;
}
{
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;
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);
* 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)
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);
{
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);
#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,
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
-#include <drm/drm_encoder_slave.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/i2c/tda998x.h>
struct i2c_client *cec;
struct i2c_client *hdmi;
struct mutex mutex;
- struct delayed_work dwork;
- uint16_t rev;
- uint8_t current_page;
+ u16 rev;
+ u8 current_page;
int dpms;
bool is_hdmi_sink;
u8 vip_cntrl_0;
wait_queue_head_t wq_edid;
volatile int wq_edid_wait;
- struct drm_encoder *encoder;
+
+ struct work_struct detect_work;
+ struct timer_list edid_delay_timer;
+ wait_queue_head_t edid_delay_waitq;
+ bool edid_delay_active;
+
+ struct drm_encoder encoder;
+ struct drm_connector connector;
};
-#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
+#define conn_to_tda998x_priv(x) \
+ container_of(x, struct tda998x_priv, connector)
+
+#define enc_to_tda998x_priv(x) \
+ container_of(x, struct tda998x_priv, encoder)
/* The TDA9988 series of devices use a paged register scheme.. to simplify
* things we encode the page # in upper bits of the register #. To read/
# define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0)
#define REG_CEC_RXSHPDINTENA 0xfc /* read/write */
#define REG_CEC_RXSHPDINT 0xfd /* read */
+# define CEC_RXSHPDINT_RXSENS BIT(0)
+# define CEC_RXSHPDINT_HPD BIT(1)
#define REG_CEC_RXSHPDLEV 0xfe /* read */
# define CEC_RXSHPDLEV_RXSENS (1 << 0)
# define CEC_RXSHPDLEV_HPD (1 << 1)
#define TDA19988 0x0301
static void
-cec_write(struct tda998x_priv *priv, uint16_t addr, uint8_t val)
+cec_write(struct tda998x_priv *priv, u16 addr, u8 val)
{
struct i2c_client *client = priv->cec;
- uint8_t buf[] = {addr, val};
+ u8 buf[] = {addr, val};
int ret;
ret = i2c_master_send(client, buf, sizeof(buf));
dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
}
-static uint8_t
-cec_read(struct tda998x_priv *priv, uint8_t addr)
+static u8
+cec_read(struct tda998x_priv *priv, u8 addr)
{
struct i2c_client *client = priv->cec;
- uint8_t val;
+ u8 val;
int ret;
ret = i2c_master_send(client, &addr, sizeof(addr));
}
static int
-set_page(struct tda998x_priv *priv, uint16_t reg)
+set_page(struct tda998x_priv *priv, u16 reg)
{
if (REG2PAGE(reg) != priv->current_page) {
struct i2c_client *client = priv->hdmi;
- uint8_t buf[] = {
+ u8 buf[] = {
REG_CURPAGE, REG2PAGE(reg)
};
int ret = i2c_master_send(client, buf, sizeof(buf));
}
static int
-reg_read_range(struct tda998x_priv *priv, uint16_t reg, char *buf, int cnt)
+reg_read_range(struct tda998x_priv *priv, u16 reg, char *buf, int cnt)
{
struct i2c_client *client = priv->hdmi;
- uint8_t addr = REG2ADDR(reg);
+ u8 addr = REG2ADDR(reg);
int ret;
mutex_lock(&priv->mutex);
}
static void
-reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt)
+reg_write_range(struct tda998x_priv *priv, u16 reg, u8 *p, int cnt)
{
struct i2c_client *client = priv->hdmi;
- uint8_t buf[cnt+1];
+ u8 buf[cnt+1];
int ret;
buf[0] = REG2ADDR(reg);
}
static int
-reg_read(struct tda998x_priv *priv, uint16_t reg)
+reg_read(struct tda998x_priv *priv, u16 reg)
{
- uint8_t val = 0;
+ u8 val = 0;
int ret;
ret = reg_read_range(priv, reg, &val, sizeof(val));
}
static void
-reg_write(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
+reg_write(struct tda998x_priv *priv, u16 reg, u8 val)
{
struct i2c_client *client = priv->hdmi;
- uint8_t buf[] = {REG2ADDR(reg), val};
+ u8 buf[] = {REG2ADDR(reg), val};
int ret;
mutex_lock(&priv->mutex);
}
static void
-reg_write16(struct tda998x_priv *priv, uint16_t reg, uint16_t val)
+reg_write16(struct tda998x_priv *priv, u16 reg, u16 val)
{
struct i2c_client *client = priv->hdmi;
- uint8_t buf[] = {REG2ADDR(reg), val >> 8, val};
+ u8 buf[] = {REG2ADDR(reg), val >> 8, val};
int ret;
mutex_lock(&priv->mutex);
}
static void
-reg_set(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
+reg_set(struct tda998x_priv *priv, u16 reg, u8 val)
{
int old_val;
}
static void
-reg_clear(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
+reg_clear(struct tda998x_priv *priv, u16 reg, u8 val)
{
int old_val;
reg_write(priv, REG_MUX_VP_VIP_OUT, 0x24);
}
-/* handle HDMI connect/disconnect */
-static void tda998x_hpd(struct work_struct *work)
+/*
+ * The TDA998x has a problem when trying to read the EDID close to a
+ * HPD assertion: it needs a delay of 100ms to avoid timing out while
+ * trying to read EDID data.
+ *
+ * However, tda998x_encoder_get_modes() may be called at any moment
+ * after tda998x_connector_detect() indicates that we are connected, so
+ * we need to delay probing modes in tda998x_encoder_get_modes() after
+ * we have seen a HPD inactive->active transition. This code implements
+ * that delay.
+ */
+static void tda998x_edid_delay_done(unsigned long data)
+{
+ struct tda998x_priv *priv = (struct tda998x_priv *)data;
+
+ priv->edid_delay_active = false;
+ wake_up(&priv->edid_delay_waitq);
+ schedule_work(&priv->detect_work);
+}
+
+static void tda998x_edid_delay_start(struct tda998x_priv *priv)
+{
+ priv->edid_delay_active = true;
+ mod_timer(&priv->edid_delay_timer, jiffies + HZ/10);
+}
+
+static int tda998x_edid_delay_wait(struct tda998x_priv *priv)
+{
+ return wait_event_killable(priv->edid_delay_waitq, !priv->edid_delay_active);
+}
+
+/*
+ * We need to run the KMS hotplug event helper outside of our threaded
+ * interrupt routine as this can call back into our get_modes method,
+ * which will want to make use of interrupts.
+ */
+static void tda998x_detect_work(struct work_struct *work)
{
- struct delayed_work *dwork = to_delayed_work(work);
struct tda998x_priv *priv =
- container_of(dwork, struct tda998x_priv, dwork);
+ container_of(work, struct tda998x_priv, detect_work);
+ struct drm_device *dev = priv->encoder.dev;
- if (priv->encoder && priv->encoder->dev)
- drm_kms_helper_hotplug_event(priv->encoder->dev);
+ if (dev)
+ drm_kms_helper_hotplug_event(dev);
}
/*
{
struct tda998x_priv *priv = data;
u8 sta, cec, lvl, flag0, flag1, flag2;
+ bool handled = false;
- if (!priv)
- return IRQ_HANDLED;
sta = cec_read(priv, REG_CEC_INTSTATUS);
cec = cec_read(priv, REG_CEC_RXSHPDINT);
lvl = cec_read(priv, REG_CEC_RXSHPDLEV);
DRM_DEBUG_DRIVER(
"tda irq sta %02x cec %02x lvl %02x f0 %02x f1 %02x f2 %02x\n",
sta, cec, lvl, flag0, flag1, flag2);
+
+ if (cec & CEC_RXSHPDINT_HPD) {
+ if (lvl & CEC_RXSHPDLEV_HPD)
+ tda998x_edid_delay_start(priv);
+ else
+ schedule_work(&priv->detect_work);
+
+ handled = true;
+ }
+
if ((flag2 & INT_FLAGS_2_EDID_BLK_RD) && priv->wq_edid_wait) {
priv->wq_edid_wait = 0;
wake_up(&priv->wq_edid);
- } else if (cec != 0) { /* HPD change */
- schedule_delayed_work(&priv->dwork, HZ/10);
+ handled = true;
}
- return IRQ_HANDLED;
-}
-static uint8_t tda998x_cksum(uint8_t *buf, size_t bytes)
-{
- int sum = 0;
-
- while (bytes--)
- sum -= *buf++;
- return sum;
+ return IRQ_RETVAL(handled);
}
-#define HB(x) (x)
-#define PB(x) (HB(2) + 1 + (x))
-
static void
-tda998x_write_if(struct tda998x_priv *priv, uint8_t bit, uint16_t addr,
- uint8_t *buf, size_t size)
+tda998x_write_if(struct tda998x_priv *priv, u8 bit, u16 addr,
+ union hdmi_infoframe *frame)
{
+ u8 buf[32];
+ ssize_t len;
+
+ len = hdmi_infoframe_pack(frame, buf, sizeof(buf));
+ if (len < 0) {
+ dev_err(&priv->hdmi->dev,
+ "hdmi_infoframe_pack() type=0x%02x failed: %zd\n",
+ frame->any.type, len);
+ return;
+ }
+
reg_clear(priv, REG_DIP_IF_FLAGS, bit);
- reg_write_range(priv, addr, buf, size);
+ reg_write_range(priv, addr, buf, len);
reg_set(priv, REG_DIP_IF_FLAGS, bit);
}
static void
tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p)
{
- u8 buf[PB(HDMI_AUDIO_INFOFRAME_SIZE) + 1];
+ union hdmi_infoframe frame;
+
+ hdmi_audio_infoframe_init(&frame.audio);
- memset(buf, 0, sizeof(buf));
- buf[HB(0)] = HDMI_INFOFRAME_TYPE_AUDIO;
- buf[HB(1)] = 0x01;
- buf[HB(2)] = HDMI_AUDIO_INFOFRAME_SIZE;
- buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */
- buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */
- buf[PB(4)] = p->audio_frame[4];
- buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */
+ frame.audio.channels = p->audio_frame[1] & 0x07;
+ frame.audio.channel_allocation = p->audio_frame[4];
+ frame.audio.level_shift_value = (p->audio_frame[5] & 0x78) >> 3;
+ frame.audio.downmix_inhibit = (p->audio_frame[5] & 0x80) >> 7;
- buf[PB(0)] = tda998x_cksum(buf, sizeof(buf));
+ /*
+ * L-PCM and IEC61937 compressed audio shall always set sample
+ * frequency to "refer to stream". For others, see the HDMI
+ * specification.
+ */
+ frame.audio.sample_frequency = (p->audio_frame[2] & 0x1c) >> 2;
- tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf,
- sizeof(buf));
+ tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, &frame);
}
static void
tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode)
{
- struct hdmi_avi_infoframe frame;
- u8 buf[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE];
- ssize_t len;
+ union hdmi_infoframe frame;
- drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+ drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode);
+ frame.avi.quantization_range = HDMI_QUANTIZATION_RANGE_FULL;
- frame.quantization_range = HDMI_QUANTIZATION_RANGE_FULL;
-
- len = hdmi_avi_infoframe_pack(&frame, buf, sizeof(buf));
- if (len < 0) {
- dev_err(&priv->hdmi->dev,
- "hdmi_avi_infoframe_pack() failed: %zd\n", len);
- return;
- }
-
- tda998x_write_if(priv, DIP_IF_FLAGS_IF2, REG_IF2_HB0, buf, len);
+ tda998x_write_if(priv, DIP_IF_FLAGS_IF2, REG_IF2_HB0, &frame);
}
static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
tda998x_configure_audio(struct tda998x_priv *priv,
struct drm_display_mode *mode, struct tda998x_encoder_params *p)
{
- uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv;
- uint32_t n;
+ u8 buf[6], clksel_aip, clksel_fs, cts_n, adiv;
+ u32 n;
/* Enable audio ports */
reg_write(priv, REG_ENA_AP, p->audio_cfg);
priv->params = *p;
}
-static void tda998x_encoder_dpms(struct tda998x_priv *priv, int mode)
+static void tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
{
+ struct tda998x_priv *priv = enc_to_tda998x_priv(encoder);
+
/* we only care about on or off: */
if (mode != DRM_MODE_DPMS_ON)
mode = DRM_MODE_DPMS_OFF;
return true;
}
-static int tda998x_encoder_mode_valid(struct tda998x_priv *priv,
- struct drm_display_mode *mode)
+static int tda998x_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
{
if (mode->clock > 150000)
return MODE_CLOCK_HIGH;
}
static void
-tda998x_encoder_mode_set(struct tda998x_priv *priv,
+tda998x_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
- uint16_t ref_pix, ref_line, n_pix, n_line;
- uint16_t hs_pix_s, hs_pix_e;
- uint16_t vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e;
- uint16_t vs2_pix_s, vs2_pix_e, vs2_line_s, vs2_line_e;
- uint16_t vwin1_line_s, vwin1_line_e;
- uint16_t vwin2_line_s, vwin2_line_e;
- uint16_t de_pix_s, de_pix_e;
- uint8_t reg, div, rep;
+ struct tda998x_priv *priv = enc_to_tda998x_priv(encoder);
+ u16 ref_pix, ref_line, n_pix, n_line;
+ u16 hs_pix_s, hs_pix_e;
+ u16 vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e;
+ u16 vs2_pix_s, vs2_pix_e, vs2_line_s, vs2_line_e;
+ u16 vwin1_line_s, vwin1_line_e;
+ u16 vwin2_line_s, vwin2_line_e;
+ u16 de_pix_s, de_pix_e;
+ u8 reg, div, rep;
/*
* Internally TDA998x is using ITU-R BT.656 style sync but
}
static enum drm_connector_status
-tda998x_encoder_detect(struct tda998x_priv *priv)
+tda998x_connector_detect(struct drm_connector *connector, bool force)
{
- uint8_t val = cec_read(priv, REG_CEC_RXSHPDLEV);
+ struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
+ u8 val = cec_read(priv, REG_CEC_RXSHPDLEV);
return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected :
connector_status_disconnected;
static int read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length)
{
struct tda998x_priv *priv = data;
- uint8_t offset, segptr;
+ u8 offset, segptr;
int ret, i;
offset = (blk & 1) ? 128 : 0;
return 0;
}
-static int
-tda998x_encoder_get_modes(struct tda998x_priv *priv,
- struct drm_connector *connector)
+static int tda998x_connector_get_modes(struct drm_connector *connector)
{
+ struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
struct edid *edid;
int n;
+ /*
+ * If we get killed while waiting for the HPD timeout, return
+ * no modes found: we are not in a restartable path, so we
+ * can't handle signals gracefully.
+ */
+ if (tda998x_edid_delay_wait(priv))
+ return 0;
+
if (priv->rev == TDA19988)
reg_clear(priv, REG_TX4, TX4_PD_RAM);
DRM_CONNECTOR_POLL_DISCONNECT;
}
-static int
-tda998x_encoder_set_property(struct drm_encoder *encoder,
- struct drm_connector *connector,
- struct drm_property *property,
- uint64_t val)
-{
- DBG("");
- return 0;
-}
-
static void tda998x_destroy(struct tda998x_priv *priv)
{
/* disable all IRQs and free the IRQ handler */
cec_write(priv, REG_CEC_RXSHPDINTENA, 0);
reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
- if (priv->hdmi->irq) {
- free_irq(priv->hdmi->irq, priv);
- cancel_delayed_work_sync(&priv->dwork);
- }
-
- i2c_unregister_device(priv->cec);
-}
-
-/* Slave encoder support */
-
-static void
-tda998x_encoder_slave_set_config(struct drm_encoder *encoder, void *params)
-{
- tda998x_encoder_set_config(to_tda998x_priv(encoder), params);
-}
-static void tda998x_encoder_slave_destroy(struct drm_encoder *encoder)
-{
- struct tda998x_priv *priv = to_tda998x_priv(encoder);
-
- tda998x_destroy(priv);
- drm_i2c_encoder_destroy(encoder);
- kfree(priv);
-}
-
-static void tda998x_encoder_slave_dpms(struct drm_encoder *encoder, int mode)
-{
- tda998x_encoder_dpms(to_tda998x_priv(encoder), mode);
-}
-
-static int tda998x_encoder_slave_mode_valid(struct drm_encoder *encoder,
- struct drm_display_mode *mode)
-{
- return tda998x_encoder_mode_valid(to_tda998x_priv(encoder), mode);
-}
+ if (priv->hdmi->irq)
+ free_irq(priv->hdmi->irq, priv);
-static void
-tda998x_encoder_slave_mode_set(struct drm_encoder *encoder,
- struct drm_display_mode *mode,
- struct drm_display_mode *adjusted_mode)
-{
- tda998x_encoder_mode_set(to_tda998x_priv(encoder), mode, adjusted_mode);
-}
+ del_timer_sync(&priv->edid_delay_timer);
+ cancel_work_sync(&priv->detect_work);
-static enum drm_connector_status
-tda998x_encoder_slave_detect(struct drm_encoder *encoder,
- struct drm_connector *connector)
-{
- return tda998x_encoder_detect(to_tda998x_priv(encoder));
-}
-
-static int tda998x_encoder_slave_get_modes(struct drm_encoder *encoder,
- struct drm_connector *connector)
-{
- return tda998x_encoder_get_modes(to_tda998x_priv(encoder), connector);
-}
-
-static int
-tda998x_encoder_slave_create_resources(struct drm_encoder *encoder,
- struct drm_connector *connector)
-{
- tda998x_encoder_set_polling(to_tda998x_priv(encoder), connector);
- return 0;
+ i2c_unregister_device(priv->cec);
}
-static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = {
- .set_config = tda998x_encoder_slave_set_config,
- .destroy = tda998x_encoder_slave_destroy,
- .dpms = tda998x_encoder_slave_dpms,
- .save = tda998x_encoder_save,
- .restore = tda998x_encoder_restore,
- .mode_fixup = tda998x_encoder_mode_fixup,
- .mode_valid = tda998x_encoder_slave_mode_valid,
- .mode_set = tda998x_encoder_slave_mode_set,
- .detect = tda998x_encoder_slave_detect,
- .get_modes = tda998x_encoder_slave_get_modes,
- .create_resources = tda998x_encoder_slave_create_resources,
- .set_property = tda998x_encoder_set_property,
-};
-
/* I2C driver functions */
static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
priv->dpms = DRM_MODE_DPMS_OFF;
mutex_init(&priv->mutex); /* protect the page access */
+ init_waitqueue_head(&priv->edid_delay_waitq);
+ setup_timer(&priv->edid_delay_timer, tda998x_edid_delay_done,
+ (unsigned long)priv);
+ INIT_WORK(&priv->detect_work, tda998x_detect_work);
/* wake up the device: */
cec_write(priv, REG_CEC_ENAMODS,
/* init read EDID waitqueue and HDP work */
init_waitqueue_head(&priv->wq_edid);
- INIT_DELAYED_WORK(&priv->dwork, tda998x_hpd);
/* clear pending interrupts */
reg_read(priv, REG_INT_FLAGS_0);
return -ENXIO;
}
-static int tda998x_encoder_init(struct i2c_client *client,
- struct drm_device *dev,
- struct drm_encoder_slave *encoder_slave)
-{
- struct tda998x_priv *priv;
- int ret;
-
- priv = kzalloc(sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->encoder = &encoder_slave->base;
-
- ret = tda998x_create(client, priv);
- if (ret) {
- kfree(priv);
- return ret;
- }
-
- encoder_slave->slave_priv = priv;
- encoder_slave->slave_funcs = &tda998x_encoder_slave_funcs;
-
- return 0;
-}
-
-struct tda998x_priv2 {
- struct tda998x_priv base;
- struct drm_encoder encoder;
- struct drm_connector connector;
-};
-
-#define conn_to_tda998x_priv2(x) \
- container_of(x, struct tda998x_priv2, connector);
-
-#define enc_to_tda998x_priv2(x) \
- container_of(x, struct tda998x_priv2, encoder);
-
-static void tda998x_encoder2_dpms(struct drm_encoder *encoder, int mode)
-{
- struct tda998x_priv2 *priv = enc_to_tda998x_priv2(encoder);
-
- tda998x_encoder_dpms(&priv->base, mode);
-}
-
static void tda998x_encoder_prepare(struct drm_encoder *encoder)
{
- tda998x_encoder2_dpms(encoder, DRM_MODE_DPMS_OFF);
+ tda998x_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}
static void tda998x_encoder_commit(struct drm_encoder *encoder)
{
- tda998x_encoder2_dpms(encoder, DRM_MODE_DPMS_ON);
-}
-
-static void tda998x_encoder2_mode_set(struct drm_encoder *encoder,
- struct drm_display_mode *mode,
- struct drm_display_mode *adjusted_mode)
-{
- struct tda998x_priv2 *priv = enc_to_tda998x_priv2(encoder);
-
- tda998x_encoder_mode_set(&priv->base, mode, adjusted_mode);
+ tda998x_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static const struct drm_encoder_helper_funcs tda998x_encoder_helper_funcs = {
- .dpms = tda998x_encoder2_dpms,
+ .dpms = tda998x_encoder_dpms,
.save = tda998x_encoder_save,
.restore = tda998x_encoder_restore,
.mode_fixup = tda998x_encoder_mode_fixup,
.prepare = tda998x_encoder_prepare,
.commit = tda998x_encoder_commit,
- .mode_set = tda998x_encoder2_mode_set,
+ .mode_set = tda998x_encoder_mode_set,
};
static void tda998x_encoder_destroy(struct drm_encoder *encoder)
{
- struct tda998x_priv2 *priv = enc_to_tda998x_priv2(encoder);
+ struct tda998x_priv *priv = enc_to_tda998x_priv(encoder);
- tda998x_destroy(&priv->base);
+ tda998x_destroy(priv);
drm_encoder_cleanup(encoder);
}
.destroy = tda998x_encoder_destroy,
};
-static int tda998x_connector_get_modes(struct drm_connector *connector)
-{
- struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
-
- return tda998x_encoder_get_modes(&priv->base, connector);
-}
-
-static int tda998x_connector_mode_valid(struct drm_connector *connector,
- struct drm_display_mode *mode)
-{
- struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
-
- return tda998x_encoder_mode_valid(&priv->base, mode);
-}
-
static struct drm_encoder *
tda998x_connector_best_encoder(struct drm_connector *connector)
{
- struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
+ struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
return &priv->encoder;
}
.best_encoder = tda998x_connector_best_encoder,
};
-static enum drm_connector_status
-tda998x_connector_detect(struct drm_connector *connector, bool force)
-{
- struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
-
- return tda998x_encoder_detect(&priv->base);
-}
-
static void tda998x_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
struct tda998x_encoder_params *params = dev->platform_data;
struct i2c_client *client = to_i2c_client(dev);
struct drm_device *drm = data;
- struct tda998x_priv2 *priv;
- uint32_t crtcs = 0;
+ struct tda998x_priv *priv;
+ u32 crtcs = 0;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
crtcs = 1 << 0;
}
- priv->base.encoder = &priv->encoder;
priv->connector.interlace_allowed = 1;
priv->encoder.possible_crtcs = crtcs;
- ret = tda998x_create(client, &priv->base);
+ ret = tda998x_create(client, priv);
if (ret)
return ret;
if (!dev->of_node && params)
- tda998x_encoder_set_config(&priv->base, params);
+ tda998x_encoder_set_config(priv, params);
- tda998x_encoder_set_polling(&priv->base, &priv->connector);
+ tda998x_encoder_set_polling(priv, &priv->connector);
drm_encoder_helper_add(&priv->encoder, &tda998x_encoder_helper_funcs);
ret = drm_encoder_init(drm, &priv->encoder, &tda998x_encoder_funcs,
err_connector:
drm_encoder_cleanup(&priv->encoder);
err_encoder:
- tda998x_destroy(&priv->base);
+ tda998x_destroy(priv);
return ret;
}
static void tda998x_unbind(struct device *dev, struct device *master,
void *data)
{
- struct tda998x_priv2 *priv = dev_get_drvdata(dev);
+ struct tda998x_priv *priv = dev_get_drvdata(dev);
drm_connector_cleanup(&priv->connector);
drm_encoder_cleanup(&priv->encoder);
- tda998x_destroy(&priv->base);
+ tda998x_destroy(priv);
}
static const struct component_ops tda998x_ops = {
};
MODULE_DEVICE_TABLE(i2c, tda998x_ids);
-static struct drm_i2c_encoder_driver tda998x_driver = {
- .i2c_driver = {
- .probe = tda998x_probe,
- .remove = tda998x_remove,
- .driver = {
- .name = "tda998x",
- .of_match_table = of_match_ptr(tda998x_dt_ids),
- },
- .id_table = tda998x_ids,
+static struct i2c_driver tda998x_driver = {
+ .probe = tda998x_probe,
+ .remove = tda998x_remove,
+ .driver = {
+ .name = "tda998x",
+ .of_match_table = of_match_ptr(tda998x_dt_ids),
},
- .encoder_init = tda998x_encoder_init,
+ .id_table = tda998x_ids,
};
-/* Module initialization */
-
-static int __init
-tda998x_init(void)
-{
- DBG("");
- return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver);
-}
-
-static void __exit
-tda998x_exit(void)
-{
- DBG("");
- drm_i2c_encoder_unregister(&tda998x_driver);
-}
+module_i2c_driver(tda998x_driver);
MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
MODULE_LICENSE("GPL");
-
-module_init(tda998x_init);
-module_exit(tda998x_exit);
}
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);
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
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);
}
}
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;
/* 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);
*/
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;