diff options
Diffstat (limited to 'drivers/gpu')
216 files changed, 16070 insertions, 3203 deletions
diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile index cc9277885dd0..30879df3daea 100644 --- a/drivers/gpu/Makefile +++ b/drivers/gpu/Makefile @@ -1 +1 @@ -obj-y += drm/ vga/ stub/ +obj-y += drm/ vga/ diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ba74e75f7d54..f8dae851130c 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -216,4 +216,6 @@ source "drivers/gpu/drm/shmobile/Kconfig" source "drivers/gpu/drm/tegra/Kconfig" +source "drivers/gpu/drm/omapdrm/Kconfig" + source "drivers/gpu/drm/tilcdc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 3af934dffe68..0d59b24f8d23 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -50,5 +50,6 @@ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-$(CONFIG_DRM_TEGRA) += tegra/ +obj-$(CONFIG_DRM_OMAP) += omapdrm/ obj-$(CONFIG_DRM_TILCDC) += tilcdc/ obj-y += i2c/ diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index e7471b0880b7..781aef524b3b 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -2267,7 +2267,7 @@ uint32_t drm_mode_legacy_fb_format(uint32_t bpp, uint32_t depth) switch (bpp) { case 8: - fmt = DRM_FORMAT_RGB332; + fmt = DRM_FORMAT_C8; break; case 16: if (depth == 15) @@ -3870,6 +3870,7 @@ void drm_fb_get_bpp_depth(uint32_t format, unsigned int *depth, int *bpp) { switch (format) { + case DRM_FORMAT_C8: case DRM_FORMAT_RGB332: case DRM_FORMAT_BGR233: *depth = 8; diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index e1aca7b53987..67aa0dd68250 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -2054,7 +2054,8 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) num_modes += add_cvt_modes(connector, edid); num_modes += add_standard_modes(connector, edid); num_modes += add_established_modes(connector, edid); - num_modes += add_inferred_modes(connector, edid); + if (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF) + num_modes += add_inferred_modes(connector, edid); num_modes += add_cea_modes(connector, edid); if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75)) diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index 8044c5e56665..0b5af7d0edb1 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -430,12 +430,13 @@ EXPORT_SYMBOL_GPL(drm_fbdev_cma_fini); */ void drm_fbdev_cma_restore_mode(struct drm_fbdev_cma *fbdev_cma) { - struct drm_device *dev = fbdev_cma->fb_helper.dev; + if (fbdev_cma) { + struct drm_device *dev = fbdev_cma->fb_helper.dev; - drm_modeset_lock_all(dev); - if (fbdev_cma) + drm_modeset_lock_all(dev); drm_fb_helper_restore_fbdev_mode(&fbdev_cma->fb_helper); - drm_modeset_unlock_all(dev); + drm_modeset_unlock_all(dev); + } } EXPORT_SYMBOL_GPL(drm_fbdev_cma_restore_mode); diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c index e0e77b89d114..7c65ab83914a 100644 --- a/drivers/gpu/drm/i915/i915_debugfs.c +++ b/drivers/gpu/drm/i915/i915_debugfs.c @@ -694,7 +694,7 @@ static int i915_error_state(struct seq_file *m, void *unused) seq_printf(m, "Time: %ld s %ld us\n", error->time.tv_sec, error->time.tv_usec); - seq_printf(m, "Kernel: " UTS_RELEASE); + seq_printf(m, "Kernel: " UTS_RELEASE "\n"); seq_printf(m, "PCI ID: 0x%04x\n", dev->pci_device); seq_printf(m, "EIR: 0x%08x\n", error->eir); seq_printf(m, "IER: 0x%08x\n", error->ier); @@ -1484,7 +1484,8 @@ static int i915_context_status(struct seq_file *m, void *unused) struct drm_info_node *node = (struct drm_info_node *) m->private; struct drm_device *dev = node->minor->dev; drm_i915_private_t *dev_priv = dev->dev_private; - int ret; + struct intel_ring_buffer *ring; + int ret, i; ret = mutex_lock_interruptible(&dev->mode_config.mutex); if (ret) @@ -1502,6 +1503,14 @@ static int i915_context_status(struct seq_file *m, void *unused) seq_printf(m, "\n"); } + for_each_ring(ring, dev_priv, i) { + if (ring->default_context) { + seq_printf(m, "HW default context %s ring ", ring->name); + describe_obj(m, ring->default_context->obj); + seq_printf(m, "\n"); + } + } + mutex_unlock(&dev->mode_config.mutex); return 0; diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index cf0610330135..4fa6beb14c77 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -1610,6 +1610,7 @@ int i915_driver_load(struct drm_device *dev, unsigned long flags) mutex_init(&dev_priv->dpio_lock); mutex_init(&dev_priv->rps.hw_lock); + mutex_init(&dev_priv->modeset_restore_lock); if (IS_IVYBRIDGE(dev) || IS_HASWELL(dev)) dev_priv->num_pipe = 3; diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index d159d7a402e9..c5b8c81b9440 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -470,6 +470,11 @@ static int i915_drm_freeze(struct drm_device *dev) { struct drm_i915_private *dev_priv = dev->dev_private; + /* ignore lid events during suspend */ + mutex_lock(&dev_priv->modeset_restore_lock); + dev_priv->modeset_restore = MODESET_SUSPENDED; + mutex_unlock(&dev_priv->modeset_restore_lock); + intel_set_power_well(dev, true); drm_kms_helper_poll_disable(dev); @@ -496,9 +501,6 @@ static int i915_drm_freeze(struct drm_device *dev) intel_opregion_fini(dev); - /* Modeset on resume, not lid events */ - dev_priv->modeset_on_lid = 0; - console_lock(); intel_fbdev_set_suspend(dev, 1); console_unlock(); @@ -574,8 +576,6 @@ static int __i915_drm_thaw(struct drm_device *dev) intel_opregion_init(dev); - dev_priv->modeset_on_lid = 0; - /* * The console lock can be pretty contented on resume due * to all the printk activity. Try to keep it out of the hot @@ -588,6 +588,9 @@ static int __i915_drm_thaw(struct drm_device *dev) schedule_work(&dev_priv->console_resume_work); } + mutex_lock(&dev_priv->modeset_restore_lock); + dev_priv->modeset_restore = MODESET_DONE; + mutex_unlock(&dev_priv->modeset_restore_lock); return error; } diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index c338b4443fd9..e95337c97459 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -399,7 +399,8 @@ struct i915_gtt { /* global gtt ops */ int (*gtt_probe)(struct drm_device *dev, size_t *gtt_total, - size_t *stolen); + size_t *stolen, phys_addr_t *mappable_base, + unsigned long *mappable_end); void (*gtt_remove)(struct drm_device *dev); void (*gtt_clear_range)(struct drm_device *dev, unsigned int first_entry, @@ -846,6 +847,12 @@ struct i915_gpu_error { unsigned int stop_rings; }; +enum modeset_restore { + MODESET_ON_LID_OPEN, + MODESET_DONE, + MODESET_SUSPENDED, +}; + typedef struct drm_i915_private { struct drm_device *dev; struct kmem_cache *slab; @@ -919,7 +926,7 @@ typedef struct drm_i915_private { /* overlay */ struct intel_overlay *overlay; - bool sprite_scaling_enabled; + unsigned int sprite_scaling_enabled; /* LVDS info */ int backlight_level; /* restore backlight to this value */ @@ -967,8 +974,8 @@ typedef struct drm_i915_private { unsigned long quirks; - /* Register state */ - bool modeset_on_lid; + enum modeset_restore modeset_restore; + struct mutex modeset_restore_lock; struct i915_gtt gtt; @@ -1033,7 +1040,7 @@ typedef struct drm_i915_private { bool hw_contexts_disabled; uint32_t hw_context_size; - bool fdi_rx_polarity_reversed; + u32 fdi_rx_config; struct i915_suspend_saved_registers regfile; @@ -1208,13 +1215,6 @@ struct drm_i915_gem_object { /** for phy allocated objects */ struct drm_i915_gem_phys_object *phys_obj; - - /** - * Number of crtcs where this object is currently the fb, but - * will be page flipped away on the next vblank. When it - * reaches 0, dev_priv->pending_flip_queue will be woken up. - */ - atomic_t pending_flip; }; #define to_gem_object(obj) (&((struct drm_i915_gem_object *)(obj))->base) diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index 62be74899c2b..8413ffced815 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -3021,6 +3021,13 @@ i915_gem_clflush_object(struct drm_i915_gem_object *obj) if (obj->pages == NULL) return; + /* + * Stolen memory is always coherent with the GPU as it is explicitly + * marked as wc by the system, or the system is cache-coherent. + */ + if (obj->stolen) + return; + /* If the GPU is snooping the contents of the CPU cache, * we do not need to manually clear the CPU cache lines. However, * the caches are only snooped when the render cache is @@ -3865,7 +3872,7 @@ void i915_gem_l3_remap(struct drm_device *dev) u32 misccpctl; int i; - if (!IS_IVYBRIDGE(dev)) + if (!HAS_L3_GPU_CACHE(dev)) return; if (!dev_priv->l3_parity.remap_info) @@ -3930,22 +3937,11 @@ intel_enable_blt(struct drm_device *dev) return true; } -int -i915_gem_init_hw(struct drm_device *dev) +static int i915_gem_init_rings(struct drm_device *dev) { - drm_i915_private_t *dev_priv = dev->dev_private; + struct drm_i915_private *dev_priv = dev->dev_private; int ret; - if (INTEL_INFO(dev)->gen < 6 && !intel_enable_gtt()) - return -EIO; - - if (IS_HASWELL(dev) && (I915_READ(0x120010) == 1)) - I915_WRITE(0x9008, I915_READ(0x9008) | 0xf0000); - - i915_gem_l3_remap(dev); - - i915_gem_init_swizzling(dev); - ret = intel_init_render_ring_buffer(dev); if (ret) return ret; @@ -3964,6 +3960,38 @@ i915_gem_init_hw(struct drm_device *dev) ret = i915_gem_set_seqno(dev, ((u32)~0 - 0x1000)); if (ret) + goto cleanup_blt_ring; + + return 0; + +cleanup_blt_ring: + intel_cleanup_ring_buffer(&dev_priv->ring[BCS]); +cleanup_bsd_ring: + intel_cleanup_ring_buffer(&dev_priv->ring[VCS]); +cleanup_render_ring: + intel_cleanup_ring_buffer(&dev_priv->ring[RCS]); + + return ret; +} + +int +i915_gem_init_hw(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; + + if (INTEL_INFO(dev)->gen < 6 && !intel_enable_gtt()) + return -EIO; + + if (IS_HASWELL(dev) && (I915_READ(0x120010) == 1)) + I915_WRITE(0x9008, I915_READ(0x9008) | 0xf0000); + + i915_gem_l3_remap(dev); + + i915_gem_init_swizzling(dev); + + ret = i915_gem_init_rings(dev); + if (ret) return ret; /* @@ -3974,12 +4002,6 @@ i915_gem_init_hw(struct drm_device *dev) i915_gem_init_ppgtt(dev); return 0; - -cleanup_bsd_ring: - intel_cleanup_ring_buffer(&dev_priv->ring[VCS]); -cleanup_render_ring: - intel_cleanup_ring_buffer(&dev_priv->ring[RCS]); - return ret; } int i915_gem_init(struct drm_device *dev) diff --git a/drivers/gpu/drm/i915/i915_gem_context.c b/drivers/gpu/drm/i915/i915_gem_context.c index a3f06bcad551..21177d9df423 100644 --- a/drivers/gpu/drm/i915/i915_gem_context.c +++ b/drivers/gpu/drm/i915/i915_gem_context.c @@ -126,13 +126,8 @@ static int get_context_size(struct drm_device *dev) static void do_destroy(struct i915_hw_context *ctx) { - struct drm_device *dev = ctx->obj->base.dev; - struct drm_i915_private *dev_priv = dev->dev_private; - if (ctx->file_priv) idr_remove(&ctx->file_priv->context_idr, ctx->id); - else - BUG_ON(ctx != dev_priv->ring[RCS].default_context); drm_gem_object_unreference(&ctx->obj->base); kfree(ctx); @@ -242,7 +237,6 @@ err_destroy: void i915_gem_context_init(struct drm_device *dev) { struct drm_i915_private *dev_priv = dev->dev_private; - uint32_t ctx_size; if (!HAS_HW_CONTEXTS(dev)) { dev_priv->hw_contexts_disabled = true; @@ -254,11 +248,9 @@ void i915_gem_context_init(struct drm_device *dev) dev_priv->ring[RCS].default_context) return; - ctx_size = get_context_size(dev); - dev_priv->hw_context_size = get_context_size(dev); - dev_priv->hw_context_size = round_up(dev_priv->hw_context_size, 4096); + dev_priv->hw_context_size = round_up(get_context_size(dev), 4096); - if (ctx_size <= 0 || ctx_size > (1<<20)) { + if (dev_priv->hw_context_size > (1<<20)) { dev_priv->hw_contexts_disabled = true; return; } diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.c b/drivers/gpu/drm/i915/i915_gem_gtt.c index bdaca3f47988..926a1e2dd234 100644 --- a/drivers/gpu/drm/i915/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/i915_gem_gtt.c @@ -725,7 +725,9 @@ static inline size_t gen7_get_stolen_size(u16 snb_gmch_ctl) static int gen6_gmch_probe(struct drm_device *dev, size_t *gtt_total, - size_t *stolen) + size_t *stolen, + phys_addr_t *mappable_base, + unsigned long *mappable_end) { struct drm_i915_private *dev_priv = dev->dev_private; phys_addr_t gtt_bus_addr; @@ -733,11 +735,13 @@ static int gen6_gmch_probe(struct drm_device *dev, u16 snb_gmch_ctl; int ret; + *mappable_base = pci_resource_start(dev->pdev, 2); + *mappable_end = pci_resource_len(dev->pdev, 2); + /* 64/512MB is the current min/max we actually know of, but this is just * a coarse sanity check. */ - if ((dev_priv->gtt.mappable_end < (64<<20) || - (dev_priv->gtt.mappable_end > (512<<20)))) { + if ((*mappable_end < (64<<20) || (*mappable_end > (512<<20)))) { DRM_ERROR("Unknown GMADR size (%lx)\n", dev_priv->gtt.mappable_end); return -ENXIO; @@ -782,7 +786,9 @@ static void gen6_gmch_remove(struct drm_device *dev) static int i915_gmch_probe(struct drm_device *dev, size_t *gtt_total, - size_t *stolen) + size_t *stolen, + phys_addr_t *mappable_base, + unsigned long *mappable_end) { struct drm_i915_private *dev_priv = dev->dev_private; int ret; @@ -793,7 +799,7 @@ static int i915_gmch_probe(struct drm_device *dev, return -EIO; } - intel_gtt_get(gtt_total, stolen); + intel_gtt_get(gtt_total, stolen, mappable_base, mappable_end); dev_priv->gtt.do_idle_maps = needs_idle_maps(dev_priv->dev); dev_priv->gtt.gtt_clear_range = i915_ggtt_clear_range; @@ -814,9 +820,6 @@ int i915_gem_gtt_init(struct drm_device *dev) unsigned long gtt_size; int ret; - gtt->mappable_base = pci_resource_start(dev->pdev, 2); - gtt->mappable_end = pci_resource_len(dev->pdev, 2); - if (INTEL_INFO(dev)->gen <= 5) { dev_priv->gtt.gtt_probe = i915_gmch_probe; dev_priv->gtt.gtt_remove = i915_gmch_remove; @@ -826,7 +829,9 @@ int i915_gem_gtt_init(struct drm_device *dev) } ret = dev_priv->gtt.gtt_probe(dev, &dev_priv->gtt.total, - &dev_priv->gtt.stolen_size); + &dev_priv->gtt.stolen_size, + >t->mappable_base, + >t->mappable_end); if (ret) return ret; diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 13bb8d3f2a77..2cd97d1cc920 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1924,7 +1924,7 @@ static void valleyview_irq_preinstall(struct drm_device *dev) * This register is the same on all known PCH chips. */ -static void ironlake_enable_pch_hotplug(struct drm_device *dev) +static void ibx_enable_hotplug(struct drm_device *dev) { drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; u32 hotplug; @@ -1937,6 +1937,28 @@ static void ironlake_enable_pch_hotplug(struct drm_device *dev) I915_WRITE(PCH_PORT_HOTPLUG, hotplug); } +static void ibx_irq_postinstall(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; + u32 mask; + + if (HAS_PCH_IBX(dev)) + mask = SDE_HOTPLUG_MASK | + SDE_GMBUS | + SDE_AUX_MASK; + else + mask = SDE_HOTPLUG_MASK_CPT | + SDE_GMBUS_CPT | + SDE_AUX_MASK_CPT; + + I915_WRITE(SDEIIR, I915_READ(SDEIIR)); + I915_WRITE(SDEIMR, ~mask); + I915_WRITE(SDEIER, mask); + POSTING_READ(SDEIER); + + ibx_enable_hotplug(dev); +} + static int ironlake_irq_postinstall(struct drm_device *dev) { drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; @@ -1945,8 +1967,6 @@ static int ironlake_irq_postinstall(struct drm_device *dev) DE_PLANEA_FLIP_DONE | DE_PLANEB_FLIP_DONE | DE_AUX_CHANNEL_A; u32 render_irqs; - u32 hotplug_mask; - u32 pch_irq_mask; dev_priv->irq_mask = ~display_mask; @@ -1974,30 +1994,7 @@ static int ironlake_irq_postinstall(struct drm_device *dev) I915_WRITE(GTIER, render_irqs); POSTING_READ(GTIER); - if (HAS_PCH_CPT(dev)) { - hotplug_mask = (SDE_CRT_HOTPLUG_CPT | - SDE_PORTB_HOTPLUG_CPT | - SDE_PORTC_HOTPLUG_CPT | - SDE_PORTD_HOTPLUG_CPT | - SDE_GMBUS_CPT | - SDE_AUX_MASK_CPT); - } else { - hotplug_mask = (SDE_CRT_HOTPLUG | - SDE_PORTB_HOTPLUG | - SDE_PORTC_HOTPLUG | - SDE_PORTD_HOTPLUG | - SDE_GMBUS | - SDE_AUX_MASK); - } - - pch_irq_mask = ~hotplug_mask; - - I915_WRITE(SDEIIR, I915_READ(SDEIIR)); - I915_WRITE(SDEIMR, pch_irq_mask); - I915_WRITE(SDEIER, hotplug_mask); - POSTING_READ(SDEIER); - - ironlake_enable_pch_hotplug(dev); + ibx_irq_postinstall(dev); if (IS_IRONLAKE_M(dev)) { /* Clear & enable PCU event interrupts */ @@ -2020,8 +2017,6 @@ static int ivybridge_irq_postinstall(struct drm_device *dev) DE_PLANEA_FLIP_DONE_IVB | DE_AUX_CHANNEL_A_IVB; u32 render_irqs; - u32 hotplug_mask; - u32 pch_irq_mask; dev_priv->irq_mask = ~display_mask; @@ -2045,20 +2040,7 @@ static int ivybridge_irq_postinstall(struct drm_device *dev) I915_WRITE(GTIER, render_irqs); POSTING_READ(GTIER); - hotplug_mask = (SDE_CRT_HOTPLUG_CPT | - SDE_PORTB_HOTPLUG_CPT | - SDE_PORTC_HOTPLUG_CPT | - SDE_PORTD_HOTPLUG_CPT | - SDE_GMBUS_CPT | - SDE_AUX_MASK_CPT); - pch_irq_mask = ~hotplug_mask; - - I915_WRITE(SDEIIR, I915_READ(SDEIIR)); - I915_WRITE(SDEIMR, pch_irq_mask); - I915_WRITE(SDEIER, hotplug_mask); - POSTING_READ(SDEIER); - - ironlake_enable_pch_hotplug(dev); + ibx_irq_postinstall(dev); return 0; } @@ -2137,12 +2119,12 @@ static void valleyview_hpd_irq_setup(struct drm_device *dev) u32 hotplug_en = I915_READ(PORT_HOTPLUG_EN); /* Note HDMI and DP share bits */ - if (dev_priv->hotplug_supported_mask & HDMIB_HOTPLUG_INT_STATUS) - hotplug_en |= HDMIB_HOTPLUG_INT_EN; - if (dev_priv->hotplug_supported_mask & HDMIC_HOTPLUG_INT_STATUS) - hotplug_en |= HDMIC_HOTPLUG_INT_EN; - if (dev_priv->hotplug_supported_mask & HDMID_HOTPLUG_INT_STATUS) - hotplug_en |= HDMID_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTB_HOTPLUG_INT_STATUS) + hotplug_en |= PORTB_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTC_HOTPLUG_INT_STATUS) + hotplug_en |= PORTC_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTD_HOTPLUG_INT_STATUS) + hotplug_en |= PORTD_HOTPLUG_INT_EN; if (dev_priv->hotplug_supported_mask & SDVOC_HOTPLUG_INT_STATUS_I915) hotplug_en |= SDVOC_HOTPLUG_INT_EN; if (dev_priv->hotplug_supported_mask & SDVOB_HOTPLUG_INT_STATUS_I915) @@ -2408,12 +2390,12 @@ static void i915_hpd_irq_setup(struct drm_device *dev) if (I915_HAS_HOTPLUG(dev)) { hotplug_en = I915_READ(PORT_HOTPLUG_EN); - if (dev_priv->hotplug_supported_mask & HDMIB_HOTPLUG_INT_STATUS) - hotplug_en |= HDMIB_HOTPLUG_INT_EN; - if (dev_priv->hotplug_supported_mask & HDMIC_HOTPLUG_INT_STATUS) - hotplug_en |= HDMIC_HOTPLUG_INT_EN; - if (dev_priv->hotplug_supported_mask & HDMID_HOTPLUG_INT_STATUS) - hotplug_en |= HDMID_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTB_HOTPLUG_INT_STATUS) + hotplug_en |= PORTB_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTC_HOTPLUG_INT_STATUS) + hotplug_en |= PORTC_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTD_HOTPLUG_INT_STATUS) + hotplug_en |= PORTD_HOTPLUG_INT_EN; if (dev_priv->hotplug_supported_mask & SDVOC_HOTPLUG_INT_STATUS_I915) hotplug_en |= SDVOC_HOTPLUG_INT_EN; if (dev_priv->hotplug_supported_mask & SDVOB_HOTPLUG_INT_STATUS_I915) @@ -2642,12 +2624,12 @@ static void i965_hpd_irq_setup(struct drm_device *dev) /* Note HDMI and DP share hotplug bits */ hotplug_en = 0; - if (dev_priv->hotplug_supported_mask & HDMIB_HOTPLUG_INT_STATUS) - hotplug_en |= HDMIB_HOTPLUG_INT_EN; - if (dev_priv->hotplug_supported_mask & HDMIC_HOTPLUG_INT_STATUS) - hotplug_en |= HDMIC_HOTPLUG_INT_EN; - if (dev_priv->hotplug_supported_mask & HDMID_HOTPLUG_INT_STATUS) - hotplug_en |= HDMID_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTB_HOTPLUG_INT_STATUS) + hotplug_en |= PORTB_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTC_HOTPLUG_INT_STATUS) + hotplug_en |= PORTC_HOTPLUG_INT_EN; + if (dev_priv->hotplug_supported_mask & PORTD_HOTPLUG_INT_STATUS) + hotplug_en |= PORTD_HOTPLUG_INT_EN; if (IS_G4X(dev)) { if (dev_priv->hotplug_supported_mask & SDVOC_HOTPLUG_INT_STATUS_G4X) hotplug_en |= SDVOC_HOTPLUG_INT_EN; diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 15f5e7f9cded..527b664d3434 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -308,6 +308,7 @@ #define DISPLAY_PLANE_A (0<<20) #define DISPLAY_PLANE_B (1<<20) #define GFX_OP_PIPE_CONTROL(len) ((0x3<<29)|(0x3<<27)|(0x2<<24)|(len-2)) +#define PIPE_CONTROL_GLOBAL_GTT_IVB (1<<24) /* gen7+ */ #define PIPE_CONTROL_CS_STALL (1<<20) #define PIPE_CONTROL_TLB_INVALIDATE (1<<18) #define PIPE_CONTROL_QW_WRITE (1<<14) @@ -1235,6 +1236,10 @@ #define MAD_DIMM_A_SIZE_SHIFT 0 #define MAD_DIMM_A_SIZE_MASK (0xff << MAD_DIMM_A_SIZE_SHIFT) +/** snb MCH registers for priority tuning */ +#define MCH_SSKPD (MCHBAR_MIRROR_BASE_SNB + 0x5d10) +#define MCH_SSKPD_WM0_MASK 0x3f +#define MCH_SSKPD_WM0_VAL 0xc /* Clocking configuration register */ #define CLKCFG 0x10c00 @@ -1625,12 +1630,9 @@ /* Hotplug control (945+ only) */ #define PORT_HOTPLUG_EN (dev_priv->info->display_mmio_offset + 0x61110) -#define HDMIB_HOTPLUG_INT_EN (1 << 29) -#define DPB_HOTPLUG_INT_EN (1 << 29) -#define HDMIC_HOTPLUG_INT_EN (1 << 28) -#define DPC_HOTPLUG_INT_EN (1 << 28) -#define HDMID_HOTPLUG_INT_EN (1 << 27) -#define DPD_HOTPLUG_INT_EN (1 << 27) +#define PORTB_HOTPLUG_INT_EN (1 << 29) +#define PORTC_HOTPLUG_INT_EN (1 << 28) +#define PORTD_HOTPLUG_INT_EN (1 << 27) #define SDVOB_HOTPLUG_INT_EN (1 << 26) #define SDVOC_HOTPLUG_INT_EN (1 << 25) #define TV_HOTPLUG_INT_EN (1 << 18) @@ -1653,19 +1655,12 @@ #define PORT_HOTPLUG_STAT (dev_priv->info->display_mmio_offset + 0x61114) /* HDMI/DP bits are gen4+ */ -#define DPB_HOTPLUG_LIVE_STATUS (1 << 29) -#define DPC_HOTPLUG_LIVE_STATUS (1 << 28) -#define DPD_HOTPLUG_LIVE_STATUS (1 << 27) -#define DPD_HOTPLUG_INT_STATUS (3 << 21) -#define DPC_HOTPLUG_INT_STATUS (3 << 19) -#define DPB_HOTPLUG_INT_STATUS (3 << 17) -/* HDMI bits are shared with the DP bits */ -#define HDMIB_HOTPLUG_LIVE_STATUS (1 << 29) -#define HDMIC_HOTPLUG_LIVE_STATUS (1 << 28) -#define HDMID_HOTPLUG_LIVE_STATUS (1 << 27) -#define HDMID_HOTPLUG_INT_STATUS (3 << 21) -#define HDMIC_HOTPLUG_INT_STATUS (3 << 19) -#define HDMIB_HOTPLUG_INT_STATUS (3 << 17) +#define PORTB_HOTPLUG_LIVE_STATUS (1 << 29) +#define PORTC_HOTPLUG_LIVE_STATUS (1 << 28) +#define PORTD_HOTPLUG_LIVE_STATUS (1 << 27) +#define PORTD_HOTPLUG_INT_STATUS (3 << 21) +#define PORTC_HOTPLUG_INT_STATUS (3 << 19) +#define PORTB_HOTPLUG_INT_STATUS (3 << 17) /* CRT/TV common between gen3+ */ #define CRT_HOTPLUG_INT_STATUS (1 << 11) #define TV_HOTPLUG_INT_STATUS (1 << 10) @@ -2954,6 +2949,7 @@ #define CURSOR_ENABLE 0x80000000 #define CURSOR_GAMMA_ENABLE 0x40000000 #define CURSOR_STRIDE_MASK 0x30000000 +#define CURSOR_PIPE_CSC_ENABLE (1<<24) #define CURSOR_FORMAT_SHIFT 24 #define CURSOR_FORMAT_MASK (0x07 << CURSOR_FORMAT_SHIFT) #define CURSOR_FORMAT_2C (0x00 << CURSOR_FORMAT_SHIFT) @@ -3015,6 +3011,7 @@ #define DISPPLANE_RGBA888 (0xf<<26) #define DISPPLANE_STEREO_ENABLE (1<<25) #define DISPPLANE_STEREO_DISABLE 0 +#define DISPPLANE_PIPE_CSC_ENABLE (1<<24) #define DISPPLANE_SEL_PIPE_SHIFT 24 #define DISPPLANE_SEL_PIPE_MASK (3<<DISPPLANE_SEL_PIPE_SHIFT) #define DISPPLANE_SEL_PIPE_A 0 @@ -3103,6 +3100,7 @@ #define DVS_FORMAT_RGBX101010 (1<<25) #define DVS_FORMAT_RGBX888 (2<<25) #define DVS_FORMAT_RGBX161616 (3<<25) +#define DVS_PIPE_CSC_ENABLE (1<<24) #define DVS_SOURCE_KEY (1<<22) #define DVS_RGB_ORDER_XBGR (1<<20) #define DVS_YUV_BYTE_ORDER_MASK (3<<16) @@ -3170,7 +3168,7 @@ #define SPRITE_FORMAT_RGBX161616 (3<<25) #define SPRITE_FORMAT_YUV444 (4<<25) #define SPRITE_FORMAT_XR_BGR101010 (5<<25) /* Extended range */ -#define SPRITE_CSC_ENABLE (1<<24) +#define SPRITE_PIPE_CSC_ENABLE (1<<24) #define SPRITE_SOURCE_KEY (1<<22) #define SPRITE_RGB_ORDER_RGBX (1<<20) /* only for 888 and 161616 */ #define SPRITE_YUV_TO_RGB_CSC_DISABLE (1<<19) @@ -3917,7 +3915,7 @@ #define FDI_10BPC (1<<16) #define FDI_6BPC (2<<16) #define FDI_12BPC (3<<16) -#define FDI_LINK_REVERSE_OVERWRITE (1<<15) +#define FDI_RX_LINK_REVERSAL_OVERRIDE (1<<15) #define FDI_DMI_LINK_REVERSE_MASK (1<<14) #define FDI_RX_PLL_ENABLE (1<<13) #define FDI_FS_ERR_CORRECT_ENABLE (1<<11) @@ -4272,8 +4270,8 @@ #define GEN6_PCODE_READ_MIN_FREQ_TABLE 0x9 #define GEN6_PCODE_WRITE_RC6VIDS 0x4 #define GEN6_PCODE_READ_RC6VIDS 0x5 -#define GEN6_ENCODE_RC6_VID(mv) (((mv) / 5) - 245) < 0 ?: 0 -#define GEN6_DECODE_RC6_VID(vids) (((vids) * 5) > 0 ? ((vids) * 5) + 245 : 0) +#define GEN6_ENCODE_RC6_VID(mv) (((mv) - 245) / 5) +#define GEN6_DECODE_RC6_VID(vids) (((vids) * 5) + 245) #define GEN6_PCODE_DATA 0x138128 #define GEN6_PCODE_FREQ_IA_RATIO_SHIFT 8 @@ -4516,6 +4514,7 @@ #define DDI_BUF_EMP_800MV_0DB_HSW (7<<24) /* Sel7 */ #define DDI_BUF_EMP_800MV_3_5DB_HSW (8<<24) /* Sel8 */ #define DDI_BUF_EMP_MASK (0xf<<24) +#define DDI_BUF_PORT_REVERSAL (1<<16) #define DDI_BUF_IS_IDLE (1<<7) #define DDI_A_4_LANES (1<<4) #define DDI_PORT_WIDTH_X1 (0<<1) @@ -4649,4 +4648,51 @@ #define WM_DBG_DISALLOW_MAXFIFO (1<<1) #define WM_DBG_DISALLOW_SPRITE (1<<2) +/* pipe CSC */ +#define _PIPE_A_CSC_COEFF_RY_GY 0x49010 +#define _PIPE_A_CSC_COEFF_BY 0x49014 +#define _PIPE_A_CSC_COEFF_RU_GU 0x49018 +#define _PIPE_A_CSC_COEFF_BU 0x4901c +#define _PIPE_A_CSC_COEFF_RV_GV 0x49020 +#define _PIPE_A_CSC_COEFF_BV 0x49024 +#define _PIPE_A_CSC_MODE 0x49028 +#define _PIPE_A_CSC_PREOFF_HI 0x49030 +#define _PIPE_A_CSC_PREOFF_ME 0x49034 +#define _PIPE_A_CSC_PREOFF_LO 0x49038 +#define _PIPE_A_CSC_POSTOFF_HI 0x49040 +#define _PIPE_A_CSC_POSTOFF_ME 0x49044 +#define _PIPE_A_CSC_POSTOFF_LO 0x49048 + +#define _PIPE_B_CSC_COEFF_RY_GY 0x49110 +#define _PIPE_B_CSC_COEFF_BY 0x49114 +#define _PIPE_B_CSC_COEFF_RU_GU 0x49118 +#define _PIPE_B_CSC_COEFF_BU 0x4911c +#define _PIPE_B_CSC_COEFF_RV_GV 0x49120 +#define _PIPE_B_CSC_COEFF_BV 0x49124 +#define _PIPE_B_CSC_MODE 0x49128 +#define _PIPE_B_CSC_PREOFF_HI 0x49130 +#define _PIPE_B_CSC_PREOFF_ME 0x49134 +#define _PIPE_B_CSC_PREOFF_LO 0x49138 +#define _PIPE_B_CSC_POSTOFF_HI 0x49140 +#define _PIPE_B_CSC_POSTOFF_ME 0x49144 +#define _PIPE_B_CSC_POSTOFF_LO 0x49148 + +#define CSC_BLACK_SCREEN_OFFSET (1 << 2) +#define CSC_POSITION_BEFORE_GAMMA (1 << 1) +#define CSC_MODE_YUV_TO_RGB (1 << 0) + +#define PIPE_CSC_COEFF_RY_GY(pipe) _PIPE(pipe, _PIPE_A_CSC_COEFF_RY_GY, _PIPE_B_CSC_COEFF_RY_GY) +#define PIPE_CSC_COEFF_BY(pipe) _PIPE(pipe, _PIPE_A_CSC_COEFF_BY, _PIPE_B_CSC_COEFF_BY) +#define PIPE_CSC_COEFF_RU_GU(pipe) _PIPE(pipe, _PIPE_A_CSC_COEFF_RU_GU, _PIPE_B_CSC_COEFF_RU_GU) +#define PIPE_CSC_COEFF_BU(pipe) _PIPE(pipe, _PIPE_A_CSC_COEFF_BU, _PIPE_B_CSC_COEFF_BU) +#define PIPE_CSC_COEFF_RV_GV(pipe) _PIPE(pipe, _PIPE_A_CSC_COEFF_RV_GV, _PIPE_B_CSC_COEFF_RV_GV) +#define PIPE_CSC_COEFF_BV(pipe) _PIPE(pipe, _PIPE_A_CSC_COEFF_BV, _PIPE_B_CSC_COEFF_BV) +#define PIPE_CSC_MODE(pipe) _PIPE(pipe, _PIPE_A_CSC_MODE, _PIPE_B_CSC_MODE) +#define PIPE_CSC_PREOFF_HI(pipe) _PIPE(pipe, _PIPE_A_CSC_PREOFF_HI, _PIPE_B_CSC_PREOFF_HI) +#define PIPE_CSC_PREOFF_ME(pipe) _PIPE(pipe, _PIPE_A_CSC_PREOFF_ME, _PIPE_B_CSC_PREOFF_ME) +#define PIPE_CSC_PREOFF_LO(pipe) _PIPE(pipe, _PIPE_A_CSC_PREOFF_LO, _PIPE_B_CSC_PREOFF_LO) +#define PIPE_CSC_POSTOFF_HI(pipe) _PIPE(pipe, _PIPE_A_CSC_POSTOFF_HI, _PIPE_B_CSC_POSTOFF_HI) +#define PIPE_CSC_POSTOFF_ME(pipe) _PIPE(pipe, _PIPE_A_CSC_POSTOFF_ME, _PIPE_B_CSC_POSTOFF_ME) +#define PIPE_CSC_POSTOFF_LO(pipe) _PIPE(pipe, _PIPE_A_CSC_POSTOFF_LO, _PIPE_B_CSC_POSTOFF_LO) + #endif /* _I915_REG_H_ */ diff --git a/drivers/gpu/drm/i915/intel_crt.c b/drivers/gpu/drm/i915/intel_crt.c index 729dd1a3fe72..969d08c72d10 100644 --- a/drivers/gpu/drm/i915/intel_crt.c +++ b/drivers/gpu/drm/i915/intel_crt.c @@ -800,10 +800,14 @@ void intel_crt_init(struct drm_device *dev) dev_priv->hotplug_supported_mask |= CRT_HOTPLUG_INT_STATUS; /* - * TODO: find a proper way to discover whether we need to set the - * polarity reversal bit or not, instead of relying on the BIOS. + * TODO: find a proper way to discover whether we need to set the the + * polarity and link reversal bits or not, instead of relying on the + * BIOS. */ - if (HAS_PCH_LPT(dev)) - dev_priv->fdi_rx_polarity_reversed = - !!(I915_READ(_FDI_RXA_CTL) & FDI_RX_POLARITY_REVERSED_LPT); + if (HAS_PCH_LPT(dev)) { + u32 fdi_config = FDI_RX_POLARITY_REVERSED_LPT | + FDI_RX_LINK_REVERSAL_OVERRIDE; + + dev_priv->fdi_rx_config = I915_READ(_FDI_RXA_CTL) & fdi_config; + } } diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index a259e09eb6a8..d64af5aa4a1c 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -180,10 +180,8 @@ void hsw_fdi_link_train(struct drm_crtc *crtc) FDI_RX_TP1_TO_TP2_48 | FDI_RX_FDI_DELAY_90); /* Enable the PCH Receiver FDI PLL */ - rx_ctl_val = FDI_RX_PLL_ENABLE | FDI_RX_ENHANCE_FRAME_ENABLE | - ((intel_crtc->fdi_lanes - 1) << 19); - if (dev_priv->fdi_rx_polarity_reversed) - rx_ctl_val |= FDI_RX_POLARITY_REVERSED_LPT; + rx_ctl_val = dev_priv->fdi_rx_config | FDI_RX_ENHANCE_FRAME_ENABLE | + FDI_RX_PLL_ENABLE | ((intel_crtc->fdi_lanes - 1) << 19); I915_WRITE(_FDI_RXA_CTL, rx_ctl_val); POSTING_READ(_FDI_RXA_CTL); udelay(220); @@ -205,7 +203,10 @@ void hsw_fdi_link_train(struct drm_crtc *crtc) DP_TP_CTL_LINK_TRAIN_PAT1 | DP_TP_CTL_ENABLE); - /* Configure and enable DDI_BUF_CTL for DDI E with next voltage */ + /* Configure and enable DDI_BUF_CTL for DDI E with next voltage. + * DDI E does not support port reversal, the functionality is + * achieved on the PCH side in FDI_RX_CTL, so no need to set the + * port reversal bit */ I915_WRITE(DDI_BUF_CTL(PORT_E), DDI_BUF_CTL_ENABLE | ((intel_crtc->fdi_lanes - 1) << 1) | @@ -680,8 +681,11 @@ static void intel_ddi_mode_set(struct drm_encoder *encoder, intel_crtc->eld_vld = false; if (type == INTEL_OUTPUT_DISPLAYPORT || type == INTEL_OUTPUT_EDP) { struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_digital_port *intel_dig_port = + enc_to_dig_port(encoder); - intel_dp->DP = DDI_BUF_CTL_ENABLE | DDI_BUF_EMP_400MV_0DB_HSW; + intel_dp->DP = intel_dig_port->port_reversal | + DDI_BUF_CTL_ENABLE | DDI_BUF_EMP_400MV_0DB_HSW; switch (intel_dp->lane_count) { case 1: intel_dp->DP |= DDI_PORT_WIDTH_X1; @@ -1304,11 +1308,15 @@ static void intel_enable_ddi(struct intel_encoder *intel_encoder) uint32_t tmp; if (type == INTEL_OUTPUT_HDMI) { + struct intel_digital_port *intel_dig_port = + enc_to_dig_port(encoder); + /* In HDMI/DVI mode, the port width, and swing/emphasis values * are ignored so nothing special needs to be done besides * enabling the port. */ - I915_WRITE(DDI_BUF_CTL(port), DDI_BUF_CTL_ENABLE); + I915_WRITE(DDI_BUF_CTL(port), + intel_dig_port->port_reversal | DDI_BUF_CTL_ENABLE); } else if (type == INTEL_OUTPUT_EDP) { struct intel_dp *intel_dp = enc_to_intel_dp(encoder); @@ -1485,6 +1493,7 @@ static const struct drm_encoder_helper_funcs intel_ddi_helper_funcs = { void intel_ddi_init(struct drm_device *dev, enum port port) { + struct drm_i915_private *dev_priv = dev->dev_private; struct intel_digital_port *intel_dig_port; struct intel_encoder *intel_encoder; struct drm_encoder *encoder; @@ -1525,6 +1534,8 @@ void intel_ddi_init(struct drm_device *dev, enum port port) intel_encoder->get_hw_state = intel_ddi_get_hw_state; intel_dig_port->port = port; + intel_dig_port->port_reversal = I915_READ(DDI_BUF_CTL(port)) & + DDI_BUF_PORT_REVERSAL; if (hdmi_connector) intel_dig_port->hdmi.sdvox_reg = DDI_BUF_CTL(port); else diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index ca8d5929063e..6337196b7931 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -154,8 +154,8 @@ static const intel_limit_t intel_limits_i9xx_sdvo = { .vco = { .min = 1400000, .max = 2800000 }, .n = { .min = 1, .max = 6 }, .m = { .min = 70, .max = 120 }, - .m1 = { .min = 10, .max = 22 }, - .m2 = { .min = 5, .max = 9 }, + .m1 = { .min = 8, .max = 18 }, + .m2 = { .min = 3, .max = 7 }, .p = { .min = 5, .max = 80 }, .p1 = { .min = 1, .max = 8 }, .p2 = { .dot_limit = 200000, @@ -168,8 +168,8 @@ static const intel_limit_t intel_limits_i9xx_lvds = { .vco = { .min = 1400000, .max = 2800000 }, .n = { .min = 1, .max = 6 }, .m = { .min = 70, .max = 120 }, - .m1 = { .min = 10, .max = 22 }, - .m2 = { .min = 5, .max = 9 }, + .m1 = { .min = 8, .max = 18 }, + .m2 = { .min = 3, .max = 7 }, .p = { .min = 7, .max = 98 }, .p1 = { .min = 1, .max = 8 }, .p2 = { .dot_limit = 112000, @@ -2226,12 +2226,6 @@ intel_finish_fb(struct drm_framebuffer *old_fb) bool was_interruptible = dev_priv->mm.interruptible; int ret; - WARN_ON(waitqueue_active(&dev_priv->pending_flip_queue)); - - wait_event(dev_priv->pending_flip_queue, - i915_reset_in_progress(&dev_priv->gpu_error) || - atomic_read(&obj->pending_flip) == 0); - /* Big Hammer, we also need to ensure that any pending * MI_WAIT_FOR_EVENT inside a user batch buffer on the * current scanout is retired before unpinning the old @@ -2874,10 +2868,12 @@ static bool intel_crtc_has_pending_flip(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); unsigned long flags; bool pending; - if (i915_reset_in_progress(&dev_priv->gpu_error)) + if (i915_reset_in_progress(&dev_priv->gpu_error) || + intel_crtc->reset_counter != atomic_read(&dev_priv->gpu_error.reset_counter)) return false; spin_lock_irqsave(&dev->event_lock, flags); @@ -3615,6 +3611,11 @@ static void i9xx_crtc_enable(struct drm_crtc *crtc) intel_update_watermarks(dev); intel_enable_pll(dev_priv, pipe); + + for_each_encoder_on_crtc(dev, crtc, encoder) + if (encoder->pre_enable) + encoder->pre_enable(encoder); + intel_enable_pipe(dev_priv, pipe, false); intel_enable_plane(dev_priv, plane, pipe); @@ -3637,6 +3638,7 @@ static void i9xx_crtc_disable(struct drm_crtc *crtc) struct intel_encoder *encoder; int pipe = intel_crtc->pipe; int plane = intel_crtc->plane; + u32 pctl; if (!intel_crtc->active) @@ -3656,6 +3658,13 @@ static void i9xx_crtc_disable(struct drm_crtc *crtc) intel_disable_plane(dev_priv, plane, pipe); intel_disable_pipe(dev_priv, pipe); + + /* Disable pannel fitter if it is on this pipe. */ + pctl = I915_READ(PFIT_CONTROL); + if ((pctl & PFIT_ENABLE) && + ((pctl & PFIT_PIPE_MASK) >> PFIT_PIPE_SHIFT) == pipe) + I915_WRITE(PFIT_CONTROL, 0); + intel_disable_pll(dev_priv, pipe); intel_crtc->active = false; @@ -5109,6 +5118,71 @@ static void ironlake_set_pipeconf(struct drm_crtc *crtc, POSTING_READ(PIPECONF(pipe)); } +/* + * Set up the pipe CSC unit. + * + * Currently only full range RGB to limited range RGB conversion + * is supported, but eventually this should handle various + * RGB<->YCbCr scenarios as well. + */ +static void intel_set_pipe_csc(struct drm_crtc *crtc, + const struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = crtc->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + int pipe = intel_crtc->pipe; + uint16_t coeff = 0x7800; /* 1.0 */ + + /* + * TODO: Check what kind of values actually come out of the pipe + * with these coeff/postoff values and adjust to get the best + * accuracy. Perhaps we even need to take the bpc value into + * consideration. + */ + + if (adjusted_mode->private_flags & INTEL_MODE_LIMITED_COLOR_RANGE) + coeff = ((235 - 16) * (1 << 12) / 255) & 0xff8; /* 0.xxx... */ + + /* + * GY/GU and RY/RU should be the other way around according + * to BSpec, but reality doesn't agree. Just set them up in + * a way that results in the correct picture. + */ + I915_WRITE(PIPE_CSC_COEFF_RY_GY(pipe), coeff << 16); + I915_WRITE(PIPE_CSC_COEFF_BY(pipe), 0); + + I915_WRITE(PIPE_CSC_COEFF_RU_GU(pipe), coeff); + I915_WRITE(PIPE_CSC_COEFF_BU(pipe), 0); + + I915_WRITE(PIPE_CSC_COEFF_RV_GV(pipe), 0); + I915_WRITE(PIPE_CSC_COEFF_BV(pipe), coeff << 16); + + I915_WRITE(PIPE_CSC_PREOFF_HI(pipe), 0); + I915_WRITE(PIPE_CSC_PREOFF_ME(pipe), 0); + I915_WRITE(PIPE_CSC_PREOFF_LO(pipe), 0); + + if (INTEL_INFO(dev)->gen > 6) { + uint16_t postoff = 0; + + if (adjusted_mode->private_flags & INTEL_MODE_LIMITED_COLOR_RANGE) + postoff = (16 * (1 << 13) / 255) & 0x1fff; + + I915_WRITE(PIPE_CSC_POSTOFF_HI(pipe), postoff); + I915_WRITE(PIPE_CSC_POSTOFF_ME(pipe), postoff); + I915_WRITE(PIPE_CSC_POSTOFF_LO(pipe), postoff); + + I915_WRITE(PIPE_CSC_MODE(pipe), 0); + } else { + uint32_t mode = CSC_MODE_YUV_TO_RGB; + + if (adjusted_mode->private_flags & INTEL_MODE_LIMITED_COLOR_RANGE) + mode |= CSC_BLACK_SCREEN_OFFSET; + + I915_WRITE(PIPE_CSC_MODE(pipe), mode); + } +} + static void haswell_set_pipeconf(struct drm_crtc *crtc, struct drm_display_mode *adjusted_mode, bool dither) @@ -5697,8 +5771,10 @@ static int haswell_crtc_mode_set(struct drm_crtc *crtc, haswell_set_pipeconf(crtc, adjusted_mode, dither); + intel_set_pipe_csc(crtc, adjusted_mode); + /* Set up the display plane register */ - I915_WRITE(DSPCNTR(plane), DISPPLANE_GAMMA_ENABLE); + I915_WRITE(DSPCNTR(plane), DISPPLANE_GAMMA_ENABLE | DISPPLANE_PIPE_CSC_ENABLE); POSTING_READ(DSPCNTR(plane)); ret = intel_pipe_set_base(crtc, x, y, fb); @@ -6103,6 +6179,8 @@ static void ivb_update_cursor(struct drm_crtc *crtc, u32 base) cntl &= ~(CURSOR_MODE | MCURSOR_GAMMA_ENABLE); cntl |= CURSOR_MODE_DISABLE; } + if (IS_HASWELL(dev)) + cntl |= CURSOR_PIPE_CSC_ENABLE; I915_WRITE(CURCNTR_IVB(pipe), cntl); intel_crtc->cursor_visible = visible; @@ -7235,6 +7313,7 @@ static int intel_crtc_page_flip(struct drm_crtc *crtc, work->enable_stall_check = true; atomic_inc(&intel_crtc->unpin_work_count); + intel_crtc->reset_counter = atomic_read(&dev_priv->gpu_error.reset_counter); ret = dev_priv->display.queue_flip(dev, crtc, fb, obj); if (ret) @@ -7876,7 +7955,7 @@ intel_modeset_stage_output_state(struct drm_device *dev, struct intel_encoder *encoder; int count, ro; - /* The upper layers ensure that we either disabl a crtc or have a list + /* The upper layers ensure that we either disable a crtc or have a list * of connectors. For paranoia, double-check this. */ WARN_ON(!set->fb && (set->num_connectors != 0)); WARN_ON(set->fb && (set->num_connectors == 0)); @@ -8655,6 +8734,9 @@ static struct intel_quirk intel_quirks[] = { /* Acer/Packard Bell NCL20 */ { 0x2a42, 0x1025, 0x034b, quirk_invert_brightness }, + + /* Acer Aspire 4736Z */ + { 0x2a42, 0x1025, 0x0260, quirk_invert_brightness }, }; static void intel_init_quirks(struct drm_device *dev) diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index 13c1536a8bb2..31c0205685ab 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -2302,13 +2302,13 @@ g4x_dp_detect(struct intel_dp *intel_dp) switch (intel_dig_port->port) { case PORT_B: - bit = DPB_HOTPLUG_LIVE_STATUS; + bit = PORTB_HOTPLUG_LIVE_STATUS; break; case PORT_C: - bit = DPC_HOTPLUG_LIVE_STATUS; + bit = PORTC_HOTPLUG_LIVE_STATUS; break; case PORT_D: - bit = DPD_HOTPLUG_LIVE_STATUS; + bit = PORTD_HOTPLUG_LIVE_STATUS; break; default: return connector_status_unknown; @@ -2837,15 +2837,15 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port, name = "DPDDC-A"; break; case PORT_B: - dev_priv->hotplug_supported_mask |= DPB_HOTPLUG_INT_STATUS; + dev_priv->hotplug_supported_mask |= PORTB_HOTPLUG_INT_STATUS; name = "DPDDC-B"; break; case PORT_C: - dev_priv->hotplug_supported_mask |= DPC_HOTPLUG_INT_STATUS; + dev_priv->hotplug_supported_mask |= PORTC_HOTPLUG_INT_STATUS; name = "DPDDC-C"; break; case PORT_D: - dev_priv->hotplug_supported_mask |= DPD_HOTPLUG_INT_STATUS; + dev_priv->hotplug_supported_mask |= PORTD_HOTPLUG_INT_STATUS; name = "DPDDC-D"; break; default: diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index a2ec01c49f40..d282052aadd4 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -235,6 +235,9 @@ struct intel_crtc { /* We can share PLLs across outputs if the timings match */ struct intel_pch_pll *pch_pll; uint32_t ddi_pll_sel; + + /* reset counter value when the last flip was submitted */ + unsigned int reset_counter; }; struct intel_plane { @@ -390,6 +393,7 @@ struct intel_dp { struct intel_digital_port { struct intel_encoder base; enum port port; + u32 port_reversal; struct intel_dp dp; struct intel_hdmi hdmi; }; diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 3883bed80faa..3ea0c8b6a00f 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -802,10 +802,10 @@ static bool g4x_hdmi_connected(struct intel_hdmi *intel_hdmi) switch (intel_dig_port->port) { case PORT_B: - bit = HDMIB_HOTPLUG_LIVE_STATUS; + bit = PORTB_HOTPLUG_LIVE_STATUS; break; case PORT_C: - bit = HDMIC_HOTPLUG_LIVE_STATUS; + bit = PORTC_HOTPLUG_LIVE_STATUS; break; default: bit = 0; @@ -1021,15 +1021,15 @@ void intel_hdmi_init_connector(struct intel_digital_port *intel_dig_port, switch (port) { case PORT_B: intel_hdmi->ddc_bus = GMBUS_PORT_DPB; - dev_priv->hotplug_supported_mask |= HDMIB_HOTPLUG_INT_STATUS; + dev_priv->hotplug_supported_mask |= PORTB_HOTPLUG_INT_STATUS; break; case PORT_C: intel_hdmi->ddc_bus = GMBUS_PORT_DPC; - dev_priv->hotplug_supported_mask |= HDMIC_HOTPLUG_INT_STATUS; + dev_priv->hotplug_supported_mask |= PORTC_HOTPLUG_INT_STATUS; break; case PORT_D: intel_hdmi->ddc_bus = GMBUS_PORT_DPD; - dev_priv->hotplug_supported_mask |= HDMID_HOTPLUG_INT_STATUS; + dev_priv->hotplug_supported_mask |= PORTD_HOTPLUG_INT_STATUS; break; case PORT_A: /* Internal port only for eDP. */ diff --git a/drivers/gpu/drm/i915/intel_lvds.c b/drivers/gpu/drm/i915/intel_lvds.c index feb43fd7debf..3d1d97488cc9 100644 --- a/drivers/gpu/drm/i915/intel_lvds.c +++ b/drivers/gpu/drm/i915/intel_lvds.c @@ -51,7 +51,6 @@ struct intel_lvds_encoder { u32 pfit_control; u32 pfit_pgm_ratios; - bool pfit_dirty; bool is_dual_link; u32 reg; @@ -151,6 +150,29 @@ static void intel_pre_pll_enable_lvds(struct intel_encoder *encoder) I915_WRITE(lvds_encoder->reg, temp); } +static void intel_pre_enable_lvds(struct intel_encoder *encoder) +{ + struct drm_device *dev = encoder->base.dev; + struct intel_lvds_encoder *enc = to_lvds_encoder(&encoder->base); + struct drm_i915_private *dev_priv = dev->dev_private; + + if (HAS_PCH_SPLIT(dev) || !enc->pfit_control) + return; + + /* + * Enable automatic panel scaling so that non-native modes + * fill the screen. The panel fitter should only be + * adjusted whilst the pipe is disabled, according to + * register description and PRM. + */ + DRM_DEBUG_KMS("applying panel-fitter: %x, %x\n", + enc->pfit_control, + enc->pfit_pgm_ratios); + + I915_WRITE(PFIT_PGM_RATIOS, enc->pfit_pgm_ratios); + I915_WRITE(PFIT_CONTROL, enc->pfit_control); +} + /** * Sets the power state for the panel. */ @@ -172,22 +194,6 @@ static void intel_enable_lvds(struct intel_encoder *encoder) I915_WRITE(lvds_encoder->reg, I915_READ(lvds_encoder->reg) | LVDS_PORT_EN); - if (lvds_encoder->pfit_dirty) { - /* - * Enable automatic panel scaling so that non-native modes - * fill the screen. The panel fitter should only be - * adjusted whilst the pipe is disabled, according to - * register description and PRM. - */ - DRM_DEBUG_KMS("applying panel-fitter: %x, %x\n", - lvds_encoder->pfit_control, - lvds_encoder->pfit_pgm_ratios); - - I915_WRITE(PFIT_PGM_RATIOS, lvds_encoder->pfit_pgm_ratios); - I915_WRITE(PFIT_CONTROL, lvds_encoder->pfit_control); - lvds_encoder->pfit_dirty = false; - } - I915_WRITE(ctl_reg, I915_READ(ctl_reg) | POWER_TARGET_ON); POSTING_READ(lvds_encoder->reg); if (wait_for((I915_READ(stat_reg) & PP_ON) != 0, 1000)) @@ -217,11 +223,6 @@ static void intel_disable_lvds(struct intel_encoder *encoder) if (wait_for((I915_READ(stat_reg) & PP_ON) == 0, 1000)) DRM_ERROR("timed out waiting for panel to power off\n"); - if (lvds_encoder->pfit_control) { - I915_WRITE(PFIT_CONTROL, 0); - lvds_encoder->pfit_dirty = true; - } - I915_WRITE(lvds_encoder->reg, I915_READ(lvds_encoder->reg) & ~LVDS_PORT_EN); POSTING_READ(lvds_encoder->reg); } @@ -461,7 +462,6 @@ out: pfit_pgm_ratios != lvds_encoder->pfit_pgm_ratios) { lvds_encoder->pfit_control = pfit_control; lvds_encoder->pfit_pgm_ratios = pfit_pgm_ratios; - lvds_encoder->pfit_dirty = true; } dev_priv->lvds_border_bits = border; @@ -547,13 +547,14 @@ static const struct dmi_system_id intel_no_modeset_on_lid[] = { }; /* - * Lid events. Note the use of 'modeset_on_lid': - * - we set it on lid close, and reset it on open + * Lid events. Note the use of 'modeset': + * - we set it to MODESET_ON_LID_OPEN on lid close, + * and set it to MODESET_DONE on open * - we use it as a "only once" bit (ie we ignore - * duplicate events where it was already properly - * set/reset) - * - the suspend/resume paths will also set it to - * zero, since they restore the mode ("lid open"). + * duplicate events where it was already properly set) + * - the suspend/resume paths will set it to + * MODESET_SUSPENDED and ignore the lid open event, + * because they restore the mode ("lid open"). */ static int intel_lid_notify(struct notifier_block *nb, unsigned long val, void *unused) @@ -567,6 +568,9 @@ static int intel_lid_notify(struct notifier_block *nb, unsigned long val, if (dev->switch_power_state != DRM_SWITCH_POWER_ON) return NOTIFY_OK; + mutex_lock(&dev_priv->modeset_restore_lock); + if (dev_priv->modeset_restore == MODESET_SUSPENDED) + goto exit; /* * check and update the status of LVDS connector after receiving * the LID nofication event. @@ -575,21 +579,24 @@ static int intel_lid_notify(struct notifier_block *nb, unsigned long val, /* Don't force modeset on machines where it causes a GPU lockup */ if (dmi_check_system(intel_no_modeset_on_lid)) - return NOTIFY_OK; + goto exit; if (!acpi_lid_open()) { - dev_priv->modeset_on_lid = 1; - return NOTIFY_OK; + /* do modeset on next lid open event */ + dev_priv->modeset_restore = MODESET_ON_LID_OPEN; + goto exit; } - if (!dev_priv->modeset_on_lid) - return NOTIFY_OK; - - dev_priv->modeset_on_lid = 0; + if (dev_priv->modeset_restore == MODESET_DONE) + goto exit; drm_modeset_lock_all(dev); intel_modeset_setup_hw_state(dev, true); drm_modeset_unlock_all(dev); + dev_priv->modeset_restore = MODESET_DONE; + +exit: + mutex_unlock(&dev_priv->modeset_restore_lock); return NOTIFY_OK; } @@ -1093,6 +1100,7 @@ bool intel_lvds_init(struct drm_device *dev) DRM_MODE_ENCODER_LVDS); intel_encoder->enable = intel_enable_lvds; + intel_encoder->pre_enable = intel_pre_enable_lvds; intel_encoder->pre_pll_enable = intel_pre_pll_enable_lvds; intel_encoder->disable = intel_disable_lvds; intel_encoder->get_hw_state = intel_lvds_get_hw_state; diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c index bee8cb6108a7..a3730e0289e5 100644 --- a/drivers/gpu/drm/i915/intel_panel.c +++ b/drivers/gpu/drm/i915/intel_panel.c @@ -321,6 +321,9 @@ void intel_panel_enable_backlight(struct drm_device *dev, if (dev_priv->backlight_level == 0) dev_priv->backlight_level = intel_panel_get_max_backlight(dev); + dev_priv->backlight_enabled = true; + intel_panel_actually_set_backlight(dev, dev_priv->backlight_level); + if (INTEL_INFO(dev)->gen >= 4) { uint32_t reg, tmp; @@ -356,12 +359,12 @@ void intel_panel_enable_backlight(struct drm_device *dev, } set_level: - /* Call below after setting BLC_PWM_CPU_CTL2 and BLC_PWM_PCH_CTL1. - * BLC_PWM_CPU_CTL may be cleared to zero automatically when these - * registers are set. + /* Check the current backlight level and try to set again if it's zero. + * On some machines, BLC_PWM_CPU_CTL is cleared to zero automatically + * when BLC_PWM_CPU_CTL2 and BLC_PWM_PCH_CTL1 are written. */ - dev_priv->backlight_enabled = true; - intel_panel_actually_set_backlight(dev, dev_priv->backlight_level); + if (!intel_panel_get_backlight(dev)) + intel_panel_actually_set_backlight(dev, dev_priv->backlight_level); } static void intel_panel_init_backlight(struct drm_device *dev) diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c index 7c9a6d11700e..61fee7fcdc2c 100644 --- a/drivers/gpu/drm/i915/intel_pm.c +++ b/drivers/gpu/drm/i915/intel_pm.c @@ -2289,7 +2289,6 @@ err_unpin: i915_gem_object_unpin(ctx); err_unref: drm_gem_object_unreference(&ctx->base); - mutex_unlock(&dev->struct_mutex); return NULL; } @@ -3584,6 +3583,19 @@ static void cpt_init_clock_gating(struct drm_device *dev) } } +static void gen6_check_mch_setup(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + uint32_t tmp; + + tmp = I915_READ(MCH_SSKPD); + if ((tmp & MCH_SSKPD_WM0_MASK) != MCH_SSKPD_WM0_VAL) { + DRM_INFO("Wrong MCH_SSKPD value: 0x%08x\n", tmp); + DRM_INFO("This can cause pipe underruns and display issues.\n"); + DRM_INFO("Please upgrade your BIOS to fix this.\n"); + } +} + static void gen6_init_clock_gating(struct drm_device *dev) { struct drm_i915_private *dev_priv = dev->dev_private; @@ -3676,6 +3688,8 @@ static void gen6_init_clock_gating(struct drm_device *dev) I915_WRITE(GEN6_GT_MODE, _MASKED_BIT_ENABLE(GEN6_GT_MODE_HI)); cpt_init_clock_gating(dev); + + gen6_check_mch_setup(dev); } static void gen7_setup_fixed_func_scheduler(struct drm_i915_private *dev_priv) @@ -3861,6 +3875,8 @@ static void ivybridge_init_clock_gating(struct drm_device *dev) I915_WRITE(GEN6_MBCUNIT_SNPCR, snpcr); cpt_init_clock_gating(dev); + + gen6_check_mch_setup(dev); } static void valleyview_init_clock_gating(struct drm_device *dev) diff --git a/drivers/gpu/drm/i915/intel_ringbuffer.c b/drivers/gpu/drm/i915/intel_ringbuffer.c index 00525ff59045..1d5d613eb6be 100644 --- a/drivers/gpu/drm/i915/intel_ringbuffer.c +++ b/drivers/gpu/drm/i915/intel_ringbuffer.c @@ -318,6 +318,7 @@ gen7_render_ring_flush(struct intel_ring_buffer *ring, * TLB invalidate requires a post-sync write. */ flags |= PIPE_CONTROL_QW_WRITE; + flags |= PIPE_CONTROL_GLOBAL_GTT_IVB; /* Workaround: we must issue a pipe_control with CS-stall bit * set before a pipe_control command that has the state cache @@ -331,7 +332,7 @@ gen7_render_ring_flush(struct intel_ring_buffer *ring, intel_ring_emit(ring, GFX_OP_PIPE_CONTROL(4)); intel_ring_emit(ring, flags); - intel_ring_emit(ring, scratch_addr | PIPE_CONTROL_GLOBAL_GTT); + intel_ring_emit(ring, scratch_addr); intel_ring_emit(ring, 0); intel_ring_advance(ring); @@ -467,6 +468,9 @@ init_pipe_control(struct intel_ring_buffer *ring) if (pc->cpu_page == NULL) goto err_unpin; + DRM_DEBUG_DRIVER("%s pipe control offset: 0x%08x\n", + ring->name, pc->gtt_offset); + pc->obj = obj; ring->private = pc; return 0; diff --git a/drivers/gpu/drm/i915/intel_sprite.c b/drivers/gpu/drm/i915/intel_sprite.c index f8293061d6bd..d086e48a831a 100644 --- a/drivers/gpu/drm/i915/intel_sprite.c +++ b/drivers/gpu/drm/i915/intel_sprite.c @@ -50,6 +50,7 @@ ivb_update_plane(struct drm_plane *plane, struct drm_framebuffer *fb, u32 sprctl, sprscale = 0; unsigned long sprsurf_offset, linear_offset; int pixel_size = drm_format_plane_cpp(fb->pixel_format, 0); + bool scaling_was_enabled = dev_priv->sprite_scaling_enabled; sprctl = I915_READ(SPRCTL(pipe)); @@ -89,6 +90,9 @@ ivb_update_plane(struct drm_plane *plane, struct drm_framebuffer *fb, sprctl |= SPRITE_TRICKLE_FEED_DISABLE; sprctl |= SPRITE_ENABLE; + if (IS_HASWELL(dev)) + sprctl |= SPRITE_PIPE_CSC_ENABLE; + /* Sizes are 0 based */ src_w--; src_h--; @@ -103,19 +107,15 @@ ivb_update_plane(struct drm_plane *plane, struct drm_framebuffer *fb, * when scaling is disabled. */ if (crtc_w != src_w || crtc_h != src_h) { - if (!dev_priv->sprite_scaling_enabled) { - dev_priv->sprite_scaling_enabled = true; + dev_priv->sprite_scaling_enabled |= 1 << pipe; + + if (!scaling_was_enabled) { intel_update_watermarks(dev); intel_wait_for_vblank(dev, pipe); } sprscale = SPRITE_SCALE_ENABLE | (src_w << 16) | src_h; - } else { - if (dev_priv->sprite_scaling_enabled) { - dev_priv->sprite_scaling_enabled = false; - /* potentially re-enable LP watermarks */ - intel_update_watermarks(dev); - } - } + } else + dev_priv->sprite_scaling_enabled &= ~(1 << pipe); I915_WRITE(SPRSTRIDE(pipe), fb->pitches[0]); I915_WRITE(SPRPOS(pipe), (crtc_y << 16) | crtc_x); @@ -141,6 +141,10 @@ ivb_update_plane(struct drm_plane *plane, struct drm_framebuffer *fb, I915_WRITE(SPRCTL(pipe), sprctl); I915_MODIFY_DISPBASE(SPRSURF(pipe), obj->gtt_offset + sprsurf_offset); POSTING_READ(SPRSURF(pipe)); + + /* potentially re-enable LP watermarks */ + if (scaling_was_enabled && !dev_priv->sprite_scaling_enabled) + intel_update_watermarks(dev); } static void @@ -150,6 +154,7 @@ ivb_disable_plane(struct drm_plane *plane) struct drm_i915_private *dev_priv = dev->dev_private; struct intel_plane *intel_plane = to_intel_plane(plane); int pipe = intel_plane->pipe; + bool scaling_was_enabled = dev_priv->sprite_scaling_enabled; I915_WRITE(SPRCTL(pipe), I915_READ(SPRCTL(pipe)) & ~SPRITE_ENABLE); /* Can't leave the scaler enabled... */ @@ -159,8 +164,11 @@ ivb_disable_plane(struct drm_plane *plane) I915_MODIFY_DISPBASE(SPRSURF(pipe), 0); POSTING_READ(SPRSURF(pipe)); - dev_priv->sprite_scaling_enabled = false; - intel_update_watermarks(dev); + dev_priv->sprite_scaling_enabled &= ~(1 << pipe); + + /* potentially re-enable LP watermarks */ + if (scaling_was_enabled && !dev_priv->sprite_scaling_enabled) + intel_update_watermarks(dev); } static int diff --git a/drivers/gpu/drm/nouveau/Kconfig b/drivers/gpu/drm/nouveau/Kconfig index 47ccc1ad5405..a7ff6d5a34b9 100644 --- a/drivers/gpu/drm/nouveau/Kconfig +++ b/drivers/gpu/drm/nouveau/Kconfig @@ -11,8 +11,9 @@ config DRM_NOUVEAU select FRAMEBUFFER_CONSOLE if !EXPERT select FB_BACKLIGHT if DRM_NOUVEAU_BACKLIGHT select ACPI_VIDEO if ACPI && X86 && BACKLIGHT_CLASS_DEVICE && VIDEO_OUTPUT_CONTROL && INPUT - select ACPI_WMI if ACPI - select MXM_WMI if ACPI + select X86_PLATFORM_DEVICES if ACPI && X86 + select ACPI_WMI if ACPI && X86 + select MXM_WMI if ACPI && X86 select POWER_SUPPLY help Choose this option for open-source nVidia support. diff --git a/drivers/gpu/drm/nouveau/Makefile b/drivers/gpu/drm/nouveau/Makefile index ab25752a0b1e..90f9140eeefd 100644 --- a/drivers/gpu/drm/nouveau/Makefile +++ b/drivers/gpu/drm/nouveau/Makefile @@ -11,6 +11,7 @@ nouveau-y := core/core/client.o nouveau-y += core/core/engctx.o nouveau-y += core/core/engine.o nouveau-y += core/core/enum.o +nouveau-y += core/core/event.o nouveau-y += core/core/falcon.o nouveau-y += core/core/gpuobj.o nouveau-y += core/core/handle.o @@ -40,6 +41,11 @@ nouveau-y += core/subdev/bios/mxm.o nouveau-y += core/subdev/bios/perf.o nouveau-y += core/subdev/bios/pll.o nouveau-y += core/subdev/bios/therm.o +nouveau-y += core/subdev/bios/xpio.o +nouveau-y += core/subdev/bus/nv04.o +nouveau-y += core/subdev/bus/nv31.o +nouveau-y += core/subdev/bus/nv50.o +nouveau-y += core/subdev/bus/nvc0.o nouveau-y += core/subdev/clock/nv04.o nouveau-y += core/subdev/clock/nv40.o nouveau-y += core/subdev/clock/nv50.o @@ -85,9 +91,16 @@ nouveau-y += core/subdev/gpio/base.o nouveau-y += core/subdev/gpio/nv10.o nouveau-y += core/subdev/gpio/nv50.o nouveau-y += core/subdev/gpio/nvd0.o +nouveau-y += core/subdev/gpio/nve0.o nouveau-y += core/subdev/i2c/base.o +nouveau-y += core/subdev/i2c/anx9805.o nouveau-y += core/subdev/i2c/aux.o nouveau-y += core/subdev/i2c/bit.o +nouveau-y += core/subdev/i2c/nv04.o +nouveau-y += core/subdev/i2c/nv4e.o +nouveau-y += core/subdev/i2c/nv50.o +nouveau-y += core/subdev/i2c/nv94.o +nouveau-y += core/subdev/i2c/nvd0.o nouveau-y += core/subdev/ibus/nvc0.o nouveau-y += core/subdev/ibus/nve0.o nouveau-y += core/subdev/instmem/base.o @@ -106,10 +119,15 @@ nouveau-y += core/subdev/mxm/mxms.o nouveau-y += core/subdev/mxm/nv50.o nouveau-y += core/subdev/therm/base.o nouveau-y += core/subdev/therm/fan.o +nouveau-y += core/subdev/therm/fannil.o +nouveau-y += core/subdev/therm/fanpwm.o +nouveau-y += core/subdev/therm/fantog.o nouveau-y += core/subdev/therm/ic.o +nouveau-y += core/subdev/therm/temp.o nouveau-y += core/subdev/therm/nv40.o nouveau-y += core/subdev/therm/nv50.o -nouveau-y += core/subdev/therm/temp.o +nouveau-y += core/subdev/therm/nva3.o +nouveau-y += core/subdev/therm/nvd0.o nouveau-y += core/subdev/timer/base.o nouveau-y += core/subdev/timer/nv04.o nouveau-y += core/subdev/vm/base.o @@ -132,6 +150,7 @@ nouveau-y += core/engine/copy/nvc0.o nouveau-y += core/engine/copy/nve0.o nouveau-y += core/engine/crypt/nv84.o nouveau-y += core/engine/crypt/nv98.o +nouveau-y += core/engine/disp/base.o nouveau-y += core/engine/disp/nv04.o nouveau-y += core/engine/disp/nv50.o nouveau-y += core/engine/disp/nv84.o @@ -141,11 +160,13 @@ nouveau-y += core/engine/disp/nva3.o nouveau-y += core/engine/disp/nvd0.o nouveau-y += core/engine/disp/nve0.o nouveau-y += core/engine/disp/dacnv50.o +nouveau-y += core/engine/disp/dport.o nouveau-y += core/engine/disp/hdanva3.o nouveau-y += core/engine/disp/hdanvd0.o nouveau-y += core/engine/disp/hdminv84.o nouveau-y += core/engine/disp/hdminva3.o nouveau-y += core/engine/disp/hdminvd0.o +nouveau-y += core/engine/disp/piornv50.o nouveau-y += core/engine/disp/sornv50.o nouveau-y += core/engine/disp/sornv94.o nouveau-y += core/engine/disp/sornvd0.o @@ -194,7 +215,8 @@ nouveau-y += nouveau_drm.o nouveau_chan.o nouveau_dma.o nouveau_fence.o nouveau-y += nouveau_irq.o nouveau_vga.o nouveau_agp.o nouveau-y += nouveau_ttm.o nouveau_sgdma.o nouveau_bo.o nouveau_gem.o nouveau-y += nouveau_prime.o nouveau_abi16.o -nouveau-y += nv04_fence.o nv10_fence.o nv50_fence.o nv84_fence.o nvc0_fence.o +nouveau-y += nv04_fence.o nv10_fence.o nv17_fence.o +nouveau-y += nv50_fence.o nv84_fence.o nvc0_fence.o # drm/kms nouveau-y += nouveau_bios.o nouveau_fbcon.o nouveau_display.o @@ -216,7 +238,10 @@ nouveau-y += nouveau_mem.o # other random bits nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o +ifdef CONFIG_X86 nouveau-$(CONFIG_ACPI) += nouveau_acpi.o +endif nouveau-$(CONFIG_DRM_NOUVEAU_BACKLIGHT) += nouveau_backlight.o +nouveau-$(CONFIG_DEBUG_FS) += nouveau_debugfs.o obj-$(CONFIG_DRM_NOUVEAU)+= nouveau.o diff --git a/drivers/gpu/drm/nouveau/core/core/client.c b/drivers/gpu/drm/nouveau/core/core/client.c index 8bbb58f94a19..295c22165eac 100644 --- a/drivers/gpu/drm/nouveau/core/core/client.c +++ b/drivers/gpu/drm/nouveau/core/core/client.c @@ -99,3 +99,13 @@ nouveau_client_fini(struct nouveau_client *client, bool suspend) nv_debug(client, "%s completed with %d\n", name[suspend], ret); return ret; } + +const char * +nouveau_client_name(void *obj) +{ + const char *client_name = "unknown"; + struct nouveau_client *client = nouveau_client(obj); + if (client) + client_name = client->name; + return client_name; +} diff --git a/drivers/gpu/drm/nouveau/core/core/enum.c b/drivers/gpu/drm/nouveau/core/core/enum.c index 7cc7133d82de..dd434790ccc4 100644 --- a/drivers/gpu/drm/nouveau/core/core/enum.c +++ b/drivers/gpu/drm/nouveau/core/core/enum.c @@ -40,14 +40,15 @@ nouveau_enum_find(const struct nouveau_enum *en, u32 value) return NULL; } -void +const struct nouveau_enum * nouveau_enum_print(const struct nouveau_enum *en, u32 value) { en = nouveau_enum_find(en, value); if (en) - printk("%s", en->name); + pr_cont("%s", en->name); else - printk("(unknown enum 0x%08x)", value); + pr_cont("(unknown enum 0x%08x)", value); + return en; } void @@ -55,7 +56,7 @@ nouveau_bitfield_print(const struct nouveau_bitfield *bf, u32 value) { while (bf->name) { if (value & bf->mask) { - printk(" %s", bf->name); + pr_cont(" %s", bf->name); value &= ~bf->mask; } @@ -63,5 +64,5 @@ nouveau_bitfield_print(const struct nouveau_bitfield *bf, u32 value) } if (value) - printk(" (unknown bits 0x%08x)", value); + pr_cont(" (unknown bits 0x%08x)", value); } diff --git a/drivers/gpu/drm/nouveau/core/core/event.c b/drivers/gpu/drm/nouveau/core/core/event.c new file mode 100644 index 000000000000..6d01e0f0fc8a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/core/event.c @@ -0,0 +1,106 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <core/os.h> +#include <core/event.h> + +static void +nouveau_event_put_locked(struct nouveau_event *event, int index, + struct nouveau_eventh *handler) +{ + if (!--event->index[index].refs) + event->disable(event, index); + list_del(&handler->head); +} + +void +nouveau_event_put(struct nouveau_event *event, int index, + struct nouveau_eventh *handler) +{ + unsigned long flags; + + spin_lock_irqsave(&event->lock, flags); + if (index < event->index_nr) + nouveau_event_put_locked(event, index, handler); + spin_unlock_irqrestore(&event->lock, flags); +} + +void +nouveau_event_get(struct nouveau_event *event, int index, + struct nouveau_eventh *handler) +{ + unsigned long flags; + + spin_lock_irqsave(&event->lock, flags); + if (index < event->index_nr) { + list_add(&handler->head, &event->index[index].list); + if (!event->index[index].refs++) + event->enable(event, index); + } + spin_unlock_irqrestore(&event->lock, flags); +} + +void +nouveau_event_trigger(struct nouveau_event *event, int index) +{ + struct nouveau_eventh *handler, *temp; + unsigned long flags; + + if (index >= event->index_nr) + return; + + spin_lock_irqsave(&event->lock, flags); + list_for_each_entry_safe(handler, temp, &event->index[index].list, head) { + if (handler->func(handler, index) == NVKM_EVENT_DROP) { + nouveau_event_put_locked(event, index, handler); + } + } + spin_unlock_irqrestore(&event->lock, flags); +} + +void +nouveau_event_destroy(struct nouveau_event **pevent) +{ + struct nouveau_event *event = *pevent; + if (event) { + kfree(event); + *pevent = NULL; + } +} + +int +nouveau_event_create(int index_nr, struct nouveau_event **pevent) +{ + struct nouveau_event *event; + int i; + + event = *pevent = kzalloc(sizeof(*event) + index_nr * + sizeof(event->index[0]), GFP_KERNEL); + if (!event) + return -ENOMEM; + + spin_lock_init(&event->lock); + for (i = 0; i < index_nr; i++) + INIT_LIST_HEAD(&event->index[i].list); + event->index_nr = index_nr; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c b/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c index 283248c7b050..d6dc2a65ccd1 100644 --- a/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c +++ b/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/falcon.h> #include <core/class.h> #include <core/enum.h> @@ -100,8 +101,9 @@ nva3_copy_intr(struct nouveau_subdev *subdev) if (stat & 0x00000040) { nv_error(falcon, "DISPATCH_ERROR ["); nouveau_enum_print(nva3_copy_isr_error_name, ssta); - printk("] ch %d [0x%010llx] subc %d mthd 0x%04x data 0x%08x\n", - chid, inst << 12, subc, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), subc, + mthd, data); nv_wo32(falcon, 0x004, 0x00000040); stat &= ~0x00000040; } diff --git a/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c b/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c index b97490512723..5bc021f471f9 100644 --- a/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c +++ b/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/enum.h> #include <core/class.h> @@ -126,10 +127,11 @@ nv84_crypt_intr(struct nouveau_subdev *subdev) chid = pfifo->chid(pfifo, engctx); if (stat) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv84_crypt_intr_mask, stat); - printk(" ch %d [0x%010llx] mthd 0x%04x data 0x%08x\n", - chid, (u64)inst << 12, mthd, data); + pr_cont(" ch %d [0x%010llx %s] mthd 0x%04x data 0x%08x\n", + chid, (u64)inst << 12, nouveau_client_name(engctx), + mthd, data); } nv_wr32(priv, 0x102130, stat); diff --git a/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c b/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c index 21986f3bf0c8..8bf8955051d4 100644 --- a/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c +++ b/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/enum.h> #include <core/class.h> @@ -102,8 +103,9 @@ nv98_crypt_intr(struct nouveau_subdev *subdev) if (stat & 0x00000040) { nv_error(priv, "DISPATCH_ERROR ["); nouveau_enum_print(nv98_crypt_isr_error_name, ssta); - printk("] ch %d [0x%010llx] subc %d mthd 0x%04x data 0x%08x\n", - chid, (u64)inst << 12, subc, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d mthd 0x%04x data 0x%08x\n", + chid, (u64)inst << 12, nouveau_client_name(engctx), + subc, mthd, data); nv_wr32(priv, 0x087004, 0x00000040); stat &= ~0x00000040; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/base.c b/drivers/gpu/drm/nouveau/core/engine/disp/base.c new file mode 100644 index 000000000000..7a5cae42834f --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/base.c @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <engine/disp.h> + +void +_nouveau_disp_dtor(struct nouveau_object *object) +{ + struct nouveau_disp *disp = (void *)object; + nouveau_event_destroy(&disp->vblank); + nouveau_engine_destroy(&disp->base); +} + +int +nouveau_disp_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, int heads, + const char *intname, const char *extname, + int length, void **pobject) +{ + struct nouveau_disp *disp; + int ret; + + ret = nouveau_engine_create_(parent, engine, oclass, true, + intname, extname, length, pobject); + disp = *pobject; + if (ret) + return ret; + + return nouveau_event_create(heads, &disp->vblank); +} diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/dport.c b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c new file mode 100644 index 000000000000..fa27b02ff829 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c @@ -0,0 +1,346 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/bios.h> +#include <subdev/bios/dcb.h> +#include <subdev/bios/dp.h> +#include <subdev/bios/init.h> +#include <subdev/i2c.h> + +#include <engine/disp.h> + +#include "dport.h" + +#define DBG(fmt, args...) nv_debug(dp->disp, "DP:%04x:%04x: " fmt, \ + dp->outp->hasht, dp->outp->hashm, ##args) +#define ERR(fmt, args...) nv_error(dp->disp, "DP:%04x:%04x: " fmt, \ + dp->outp->hasht, dp->outp->hashm, ##args) + +/****************************************************************************** + * link training + *****************************************************************************/ +struct dp_state { + const struct nouveau_dp_func *func; + struct nouveau_disp *disp; + struct dcb_output *outp; + struct nvbios_dpout info; + u8 version; + struct nouveau_i2c_port *aux; + int head; + u8 dpcd[4]; + int link_nr; + u32 link_bw; + u8 stat[6]; + u8 conf[4]; +}; + +static int +dp_set_link_config(struct dp_state *dp) +{ + struct nouveau_disp *disp = dp->disp; + struct nouveau_bios *bios = nouveau_bios(disp); + struct nvbios_init init = { + .subdev = nv_subdev(dp->disp), + .bios = bios, + .offset = 0x0000, + .outp = dp->outp, + .crtc = dp->head, + .execute = 1, + }; + u32 lnkcmp; + u8 sink[2]; + + DBG("%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw); + + /* set desired link configuration on the sink */ + sink[0] = dp->link_bw / 27000; + sink[1] = dp->link_nr; + if (dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP) + sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN; + + nv_wraux(dp->aux, DPCD_LC00, sink, 2); + + /* set desired link configuration on the source */ + if ((lnkcmp = dp->info.lnkcmp)) { + if (dp->version < 0x30) { + while ((dp->link_bw / 10) < nv_ro16(bios, lnkcmp)) + lnkcmp += 4; + init.offset = nv_ro16(bios, lnkcmp + 2); + } else { + while ((dp->link_bw / 27000) < nv_ro08(bios, lnkcmp)) + lnkcmp += 3; + init.offset = nv_ro16(bios, lnkcmp + 1); + } + + nvbios_exec(&init); + } + + return dp->func->lnk_ctl(dp->disp, dp->outp, dp->head, + dp->link_nr, dp->link_bw / 27000, + dp->dpcd[DPCD_RC02] & + DPCD_RC02_ENHANCED_FRAME_CAP); +} + +static void +dp_set_training_pattern(struct dp_state *dp, u8 pattern) +{ + u8 sink_tp; + + DBG("training pattern %d\n", pattern); + dp->func->pattern(dp->disp, dp->outp, dp->head, pattern); + + nv_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1); + sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET; + sink_tp |= pattern; + nv_wraux(dp->aux, DPCD_LC02, &sink_tp, 1); +} + +static int +dp_link_train_commit(struct dp_state *dp) +{ + int i; + + for (i = 0; i < dp->link_nr; i++) { + u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf; + u8 lpre = (lane & 0x0c) >> 2; + u8 lvsw = (lane & 0x03) >> 0; + + dp->conf[i] = (lpre << 3) | lvsw; + if (lvsw == 3) + dp->conf[i] |= DPCD_LC03_MAX_SWING_REACHED; + if (lpre == 3) + dp->conf[i] |= DPCD_LC03_MAX_PRE_EMPHASIS_REACHED; + + DBG("config lane %d %02x\n", i, dp->conf[i]); + dp->func->drv_ctl(dp->disp, dp->outp, dp->head, i, lvsw, lpre); + } + + return nv_wraux(dp->aux, DPCD_LC03(0), dp->conf, 4); +} + +static int +dp_link_train_update(struct dp_state *dp, u32 delay) +{ + int ret; + + udelay(delay); + + ret = nv_rdaux(dp->aux, DPCD_LS02, dp->stat, 6); + if (ret) + return ret; + + DBG("status %*ph\n", 6, dp->stat); + return 0; +} + +static int +dp_link_train_cr(struct dp_state *dp) +{ + bool cr_done = false, abort = false; + int voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET; + int tries = 0, i; + + dp_set_training_pattern(dp, 1); + + do { + if (dp_link_train_commit(dp) || + dp_link_train_update(dp, 100)) + break; + + cr_done = true; + for (i = 0; i < dp->link_nr; i++) { + u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; + if (!(lane & DPCD_LS02_LANE0_CR_DONE)) { + cr_done = false; + if (dp->conf[i] & DPCD_LC03_MAX_SWING_REACHED) + abort = true; + break; + } + } + + if ((dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) { + voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET; + tries = 0; + } + } while (!cr_done && !abort && ++tries < 5); + + return cr_done ? 0 : -1; +} + +static int +dp_link_train_eq(struct dp_state *dp) +{ + bool eq_done, cr_done = true; + int tries = 0, i; + + dp_set_training_pattern(dp, 2); + + do { + if (dp_link_train_update(dp, 400)) + break; + + eq_done = !!(dp->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE); + for (i = 0; i < dp->link_nr && eq_done; i++) { + u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; + if (!(lane & DPCD_LS02_LANE0_CR_DONE)) + cr_done = false; + if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) || + !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) + eq_done = false; + } + + if (dp_link_train_commit(dp)) + break; + } while (!eq_done && cr_done && ++tries <= 5); + + return eq_done ? 0 : -1; +} + +static void +dp_link_train_init(struct dp_state *dp, bool spread) +{ + struct nvbios_init init = { + .subdev = nv_subdev(dp->disp), + .bios = nouveau_bios(dp->disp), + .outp = dp->outp, + .crtc = dp->head, + .execute = 1, + }; + + /* set desired spread */ + if (spread) + init.offset = dp->info.script[2]; + else + init.offset = dp->info.script[3]; + nvbios_exec(&init); + + /* pre-train script */ + init.offset = dp->info.script[0]; + nvbios_exec(&init); +} + +static void +dp_link_train_fini(struct dp_state *dp) +{ + struct nvbios_init init = { + .subdev = nv_subdev(dp->disp), + .bios = nouveau_bios(dp->disp), + .outp = dp->outp, + .crtc = dp->head, + .execute = 1, + }; + + /* post-train script */ + init.offset = dp->info.script[1], + nvbios_exec(&init); +} + +int +nouveau_dp_train(struct nouveau_disp *disp, const struct nouveau_dp_func *func, + struct dcb_output *outp, int head, u32 datarate) +{ + struct nouveau_bios *bios = nouveau_bios(disp); + struct nouveau_i2c *i2c = nouveau_i2c(disp); + struct dp_state _dp = { + .disp = disp, + .func = func, + .outp = outp, + .head = head, + }, *dp = &_dp; + const u32 bw_list[] = { 270000, 162000, 0 }; + const u32 *link_bw = bw_list; + u8 hdr, cnt, len; + u32 data; + int ret; + + /* find the bios displayport data relevant to this output */ + data = nvbios_dpout_match(bios, outp->hasht, outp->hashm, &dp->version, + &hdr, &cnt, &len, &dp->info); + if (!data) { + ERR("bios data not found\n"); + return -EINVAL; + } + + /* acquire the aux channel and fetch some info about the display */ + if (outp->location) + dp->aux = i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(outp->extdev)); + else + dp->aux = i2c->find(i2c, NV_I2C_TYPE_DCBI2C(outp->i2c_index)); + if (!dp->aux) { + ERR("no aux channel?!\n"); + return -ENODEV; + } + + ret = nv_rdaux(dp->aux, 0x00000, dp->dpcd, sizeof(dp->dpcd)); + if (ret) { + ERR("failed to read DPCD\n"); + return ret; + } + + /* adjust required bandwidth for 8B/10B coding overhead */ + datarate = (datarate / 8) * 10; + + /* enable down-spreading and execute pre-train script from vbios */ + dp_link_train_init(dp, dp->dpcd[3] & 0x01); + + /* start off at highest link rate supported by encoder and display */ + while (*link_bw > (dp->dpcd[1] * 27000)) + link_bw++; + + while (link_bw[0]) { + /* find minimum required lane count at this link rate */ + dp->link_nr = dp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT; + while ((dp->link_nr >> 1) * link_bw[0] > datarate) + dp->link_nr >>= 1; + + /* drop link rate to minimum with this lane count */ + while ((link_bw[1] * dp->link_nr) > datarate) + link_bw++; + dp->link_bw = link_bw[0]; + + /* program selected link configuration */ + ret = dp_set_link_config(dp); + if (ret == 0) { + /* attempt to train the link at this configuration */ + memset(dp->stat, 0x00, sizeof(dp->stat)); + if (!dp_link_train_cr(dp) && + !dp_link_train_eq(dp)) + break; + } else + if (ret >= 1) { + /* dp_set_link_config() handled training */ + break; + } + + /* retry at lower rate */ + link_bw++; + } + + /* finish link training */ + dp_set_training_pattern(dp, 0); + + /* execute post-train script from vbios */ + dp_link_train_fini(dp); + return true; +} diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/dport.h b/drivers/gpu/drm/nouveau/core/engine/disp/dport.h new file mode 100644 index 000000000000..0e1bbd18ff6c --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/dport.h @@ -0,0 +1,78 @@ +#ifndef __NVKM_DISP_DPORT_H__ +#define __NVKM_DISP_DPORT_H__ + +/* DPCD Receiver Capabilities */ +#define DPCD_RC00 0x00000 +#define DPCD_RC00_DPCD_REV 0xff +#define DPCD_RC01 0x00001 +#define DPCD_RC01_MAX_LINK_RATE 0xff +#define DPCD_RC02 0x00002 +#define DPCD_RC02_ENHANCED_FRAME_CAP 0x80 +#define DPCD_RC02_MAX_LANE_COUNT 0x1f +#define DPCD_RC03 0x00003 +#define DPCD_RC03_MAX_DOWNSPREAD 0x01 + +/* DPCD Link Configuration */ +#define DPCD_LC00 0x00100 +#define DPCD_LC00_LINK_BW_SET 0xff +#define DPCD_LC01 0x00101 +#define DPCD_LC01_ENHANCED_FRAME_EN 0x80 +#define DPCD_LC01_LANE_COUNT_SET 0x1f +#define DPCD_LC02 0x00102 +#define DPCD_LC02_TRAINING_PATTERN_SET 0x03 +#define DPCD_LC03(l) ((l) + 0x00103) +#define DPCD_LC03_MAX_PRE_EMPHASIS_REACHED 0x20 +#define DPCD_LC03_PRE_EMPHASIS_SET 0x18 +#define DPCD_LC03_MAX_SWING_REACHED 0x04 +#define DPCD_LC03_VOLTAGE_SWING_SET 0x03 + +/* DPCD Link/Sink Status */ +#define DPCD_LS02 0x00202 +#define DPCD_LS02_LANE1_SYMBOL_LOCKED 0x40 +#define DPCD_LS02_LANE1_CHANNEL_EQ_DONE 0x20 +#define DPCD_LS02_LANE1_CR_DONE 0x10 +#define DPCD_LS02_LANE0_SYMBOL_LOCKED 0x04 +#define DPCD_LS02_LANE0_CHANNEL_EQ_DONE 0x02 +#define DPCD_LS02_LANE0_CR_DONE 0x01 +#define DPCD_LS03 0x00203 +#define DPCD_LS03_LANE3_SYMBOL_LOCKED 0x40 +#define DPCD_LS03_LANE3_CHANNEL_EQ_DONE 0x20 +#define DPCD_LS03_LANE3_CR_DONE 0x10 +#define DPCD_LS03_LANE2_SYMBOL_LOCKED 0x04 +#define DPCD_LS03_LANE2_CHANNEL_EQ_DONE 0x02 +#define DPCD_LS03_LANE2_CR_DONE 0x01 +#define DPCD_LS04 0x00204 +#define DPCD_LS04_LINK_STATUS_UPDATED 0x80 +#define DPCD_LS04_DOWNSTREAM_PORT_STATUS_CHANGED 0x40 +#define DPCD_LS04_INTERLANE_ALIGN_DONE 0x01 +#define DPCD_LS06 0x00206 +#define DPCD_LS06_LANE1_PRE_EMPHASIS 0xc0 +#define DPCD_LS06_LANE1_VOLTAGE_SWING 0x30 +#define DPCD_LS06_LANE0_PRE_EMPHASIS 0x0c +#define DPCD_LS06_LANE0_VOLTAGE_SWING 0x03 +#define DPCD_LS07 0x00207 +#define DPCD_LS07_LANE3_PRE_EMPHASIS 0xc0 +#define DPCD_LS07_LANE3_VOLTAGE_SWING 0x30 +#define DPCD_LS07_LANE2_PRE_EMPHASIS 0x0c +#define DPCD_LS07_LANE2_VOLTAGE_SWING 0x03 + +struct nouveau_disp; +struct dcb_output; + +struct nouveau_dp_func { + int (*pattern)(struct nouveau_disp *, struct dcb_output *, + int head, int pattern); + int (*lnk_ctl)(struct nouveau_disp *, struct dcb_output *, int head, + int link_nr, int link_bw, bool enh_frame); + int (*drv_ctl)(struct nouveau_disp *, struct dcb_output *, int head, + int lane, int swing, int preem); +}; + +extern const struct nouveau_dp_func nv94_sor_dp_func; +extern const struct nouveau_dp_func nvd0_sor_dp_func; +extern const struct nouveau_dp_func nv50_pior_dp_func; + +int nouveau_dp_train(struct nouveau_disp *, const struct nouveau_dp_func *, + struct dcb_output *, int, u32); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c index 1c919f2af89f..05e903f08a36 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c @@ -24,21 +24,33 @@ #include <engine/disp.h> +#include <core/event.h> +#include <core/class.h> + struct nv04_disp_priv { struct nouveau_disp base; }; static struct nouveau_oclass nv04_disp_sclass[] = { + { NV04_DISP_CLASS, &nouveau_object_ofuncs }, {}, }; +/******************************************************************************* + * Display engine implementation + ******************************************************************************/ + +static void +nv04_disp_vblank_enable(struct nouveau_event *event, int head) +{ + nv_wr32(event->priv, 0x600140 + (head * 0x2000) , 0x00000001); +} + static void -nv04_disp_intr_vblank(struct nv04_disp_priv *priv, int crtc) +nv04_disp_vblank_disable(struct nouveau_event *event, int head) { - struct nouveau_disp *disp = &priv->base; - if (disp->vblank.notify) - disp->vblank.notify(disp->vblank.data, crtc); + nv_wr32(event->priv, 0x600140 + (head * 0x2000) , 0x00000000); } static void @@ -49,25 +61,25 @@ nv04_disp_intr(struct nouveau_subdev *subdev) u32 crtc1 = nv_rd32(priv, 0x602100); if (crtc0 & 0x00000001) { - nv04_disp_intr_vblank(priv, 0); + nouveau_event_trigger(priv->base.vblank, 0); nv_wr32(priv, 0x600100, 0x00000001); } if (crtc1 & 0x00000001) { - nv04_disp_intr_vblank(priv, 1); + nouveau_event_trigger(priv->base.vblank, 1); nv_wr32(priv, 0x602100, 0x00000001); } } static int nv04_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { struct nv04_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "DISPLAY", + ret = nouveau_disp_create(parent, engine, oclass, 2, "DISPLAY", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -75,6 +87,9 @@ nv04_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv04_disp_sclass; nv_subdev(priv)->intr = nv04_disp_intr; + priv->base.vblank->priv = priv; + priv->base.vblank->enable = nv04_disp_vblank_enable; + priv->base.vblank->disable = nv04_disp_vblank_disable; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c index ca1a7d76a95b..5fa13267bd9f 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c @@ -27,7 +27,6 @@ #include <core/handle.h> #include <core/class.h> -#include <engine/software.h> #include <engine/disp.h> #include <subdev/bios.h> @@ -37,7 +36,6 @@ #include <subdev/bios/pll.h> #include <subdev/timer.h> #include <subdev/fb.h> -#include <subdev/bar.h> #include <subdev/clock.h> #include "nv50.h" @@ -335,7 +333,7 @@ nv50_disp_sync_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head > 1) + if (size < sizeof(*args) || args->head > 1) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -374,7 +372,7 @@ nv50_disp_ovly_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head > 1) + if (size < sizeof(*args) || args->head > 1) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -543,6 +541,18 @@ nv50_disp_curs_ofuncs = { * Base display object ******************************************************************************/ +static void +nv50_disp_base_vblank_enable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x61002c, (1 << head), (1 << head)); +} + +static void +nv50_disp_base_vblank_disable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x61002c, (1 << head), (0 << head)); +} + static int nv50_disp_base_ctor(struct nouveau_object *parent, struct nouveau_object *engine, @@ -559,6 +569,9 @@ nv50_disp_base_ctor(struct nouveau_object *parent, if (ret) return ret; + priv->base.vblank->priv = priv; + priv->base.vblank->enable = nv50_disp_base_vblank_enable; + priv->base.vblank->disable = nv50_disp_base_vblank_disable; return nouveau_ramht_new(parent, parent, 0x1000, 0, &base->ramht); } @@ -613,7 +626,7 @@ nv50_disp_base_init(struct nouveau_object *object) nv_wr32(priv, 0x6101e0 + (i * 0x04), tmp); } - /* ... EXT caps */ + /* ... PIOR caps */ for (i = 0; i < 3; i++) { tmp = nv_rd32(priv, 0x61e000 + (i * 0x800)); nv_wr32(priv, 0x6101f0 + (i * 0x04), tmp); @@ -665,6 +678,9 @@ nv50_disp_base_omthds[] = { { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -756,50 +772,6 @@ nv50_disp_intr_error(struct nv50_disp_priv *priv) } } -static void -nv50_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc) -{ - struct nouveau_bar *bar = nouveau_bar(priv); - struct nouveau_disp *disp = &priv->base; - struct nouveau_software_chan *chan, *temp; - unsigned long flags; - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_for_each_entry_safe(chan, temp, &disp->vblank.list, vblank.head) { - if (chan->vblank.crtc != crtc) - continue; - - if (nv_device(priv)->chipset >= 0xc0) { - nv_wr32(priv, 0x001718, 0x80000000 | chan->vblank.channel); - bar->flush(bar); - nv_wr32(priv, 0x06000c, - upper_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060010, - lower_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060014, chan->vblank.value); - } else { - nv_wr32(priv, 0x001704, chan->vblank.channel); - nv_wr32(priv, 0x001710, 0x80000000 | chan->vblank.ctxdma); - bar->flush(bar); - if (nv_device(priv)->chipset == 0x50) { - nv_wr32(priv, 0x001570, chan->vblank.offset); - nv_wr32(priv, 0x001574, chan->vblank.value); - } else { - nv_wr32(priv, 0x060010, chan->vblank.offset); - nv_wr32(priv, 0x060014, chan->vblank.value); - } - } - - list_del(&chan->vblank.head); - if (disp->vblank.put) - disp->vblank.put(disp->vblank.data, crtc); - } - spin_unlock_irqrestore(&disp->vblank.lock, flags); - - if (disp->vblank.notify) - disp->vblank.notify(disp->vblank.data, crtc); -} - static u16 exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, struct dcb_output *dcb, u8 *ver, u8 *hdr, u8 *cnt, u8 *len, @@ -811,8 +783,8 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, if (outp < 4) { type = DCB_OUTPUT_ANALOG; mask = 0; - } else { - outp -= 4; + } else + if (outp < 8) { switch (ctrl & 0x00000f00) { case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break; case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break; @@ -824,6 +796,17 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, nv_error(priv, "unknown SOR mc 0x%08x\n", ctrl); return 0x0000; } + outp -= 4; + } else { + outp = outp - 8; + type = 0x0010; + mask = 0; + switch (ctrl & 0x00000f00) { + case 0x00000000: type |= priv->pior.type[outp]; break; + default: + nv_error(priv, "unknown PIOR mc 0x%08x\n", ctrl); + return 0x0000; + } } mask = 0x00c0 & (mask << 6); @@ -834,6 +817,10 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, if (!data) return 0x0000; + /* off-chip encoders require matching the exact encoder type */ + if (dcb->location != 0) + type |= dcb->extdev << 8; + return nvbios_outp_match(bios, type, mask, ver, hdr, cnt, len, info); } @@ -848,9 +835,11 @@ exec_script(struct nv50_disp_priv *priv, int head, int id) u32 ctrl = 0x00000000; int i; + /* DAC */ for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) ctrl = nv_rd32(priv, 0x610b5c + (i * 8)); + /* SOR */ if (!(ctrl & (1 << head))) { if (nv_device(priv)->chipset < 0x90 || nv_device(priv)->chipset == 0x92 || @@ -865,6 +854,13 @@ exec_script(struct nv50_disp_priv *priv, int head, int id) } } + /* PIOR */ + if (!(ctrl & (1 << head))) { + for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) + ctrl = nv_rd32(priv, 0x610b84 + (i * 8)); + i += 8; + } + if (!(ctrl & (1 << head))) return false; i--; @@ -894,13 +890,15 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, u32 pclk, struct nvbios_outp info1; struct nvbios_ocfg info2; u8 ver, hdr, cnt, len; - u16 data, conf; u32 ctrl = 0x00000000; + u32 data, conf = ~0; int i; + /* DAC */ for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) ctrl = nv_rd32(priv, 0x610b58 + (i * 8)); + /* SOR */ if (!(ctrl & (1 << head))) { if (nv_device(priv)->chipset < 0x90 || nv_device(priv)->chipset == 0x92 || @@ -915,34 +913,46 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, u32 pclk, } } + /* PIOR */ + if (!(ctrl & (1 << head))) { + for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) + ctrl = nv_rd32(priv, 0x610b80 + (i * 8)); + i += 8; + } + if (!(ctrl & (1 << head))) - return 0x0000; + return conf; i--; data = exec_lookup(priv, head, i, ctrl, outp, &ver, &hdr, &cnt, &len, &info1); if (!data) - return 0x0000; - - switch (outp->type) { - case DCB_OUTPUT_TMDS: - conf = (ctrl & 0x00000f00) >> 8; - if (pclk >= 165000) - conf |= 0x0100; - break; - case DCB_OUTPUT_LVDS: - conf = priv->sor.lvdsconf; - break; - case DCB_OUTPUT_DP: + return conf; + + if (outp->location == 0) { + switch (outp->type) { + case DCB_OUTPUT_TMDS: + conf = (ctrl & 0x00000f00) >> 8; + if (pclk >= 165000) + conf |= 0x0100; + break; + case DCB_OUTPUT_LVDS: + conf = priv->sor.lvdsconf; + break; + case DCB_OUTPUT_DP: + conf = (ctrl & 0x00000f00) >> 8; + break; + case DCB_OUTPUT_ANALOG: + default: + conf = 0x00ff; + break; + } + } else { conf = (ctrl & 0x00000f00) >> 8; - break; - case DCB_OUTPUT_ANALOG: - default: - conf = 0x00ff; - break; + pclk = pclk / 2; } data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2); - if (data) { + if (data && id < 0xff) { data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk); if (data) { struct nvbios_init init = { @@ -954,32 +964,37 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, u32 pclk, .execute = 1, }; - if (nvbios_exec(&init)) - return 0x0000; - return conf; + nvbios_exec(&init); } } - return 0x0000; + return conf; } static void -nv50_disp_intr_unk10(struct nv50_disp_priv *priv, u32 super) +nv50_disp_intr_unk10_0(struct nv50_disp_priv *priv, int head) { - int head = ffs((super & 0x00000060) >> 5) - 1; - if (head >= 0) { - head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) - exec_script(priv, head, 1); - } + exec_script(priv, head, 1); +} - nv_wr32(priv, 0x610024, 0x00000010); - nv_wr32(priv, 0x610030, 0x80000000); +static void +nv50_disp_intr_unk20_0(struct nv50_disp_priv *priv, int head) +{ + exec_script(priv, head, 2); +} + +static void +nv50_disp_intr_unk20_1(struct nv50_disp_priv *priv, int head) +{ + struct nouveau_clock *clk = nouveau_clock(priv); + u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; + if (pclk) + clk->pll_set(clk, PLL_VPLL0 + head, pclk); } static void -nv50_disp_intr_unk20_dp(struct nv50_disp_priv *priv, - struct dcb_output *outp, u32 pclk) +nv50_disp_intr_unk20_2_dp(struct nv50_disp_priv *priv, + struct dcb_output *outp, u32 pclk) { const int link = !(outp->sorconf.link & 1); const int or = ffs(outp->or) - 1; @@ -1085,53 +1100,54 @@ nv50_disp_intr_unk20_dp(struct nv50_disp_priv *priv, } static void -nv50_disp_intr_unk20(struct nv50_disp_priv *priv, u32 super) +nv50_disp_intr_unk20_2(struct nv50_disp_priv *priv, int head) { struct dcb_output outp; - u32 addr, mask, data; - int head; + u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; + u32 hval, hreg = 0x614200 + (head * 0x800); + u32 oval, oreg; + u32 conf = exec_clkcmp(priv, head, 0xff, pclk, &outp); + if (conf != ~0) { + if (outp.location == 0 && outp.type == DCB_OUTPUT_DP) { + u32 soff = (ffs(outp.or) - 1) * 0x08; + u32 ctrl = nv_rd32(priv, 0x610798 + soff); + u32 datarate; + + switch ((ctrl & 0x000f0000) >> 16) { + case 6: datarate = pclk * 30 / 8; break; + case 5: datarate = pclk * 24 / 8; break; + case 2: + default: + datarate = pclk * 18 / 8; + break; + } - /* finish detaching encoder? */ - head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) - exec_script(priv, head, 2); - - /* check whether a vpll change is required */ - head = ffs((super & 0x00000600) >> 9) - 1; - if (head >= 0) { - u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; - if (pclk) { - struct nouveau_clock *clk = nouveau_clock(priv); - clk->pll_set(clk, PLL_VPLL0 + head, pclk); + nouveau_dp_train(&priv->base, priv->sor.dp, + &outp, head, datarate); } - nv_mask(priv, 0x614200 + head * 0x800, 0x0000000f, 0x00000000); - } - - /* (re)attach the relevant OR to the head */ - head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) { - u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; - u32 conf = exec_clkcmp(priv, head, 0, pclk, &outp); - if (conf) { - if (outp.type == DCB_OUTPUT_ANALOG) { - addr = 0x614280 + (ffs(outp.or) - 1) * 0x800; - mask = 0xffffffff; - data = 0x00000000; - } else { - if (outp.type == DCB_OUTPUT_DP) - nv50_disp_intr_unk20_dp(priv, &outp, pclk); - addr = 0x614300 + (ffs(outp.or) - 1) * 0x800; - mask = 0x00000707; - data = (conf & 0x0100) ? 0x0101 : 0x0000; - } - - nv_mask(priv, addr, mask, data); + exec_clkcmp(priv, head, 0, pclk, &outp); + + if (!outp.location && outp.type == DCB_OUTPUT_ANALOG) { + oreg = 0x614280 + (ffs(outp.or) - 1) * 0x800; + oval = 0x00000000; + hval = 0x00000000; + } else + if (!outp.location) { + if (outp.type == DCB_OUTPUT_DP) + nv50_disp_intr_unk20_2_dp(priv, &outp, pclk); + oreg = 0x614300 + (ffs(outp.or) - 1) * 0x800; + oval = (conf & 0x0100) ? 0x00000101 : 0x00000000; + hval = 0x00000000; + } else { + oreg = 0x614380 + (ffs(outp.or) - 1) * 0x800; + oval = 0x00000001; + hval = 0x00000001; } - } - nv_wr32(priv, 0x610024, 0x00000020); - nv_wr32(priv, 0x610030, 0x80000000); + nv_mask(priv, hreg, 0x0000000f, hval); + nv_mask(priv, oreg, 0x00000707, oval); + } } /* If programming a TMDS output on a SOR that can also be configured for @@ -1143,7 +1159,7 @@ nv50_disp_intr_unk20(struct nv50_disp_priv *priv, u32 super) * programmed for DisplayPort. */ static void -nv50_disp_intr_unk40_tmds(struct nv50_disp_priv *priv, struct dcb_output *outp) +nv50_disp_intr_unk40_0_tmds(struct nv50_disp_priv *priv, struct dcb_output *outp) { struct nouveau_bios *bios = nouveau_bios(priv); const int link = !(outp->sorconf.link & 1); @@ -1157,35 +1173,79 @@ nv50_disp_intr_unk40_tmds(struct nv50_disp_priv *priv, struct dcb_output *outp) } static void -nv50_disp_intr_unk40(struct nv50_disp_priv *priv, u32 super) +nv50_disp_intr_unk40_0(struct nv50_disp_priv *priv, int head) { - int head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) { - struct dcb_output outp; - u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; - if (pclk && exec_clkcmp(priv, head, 1, pclk, &outp)) { - if (outp.type == DCB_OUTPUT_TMDS) - nv50_disp_intr_unk40_tmds(priv, &outp); + struct dcb_output outp; + u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; + if (exec_clkcmp(priv, head, 1, pclk, &outp) != ~0) { + if (outp.location == 0 && outp.type == DCB_OUTPUT_TMDS) + nv50_disp_intr_unk40_0_tmds(priv, &outp); + else + if (outp.location == 1 && outp.type == DCB_OUTPUT_DP) { + u32 soff = (ffs(outp.or) - 1) * 0x08; + u32 ctrl = nv_rd32(priv, 0x610b84 + soff); + u32 datarate; + + switch ((ctrl & 0x000f0000) >> 16) { + case 6: datarate = pclk * 30 / 8; break; + case 5: datarate = pclk * 24 / 8; break; + case 2: + default: + datarate = pclk * 18 / 8; + break; + } + + nouveau_dp_train(&priv->base, priv->pior.dp, + &outp, head, datarate); } } - - nv_wr32(priv, 0x610024, 0x00000040); - nv_wr32(priv, 0x610030, 0x80000000); } -static void -nv50_disp_intr_super(struct nv50_disp_priv *priv, u32 intr1) +void +nv50_disp_intr_supervisor(struct work_struct *work) { + struct nv50_disp_priv *priv = + container_of(work, struct nv50_disp_priv, supervisor); u32 super = nv_rd32(priv, 0x610030); + int head; - nv_debug(priv, "supervisor 0x%08x 0x%08x\n", intr1, super); + nv_debug(priv, "supervisor 0x%08x 0x%08x\n", priv->super, super); - if (intr1 & 0x00000010) - nv50_disp_intr_unk10(priv, super); - if (intr1 & 0x00000020) - nv50_disp_intr_unk20(priv, super); - if (intr1 & 0x00000040) - nv50_disp_intr_unk40(priv, super); + if (priv->super & 0x00000010) { + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000020 << head))) + continue; + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk10_0(priv, head); + } + } else + if (priv->super & 0x00000020) { + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk20_0(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000200 << head))) + continue; + nv50_disp_intr_unk20_1(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk20_2(priv, head); + } + } else + if (priv->super & 0x00000040) { + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk40_0(priv, head); + } + } + + nv_wr32(priv, 0x610030, 0x80000000); } void @@ -1201,19 +1261,21 @@ nv50_disp_intr(struct nouveau_subdev *subdev) } if (intr1 & 0x00000004) { - nv50_disp_intr_vblank(priv, 0); + nouveau_event_trigger(priv->base.vblank, 0); nv_wr32(priv, 0x610024, 0x00000004); intr1 &= ~0x00000004; } if (intr1 & 0x00000008) { - nv50_disp_intr_vblank(priv, 1); + nouveau_event_trigger(priv->base.vblank, 1); nv_wr32(priv, 0x610024, 0x00000008); intr1 &= ~0x00000008; } if (intr1 & 0x00000070) { - nv50_disp_intr_super(priv, intr1); + priv->super = (intr1 & 0x00000070); + schedule_work(&priv->supervisor); + nv_wr32(priv, 0x610024, priv->super); intr1 &= ~0x00000070; } } @@ -1226,7 +1288,7 @@ nv50_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -1235,16 +1297,17 @@ nv50_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv50_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nv50_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 2; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h index a6bb931450f1..1ae6ceb56704 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h @@ -3,16 +3,22 @@ #include <core/parent.h> #include <core/namedb.h> +#include <core/engctx.h> #include <core/ramht.h> +#include <core/event.h> #include <engine/dmaobj.h> #include <engine/disp.h> -struct dcb_output; +#include "dport.h" struct nv50_disp_priv { struct nouveau_disp base; struct nouveau_oclass *sclass; + + struct work_struct supervisor; + u32 super; + struct { int nr; } head; @@ -26,23 +32,15 @@ struct nv50_disp_priv { int (*power)(struct nv50_disp_priv *, int sor, u32 data); int (*hda_eld)(struct nv50_disp_priv *, int sor, u8 *, u32); int (*hdmi)(struct nv50_disp_priv *, int head, int sor, u32); - int (*dp_train_init)(struct nv50_disp_priv *, int sor, int link, - int head, u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_train_fini)(struct nv50_disp_priv *, int sor, int link, - int head, u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_train)(struct nv50_disp_priv *, int sor, int link, - u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_lnkctl)(struct nv50_disp_priv *, int sor, int link, - int head, u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_drvctl)(struct nv50_disp_priv *, int sor, int link, - int lane, u16 type, u16 mask, u32 data, - struct dcb_output *); u32 lvdsconf; + const struct nouveau_dp_func *dp; } sor; + struct { + int nr; + int (*power)(struct nv50_disp_priv *, int ext, u32 data); + u8 type[3]; + const struct nouveau_dp_func *dp; + } pior; }; #define DAC_MTHD(n) (n), (n) + 0x03 @@ -81,6 +79,11 @@ int nvd0_sor_dp_lnkctl(struct nv50_disp_priv *, int, int, int, u16, u16, u32, int nvd0_sor_dp_drvctl(struct nv50_disp_priv *, int, int, int, u16, u16, u32, struct dcb_output *); +#define PIOR_MTHD(n) (n), (n) + 0x03 + +int nv50_pior_mthd(struct nouveau_object *, u32, void *, u32); +int nv50_pior_power(struct nv50_disp_priv *, int, u32); + struct nv50_disp_base { struct nouveau_parent base; struct nouveau_ramht *ramht; @@ -124,6 +127,7 @@ extern struct nouveau_ofuncs nv50_disp_oimm_ofuncs; extern struct nouveau_ofuncs nv50_disp_curs_ofuncs; extern struct nouveau_ofuncs nv50_disp_base_ofuncs; extern struct nouveau_oclass nv50_disp_cclass; +void nv50_disp_intr_supervisor(struct work_struct *); void nv50_disp_intr(struct nouveau_subdev *); extern struct nouveau_omthds nv84_disp_base_omthds[]; @@ -137,6 +141,7 @@ extern struct nouveau_ofuncs nvd0_disp_oimm_ofuncs; extern struct nouveau_ofuncs nvd0_disp_curs_ofuncs; extern struct nouveau_ofuncs nvd0_disp_base_ofuncs; extern struct nouveau_oclass nvd0_disp_cclass; +void nvd0_disp_intr_supervisor(struct work_struct *); void nvd0_disp_intr(struct nouveau_subdev *); #endif diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c index fc84eacdfbec..d8c74c0883a1 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c @@ -46,6 +46,9 @@ nv84_disp_base_omthds[] = { { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -63,7 +66,7 @@ nv84_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -72,17 +75,18 @@ nv84_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv84_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nv84_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 2; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hdmi = nv84_hdmi_ctrl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c index ba9dfd4669a2..a66f949c1f84 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c @@ -44,14 +44,11 @@ nv94_disp_base_omthds[] = { { SOR_MTHD(NV50_DISP_SOR_PWR) , nv50_sor_mthd }, { SOR_MTHD(NV84_DISP_SOR_HDMI_PWR) , nv50_sor_mthd }, { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_TRAIN) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_LNKCTL) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(0)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(1)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(2)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(3)), nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -69,7 +66,7 @@ nv94_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -78,22 +75,19 @@ nv94_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv94_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nv94_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 4; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hdmi = nv84_hdmi_ctrl; - priv->sor.dp_train = nv94_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nv94_sor_dp_lnkctl; - priv->sor.dp_drvctl = nv94_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nv94_sor_dp_func; + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c index 5d63902cdeda..6cf8eefac368 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c @@ -53,7 +53,7 @@ nva0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -62,17 +62,18 @@ nva0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nva0_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nva0_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 2; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hdmi = nv84_hdmi_ctrl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c index e9192ca389fa..b75413169eae 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c @@ -45,14 +45,11 @@ nva3_disp_base_omthds[] = { { SOR_MTHD(NVA3_DISP_SOR_HDA_ELD) , nv50_sor_mthd }, { SOR_MTHD(NV84_DISP_SOR_HDMI_PWR) , nv50_sor_mthd }, { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_TRAIN) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_LNKCTL) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(0)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(1)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(2)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(3)), nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -70,7 +67,7 @@ nva3_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -79,23 +76,20 @@ nva3_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nva3_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nva3_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 4; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hda_eld = nva3_hda_eld; priv->sor.hdmi = nva3_hdmi_ctrl; - priv->sor.dp_train = nv94_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nv94_sor_dp_lnkctl; - priv->sor.dp_drvctl = nv94_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nv94_sor_dp_func; + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c index 9e38ebff5fb3..788dd34ccb54 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c @@ -27,12 +27,10 @@ #include <core/handle.h> #include <core/class.h> -#include <engine/software.h> #include <engine/disp.h> #include <subdev/timer.h> #include <subdev/fb.h> -#include <subdev/bar.h> #include <subdev/clock.h> #include <subdev/bios.h> @@ -230,7 +228,7 @@ nvd0_disp_sync_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head >= priv->head.nr) + if (size < sizeof(*args) || args->head >= priv->head.nr) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -270,7 +268,7 @@ nvd0_disp_ovly_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head >= priv->head.nr) + if (size < sizeof(*args) || args->head >= priv->head.nr) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -443,6 +441,18 @@ nvd0_disp_curs_ofuncs = { * Base display object ******************************************************************************/ +static void +nvd0_disp_base_vblank_enable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000001); +} + +static void +nvd0_disp_base_vblank_disable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000000); +} + static int nvd0_disp_base_ctor(struct nouveau_object *parent, struct nouveau_object *engine, @@ -459,6 +469,10 @@ nvd0_disp_base_ctor(struct nouveau_object *parent, if (ret) return ret; + priv->base.vblank->priv = priv; + priv->base.vblank->enable = nvd0_disp_base_vblank_enable; + priv->base.vblank->disable = nvd0_disp_base_vblank_disable; + return nouveau_ramht_new(parent, parent, 0x1000, 0, &base->ramht); } @@ -609,13 +623,24 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, } static bool -exec_script(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, int id) +exec_script(struct nv50_disp_priv *priv, int head, int id) { struct nouveau_bios *bios = nouveau_bios(priv); struct nvbios_outp info; struct dcb_output dcb; u8 ver, hdr, cnt, len; + u32 ctrl = 0x00000000; u16 data; + int outp; + + for (outp = 0; !(ctrl & (1 << head)) && outp < 8; outp++) { + ctrl = nv_rd32(priv, 0x640180 + (outp * 0x20)); + if (ctrl & (1 << head)) + break; + } + + if (outp == 8) + return false; data = exec_lookup(priv, head, outp, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info); if (data) { @@ -635,21 +660,31 @@ exec_script(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, int id) } static u32 -exec_clkcmp(struct nv50_disp_priv *priv, int head, int outp, - u32 ctrl, int id, u32 pclk) +exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, + u32 pclk, struct dcb_output *dcb) { struct nouveau_bios *bios = nouveau_bios(priv); struct nvbios_outp info1; struct nvbios_ocfg info2; - struct dcb_output dcb; u8 ver, hdr, cnt, len; - u16 data, conf; + u32 ctrl = 0x00000000; + u32 data, conf = ~0; + int outp; - data = exec_lookup(priv, head, outp, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info1); - if (data == 0x0000) + for (outp = 0; !(ctrl & (1 << head)) && outp < 8; outp++) { + ctrl = nv_rd32(priv, 0x660180 + (outp * 0x20)); + if (ctrl & (1 << head)) + break; + } + + if (outp == 8) return false; - switch (dcb.type) { + data = exec_lookup(priv, head, outp, ctrl, dcb, &ver, &hdr, &cnt, &len, &info1); + if (data == 0x0000) + return conf; + + switch (dcb->type) { case DCB_OUTPUT_TMDS: conf = (ctrl & 0x00000f00) >> 8; if (pclk >= 165000) @@ -668,46 +703,52 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int outp, } data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2); - if (data) { + if (data && id < 0xff) { data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk); if (data) { struct nvbios_init init = { .subdev = nv_subdev(priv), .bios = bios, .offset = data, - .outp = &dcb, + .outp = dcb, .crtc = head, .execute = 1, }; - if (nvbios_exec(&init)) - return 0x0000; - return conf; + nvbios_exec(&init); } } - return 0x0000; + return conf; } static void -nvd0_display_unk1_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) +nvd0_disp_intr_unk1_0(struct nv50_disp_priv *priv, int head) { - int i; + exec_script(priv, head, 1); +} - for (i = 0; mask && i < 8; i++) { - u32 mcc = nv_rd32(priv, 0x640180 + (i * 0x20)); - if (mcc & (1 << head)) - exec_script(priv, head, i, mcc, 1); - } +static void +nvd0_disp_intr_unk2_0(struct nv50_disp_priv *priv, int head) +{ + exec_script(priv, head, 2); +} - nv_wr32(priv, 0x6101d4, 0x00000000); - nv_wr32(priv, 0x6109d4, 0x00000000); - nv_wr32(priv, 0x6101d0, 0x80000000); +static void +nvd0_disp_intr_unk2_1(struct nv50_disp_priv *priv, int head) +{ + struct nouveau_clock *clk = nouveau_clock(priv); + u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + if (pclk) + clk->pll_set(clk, PLL_VPLL0 + head, pclk); + nv_wr32(priv, 0x612200 + (head * 0x800), 0x00000000); } static void -nvd0_display_unk2_calc_tu(struct nv50_disp_priv *priv, int head, int or) +nvd0_disp_intr_unk2_2_tu(struct nv50_disp_priv *priv, int head, + struct dcb_output *outp) { + const int or = ffs(outp->or) - 1; const u32 ctrl = nv_rd32(priv, 0x660200 + (or * 0x020)); const u32 conf = nv_rd32(priv, 0x660404 + (head * 0x300)); const u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; @@ -750,105 +791,102 @@ nvd0_display_unk2_calc_tu(struct nv50_disp_priv *priv, int head, int or) } static void -nvd0_display_unk2_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) +nvd0_disp_intr_unk2_2(struct nv50_disp_priv *priv, int head) { - u32 pclk; - int i; - - for (i = 0; mask && i < 8; i++) { - u32 mcc = nv_rd32(priv, 0x640180 + (i * 0x20)); - if (mcc & (1 << head)) - exec_script(priv, head, i, mcc, 2); - } + struct dcb_output outp; + u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + u32 conf = exec_clkcmp(priv, head, 0xff, pclk, &outp); + if (conf != ~0) { + u32 addr, data; + + if (outp.type == DCB_OUTPUT_DP) { + u32 sync = nv_rd32(priv, 0x660404 + (head * 0x300)); + switch ((sync & 0x000003c0) >> 6) { + case 6: pclk = pclk * 30 / 8; break; + case 5: pclk = pclk * 24 / 8; break; + case 2: + default: + pclk = pclk * 18 / 8; + break; + } - pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; - nv_debug(priv, "head %d pclk %d mask 0x%08x\n", head, pclk, mask); - if (pclk && (mask & 0x00010000)) { - struct nouveau_clock *clk = nouveau_clock(priv); - clk->pll_set(clk, PLL_VPLL0 + head, pclk); - } + nouveau_dp_train(&priv->base, priv->sor.dp, + &outp, head, pclk); + } - nv_wr32(priv, 0x612200 + (head * 0x800), 0x00000000); + exec_clkcmp(priv, head, 0, pclk, &outp); - for (i = 0; mask && i < 8; i++) { - u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20)), cfg; - if (mcp & (1 << head)) { - if ((cfg = exec_clkcmp(priv, head, i, mcp, 0, pclk))) { - u32 addr, mask, data = 0x00000000; - if (i < 4) { - addr = 0x612280 + ((i - 0) * 0x800); - mask = 0xffffffff; - } else { - switch (mcp & 0x00000f00) { - case 0x00000800: - case 0x00000900: - nvd0_display_unk2_calc_tu(priv, head, i - 4); - break; - default: - break; - } - - addr = 0x612300 + ((i - 4) * 0x800); - mask = 0x00000707; - if (cfg & 0x00000100) - data = 0x00000101; - } - nv_mask(priv, addr, mask, data); - } - break; + if (outp.type == DCB_OUTPUT_ANALOG) { + addr = 0x612280 + (ffs(outp.or) - 1) * 0x800; + data = 0x00000000; + } else { + if (outp.type == DCB_OUTPUT_DP) + nvd0_disp_intr_unk2_2_tu(priv, head, &outp); + addr = 0x612300 + (ffs(outp.or) - 1) * 0x800; + data = (conf & 0x0100) ? 0x00000101 : 0x00000000; } - } - nv_wr32(priv, 0x6101d4, 0x00000000); - nv_wr32(priv, 0x6109d4, 0x00000000); - nv_wr32(priv, 0x6101d0, 0x80000000); + nv_mask(priv, addr, 0x00000707, data); + } } static void -nvd0_display_unk4_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) +nvd0_disp_intr_unk4_0(struct nv50_disp_priv *priv, int head) { - int pclk, i; - - pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + struct dcb_output outp; + u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + exec_clkcmp(priv, head, 1, pclk, &outp); +} - for (i = 0; mask && i < 8; i++) { - u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20)); - if (mcp & (1 << head)) - exec_clkcmp(priv, head, i, mcp, 1, pclk); +void +nvd0_disp_intr_supervisor(struct work_struct *work) +{ + struct nv50_disp_priv *priv = + container_of(work, struct nv50_disp_priv, supervisor); + u32 mask[4]; + int head; + + nv_debug(priv, "supervisor %08x\n", priv->super); + for (head = 0; head < priv->head.nr; head++) { + mask[head] = nv_rd32(priv, 0x6101d4 + (head * 0x800)); + nv_debug(priv, "head %d: 0x%08x\n", head, mask[head]); } - nv_wr32(priv, 0x6101d4, 0x00000000); - nv_wr32(priv, 0x6109d4, 0x00000000); - nv_wr32(priv, 0x6101d0, 0x80000000); -} - -static void -nvd0_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc) -{ - struct nouveau_bar *bar = nouveau_bar(priv); - struct nouveau_disp *disp = &priv->base; - struct nouveau_software_chan *chan, *temp; - unsigned long flags; - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_for_each_entry_safe(chan, temp, &disp->vblank.list, vblank.head) { - if (chan->vblank.crtc != crtc) - continue; - - nv_wr32(priv, 0x001718, 0x80000000 | chan->vblank.channel); - bar->flush(bar); - nv_wr32(priv, 0x06000c, upper_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060010, lower_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060014, chan->vblank.value); - - list_del(&chan->vblank.head); - if (disp->vblank.put) - disp->vblank.put(disp->vblank.data, crtc); + if (priv->super & 0x00000001) { + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk1_0(priv, head); + } + } else + if (priv->super & 0x00000002) { + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk2_0(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00010000)) + continue; + nvd0_disp_intr_unk2_1(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk2_2(priv, head); + } + } else + if (priv->super & 0x00000004) { + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk4_0(priv, head); + } } - spin_unlock_irqrestore(&disp->vblank.lock, flags); - if (disp->vblank.notify) - disp->vblank.notify(disp->vblank.data, crtc); + for (head = 0; head < priv->head.nr; head++) + nv_wr32(priv, 0x6101d4 + (head * 0x800), 0x00000000); + nv_wr32(priv, 0x6101d0, 0x80000000); } void @@ -884,27 +922,11 @@ nvd0_disp_intr(struct nouveau_subdev *subdev) if (intr & 0x00100000) { u32 stat = nv_rd32(priv, 0x6100ac); - u32 mask = 0, crtc = ~0; - - while (!mask && ++crtc < priv->head.nr) - mask = nv_rd32(priv, 0x6101d4 + (crtc * 0x800)); - - if (stat & 0x00000001) { - nv_wr32(priv, 0x6100ac, 0x00000001); - nvd0_display_unk1_handler(priv, crtc, mask); - stat &= ~0x00000001; - } - - if (stat & 0x00000002) { - nv_wr32(priv, 0x6100ac, 0x00000002); - nvd0_display_unk2_handler(priv, crtc, mask); - stat &= ~0x00000002; - } - - if (stat & 0x00000004) { - nv_wr32(priv, 0x6100ac, 0x00000004); - nvd0_display_unk4_handler(priv, crtc, mask); - stat &= ~0x00000004; + if (stat & 0x00000007) { + priv->super = (stat & 0x00000007); + schedule_work(&priv->supervisor); + nv_wr32(priv, 0x6100ac, priv->super); + stat &= ~0x00000007; } if (stat) { @@ -920,7 +942,7 @@ nvd0_disp_intr(struct nouveau_subdev *subdev) if (mask & intr) { u32 stat = nv_rd32(priv, 0x6100bc + (i * 0x800)); if (stat & 0x00000001) - nvd0_disp_intr_vblank(priv, i); + nouveau_event_trigger(priv->base.vblank, i); nv_mask(priv, 0x6100bc + (i * 0x800), 0, 0); nv_rd32(priv, 0x6100c0 + (i * 0x800)); } @@ -933,10 +955,11 @@ nvd0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_object **pobject) { struct nv50_disp_priv *priv; + int heads = nv_rd32(parent, 0x022448); int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", - "display", &priv); + ret = nouveau_disp_create(parent, engine, oclass, heads, + "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -944,8 +967,9 @@ nvd0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nvd0_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nvd0_disp_intr; + INIT_WORK(&priv->supervisor, nvd0_disp_intr_supervisor); priv->sclass = nvd0_disp_sclass; - priv->head.nr = nv_rd32(priv, 0x022448); + priv->head.nr = heads; priv->dac.nr = 3; priv->sor.nr = 4; priv->dac.power = nv50_dac_power; @@ -953,14 +977,7 @@ nvd0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->sor.power = nv50_sor_power; priv->sor.hda_eld = nvd0_hda_eld; priv->sor.hdmi = nvd0_hdmi_ctrl; - priv->sor.dp_train = nvd0_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nvd0_sor_dp_lnkctl; - priv->sor.dp_drvctl = nvd0_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nvd0_sor_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c index 259537c4587e..20725b363d58 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c @@ -51,10 +51,11 @@ nve0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_object **pobject) { struct nv50_disp_priv *priv; + int heads = nv_rd32(parent, 0x022448); int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", - "display", &priv); + ret = nouveau_disp_create(parent, engine, oclass, heads, + "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -62,8 +63,9 @@ nve0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nve0_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nvd0_disp_intr; + INIT_WORK(&priv->supervisor, nvd0_disp_intr_supervisor); priv->sclass = nve0_disp_sclass; - priv->head.nr = nv_rd32(priv, 0x022448); + priv->head.nr = heads; priv->dac.nr = 3; priv->sor.nr = 4; priv->dac.power = nv50_dac_power; @@ -71,14 +73,7 @@ nve0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->sor.power = nv50_sor_power; priv->sor.hda_eld = nvd0_hda_eld; priv->sor.hdmi = nvd0_hdmi_ctrl; - priv->sor.dp_train = nvd0_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nvd0_sor_dp_lnkctl; - priv->sor.dp_drvctl = nvd0_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nvd0_sor_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/piornv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/piornv50.c new file mode 100644 index 000000000000..2c8ce351b52d --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/piornv50.c @@ -0,0 +1,140 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <core/os.h> +#include <core/class.h> + +#include <subdev/bios.h> +#include <subdev/bios/dcb.h> +#include <subdev/timer.h> +#include <subdev/i2c.h> + +#include "nv50.h" + +/****************************************************************************** + * DisplayPort + *****************************************************************************/ +static struct nouveau_i2c_port * +nv50_pior_dp_find(struct nouveau_disp *disp, struct dcb_output *outp) +{ + struct nouveau_i2c *i2c = nouveau_i2c(disp); + return i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(outp->extdev)); +} + +static int +nv50_pior_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int pattern) +{ + struct nouveau_i2c_port *port; + int ret = -EINVAL; + + port = nv50_pior_dp_find(disp, outp); + if (port) { + if (port->func->pattern) + ret = port->func->pattern(port, pattern); + else + ret = 0; + } + + return ret; +} + +static int +nv50_pior_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane_nr, int link_bw, bool enh) +{ + struct nouveau_i2c_port *port; + int ret = -EINVAL; + + port = nv50_pior_dp_find(disp, outp); + if (port && port->func->lnk_ctl) + ret = port->func->lnk_ctl(port, lane_nr, link_bw, enh); + + return ret; +} + +static int +nv50_pior_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane, int vsw, int pre) +{ + struct nouveau_i2c_port *port; + int ret = -EINVAL; + + port = nv50_pior_dp_find(disp, outp); + if (port) { + if (port->func->drv_ctl) + ret = port->func->drv_ctl(port, lane, vsw, pre); + else + ret = 0; + } + + return ret; +} + +const struct nouveau_dp_func +nv50_pior_dp_func = { + .pattern = nv50_pior_dp_pattern, + .lnk_ctl = nv50_pior_dp_lnk_ctl, + .drv_ctl = nv50_pior_dp_drv_ctl, +}; + +/****************************************************************************** + * General PIOR handling + *****************************************************************************/ +int +nv50_pior_power(struct nv50_disp_priv *priv, int or, u32 data) +{ + const u32 stat = data & NV50_DISP_PIOR_PWR_STATE; + const u32 soff = (or * 0x800); + nv_wait(priv, 0x61e004 + soff, 0x80000000, 0x00000000); + nv_mask(priv, 0x61e004 + soff, 0x80000101, 0x80000000 | stat); + nv_wait(priv, 0x61e004 + soff, 0x80000000, 0x00000000); + return 0; +} + +int +nv50_pior_mthd(struct nouveau_object *object, u32 mthd, void *args, u32 size) +{ + struct nv50_disp_priv *priv = (void *)object->engine; + const u8 type = (mthd & NV50_DISP_PIOR_MTHD_TYPE) >> 12; + const u8 or = (mthd & NV50_DISP_PIOR_MTHD_OR); + u32 *data = args; + int ret; + + if (size < sizeof(u32)) + return -EINVAL; + + mthd &= ~NV50_DISP_PIOR_MTHD_TYPE; + mthd &= ~NV50_DISP_PIOR_MTHD_OR; + switch (mthd) { + case NV50_DISP_PIOR_PWR: + ret = priv->pior.power(priv, or, data[0]); + priv->pior.type[or] = type; + break; + default: + return -EINVAL; + } + + return ret; +} diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c index 39b6b67732d0..ab1e918469a8 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c @@ -79,31 +79,6 @@ nv50_sor_mthd(struct nouveau_object *object, u32 mthd, void *args, u32 size) priv->sor.lvdsconf = data & NV50_DISP_SOR_LVDS_SCRIPT_ID; ret = 0; break; - case NV94_DISP_SOR_DP_TRAIN: - switch (data & NV94_DISP_SOR_DP_TRAIN_OP) { - case NV94_DISP_SOR_DP_TRAIN_OP_PATTERN: - ret = priv->sor.dp_train(priv, or, link, type, mask, data, &outp); - break; - case NV94_DISP_SOR_DP_TRAIN_OP_INIT: - ret = priv->sor.dp_train_init(priv, or, link, head, type, mask, data, &outp); - break; - case NV94_DISP_SOR_DP_TRAIN_OP_FINI: - ret = priv->sor.dp_train_fini(priv, or, link, head, type, mask, data, &outp); - break; - default: - break; - } - break; - case NV94_DISP_SOR_DP_LNKCTL: - ret = priv->sor.dp_lnkctl(priv, or, link, head, type, mask, data, &outp); - break; - case NV94_DISP_SOR_DP_DRVCTL(0): - case NV94_DISP_SOR_DP_DRVCTL(1): - case NV94_DISP_SOR_DP_DRVCTL(2): - case NV94_DISP_SOR_DP_DRVCTL(3): - ret = priv->sor.dp_drvctl(priv, or, link, (mthd & 0xc0) >> 6, - type, mask, data, &outp); - break; default: BUG_ON(1); } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c b/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c index f6edd009762e..7ec4ee83fb64 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c @@ -33,124 +33,53 @@ #include "nv50.h" static inline u32 -nv94_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane) +nv94_sor_soff(struct dcb_output *outp) { - static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */ - static const u8 nv94[] = { 16, 8, 0, 24 }; - if (nv_device(priv)->chipset == 0xaf) - return nvaf[lane]; - return nv94[lane]; + return (ffs(outp->or) - 1) * 0x800; } -int -nv94_sor_dp_train_init(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static inline u32 +nv94_sor_loff(struct dcb_output *outp) { - struct nouveau_bios *bios = nouveau_bios(priv); - struct nvbios_dpout info; - u8 ver, hdr, cnt, len; - u16 outp; - - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - if (data & NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON) - init.offset = info.script[2]; - else - init.offset = info.script[3]; - nvbios_exec(&init); - - init.offset = info.script[0]; - nvbios_exec(&init); - } - - return 0; + return nv94_sor_soff(outp) + !(outp->sorconf.link & 1) * 0x80; } -int -nv94_sor_dp_train_fini(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static inline u32 +nv94_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane) { - struct nouveau_bios *bios = nouveau_bios(priv); - struct nvbios_dpout info; - u8 ver, hdr, cnt, len; - u16 outp; - - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .offset = info.script[1], - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - nvbios_exec(&init); - } - - return 0; + static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */ + static const u8 nv94[] = { 16, 8, 0, 24 }; + if (nv_device(priv)->chipset == 0xaf) + return nvaf[lane]; + return nv94[lane]; } -int -nv94_sor_dp_train(struct nv50_disp_priv *priv, int or, int link, - u16 type, u16 mask, u32 data, struct dcb_output *info) +static int +nv94_sor_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int pattern) { - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 patt = (data & NV94_DISP_SOR_DP_TRAIN_PATTERN); - nv_mask(priv, 0x61c10c + loff, 0x0f000000, patt << 24); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nv94_sor_loff(outp); + nv_mask(priv, 0x61c10c + loff, 0x0f000000, pattern << 24); return 0; } -int -nv94_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nv94_sor_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int link_nr, int link_bw, bool enh_frame) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 soff = (or * 0x800); - u16 link_bw = (data & NV94_DISP_SOR_DP_LNKCTL_WIDTH) >> 8; - u8 link_nr = (data & NV94_DISP_SOR_DP_LNKCTL_COUNT); + struct nv50_disp_priv *priv = (void *)disp; + const u32 soff = nv94_sor_soff(outp); + const u32 loff = nv94_sor_loff(outp); u32 dpctrl = 0x00000000; u32 clksor = 0x00000000; - u32 outp, lane = 0; - u8 ver, hdr, cnt, len; - struct nvbios_dpout info; + u32 lane = 0; int i; - /* -> 10Khz units */ - link_bw *= 2700; - - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp && info.lnkcmp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .offset = 0x0000, - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - while (link_bw < nv_ro16(bios, info.lnkcmp)) - info.lnkcmp += 4; - init.offset = nv_ro16(bios, info.lnkcmp + 2); - - nvbios_exec(&init); - } - dpctrl |= ((1 << link_nr) - 1) << 16; - if (data & NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH) + if (enh_frame) dpctrl |= 0x00004000; - if (link_bw > 16200) + if (link_bw > 0x06) clksor |= 0x00040000; for (i = 0; i < link_nr; i++) @@ -162,24 +91,25 @@ nv94_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, return 0; } -int -nv94_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nv94_sor_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane, int swing, int preem) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u8 swing = (data & NV94_DISP_SOR_DP_DRVCTL_VS) >> 8; - const u8 preem = (data & NV94_DISP_SOR_DP_DRVCTL_PE); + struct nouveau_bios *bios = nouveau_bios(disp); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nv94_sor_loff(outp); u32 addr, shift = nv94_sor_dp_lane_map(priv, lane); u8 ver, hdr, cnt, len; - struct nvbios_dpout outp; + struct nvbios_dpout info; struct nvbios_dpcfg ocfg; - addr = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &outp); + addr = nvbios_dpout_match(bios, outp->hasht, outp->hashm, + &ver, &hdr, &cnt, &len, &info); if (!addr) return -ENODEV; - addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, &ver, &hdr, &cnt, &len, &ocfg); + addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, + &ver, &hdr, &cnt, &len, &ocfg); if (!addr) return -EINVAL; @@ -188,3 +118,10 @@ nv94_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, nv_mask(priv, 0x61c130 + loff, 0x0000ff00, ocfg.unk << 8); return 0; } + +const struct nouveau_dp_func +nv94_sor_dp_func = { + .pattern = nv94_sor_dp_pattern, + .lnk_ctl = nv94_sor_dp_lnk_ctl, + .drv_ctl = nv94_sor_dp_drv_ctl, +}; diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c b/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c index c37ce7e29f5d..9e1d435d7282 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c @@ -33,59 +33,49 @@ #include "nv50.h" static inline u32 +nvd0_sor_soff(struct dcb_output *outp) +{ + return (ffs(outp->or) - 1) * 0x800; +} + +static inline u32 +nvd0_sor_loff(struct dcb_output *outp) +{ + return nvd0_sor_soff(outp) + !(outp->sorconf.link & 1) * 0x80; +} + +static inline u32 nvd0_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane) { static const u8 nvd0[] = { 16, 8, 0, 24 }; return nvd0[lane]; } -int -nvd0_sor_dp_train(struct nv50_disp_priv *priv, int or, int link, - u16 type, u16 mask, u32 data, struct dcb_output *info) +static int +nvd0_sor_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int pattern) { - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 patt = (data & NV94_DISP_SOR_DP_TRAIN_PATTERN); - nv_mask(priv, 0x61c110 + loff, 0x0f0f0f0f, 0x01010101 * patt); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nvd0_sor_loff(outp); + nv_mask(priv, 0x61c110 + loff, 0x0f0f0f0f, 0x01010101 * pattern); return 0; } -int -nvd0_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nvd0_sor_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int link_nr, int link_bw, bool enh_frame) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 soff = (or * 0x800); - const u8 link_bw = (data & NV94_DISP_SOR_DP_LNKCTL_WIDTH) >> 8; - const u8 link_nr = (data & NV94_DISP_SOR_DP_LNKCTL_COUNT); + struct nv50_disp_priv *priv = (void *)disp; + const u32 soff = nvd0_sor_soff(outp); + const u32 loff = nvd0_sor_loff(outp); u32 dpctrl = 0x00000000; u32 clksor = 0x00000000; - u32 outp, lane = 0; - u8 ver, hdr, cnt, len; - struct nvbios_dpout info; + u32 lane = 0; int i; - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp && info.lnkcmp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .offset = 0x0000, - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - while (nv_ro08(bios, info.lnkcmp) < link_bw) - info.lnkcmp += 3; - init.offset = nv_ro16(bios, info.lnkcmp + 1); - - nvbios_exec(&init); - } - clksor |= link_bw << 18; dpctrl |= ((1 << link_nr) - 1) << 16; - if (data & NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH) + if (enh_frame) dpctrl |= 0x00004000; for (i = 0; i < link_nr; i++) @@ -97,24 +87,25 @@ nvd0_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, return 0; } -int -nvd0_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nvd0_sor_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane, int swing, int preem) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u8 swing = (data & NV94_DISP_SOR_DP_DRVCTL_VS) >> 8; - const u8 preem = (data & NV94_DISP_SOR_DP_DRVCTL_PE); + struct nouveau_bios *bios = nouveau_bios(disp); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nvd0_sor_loff(outp); u32 addr, shift = nvd0_sor_dp_lane_map(priv, lane); u8 ver, hdr, cnt, len; - struct nvbios_dpout outp; + struct nvbios_dpout info; struct nvbios_dpcfg ocfg; - addr = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &outp); + addr = nvbios_dpout_match(bios, outp->hasht, outp->hashm, + &ver, &hdr, &cnt, &len, &info); if (!addr) return -ENODEV; - addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, &ver, &hdr, &cnt, &len, &ocfg); + addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, + &ver, &hdr, &cnt, &len, &ocfg); if (!addr) return -EINVAL; @@ -124,3 +115,10 @@ nvd0_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, nv_mask(priv, 0x61c13c + loff, 0x00000000, 0x00000000); return 0; } + +const struct nouveau_dp_func +nvd0_sor_dp_func = { + .pattern = nvd0_sor_dp_pattern, + .lnk_ctl = nvd0_sor_dp_lnk_ctl, + .drv_ctl = nvd0_sor_dp_drv_ctl, +}; diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/base.c b/drivers/gpu/drm/nouveau/core/engine/fifo/base.c index c2b9db335816..7341ebe131fa 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/base.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/base.c @@ -22,8 +22,10 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/object.h> #include <core/handle.h> +#include <core/event.h> #include <core/class.h> #include <engine/dmaobj.h> @@ -146,10 +148,25 @@ nouveau_fifo_chid(struct nouveau_fifo *priv, struct nouveau_object *object) return -1; } +const char * +nouveau_client_name_for_fifo_chid(struct nouveau_fifo *fifo, u32 chid) +{ + struct nouveau_fifo_chan *chan = NULL; + unsigned long flags; + + spin_lock_irqsave(&fifo->lock, flags); + if (chid >= fifo->min && chid <= fifo->max) + chan = (void *)fifo->channel[chid]; + spin_unlock_irqrestore(&fifo->lock, flags); + + return nouveau_client_name(chan); +} + void nouveau_fifo_destroy(struct nouveau_fifo *priv) { kfree(priv->channel); + nouveau_event_destroy(&priv->uevent); nouveau_engine_destroy(&priv->base); } @@ -174,6 +191,10 @@ nouveau_fifo_create_(struct nouveau_object *parent, if (!priv->channel) return -ENOMEM; + ret = nouveau_event_create(1, &priv->uevent); + if (ret) + return ret; + priv->chid = nouveau_fifo_chid; spin_lock_init(&priv->lock); return 0; diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c index a47a8548f9e0..f877bd524a92 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c @@ -28,6 +28,7 @@ #include <core/namedb.h> #include <core/handle.h> #include <core/ramht.h> +#include <core/event.h> #include <subdev/instmem.h> #include <subdev/instmem/nv04.h> @@ -398,6 +399,98 @@ out: return handled; } +static void +nv04_fifo_cache_error(struct nouveau_device *device, + struct nv04_fifo_priv *priv, u32 chid, u32 get) +{ + u32 mthd, data; + int ptr; + + /* NV_PFIFO_CACHE1_GET actually goes to 0xffc before wrapping on my + * G80 chips, but CACHE1 isn't big enough for this much data.. Tests + * show that it wraps around to the start at GET=0x800.. No clue as to + * why.. + */ + ptr = (get & 0x7ff) >> 2; + + if (device->card_type < NV_40) { + mthd = nv_rd32(priv, NV04_PFIFO_CACHE1_METHOD(ptr)); + data = nv_rd32(priv, NV04_PFIFO_CACHE1_DATA(ptr)); + } else { + mthd = nv_rd32(priv, NV40_PFIFO_CACHE1_METHOD(ptr)); + data = nv_rd32(priv, NV40_PFIFO_CACHE1_DATA(ptr)); + } + + if (!nv04_fifo_swmthd(priv, chid, mthd, data)) { + const char *client_name = + nouveau_client_name_for_fifo_chid(&priv->base, chid); + nv_error(priv, + "CACHE_ERROR - ch %d [%s] subc %d mthd 0x%04x data 0x%08x\n", + chid, client_name, (mthd >> 13) & 7, mthd & 0x1ffc, + data); + } + + nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, 0); + nv_wr32(priv, NV03_PFIFO_INTR_0, NV_PFIFO_INTR_CACHE_ERROR); + + nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, + nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) & ~1); + nv_wr32(priv, NV03_PFIFO_CACHE1_GET, get + 4); + nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, + nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) | 1); + nv_wr32(priv, NV04_PFIFO_CACHE1_HASH, 0); + + nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, + nv_rd32(priv, NV04_PFIFO_CACHE1_DMA_PUSH) | 1); + nv_wr32(priv, NV04_PFIFO_CACHE1_PULL0, 1); +} + +static void +nv04_fifo_dma_pusher(struct nouveau_device *device, struct nv04_fifo_priv *priv, + u32 chid) +{ + const char *client_name; + u32 dma_get = nv_rd32(priv, 0x003244); + u32 dma_put = nv_rd32(priv, 0x003240); + u32 push = nv_rd32(priv, 0x003220); + u32 state = nv_rd32(priv, 0x003228); + + client_name = nouveau_client_name_for_fifo_chid(&priv->base, chid); + + if (device->card_type == NV_50) { + u32 ho_get = nv_rd32(priv, 0x003328); + u32 ho_put = nv_rd32(priv, 0x003320); + u32 ib_get = nv_rd32(priv, 0x003334); + u32 ib_put = nv_rd32(priv, 0x003330); + + nv_error(priv, + "DMA_PUSHER - ch %d [%s] get 0x%02x%08x put 0x%02x%08x ib_get 0x%08x ib_put 0x%08x state 0x%08x (err: %s) push 0x%08x\n", + chid, client_name, ho_get, dma_get, ho_put, dma_put, + ib_get, ib_put, state, nv_dma_state_err(state), push); + + /* METHOD_COUNT, in DMA_STATE on earlier chipsets */ + nv_wr32(priv, 0x003364, 0x00000000); + if (dma_get != dma_put || ho_get != ho_put) { + nv_wr32(priv, 0x003244, dma_put); + nv_wr32(priv, 0x003328, ho_put); + } else + if (ib_get != ib_put) + nv_wr32(priv, 0x003334, ib_put); + } else { + nv_error(priv, + "DMA_PUSHER - ch %d [%s] get 0x%08x put 0x%08x state 0x%08x (err: %s) push 0x%08x\n", + chid, client_name, dma_get, dma_put, state, + nv_dma_state_err(state), push); + + if (dma_get != dma_put) + nv_wr32(priv, 0x003244, dma_put); + } + + nv_wr32(priv, 0x003228, 0x00000000); + nv_wr32(priv, 0x003220, 0x00000001); + nv_wr32(priv, 0x002100, NV_PFIFO_INTR_DMA_PUSHER); +} + void nv04_fifo_intr(struct nouveau_subdev *subdev) { @@ -416,96 +509,12 @@ nv04_fifo_intr(struct nouveau_subdev *subdev) get = nv_rd32(priv, NV03_PFIFO_CACHE1_GET); if (status & NV_PFIFO_INTR_CACHE_ERROR) { - uint32_t mthd, data; - int ptr; - - /* NV_PFIFO_CACHE1_GET actually goes to 0xffc before - * wrapping on my G80 chips, but CACHE1 isn't big - * enough for this much data.. Tests show that it - * wraps around to the start at GET=0x800.. No clue - * as to why.. - */ - ptr = (get & 0x7ff) >> 2; - - if (device->card_type < NV_40) { - mthd = nv_rd32(priv, - NV04_PFIFO_CACHE1_METHOD(ptr)); - data = nv_rd32(priv, - NV04_PFIFO_CACHE1_DATA(ptr)); - } else { - mthd = nv_rd32(priv, - NV40_PFIFO_CACHE1_METHOD(ptr)); - data = nv_rd32(priv, - NV40_PFIFO_CACHE1_DATA(ptr)); - } - - if (!nv04_fifo_swmthd(priv, chid, mthd, data)) { - nv_error(priv, "CACHE_ERROR - Ch %d/%d " - "Mthd 0x%04x Data 0x%08x\n", - chid, (mthd >> 13) & 7, mthd & 0x1ffc, - data); - } - - nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, 0); - nv_wr32(priv, NV03_PFIFO_INTR_0, - NV_PFIFO_INTR_CACHE_ERROR); - - nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, - nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) & ~1); - nv_wr32(priv, NV03_PFIFO_CACHE1_GET, get + 4); - nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, - nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) | 1); - nv_wr32(priv, NV04_PFIFO_CACHE1_HASH, 0); - - nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, - nv_rd32(priv, NV04_PFIFO_CACHE1_DMA_PUSH) | 1); - nv_wr32(priv, NV04_PFIFO_CACHE1_PULL0, 1); - + nv04_fifo_cache_error(device, priv, chid, get); status &= ~NV_PFIFO_INTR_CACHE_ERROR; } if (status & NV_PFIFO_INTR_DMA_PUSHER) { - u32 dma_get = nv_rd32(priv, 0x003244); - u32 dma_put = nv_rd32(priv, 0x003240); - u32 push = nv_rd32(priv, 0x003220); - u32 state = nv_rd32(priv, 0x003228); - - if (device->card_type == NV_50) { - u32 ho_get = nv_rd32(priv, 0x003328); - u32 ho_put = nv_rd32(priv, 0x003320); - u32 ib_get = nv_rd32(priv, 0x003334); - u32 ib_put = nv_rd32(priv, 0x003330); - - nv_error(priv, "DMA_PUSHER - Ch %d Get 0x%02x%08x " - "Put 0x%02x%08x IbGet 0x%08x IbPut 0x%08x " - "State 0x%08x (err: %s) Push 0x%08x\n", - chid, ho_get, dma_get, ho_put, - dma_put, ib_get, ib_put, state, - nv_dma_state_err(state), - push); - - /* METHOD_COUNT, in DMA_STATE on earlier chipsets */ - nv_wr32(priv, 0x003364, 0x00000000); - if (dma_get != dma_put || ho_get != ho_put) { - nv_wr32(priv, 0x003244, dma_put); - nv_wr32(priv, 0x003328, ho_put); - } else - if (ib_get != ib_put) { - nv_wr32(priv, 0x003334, ib_put); - } - } else { - nv_error(priv, "DMA_PUSHER - Ch %d Get 0x%08x " - "Put 0x%08x State 0x%08x (err: %s) Push 0x%08x\n", - chid, dma_get, dma_put, state, - nv_dma_state_err(state), push); - - if (dma_get != dma_put) - nv_wr32(priv, 0x003244, dma_put); - } - - nv_wr32(priv, 0x003228, 0x00000000); - nv_wr32(priv, 0x003220, 0x00000001); - nv_wr32(priv, 0x002100, NV_PFIFO_INTR_DMA_PUSHER); + nv04_fifo_dma_pusher(device, priv, chid); status &= ~NV_PFIFO_INTR_DMA_PUSHER; } @@ -528,6 +537,12 @@ nv04_fifo_intr(struct nouveau_subdev *subdev) status &= ~0x00000010; nv_wr32(priv, 0x002100, 0x00000010); } + + if (status & 0x40000000) { + nouveau_event_trigger(priv->base.uevent, 0); + nv_wr32(priv, 0x002100, 0x40000000); + status &= ~0x40000000; + } } if (status) { diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c index bd096364f680..840af6172788 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c @@ -129,7 +129,8 @@ nv50_fifo_context_detach(struct nouveau_object *parent, bool suspend, /* do the kickoff... */ nv_wr32(priv, 0x0032fc, nv_gpuobj(base)->addr >> 12); if (!nv_wait_ne(priv, 0x0032fc, 0xffffffff, 0xffffffff)) { - nv_error(priv, "channel %d unload timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] unload timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) ret = -EBUSY; } @@ -480,7 +481,7 @@ nv50_fifo_init(struct nouveau_object *object) nv_wr32(priv, 0x002044, 0x01003fff); nv_wr32(priv, 0x002100, 0xffffffff); - nv_wr32(priv, 0x002140, 0xffffffff); + nv_wr32(priv, 0x002140, 0xbfffffff); for (i = 0; i < 128; i++) nv_wr32(priv, 0x002600 + (i * 4), 0x00000000); diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c index 1eb1c512f503..094000e87871 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c @@ -26,6 +26,7 @@ #include <core/client.h> #include <core/engctx.h> #include <core/ramht.h> +#include <core/event.h> #include <core/class.h> #include <core/math.h> @@ -100,7 +101,8 @@ nv84_fifo_context_detach(struct nouveau_object *parent, bool suspend, done = nv_wait_ne(priv, 0x0032fc, 0xffffffff, 0xffffffff); nv_wr32(priv, 0x002520, save); if (!done) { - nv_error(priv, "channel %d unload timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] unload timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) return -EBUSY; } @@ -378,6 +380,20 @@ nv84_fifo_cclass = { * PFIFO engine ******************************************************************************/ +static void +nv84_fifo_uevent_enable(struct nouveau_event *event, int index) +{ + struct nv84_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x40000000, 0x40000000); +} + +static void +nv84_fifo_uevent_disable(struct nouveau_event *event, int index) +{ + struct nv84_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x40000000, 0x00000000); +} + static int nv84_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -401,6 +417,10 @@ nv84_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, if (ret) return ret; + priv->base.uevent->enable = nv84_fifo_uevent_enable; + priv->base.uevent->disable = nv84_fifo_uevent_disable; + priv->base.uevent->priv = priv; + nv_subdev(priv)->unit = 0x00000100; nv_subdev(priv)->intr = nv04_fifo_intr; nv_engine(priv)->cclass = &nv84_fifo_cclass; diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c index b4365dde1859..4f226afb5591 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c @@ -27,6 +27,7 @@ #include <core/namedb.h> #include <core/gpuobj.h> #include <core/engctx.h> +#include <core/event.h> #include <core/class.h> #include <core/math.h> #include <core/enum.h> @@ -149,7 +150,8 @@ nvc0_fifo_context_detach(struct nouveau_object *parent, bool suspend, nv_wr32(priv, 0x002634, chan->base.chid); if (!nv_wait(priv, 0x002634, 0xffffffff, chan->base.chid)) { - nv_error(priv, "channel %d kick timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] kick timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) return -EBUSY; } @@ -333,17 +335,17 @@ nvc0_fifo_cclass = { ******************************************************************************/ static const struct nouveau_enum nvc0_fifo_fault_unit[] = { - { 0x00, "PGRAPH" }, + { 0x00, "PGRAPH", NULL, NVDEV_ENGINE_GR }, { 0x03, "PEEPHOLE" }, { 0x04, "BAR1" }, { 0x05, "BAR3" }, - { 0x07, "PFIFO" }, - { 0x10, "PBSP" }, - { 0x11, "PPPP" }, + { 0x07, "PFIFO", NULL, NVDEV_ENGINE_FIFO }, + { 0x10, "PBSP", NULL, NVDEV_ENGINE_BSP }, + { 0x11, "PPPP", NULL, NVDEV_ENGINE_PPP }, { 0x13, "PCOUNTER" }, - { 0x14, "PVP" }, - { 0x15, "PCOPY0" }, - { 0x16, "PCOPY1" }, + { 0x14, "PVP", NULL, NVDEV_ENGINE_VP }, + { 0x15, "PCOPY0", NULL, NVDEV_ENGINE_COPY0 }, + { 0x16, "PCOPY1", NULL, NVDEV_ENGINE_COPY1 }, { 0x17, "PDAEMON" }, {} }; @@ -402,6 +404,9 @@ nvc0_fifo_isr_vm_fault(struct nvc0_fifo_priv *priv, int unit) u32 vahi = nv_rd32(priv, 0x002808 + (unit * 0x10)); u32 stat = nv_rd32(priv, 0x00280c + (unit * 0x10)); u32 client = (stat & 0x00001f00) >> 8; + const struct nouveau_enum *en; + struct nouveau_engine *engine; + struct nouveau_object *engctx = NULL; switch (unit) { case 3: /* PEEPHOLE */ @@ -420,16 +425,26 @@ nvc0_fifo_isr_vm_fault(struct nvc0_fifo_priv *priv, int unit) nv_error(priv, "%s fault at 0x%010llx [", (stat & 0x00000080) ? "write" : "read", (u64)vahi << 32 | valo); nouveau_enum_print(nvc0_fifo_fault_reason, stat & 0x0000000f); - printk("] from "); - nouveau_enum_print(nvc0_fifo_fault_unit, unit); + pr_cont("] from "); + en = nouveau_enum_print(nvc0_fifo_fault_unit, unit); if (stat & 0x00000040) { - printk("/"); + pr_cont("/"); nouveau_enum_print(nvc0_fifo_fault_hubclient, client); } else { - printk("/GPC%d/", (stat & 0x1f000000) >> 24); + pr_cont("/GPC%d/", (stat & 0x1f000000) >> 24); nouveau_enum_print(nvc0_fifo_fault_gpcclient, client); } - printk(" on channel 0x%010llx\n", (u64)inst << 12); + + if (en && en->data2) { + engine = nouveau_engine(priv, en->data2); + if (engine) + engctx = nouveau_engctx_get(engine, inst); + + } + pr_cont(" on channel 0x%010llx [%s]\n", (u64)inst << 12, + nouveau_client_name(engctx)); + + nouveau_engctx_put(engctx); } static int @@ -484,10 +499,12 @@ nvc0_fifo_isr_subfifo_intr(struct nvc0_fifo_priv *priv, int unit) if (show) { nv_error(priv, "SUBFIFO%d:", unit); nouveau_bitfield_print(nvc0_fifo_subfifo_intr, show); - printk("\n"); - nv_error(priv, "SUBFIFO%d: ch %d subc %d mthd 0x%04x " - "data 0x%08x\n", - unit, chid, subc, mthd, data); + pr_cont("\n"); + nv_error(priv, + "SUBFIFO%d: ch %d [%s] subc %d mthd 0x%04x data 0x%08x\n", + unit, chid, + nouveau_client_name_for_fifo_chid(&priv->base, chid), + subc, mthd, data); } nv_wr32(priv, 0x0400c0 + (unit * 0x2000), 0x80600008); @@ -501,12 +518,34 @@ nvc0_fifo_intr(struct nouveau_subdev *subdev) u32 mask = nv_rd32(priv, 0x002140); u32 stat = nv_rd32(priv, 0x002100) & mask; + if (stat & 0x00000001) { + u32 intr = nv_rd32(priv, 0x00252c); + nv_warn(priv, "INTR 0x00000001: 0x%08x\n", intr); + nv_wr32(priv, 0x002100, 0x00000001); + stat &= ~0x00000001; + } + if (stat & 0x00000100) { - nv_warn(priv, "unknown status 0x00000100\n"); + u32 intr = nv_rd32(priv, 0x00254c); + nv_warn(priv, "INTR 0x00000100: 0x%08x\n", intr); nv_wr32(priv, 0x002100, 0x00000100); stat &= ~0x00000100; } + if (stat & 0x00010000) { + u32 intr = nv_rd32(priv, 0x00256c); + nv_warn(priv, "INTR 0x00010000: 0x%08x\n", intr); + nv_wr32(priv, 0x002100, 0x00010000); + stat &= ~0x00010000; + } + + if (stat & 0x01000000) { + u32 intr = nv_rd32(priv, 0x00258c); + nv_warn(priv, "INTR 0x01000000: 0x%08x\n", intr); + nv_wr32(priv, 0x002100, 0x01000000); + stat &= ~0x01000000; + } + if (stat & 0x10000000) { u32 units = nv_rd32(priv, 0x00259c); u32 u = units; @@ -536,11 +575,20 @@ nvc0_fifo_intr(struct nouveau_subdev *subdev) } if (stat & 0x40000000) { - nv_warn(priv, "unknown status 0x40000000\n"); - nv_mask(priv, 0x002a00, 0x00000000, 0x00000000); + u32 intr0 = nv_rd32(priv, 0x0025a4); + u32 intr1 = nv_mask(priv, 0x002a00, 0x00000000, 0x00000); + nv_debug(priv, "INTR 0x40000000: 0x%08x 0x%08x\n", + intr0, intr1); stat &= ~0x40000000; } + if (stat & 0x80000000) { + u32 intr = nv_mask(priv, 0x0025a8, 0x00000000, 0x00000000); + nouveau_event_trigger(priv->base.uevent, 0); + nv_debug(priv, "INTR 0x80000000: 0x%08x\n", intr); + stat &= ~0x80000000; + } + if (stat) { nv_fatal(priv, "unhandled status 0x%08x\n", stat); nv_wr32(priv, 0x002100, stat); @@ -548,6 +596,20 @@ nvc0_fifo_intr(struct nouveau_subdev *subdev) } } +static void +nvc0_fifo_uevent_enable(struct nouveau_event *event, int index) +{ + struct nvc0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x80000000); +} + +static void +nvc0_fifo_uevent_disable(struct nouveau_event *event, int index) +{ + struct nvc0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x00000000); +} + static int nvc0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -581,6 +643,10 @@ nvc0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, if (ret) return ret; + priv->base.uevent->enable = nvc0_fifo_uevent_enable; + priv->base.uevent->disable = nvc0_fifo_uevent_disable; + priv->base.uevent->priv = priv; + nv_subdev(priv)->unit = 0x00000100; nv_subdev(priv)->intr = nvc0_fifo_intr; nv_engine(priv)->cclass = &nvc0_fifo_cclass; @@ -639,7 +705,8 @@ nvc0_fifo_init(struct nouveau_object *object) nv_wr32(priv, 0x002a00, 0xffffffff); /* clears PFIFO.INTR bit 30 */ nv_wr32(priv, 0x002100, 0xffffffff); - nv_wr32(priv, 0x002140, 0xbfffffff); + nv_wr32(priv, 0x002140, 0x3fffffff); + nv_wr32(priv, 0x002628, 0x00000001); /* makes mthd 0x20 work */ return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c index c930da99c2c1..4419e40d88e9 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c @@ -27,6 +27,7 @@ #include <core/namedb.h> #include <core/gpuobj.h> #include <core/engctx.h> +#include <core/event.h> #include <core/class.h> #include <core/math.h> #include <core/enum.h> @@ -184,7 +185,8 @@ nve0_fifo_context_detach(struct nouveau_object *parent, bool suspend, nv_wr32(priv, 0x002634, chan->base.chid); if (!nv_wait(priv, 0x002634, 0xffffffff, chan->base.chid)) { - nv_error(priv, "channel %d kick timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] kick timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) return -EBUSY; } @@ -412,20 +414,34 @@ nve0_fifo_isr_vm_fault(struct nve0_fifo_priv *priv, int unit) u32 vahi = nv_rd32(priv, 0x2808 + (unit * 0x10)); u32 stat = nv_rd32(priv, 0x280c + (unit * 0x10)); u32 client = (stat & 0x00001f00) >> 8; + const struct nouveau_enum *en; + struct nouveau_engine *engine; + struct nouveau_object *engctx = NULL; nv_error(priv, "PFIFO: %s fault at 0x%010llx [", (stat & 0x00000080) ? "write" : "read", (u64)vahi << 32 | valo); nouveau_enum_print(nve0_fifo_fault_reason, stat & 0x0000000f); - printk("] from "); - nouveau_enum_print(nve0_fifo_fault_unit, unit); + pr_cont("] from "); + en = nouveau_enum_print(nve0_fifo_fault_unit, unit); if (stat & 0x00000040) { - printk("/"); + pr_cont("/"); nouveau_enum_print(nve0_fifo_fault_hubclient, client); } else { - printk("/GPC%d/", (stat & 0x1f000000) >> 24); + pr_cont("/GPC%d/", (stat & 0x1f000000) >> 24); nouveau_enum_print(nve0_fifo_fault_gpcclient, client); } - printk(" on channel 0x%010llx\n", (u64)inst << 12); + + if (en && en->data2) { + engine = nouveau_engine(priv, en->data2); + if (engine) + engctx = nouveau_engctx_get(engine, inst); + + } + + pr_cont(" on channel 0x%010llx [%s]\n", (u64)inst << 12, + nouveau_client_name(engctx)); + + nouveau_engctx_put(engctx); } static int @@ -480,10 +496,12 @@ nve0_fifo_isr_subfifo_intr(struct nve0_fifo_priv *priv, int unit) if (show) { nv_error(priv, "SUBFIFO%d:", unit); nouveau_bitfield_print(nve0_fifo_subfifo_intr, show); - printk("\n"); - nv_error(priv, "SUBFIFO%d: ch %d subc %d mthd 0x%04x " - "data 0x%08x\n", - unit, chid, subc, mthd, data); + pr_cont("\n"); + nv_error(priv, + "SUBFIFO%d: ch %d [%s] subc %d mthd 0x%04x data 0x%08x\n", + unit, chid, + nouveau_client_name_for_fifo_chid(&priv->base, chid), + subc, mthd, data); } nv_wr32(priv, 0x0400c0 + (unit * 0x2000), 0x80600008); @@ -537,6 +555,12 @@ nve0_fifo_intr(struct nouveau_subdev *subdev) stat &= ~0x40000000; } + if (stat & 0x80000000) { + nouveau_event_trigger(priv->base.uevent, 0); + nv_wr32(priv, 0x002100, 0x80000000); + stat &= ~0x80000000; + } + if (stat) { nv_fatal(priv, "unhandled status 0x%08x\n", stat); nv_wr32(priv, 0x002100, stat); @@ -544,6 +568,20 @@ nve0_fifo_intr(struct nouveau_subdev *subdev) } } +static void +nve0_fifo_uevent_enable(struct nouveau_event *event, int index) +{ + struct nve0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x80000000); +} + +static void +nve0_fifo_uevent_disable(struct nouveau_event *event, int index) +{ + struct nve0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x00000000); +} + static int nve0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -567,6 +605,10 @@ nve0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, if (ret) return ret; + priv->base.uevent->enable = nve0_fifo_uevent_enable; + priv->base.uevent->disable = nve0_fifo_uevent_disable; + priv->base.uevent->priv = priv; + nv_subdev(priv)->unit = 0x00000100; nv_subdev(priv)->intr = nve0_fifo_intr; nv_engine(priv)->cclass = &nve0_fifo_cclass; @@ -617,7 +659,7 @@ nve0_fifo_init(struct nouveau_object *object) nv_wr32(priv, 0x002a00, 0xffffffff); nv_wr32(priv, 0x002100, 0xffffffff); - nv_wr32(priv, 0x002140, 0xbfffffff); + nv_wr32(priv, 0x002140, 0x3fffffff); return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c index e30a9c5ff1fc..ad13dcdd15f9 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c @@ -22,6 +22,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/handle.h> @@ -1297,16 +1298,17 @@ nv04_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv04_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv04_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d/%d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [%s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, nouveau_client_name(chan), subc, class, mthd, + data); } nouveau_namedb_put(handle); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c index 5c0f843ea249..23c143aaa556 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c @@ -22,6 +22,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/handle.h> @@ -1193,16 +1194,17 @@ nv10_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv10_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv10_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d/%d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [%s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, nouveau_client_name(chan), subc, class, mthd, + data); } nouveau_namedb_put(handle); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c index 5b20401bf911..0607b9801748 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c @@ -1,3 +1,4 @@ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/engctx.h> @@ -224,15 +225,17 @@ nv20_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv10_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv10_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d/%d class 0x%04x mthd 0x%04x data 0x%08x\n", - chid, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [%s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, nouveau_client_name(engctx), subc, class, mthd, + data); } nouveau_engctx_put(engctx); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c index 0b36dd3deebd..17049d5c723d 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/handle.h> @@ -321,16 +322,17 @@ nv40_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv10_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv10_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d [0x%08x] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, inst << 4, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [0x%08x %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 4, nouveau_client_name(engctx), subc, + class, mthd, data); } nouveau_engctx_put(engctx); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c index b1c3d835b4c2..f2b1a7a124f2 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c @@ -24,6 +24,7 @@ #include <core/os.h> #include <core/class.h> +#include <core/client.h> #include <core/handle.h> #include <core/engctx.h> #include <core/enum.h> @@ -418,7 +419,7 @@ nv50_priv_mp_trap(struct nv50_graph_priv *priv, int tpid, int display) nv_error(priv, "TRAP_MP_EXEC - " "TP %d MP %d: ", tpid, i); nouveau_enum_print(nv50_mp_exec_error_names, status); - printk(" at %06x warp %d, opcode %08x %08x\n", + pr_cont(" at %06x warp %d, opcode %08x %08x\n", pc&0xffffff, pc >> 24, oplow, ophigh); } @@ -532,7 +533,7 @@ nv50_priv_tp_trap(struct nv50_graph_priv *priv, int type, u32 ustatus_old, static int nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, - int chid, u64 inst) + int chid, u64 inst, struct nouveau_object *engctx) { u32 status = nv_rd32(priv, 0x400108); u32 ustatus; @@ -565,12 +566,11 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, nv_error(priv, "TRAP DISPATCH_FAULT\n"); if (display && (addr & 0x80000000)) { - nv_error(priv, "ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x%08x " - "400808 0x%08x 400848 0x%08x\n", - chid, inst, subc, class, mthd, datah, - datal, addr, r848); + nv_error(priv, + "ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x%08x 400808 0x%08x 400848 0x%08x\n", + chid, inst, + nouveau_client_name(engctx), subc, + class, mthd, datah, datal, addr, r848); } else if (display) { nv_error(priv, "no stuck command?\n"); @@ -591,11 +591,11 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, nv_error(priv, "TRAP DISPATCH_QUERY\n"); if (display && (addr & 0x80000000)) { - nv_error(priv, "ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x 40084c 0x%08x\n", - chid, inst, subc, class, mthd, - data, addr); + nv_error(priv, + "ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x 40084c 0x%08x\n", + chid, inst, + nouveau_client_name(engctx), subc, + class, mthd, data, addr); } else if (display) { nv_error(priv, "no stuck command?\n"); @@ -623,7 +623,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_M2MF"); nouveau_bitfield_print(nv50_graph_trap_m2mf, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_M2MF %08x %08x %08x %08x\n", nv_rd32(priv, 0x406804), nv_rd32(priv, 0x406808), nv_rd32(priv, 0x40680c), nv_rd32(priv, 0x406810)); @@ -644,7 +644,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_VFETCH"); nouveau_bitfield_print(nv50_graph_trap_vfetch, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_VFETCH %08x %08x %08x %08x\n", nv_rd32(priv, 0x400c00), nv_rd32(priv, 0x400c08), nv_rd32(priv, 0x400c0c), nv_rd32(priv, 0x400c10)); @@ -661,7 +661,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_STRMOUT"); nouveau_bitfield_print(nv50_graph_trap_strmout, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_STRMOUT %08x %08x %08x %08x\n", nv_rd32(priv, 0x401804), nv_rd32(priv, 0x401808), nv_rd32(priv, 0x40180c), nv_rd32(priv, 0x401810)); @@ -682,7 +682,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_CCACHE"); nouveau_bitfield_print(nv50_graph_trap_ccache, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_CCACHE %08x %08x %08x %08x" " %08x %08x %08x\n", nv_rd32(priv, 0x405000), nv_rd32(priv, 0x405004), @@ -774,11 +774,12 @@ nv50_graph_intr(struct nouveau_subdev *subdev) u32 ecode = nv_rd32(priv, 0x400110); nv_error(priv, "DATA_ERROR "); nouveau_enum_print(nv50_data_error_names, ecode); - printk("\n"); + pr_cont("\n"); } if (stat & 0x00200000) { - if (!nv50_graph_trap_handler(priv, show, chid, (u64)inst << 12)) + if (!nv50_graph_trap_handler(priv, show, chid, (u64)inst << 12, + engctx)) show &= ~0x00200000; } @@ -786,12 +787,13 @@ nv50_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, 0x400500, 0x00010001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv50_graph_intr_name, show); - printk("\n"); - nv_error(priv, "ch %d [0x%010llx] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, (u64)inst << 12, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, (u64)inst << 12, nouveau_client_name(engctx), + subc, class, mthd, data); } if (nv_rd32(priv, 0x400824) & (1 << 31)) @@ -907,9 +909,8 @@ nv50_graph_init(struct nouveau_object *object) nv_wr32(priv, 0x400828, 0x00000000); nv_wr32(priv, 0x40082c, 0x00000000); nv_wr32(priv, 0x400830, 0x00000000); - nv_wr32(priv, 0x400724, 0x00000000); nv_wr32(priv, 0x40032c, 0x00000000); - nv_wr32(priv, 0x400320, 4); /* CTXCTL_CMD = NEWCTXDMA */ + nv_wr32(priv, 0x400330, 0x00000000); /* some unknown zcull magic */ switch (nv_device(priv)->chipset & 0xf0) { diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c index 45aff5f5085a..0de0dd724aff 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c @@ -433,10 +433,10 @@ nvc0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00000010) { handle = nouveau_handle_get_class(engctx, class); if (!handle || nv_call(handle->object, mthd, data)) { - nv_error(priv, "ILLEGAL_MTHD ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x\n", - chid, inst << 12, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_MTHD ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), + subc, class, mthd, data); } nouveau_handle_put(handle); nv_wr32(priv, 0x400100, 0x00000010); @@ -444,9 +444,10 @@ nvc0_graph_intr(struct nouveau_subdev *subdev) } if (stat & 0x00000020) { - nv_error(priv, "ILLEGAL_CLASS ch %d [0x%010llx] subc %d " - "class 0x%04x mthd 0x%04x data 0x%08x\n", - chid, inst << 12, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_CLASS ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), subc, + class, mthd, data); nv_wr32(priv, 0x400100, 0x00000020); stat &= ~0x00000020; } @@ -454,15 +455,16 @@ nvc0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00100000) { nv_error(priv, "DATA_ERROR ["); nouveau_enum_print(nv50_data_error_names, code); - printk("] ch %d [0x%010llx] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, inst << 12, subc, class, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), subc, + class, mthd, data); nv_wr32(priv, 0x400100, 0x00100000); stat &= ~0x00100000; } if (stat & 0x00200000) { - nv_error(priv, "TRAP ch %d [0x%010llx]\n", chid, inst << 12); + nv_error(priv, "TRAP ch %d [0x%010llx %s]\n", chid, inst << 12, + nouveau_client_name(engctx)); nvc0_graph_trap_intr(priv); nv_wr32(priv, 0x400100, 0x00200000); stat &= ~0x00200000; @@ -611,10 +613,8 @@ nvc0_graph_ctor(struct nouveau_object *parent, struct nouveau_object *engine, static void nvc0_graph_dtor_fw(struct nvc0_graph_fuc *fuc) { - if (fuc->data) { - kfree(fuc->data); - fuc->data = NULL; - } + kfree(fuc->data); + fuc->data = NULL; } void @@ -622,8 +622,7 @@ nvc0_graph_dtor(struct nouveau_object *object) { struct nvc0_graph_priv *priv = (void *)object; - if (priv->data) - kfree(priv->data); + kfree(priv->data); nvc0_graph_dtor_fw(&priv->fuc409c); nvc0_graph_dtor_fw(&priv->fuc409d); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c b/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c index 9f82e9702b46..61cec0f6ff1c 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c @@ -78,15 +78,16 @@ nve0_graph_ctxctl_isr(struct nvc0_graph_priv *priv) } static void -nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) +nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst, + struct nouveau_object *engctx) { u32 trap = nv_rd32(priv, 0x400108); int rop; if (trap & 0x00000001) { u32 stat = nv_rd32(priv, 0x404000); - nv_error(priv, "DISPATCH ch %d [0x%010llx] 0x%08x\n", - chid, inst, stat); + nv_error(priv, "DISPATCH ch %d [0x%010llx %s] 0x%08x\n", + chid, inst, nouveau_client_name(engctx), stat); nv_wr32(priv, 0x404000, 0xc0000000); nv_wr32(priv, 0x400108, 0x00000001); trap &= ~0x00000001; @@ -94,8 +95,8 @@ nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) if (trap & 0x00000010) { u32 stat = nv_rd32(priv, 0x405840); - nv_error(priv, "SHADER ch %d [0x%010llx] 0x%08x\n", - chid, inst, stat); + nv_error(priv, "SHADER ch %d [0x%010llx %s] 0x%08x\n", + chid, inst, nouveau_client_name(engctx), stat); nv_wr32(priv, 0x405840, 0xc0000000); nv_wr32(priv, 0x400108, 0x00000010); trap &= ~0x00000010; @@ -105,8 +106,10 @@ nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) for (rop = 0; rop < priv->rop_nr; rop++) { u32 statz = nv_rd32(priv, ROP_UNIT(rop, 0x070)); u32 statc = nv_rd32(priv, ROP_UNIT(rop, 0x144)); - nv_error(priv, "ROP%d ch %d [0x%010llx] 0x%08x 0x%08x\n", - rop, chid, inst, statz, statc); + nv_error(priv, + "ROP%d ch %d [0x%010llx %s] 0x%08x 0x%08x\n", + rop, chid, inst, nouveau_client_name(engctx), + statz, statc); nv_wr32(priv, ROP_UNIT(rop, 0x070), 0xc0000000); nv_wr32(priv, ROP_UNIT(rop, 0x144), 0xc0000000); } @@ -115,8 +118,8 @@ nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) } if (trap) { - nv_error(priv, "TRAP ch %d [0x%010llx] 0x%08x\n", - chid, inst, trap); + nv_error(priv, "TRAP ch %d [0x%010llx %s] 0x%08x\n", + chid, inst, nouveau_client_name(engctx), trap); nv_wr32(priv, 0x400108, trap); } } @@ -145,10 +148,10 @@ nve0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00000010) { handle = nouveau_handle_get_class(engctx, class); if (!handle || nv_call(handle->object, mthd, data)) { - nv_error(priv, "ILLEGAL_MTHD ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x\n", - chid, inst, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_MTHD ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, nouveau_client_name(engctx), subc, + class, mthd, data); } nouveau_handle_put(handle); nv_wr32(priv, 0x400100, 0x00000010); @@ -156,9 +159,10 @@ nve0_graph_intr(struct nouveau_subdev *subdev) } if (stat & 0x00000020) { - nv_error(priv, "ILLEGAL_CLASS ch %d [0x%010llx] subc %d " - "class 0x%04x mthd 0x%04x data 0x%08x\n", - chid, inst, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_CLASS ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, nouveau_client_name(engctx), subc, class, + mthd, data); nv_wr32(priv, 0x400100, 0x00000020); stat &= ~0x00000020; } @@ -166,15 +170,15 @@ nve0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00100000) { nv_error(priv, "DATA_ERROR ["); nouveau_enum_print(nv50_data_error_names, code); - printk("] ch %d [0x%010llx] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, inst, subc, class, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, nouveau_client_name(engctx), subc, class, + mthd, data); nv_wr32(priv, 0x400100, 0x00100000); stat &= ~0x00100000; } if (stat & 0x00200000) { - nve0_graph_trap_isr(priv, chid, inst); + nve0_graph_trap_isr(priv, chid, inst, engctx); nv_wr32(priv, 0x400100, 0x00200000); stat &= ~0x00200000; } diff --git a/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c b/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c index 9fd86375f4c4..49ecbb859b25 100644 --- a/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c +++ b/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/engctx.h> @@ -231,8 +232,10 @@ nv31_mpeg_intr(struct nouveau_subdev *subdev) nv_wr32(priv, 0x00b230, 0x00000001); if (show) { - nv_error(priv, "ch %d [0x%08x] 0x%08x 0x%08x 0x%08x 0x%08x\n", - chid, inst << 4, stat, type, mthd, data); + nv_error(priv, + "ch %d [0x%08x %s] 0x%08x 0x%08x 0x%08x 0x%08x\n", + chid, inst << 4, nouveau_client_name(engctx), stat, + type, mthd, data); } nouveau_engctx_put(engctx); diff --git a/drivers/gpu/drm/nouveau/core/engine/software/nv50.c b/drivers/gpu/drm/nouveau/core/engine/software/nv50.c index b0e7e1c01ce6..c48e74953771 100644 --- a/drivers/gpu/drm/nouveau/core/engine/software/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/software/nv50.c @@ -28,6 +28,9 @@ #include <core/namedb.h> #include <core/handle.h> #include <core/gpuobj.h> +#include <core/event.h> + +#include <subdev/bar.h> #include <engine/software.h> #include <engine/disp.h> @@ -90,18 +93,11 @@ nv50_software_mthd_vblsem_release(struct nouveau_object *object, u32 mthd, { struct nv50_software_chan *chan = (void *)nv_engctx(object->parent); struct nouveau_disp *disp = nouveau_disp(object); - unsigned long flags; u32 crtc = *(u32 *)args; - if (crtc > 1) return -EINVAL; - disp->vblank.get(disp->vblank.data, crtc); - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_add(&chan->base.vblank.head, &disp->vblank.list); - chan->base.vblank.crtc = crtc; - spin_unlock_irqrestore(&disp->vblank.lock, flags); + nouveau_event_get(disp->vblank, crtc, &chan->base.vblank.event); return 0; } @@ -136,6 +132,29 @@ nv50_software_sclass[] = { ******************************************************************************/ static int +nv50_software_vblsem_release(struct nouveau_eventh *event, int head) +{ + struct nouveau_software_chan *chan = + container_of(event, struct nouveau_software_chan, vblank.event); + struct nv50_software_priv *priv = (void *)nv_object(chan)->engine; + struct nouveau_bar *bar = nouveau_bar(priv); + + nv_wr32(priv, 0x001704, chan->vblank.channel); + nv_wr32(priv, 0x001710, 0x80000000 | chan->vblank.ctxdma); + bar->flush(bar); + + if (nv_device(priv)->chipset == 0x50) { + nv_wr32(priv, 0x001570, chan->vblank.offset); + nv_wr32(priv, 0x001574, chan->vblank.value); + } else { + nv_wr32(priv, 0x060010, chan->vblank.offset); + nv_wr32(priv, 0x060014, chan->vblank.value); + } + + return NVKM_EVENT_DROP; +} + +static int nv50_software_context_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -150,6 +169,7 @@ nv50_software_context_ctor(struct nouveau_object *parent, return ret; chan->base.vblank.channel = nv_gpuobj(parent->parent)->addr >> 12; + chan->base.vblank.event.func = nv50_software_vblsem_release; return 0; } @@ -170,8 +190,8 @@ nv50_software_cclass = { static int nv50_software_ctor(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { struct nv50_software_priv *priv; int ret; diff --git a/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c index 282a1cd1bc2f..a523eaad47e3 100644 --- a/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c @@ -25,6 +25,9 @@ #include <core/os.h> #include <core/class.h> #include <core/engctx.h> +#include <core/event.h> + +#include <subdev/bar.h> #include <engine/software.h> #include <engine/disp.h> @@ -72,18 +75,12 @@ nvc0_software_mthd_vblsem_release(struct nouveau_object *object, u32 mthd, { struct nvc0_software_chan *chan = (void *)nv_engctx(object->parent); struct nouveau_disp *disp = nouveau_disp(object); - unsigned long flags; u32 crtc = *(u32 *)args; if ((nv_device(object)->card_type < NV_E0 && crtc > 1) || crtc > 3) return -EINVAL; - disp->vblank.get(disp->vblank.data, crtc); - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_add(&chan->base.vblank.head, &disp->vblank.list); - chan->base.vblank.crtc = crtc; - spin_unlock_irqrestore(&disp->vblank.lock, flags); + nouveau_event_get(disp->vblank, crtc, &chan->base.vblank.event); return 0; } @@ -118,6 +115,23 @@ nvc0_software_sclass[] = { ******************************************************************************/ static int +nvc0_software_vblsem_release(struct nouveau_eventh *event, int head) +{ + struct nouveau_software_chan *chan = + container_of(event, struct nouveau_software_chan, vblank.event); + struct nvc0_software_priv *priv = (void *)nv_object(chan)->engine; + struct nouveau_bar *bar = nouveau_bar(priv); + + nv_wr32(priv, 0x001718, 0x80000000 | chan->vblank.channel); + bar->flush(bar); + nv_wr32(priv, 0x06000c, upper_32_bits(chan->vblank.offset)); + nv_wr32(priv, 0x060010, lower_32_bits(chan->vblank.offset)); + nv_wr32(priv, 0x060014, chan->vblank.value); + + return NVKM_EVENT_DROP; +} + +static int nvc0_software_context_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -132,6 +146,7 @@ nvc0_software_context_ctor(struct nouveau_object *parent, return ret; chan->base.vblank.channel = nv_gpuobj(parent->parent)->addr >> 12; + chan->base.vblank.event.func = nvc0_software_vblsem_release; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/include/core/class.h b/drivers/gpu/drm/nouveau/core/include/core/class.h index 47c4b3a5bd3a..92d3ab11d962 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/class.h +++ b/drivers/gpu/drm/nouveau/core/include/core/class.h @@ -154,6 +154,14 @@ struct nve0_channel_ind_class { u32 engine; }; +/* 0046: NV04_DISP + */ + +#define NV04_DISP_CLASS 0x00000046 + +struct nv04_display_class { +}; + /* 5070: NV50_DISP * 8270: NV84_DISP * 8370: NVA0_DISP @@ -190,25 +198,6 @@ struct nve0_channel_ind_class { #define NV84_DISP_SOR_HDMI_PWR_REKEY 0x0000007f #define NV50_DISP_SOR_LVDS_SCRIPT 0x00013000 #define NV50_DISP_SOR_LVDS_SCRIPT_ID 0x0000ffff -#define NV94_DISP_SOR_DP_TRAIN 0x00016000 -#define NV94_DISP_SOR_DP_TRAIN_OP 0xf0000000 -#define NV94_DISP_SOR_DP_TRAIN_OP_PATTERN 0x00000000 -#define NV94_DISP_SOR_DP_TRAIN_OP_INIT 0x10000000 -#define NV94_DISP_SOR_DP_TRAIN_OP_FINI 0x20000000 -#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD 0x00000001 -#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_OFF 0x00000000 -#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON 0x00000001 -#define NV94_DISP_SOR_DP_TRAIN_PATTERN 0x00000003 -#define NV94_DISP_SOR_DP_TRAIN_PATTERN_DISABLED 0x00000000 -#define NV94_DISP_SOR_DP_LNKCTL 0x00016040 -#define NV94_DISP_SOR_DP_LNKCTL_FRAME 0x80000000 -#define NV94_DISP_SOR_DP_LNKCTL_FRAME_STD 0x00000000 -#define NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH 0x80000000 -#define NV94_DISP_SOR_DP_LNKCTL_WIDTH 0x00001f00 -#define NV94_DISP_SOR_DP_LNKCTL_COUNT 0x00000007 -#define NV94_DISP_SOR_DP_DRVCTL(l) ((l) * 0x40 + 0x00016100) -#define NV94_DISP_SOR_DP_DRVCTL_VS 0x00000300 -#define NV94_DISP_SOR_DP_DRVCTL_PE 0x00000003 #define NV50_DISP_DAC_MTHD 0x00020000 #define NV50_DISP_DAC_MTHD_TYPE 0x0000f000 @@ -230,6 +219,23 @@ struct nve0_channel_ind_class { #define NV50_DISP_DAC_LOAD 0x0002000c #define NV50_DISP_DAC_LOAD_VALUE 0x00000007 +#define NV50_DISP_PIOR_MTHD 0x00030000 +#define NV50_DISP_PIOR_MTHD_TYPE 0x0000f000 +#define NV50_DISP_PIOR_MTHD_OR 0x00000003 + +#define NV50_DISP_PIOR_PWR 0x00030000 +#define NV50_DISP_PIOR_PWR_STATE 0x00000001 +#define NV50_DISP_PIOR_PWR_STATE_ON 0x00000001 +#define NV50_DISP_PIOR_PWR_STATE_OFF 0x00000000 +#define NV50_DISP_PIOR_TMDS_PWR 0x00032000 +#define NV50_DISP_PIOR_TMDS_PWR_STATE 0x00000001 +#define NV50_DISP_PIOR_TMDS_PWR_STATE_ON 0x00000001 +#define NV50_DISP_PIOR_TMDS_PWR_STATE_OFF 0x00000000 +#define NV50_DISP_PIOR_DP_PWR 0x00036000 +#define NV50_DISP_PIOR_DP_PWR_STATE 0x00000001 +#define NV50_DISP_PIOR_DP_PWR_STATE_ON 0x00000001 +#define NV50_DISP_PIOR_DP_PWR_STATE_OFF 0x00000000 + struct nv50_display_class { }; diff --git a/drivers/gpu/drm/nouveau/core/include/core/client.h b/drivers/gpu/drm/nouveau/core/include/core/client.h index 63acc0346ff2..c66eac513803 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/client.h +++ b/drivers/gpu/drm/nouveau/core/include/core/client.h @@ -7,7 +7,7 @@ struct nouveau_client { struct nouveau_namedb base; struct nouveau_handle *root; struct nouveau_object *device; - char name[16]; + char name[32]; u32 debug; struct nouveau_vm *vm; }; @@ -41,5 +41,6 @@ int nouveau_client_create_(const char *name, u64 device, const char *cfg, int nouveau_client_init(struct nouveau_client *); int nouveau_client_fini(struct nouveau_client *, bool suspend); +const char *nouveau_client_name(void *obj); #endif diff --git a/drivers/gpu/drm/nouveau/core/include/core/device.h b/drivers/gpu/drm/nouveau/core/include/core/device.h index e58b6f0984c1..d351a4e5819c 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/device.h +++ b/drivers/gpu/drm/nouveau/core/include/core/device.h @@ -26,6 +26,7 @@ enum nv_subdev_type { */ NVDEV_SUBDEV_MXM, NVDEV_SUBDEV_MC, + NVDEV_SUBDEV_BUS, NVDEV_SUBDEV_TIMER, NVDEV_SUBDEV_FB, NVDEV_SUBDEV_LTCG, diff --git a/drivers/gpu/drm/nouveau/core/include/core/enum.h b/drivers/gpu/drm/nouveau/core/include/core/enum.h index e7b1e181943b..4fc62bb8c1f0 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/enum.h +++ b/drivers/gpu/drm/nouveau/core/include/core/enum.h @@ -5,12 +5,13 @@ struct nouveau_enum { u32 value; const char *name; const void *data; + u32 data2; }; const struct nouveau_enum * nouveau_enum_find(const struct nouveau_enum *, u32 value); -void +const struct nouveau_enum * nouveau_enum_print(const struct nouveau_enum *en, u32 value); struct nouveau_bitfield { diff --git a/drivers/gpu/drm/nouveau/core/include/core/event.h b/drivers/gpu/drm/nouveau/core/include/core/event.h new file mode 100644 index 000000000000..9e094408f14e --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/include/core/event.h @@ -0,0 +1,36 @@ +#ifndef __NVKM_EVENT_H__ +#define __NVKM_EVENT_H__ + +/* return codes from event handlers */ +#define NVKM_EVENT_DROP 0 +#define NVKM_EVENT_KEEP 1 + +struct nouveau_eventh { + struct list_head head; + int (*func)(struct nouveau_eventh *, int index); +}; + +struct nouveau_event { + spinlock_t lock; + + void *priv; + void (*enable)(struct nouveau_event *, int index); + void (*disable)(struct nouveau_event *, int index); + + int index_nr; + struct { + struct list_head list; + int refs; + } index[]; +}; + +int nouveau_event_create(int index_nr, struct nouveau_event **); +void nouveau_event_destroy(struct nouveau_event **); +void nouveau_event_trigger(struct nouveau_event *, int index); + +void nouveau_event_get(struct nouveau_event *, int index, + struct nouveau_eventh *); +void nouveau_event_put(struct nouveau_event *, int index, + struct nouveau_eventh *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/include/core/object.h b/drivers/gpu/drm/nouveau/core/include/core/object.h index 5982935ee23a..6a902672f6f4 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/object.h +++ b/drivers/gpu/drm/nouveau/core/include/core/object.h @@ -133,7 +133,7 @@ static inline u8 nv_ro08(void *obj, u64 addr) { u8 data = nv_ofuncs(obj)->rd08(obj, addr); - nv_spam(obj, "nv_ro08 0x%08x 0x%02x\n", addr, data); + nv_spam(obj, "nv_ro08 0x%08llx 0x%02x\n", addr, data); return data; } @@ -141,7 +141,7 @@ static inline u16 nv_ro16(void *obj, u64 addr) { u16 data = nv_ofuncs(obj)->rd16(obj, addr); - nv_spam(obj, "nv_ro16 0x%08x 0x%04x\n", addr, data); + nv_spam(obj, "nv_ro16 0x%08llx 0x%04x\n", addr, data); return data; } @@ -149,28 +149,28 @@ static inline u32 nv_ro32(void *obj, u64 addr) { u32 data = nv_ofuncs(obj)->rd32(obj, addr); - nv_spam(obj, "nv_ro32 0x%08x 0x%08x\n", addr, data); + nv_spam(obj, "nv_ro32 0x%08llx 0x%08x\n", addr, data); return data; } static inline void nv_wo08(void *obj, u64 addr, u8 data) { - nv_spam(obj, "nv_wo08 0x%08x 0x%02x\n", addr, data); + nv_spam(obj, "nv_wo08 0x%08llx 0x%02x\n", addr, data); nv_ofuncs(obj)->wr08(obj, addr, data); } static inline void nv_wo16(void *obj, u64 addr, u16 data) { - nv_spam(obj, "nv_wo16 0x%08x 0x%04x\n", addr, data); + nv_spam(obj, "nv_wo16 0x%08llx 0x%04x\n", addr, data); nv_ofuncs(obj)->wr16(obj, addr, data); } static inline void nv_wo32(void *obj, u64 addr, u32 data) { - nv_spam(obj, "nv_wo32 0x%08x 0x%08x\n", addr, data); + nv_spam(obj, "nv_wo32 0x%08llx 0x%08x\n", addr, data); nv_ofuncs(obj)->wr32(obj, addr, data); } diff --git a/drivers/gpu/drm/nouveau/core/include/core/printk.h b/drivers/gpu/drm/nouveau/core/include/core/printk.h index 1d629664f32d..febed2ea5c80 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/printk.h +++ b/drivers/gpu/drm/nouveau/core/include/core/printk.h @@ -15,7 +15,8 @@ struct nouveau_object; #define NV_PRINTK_TRACE KERN_DEBUG #define NV_PRINTK_SPAM KERN_DEBUG -void nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); +void __printf(4, 5) +nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); #define nv_printk(o,l,f,a...) do { \ if (NV_DBG_##l <= CONFIG_NOUVEAU_DEBUG) \ diff --git a/drivers/gpu/drm/nouveau/core/include/engine/disp.h b/drivers/gpu/drm/nouveau/core/include/engine/disp.h index 46948285f3e7..28da6772c095 100644 --- a/drivers/gpu/drm/nouveau/core/include/engine/disp.h +++ b/drivers/gpu/drm/nouveau/core/include/engine/disp.h @@ -4,18 +4,11 @@ #include <core/object.h> #include <core/engine.h> #include <core/device.h> +#include <core/event.h> struct nouveau_disp { struct nouveau_engine base; - - struct { - struct list_head list; - spinlock_t lock; - void (*notify)(void *, int); - void (*get)(void *, int); - void (*put)(void *, int); - void *data; - } vblank; + struct nouveau_event *vblank; }; static inline struct nouveau_disp * @@ -24,16 +17,22 @@ nouveau_disp(void *obj) return (void *)nv_device(obj)->subdev[NVDEV_ENGINE_DISP]; } -#define nouveau_disp_create(p,e,c,i,x,d) \ - nouveau_engine_create((p), (e), (c), true, (i), (x), (d)) -#define nouveau_disp_destroy(d) \ - nouveau_engine_destroy(&(d)->base) +#define nouveau_disp_create(p,e,c,h,i,x,d) \ + nouveau_disp_create_((p), (e), (c), (h), (i), (x), \ + sizeof(**d), (void **)d) +#define nouveau_disp_destroy(d) ({ \ + struct nouveau_disp *disp = (d); \ + _nouveau_disp_dtor(nv_object(disp)); \ +}) #define nouveau_disp_init(d) \ nouveau_engine_init(&(d)->base) #define nouveau_disp_fini(d,s) \ nouveau_engine_fini(&(d)->base, (s)) -#define _nouveau_disp_dtor _nouveau_engine_dtor +int nouveau_disp_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, int heads, + const char *, const char *, int, void **); +void _nouveau_disp_dtor(struct nouveau_object *); #define _nouveau_disp_init _nouveau_engine_init #define _nouveau_disp_fini _nouveau_engine_fini diff --git a/drivers/gpu/drm/nouveau/core/include/engine/fifo.h b/drivers/gpu/drm/nouveau/core/include/engine/fifo.h index f18846c8c6fe..b46c197709f3 100644 --- a/drivers/gpu/drm/nouveau/core/include/engine/fifo.h +++ b/drivers/gpu/drm/nouveau/core/include/engine/fifo.h @@ -65,6 +65,8 @@ struct nouveau_fifo_base { struct nouveau_fifo { struct nouveau_engine base; + struct nouveau_event *uevent; + struct nouveau_object **channel; spinlock_t lock; u16 min; @@ -92,6 +94,8 @@ int nouveau_fifo_create_(struct nouveau_object *, struct nouveau_object *, struct nouveau_oclass *, int min, int max, int size, void **); void nouveau_fifo_destroy(struct nouveau_fifo *); +const char * +nouveau_client_name_for_fifo_chid(struct nouveau_fifo *fifo, u32 chid); #define _nouveau_fifo_init _nouveau_engine_init #define _nouveau_fifo_fini _nouveau_engine_fini diff --git a/drivers/gpu/drm/nouveau/core/include/engine/software.h b/drivers/gpu/drm/nouveau/core/include/engine/software.h index c945691c8564..45799487e573 100644 --- a/drivers/gpu/drm/nouveau/core/include/engine/software.h +++ b/drivers/gpu/drm/nouveau/core/include/engine/software.h @@ -3,17 +3,17 @@ #include <core/engine.h> #include <core/engctx.h> +#include <core/event.h> struct nouveau_software_chan { struct nouveau_engctx base; struct { - struct list_head head; + struct nouveau_eventh event; u32 channel; u32 ctxdma; u64 offset; u32 value; - u32 crtc; } vblank; int (*flip)(void *); diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h index b79025da581e..123270e9813a 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h @@ -16,6 +16,8 @@ enum dcb_output_type { struct dcb_output { int index; /* may not be raw dcb index if merging has happened */ + u16 hasht; + u16 hashm; enum dcb_output_type type; uint8_t i2c_index; uint8_t heads; @@ -25,6 +27,7 @@ struct dcb_output { uint8_t or; uint8_t link; bool duallink_possible; + uint8_t extdev; union { struct sor_conf { int link; diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h index e6563b5cb08e..96d3364f6db3 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h @@ -1,17 +1,22 @@ #ifndef __NVBIOS_GPIO_H__ #define __NVBIOS_GPIO_H__ -struct nouveau_bios; - enum dcb_gpio_func_name { DCB_GPIO_PANEL_POWER = 0x01, DCB_GPIO_TVDAC0 = 0x0c, DCB_GPIO_TVDAC1 = 0x2d, - DCB_GPIO_PWM_FAN = 0x09, + DCB_GPIO_FAN = 0x09, DCB_GPIO_FAN_SENSE = 0x3d, DCB_GPIO_UNUSED = 0xff }; +#define DCB_GPIO_LOG_DIR 0x02 +#define DCB_GPIO_LOG_DIR_OUT 0x00 +#define DCB_GPIO_LOG_DIR_IN 0x02 +#define DCB_GPIO_LOG_VAL 0x01 +#define DCB_GPIO_LOG_VAL_LO 0x00 +#define DCB_GPIO_LOG_VAL_HI 0x01 + struct dcb_gpio_func { u8 func; u8 line; diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h index 5079bedfd985..10b57a19a7de 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h @@ -15,7 +15,7 @@ struct dcb_i2c_entry { enum dcb_i2c_type type; u8 drive; u8 sense; - u32 data; + u8 share; }; u16 dcb_i2c_table(struct nouveau_bios *, u8 *ver, u8 *hdr, u8 *cnt, u8 *len); diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h index a2c4296fc5f6..083541dbe9c8 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h @@ -23,11 +23,27 @@ struct nvbios_therm_sensor { struct nvbios_therm_threshold thrs_shutdown; }; +/* no vbios have more than 6 */ +#define NOUVEAU_TEMP_FAN_TRIP_MAX 10 +struct nouveau_therm_trip_point { + int fan_duty; + int temp; + int hysteresis; +}; + struct nvbios_therm_fan { u16 pwm_freq; u8 min_duty; u8 max_duty; + + u16 bump_period; + u16 slow_down_period; + + struct nouveau_therm_trip_point trip[NOUVEAU_TEMP_FAN_TRIP_MAX]; + u8 nr_fan_trip; + u8 linear_min_temp; + u8 linear_max_temp; }; enum nvbios_therm_domain { diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/xpio.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/xpio.h new file mode 100644 index 000000000000..360baab52e4c --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/xpio.h @@ -0,0 +1,19 @@ +#ifndef __NVBIOS_XPIO_H__ +#define __NVBIOS_XPIO_H__ + +#define NVBIOS_XPIO_FLAG_AUX 0x10 +#define NVBIOS_XPIO_FLAG_AUX0 0x00 +#define NVBIOS_XPIO_FLAG_AUX1 0x10 + +struct nvbios_xpio { + u8 type; + u8 addr; + u8 flags; +}; + +u16 dcb_xpio_table(struct nouveau_bios *, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len); +u16 dcb_xpio_parse(struct nouveau_bios *, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len, struct nvbios_xpio *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bus.h b/drivers/gpu/drm/nouveau/core/include/subdev/bus.h new file mode 100644 index 000000000000..7d88ec4a6d06 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bus.h @@ -0,0 +1,41 @@ +#ifndef __NOUVEAU_BUS_H__ +#define __NOUVEAU_BUS_H__ + +#include <core/subdev.h> +#include <core/device.h> + +struct nouveau_bus_intr { + u32 stat; + u32 unit; +}; + +struct nouveau_bus { + struct nouveau_subdev base; +}; + +static inline struct nouveau_bus * +nouveau_bus(void *obj) +{ + return (void *)nv_device(obj)->subdev[NVDEV_SUBDEV_BUS]; +} + +#define nouveau_bus_create(p, e, o, d) \ + nouveau_subdev_create_((p), (e), (o), 0, "PBUS", "master", \ + sizeof(**d), (void **)d) +#define nouveau_bus_destroy(p) \ + nouveau_subdev_destroy(&(p)->base) +#define nouveau_bus_init(p) \ + nouveau_subdev_init(&(p)->base) +#define nouveau_bus_fini(p, s) \ + nouveau_subdev_fini(&(p)->base, (s)) + +#define _nouveau_bus_dtor _nouveau_subdev_dtor +#define _nouveau_bus_init _nouveau_subdev_init +#define _nouveau_bus_fini _nouveau_subdev_fini + +extern struct nouveau_oclass nv04_bus_oclass; +extern struct nouveau_oclass nv31_bus_oclass; +extern struct nouveau_oclass nv50_bus_oclass; +extern struct nouveau_oclass nvc0_bus_oclass; + +#endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h b/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h index b75e8f18e52c..c85b9f1579ad 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h @@ -3,6 +3,7 @@ #include <core/subdev.h> #include <core/device.h> +#include <core/event.h> #include <subdev/bios.h> #include <subdev/bios/gpio.h> @@ -10,28 +11,18 @@ struct nouveau_gpio { struct nouveau_subdev base; + struct nouveau_event *events; + /* hardware interfaces */ void (*reset)(struct nouveau_gpio *, u8 func); int (*drive)(struct nouveau_gpio *, int line, int dir, int out); int (*sense)(struct nouveau_gpio *, int line); - void (*irq_enable)(struct nouveau_gpio *, int line, bool); /* software interfaces */ int (*find)(struct nouveau_gpio *, int idx, u8 tag, u8 line, struct dcb_gpio_func *); int (*set)(struct nouveau_gpio *, int idx, u8 tag, u8 line, int state); int (*get)(struct nouveau_gpio *, int idx, u8 tag, u8 line); - int (*irq)(struct nouveau_gpio *, int idx, u8 tag, u8 line, bool on); - - /* interrupt handling */ - struct list_head isr; - spinlock_t lock; - - void (*isr_run)(struct nouveau_gpio *, int idx, u32 mask); - int (*isr_add)(struct nouveau_gpio *, int idx, u8 tag, u8 line, - void (*)(void *, int state), void *data); - void (*isr_del)(struct nouveau_gpio *, int idx, u8 tag, u8 line, - void (*)(void *, int state), void *data); }; static inline struct nouveau_gpio * @@ -40,25 +31,23 @@ nouveau_gpio(void *obj) return (void *)nv_device(obj)->subdev[NVDEV_SUBDEV_GPIO]; } -#define nouveau_gpio_create(p,e,o,d) \ - nouveau_gpio_create_((p), (e), (o), sizeof(**d), (void **)d) -#define nouveau_gpio_destroy(p) \ - nouveau_subdev_destroy(&(p)->base) +#define nouveau_gpio_create(p,e,o,l,d) \ + nouveau_gpio_create_((p), (e), (o), (l), sizeof(**d), (void **)d) +#define nouveau_gpio_destroy(p) ({ \ + struct nouveau_gpio *gpio = (p); \ + _nouveau_gpio_dtor(nv_object(gpio)); \ +}) #define nouveau_gpio_fini(p,s) \ nouveau_subdev_fini(&(p)->base, (s)) -int nouveau_gpio_create_(struct nouveau_object *, struct nouveau_object *, - struct nouveau_oclass *, int, void **); -int nouveau_gpio_init(struct nouveau_gpio *); +int nouveau_gpio_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, int, int, void **); +void _nouveau_gpio_dtor(struct nouveau_object *); +int nouveau_gpio_init(struct nouveau_gpio *); extern struct nouveau_oclass nv10_gpio_oclass; extern struct nouveau_oclass nv50_gpio_oclass; extern struct nouveau_oclass nvd0_gpio_oclass; - -void nv50_gpio_dtor(struct nouveau_object *); -int nv50_gpio_init(struct nouveau_object *); -int nv50_gpio_fini(struct nouveau_object *, bool); -void nv50_gpio_intr(struct nouveau_subdev *); -void nv50_gpio_irq_enable(struct nouveau_gpio *, int line, bool); +extern struct nouveau_oclass nve0_gpio_oclass; #endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h b/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h index b93ab01e3785..888384c0bed8 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h @@ -10,23 +10,59 @@ #define NV_I2C_PORT(n) (0x00 + (n)) #define NV_I2C_DEFAULT(n) (0x80 + (n)) +#define NV_I2C_TYPE_DCBI2C(n) (0x0000 | (n)) +#define NV_I2C_TYPE_EXTDDC(e) (0x0005 | (e) << 8) +#define NV_I2C_TYPE_EXTAUX(e) (0x0006 | (e) << 8) + struct nouveau_i2c_port { + struct nouveau_object base; struct i2c_adapter adapter; - struct nouveau_i2c *i2c; - struct i2c_algo_bit_data bit; + struct list_head head; u8 index; - u8 type; - u32 dcb; - u32 drive; - u32 sense; - u32 state; + + const struct nouveau_i2c_func *func; +}; + +struct nouveau_i2c_func { + void (*acquire)(struct nouveau_i2c_port *); + void (*release)(struct nouveau_i2c_port *); + + void (*drive_scl)(struct nouveau_i2c_port *, int); + void (*drive_sda)(struct nouveau_i2c_port *, int); + int (*sense_scl)(struct nouveau_i2c_port *); + int (*sense_sda)(struct nouveau_i2c_port *); + + int (*aux)(struct nouveau_i2c_port *, u8, u32, u8 *, u8); + int (*pattern)(struct nouveau_i2c_port *, int pattern); + int (*lnk_ctl)(struct nouveau_i2c_port *, int nr, int bw, bool enh); + int (*drv_ctl)(struct nouveau_i2c_port *, int lane, int sw, int pe); }; +#define nouveau_i2c_port_create(p,e,o,i,a,d) \ + nouveau_i2c_port_create_((p), (e), (o), (i), (a), \ + sizeof(**d), (void **)d) +#define nouveau_i2c_port_destroy(p) ({ \ + struct nouveau_i2c_port *port = (p); \ + _nouveau_i2c_port_dtor(nv_object(i2c)); \ +}) +#define nouveau_i2c_port_init(p) \ + nouveau_object_init(&(p)->base) +#define nouveau_i2c_port_fini(p,s) \ + nouveau_object_fini(&(p)->base, (s)) + +int nouveau_i2c_port_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, u8, + const struct i2c_algorithm *, int, void **); +void _nouveau_i2c_port_dtor(struct nouveau_object *); +#define _nouveau_i2c_port_init nouveau_object_init +#define _nouveau_i2c_port_fini nouveau_object_fini + struct nouveau_i2c { struct nouveau_subdev base; struct nouveau_i2c_port *(*find)(struct nouveau_i2c *, u8 index); + struct nouveau_i2c_port *(*find_type)(struct nouveau_i2c *, u16 type); int (*identify)(struct nouveau_i2c *, int index, const char *what, struct i2c_board_info *, bool (*match)(struct nouveau_i2c_port *, @@ -40,21 +76,76 @@ nouveau_i2c(void *obj) return (void *)nv_device(obj)->subdev[NVDEV_SUBDEV_I2C]; } -extern struct nouveau_oclass nouveau_i2c_oclass; +#define nouveau_i2c_create(p,e,o,s,d) \ + nouveau_i2c_create_((p), (e), (o), (s), sizeof(**d), (void **)d) +#define nouveau_i2c_destroy(p) ({ \ + struct nouveau_i2c *i2c = (p); \ + _nouveau_i2c_dtor(nv_object(i2c)); \ +}) +#define nouveau_i2c_init(p) ({ \ + struct nouveau_i2c *i2c = (p); \ + _nouveau_i2c_init(nv_object(i2c)); \ +}) +#define nouveau_i2c_fini(p,s) ({ \ + struct nouveau_i2c *i2c = (p); \ + _nouveau_i2c_fini(nv_object(i2c), (s)); \ +}) -void nouveau_i2c_drive_scl(void *, int); -void nouveau_i2c_drive_sda(void *, int); -int nouveau_i2c_sense_scl(void *); -int nouveau_i2c_sense_sda(void *); +int nouveau_i2c_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, struct nouveau_oclass *, + int, void **); +void _nouveau_i2c_dtor(struct nouveau_object *); +int _nouveau_i2c_init(struct nouveau_object *); +int _nouveau_i2c_fini(struct nouveau_object *, bool); -int nv_rdi2cr(struct nouveau_i2c_port *, u8 addr, u8 reg); -int nv_wri2cr(struct nouveau_i2c_port *, u8 addr, u8 reg, u8 val); -bool nv_probe_i2c(struct nouveau_i2c_port *, u8 addr); - -int nv_rdaux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); -int nv_wraux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); +extern struct nouveau_oclass nv04_i2c_oclass; +extern struct nouveau_oclass nv4e_i2c_oclass; +extern struct nouveau_oclass nv50_i2c_oclass; +extern struct nouveau_oclass nv94_i2c_oclass; +extern struct nouveau_oclass nvd0_i2c_oclass; +extern struct nouveau_oclass nouveau_anx9805_sclass[]; extern const struct i2c_algorithm nouveau_i2c_bit_algo; extern const struct i2c_algorithm nouveau_i2c_aux_algo; +static inline int +nv_rdi2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg) +{ + u8 val; + struct i2c_msg msgs[] = { + { .addr = addr, .flags = 0, .len = 1, .buf = ® }, + { .addr = addr, .flags = I2C_M_RD, .len = 1, .buf = &val }, + }; + + int ret = i2c_transfer(&port->adapter, msgs, 2); + if (ret != 2) + return -EIO; + + return val; +} + +static inline int +nv_wri2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg, u8 val) +{ + u8 buf[2] = { reg, val }; + struct i2c_msg msgs[] = { + { .addr = addr, .flags = 0, .len = 2, .buf = buf }, + }; + + int ret = i2c_transfer(&port->adapter, msgs, 1); + if (ret != 1) + return -EIO; + + return 0; +} + +static inline bool +nv_probe_i2c(struct nouveau_i2c_port *port, u8 addr) +{ + return nv_rdi2cr(port, addr, 0) >= 0; +} + +int nv_rdaux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); +int nv_wraux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); + #endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/therm.h b/drivers/gpu/drm/nouveau/core/include/subdev/therm.h index faee569fd458..6b17b614629f 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/therm.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/therm.h @@ -4,10 +4,10 @@ #include <core/device.h> #include <core/subdev.h> -enum nouveau_therm_fan_mode { - FAN_CONTROL_NONE = 0, - FAN_CONTROL_MANUAL = 1, - FAN_CONTROL_NR, +enum nouveau_therm_mode { + NOUVEAU_THERM_CTRL_NONE = 0, + NOUVEAU_THERM_CTRL_MANUAL = 1, + NOUVEAU_THERM_CTRL_AUTO = 2, }; enum nouveau_therm_attr_type { @@ -28,6 +28,11 @@ enum nouveau_therm_attr_type { struct nouveau_therm { struct nouveau_subdev base; + int (*pwm_ctrl)(struct nouveau_therm *, int line, bool); + int (*pwm_get)(struct nouveau_therm *, int line, u32 *, u32 *); + int (*pwm_set)(struct nouveau_therm *, int line, u32, u32); + int (*pwm_clock)(struct nouveau_therm *); + int (*fan_get)(struct nouveau_therm *); int (*fan_set)(struct nouveau_therm *, int); int (*fan_sense)(struct nouveau_therm *); @@ -46,13 +51,29 @@ nouveau_therm(void *obj) } #define nouveau_therm_create(p,e,o,d) \ - nouveau_subdev_create((p), (e), (o), 0, "THERM", "therm", d) -#define nouveau_therm_destroy(p) \ - nouveau_subdev_destroy(&(p)->base) + nouveau_therm_create_((p), (e), (o), sizeof(**d), (void **)d) +#define nouveau_therm_destroy(p) ({ \ + struct nouveau_therm *therm = (p); \ + _nouveau_therm_dtor(nv_object(therm)); \ +}) +#define nouveau_therm_init(p) ({ \ + struct nouveau_therm *therm = (p); \ + _nouveau_therm_init(nv_object(therm)); \ +}) +#define nouveau_therm_fini(p,s) ({ \ + struct nouveau_therm *therm = (p); \ + _nouveau_therm_init(nv_object(therm), (s)); \ +}) -#define _nouveau_therm_dtor _nouveau_subdev_dtor +int nouveau_therm_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, int, void **); +void _nouveau_therm_dtor(struct nouveau_object *); +int _nouveau_therm_init(struct nouveau_object *); +int _nouveau_therm_fini(struct nouveau_object *, bool); extern struct nouveau_oclass nv40_therm_oclass; extern struct nouveau_oclass nv50_therm_oclass; +extern struct nouveau_oclass nva3_therm_oclass; +extern struct nouveau_oclass nvd0_therm_oclass; #endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/timer.h b/drivers/gpu/drm/nouveau/core/include/subdev/timer.h index c24ec8ab3db4..e465d158d352 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/timer.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/timer.h @@ -10,6 +10,14 @@ struct nouveau_alarm { void (*func)(struct nouveau_alarm *); }; +static inline void +nouveau_alarm_init(struct nouveau_alarm *alarm, + void (*func)(struct nouveau_alarm *)) +{ + INIT_LIST_HEAD(&alarm->head); + alarm->func = func; +} + bool nouveau_timer_wait_eq(void *, u64 nsec, u32 addr, u32 mask, u32 data); bool nouveau_timer_wait_ne(void *, u64 nsec, u32 addr, u32 mask, u32 data); bool nouveau_timer_wait_cb(void *, u64 nsec, bool (*func)(void *), void *data); diff --git a/drivers/gpu/drm/nouveau/core/os.h b/drivers/gpu/drm/nouveau/core/os.h index cfe3b9cad156..eb496033b55c 100644 --- a/drivers/gpu/drm/nouveau/core/os.h +++ b/drivers/gpu/drm/nouveau/core/os.h @@ -16,6 +16,7 @@ #include <linux/vmalloc.h> #include <linux/acpi.h> #include <linux/dmi.h> +#include <linux/reboot.h> #include <asm/unaligned.h> diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/base.c b/drivers/gpu/drm/nouveau/core/subdev/bios/base.c index f621f69fa1a2..e816f06637a7 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/base.c @@ -172,7 +172,7 @@ out: nv_wr32(bios, pcireg, access); } -#if defined(CONFIG_ACPI) +#if defined(CONFIG_ACPI) && defined(CONFIG_X86) int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len); bool nouveau_acpi_rom_supported(struct pci_dev *pdev); #else diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c b/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c index 0fd87df99dd6..2d9b9d7a7992 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c @@ -107,6 +107,18 @@ dcb_outp(struct nouveau_bios *bios, u8 idx, u8 *ver, u8 *len) return 0x0000; } +static inline u16 +dcb_outp_hasht(struct dcb_output *outp) +{ + return (outp->extdev << 8) | (outp->location << 4) | outp->type; +} + +static inline u16 +dcb_outp_hashm(struct dcb_output *outp) +{ + return (outp->heads << 8) | (outp->link << 6) | outp->or; +} + u16 dcb_outp_parse(struct nouveau_bios *bios, u8 idx, u8 *ver, u8 *len, struct dcb_output *outp) @@ -135,34 +147,28 @@ dcb_outp_parse(struct nouveau_bios *bios, u8 idx, u8 *ver, u8 *len, case DCB_OUTPUT_DP: outp->link = (conf & 0x00000030) >> 4; outp->sorconf.link = outp->link; /*XXX*/ + outp->extdev = 0x00; + if (outp->location != 0) + outp->extdev = (conf & 0x0000ff00) >> 8; break; default: break; } } + + outp->hasht = dcb_outp_hasht(outp); + outp->hashm = dcb_outp_hashm(outp); } return dcb; } -static inline u16 -dcb_outp_hasht(struct dcb_output *outp) -{ - return outp->type; -} - -static inline u16 -dcb_outp_hashm(struct dcb_output *outp) -{ - return (outp->heads << 8) | (outp->link << 6) | outp->or; -} - u16 dcb_outp_match(struct nouveau_bios *bios, u16 type, u16 mask, u8 *ver, u8 *len, struct dcb_output *outp) { u16 dcb, idx = 0; while ((dcb = dcb_outp_parse(bios, idx++, ver, len, outp))) { - if (dcb_outp_hasht(outp) == type) { + if ((dcb_outp_hasht(outp) & 0x00ff) == (type & 0x00ff)) { if ((dcb_outp_hashm(outp) & mask) == mask) break; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c b/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c index 5afb568b2d69..b2a676e53580 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c @@ -48,7 +48,7 @@ extdev_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *len, u8 *cnt) return extdev + *hdr; } -u16 +static u16 nvbios_extdev_entry(struct nouveau_bios *bios, int idx, u8 *ver, u8 *len) { u8 hdr, cnt; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c b/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c index c84e93fa6d95..172a4f999990 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c @@ -25,6 +25,7 @@ #include <subdev/bios.h> #include <subdev/bios/dcb.h> #include <subdev/bios/gpio.h> +#include <subdev/bios/xpio.h> u16 dcb_gpio_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len) @@ -60,8 +61,14 @@ dcb_gpio_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len) u16 dcb_gpio_entry(struct nouveau_bios *bios, int idx, int ent, u8 *ver, u8 *len) { - u8 hdr, cnt; - u16 gpio = !idx ? dcb_gpio_table(bios, ver, &hdr, &cnt, len) : 0x0000; + u8 hdr, cnt, xver; /* use gpio version for xpio entry parsing */ + u16 gpio; + + if (!idx--) + gpio = dcb_gpio_table(bios, ver, &hdr, &cnt, len); + else + gpio = dcb_xpio_table(bios, idx, &xver, &hdr, &cnt, len); + if (gpio && ent < cnt) return gpio + hdr + (ent * *len); return 0x0000; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c b/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c index ad577db83766..cfb9288c6d28 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c @@ -70,12 +70,12 @@ dcb_i2c_parse(struct nouveau_bios *bios, u8 idx, struct dcb_i2c_entry *info) u8 ver, len; u16 ent = dcb_i2c_entry(bios, idx, &ver, &len); if (ent) { - info->data = nv_ro32(bios, ent + 0); - info->type = nv_ro08(bios, ent + 3); + info->type = nv_ro08(bios, ent + 3); + info->share = DCB_I2C_UNUSED; if (ver < 0x30) { info->type &= 0x07; if (info->type == 0x07) - info->type = 0xff; + info->type = DCB_I2C_UNUSED; } switch (info->type) { @@ -88,7 +88,11 @@ dcb_i2c_parse(struct nouveau_bios *bios, u8 idx, struct dcb_i2c_entry *info) return 0; case DCB_I2C_NVIO_BIT: case DCB_I2C_NVIO_AUX: - info->drive = nv_ro08(bios, ent + 0); + info->drive = nv_ro08(bios, ent + 0) & 0x0f; + if (nv_ro08(bios, ent + 1) & 0x01) { + info->share = nv_ro08(bios, ent + 1) >> 1; + info->share &= 0x0f; + } return 0; case DCB_I2C_UNUSED: return 0; @@ -121,7 +125,8 @@ dcb_i2c_parse(struct nouveau_bios *bios, u8 idx, struct dcb_i2c_entry *info) if (!info->sense) info->sense = 0x36; } - info->type = DCB_I2C_NV04_BIT; + info->type = DCB_I2C_NV04_BIT; + info->share = DCB_I2C_UNUSED; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c index 690ed438b2ad..2cc1e6a5eb6a 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c @@ -231,6 +231,11 @@ init_i2c(struct nvbios_init *init, int index) return NULL; } + if (index == -2 && init->outp->location) { + index = NV_I2C_TYPE_EXTAUX(init->outp->extdev); + return i2c->find_type(i2c, index); + } + index = init->outp->i2c_index; } @@ -258,7 +263,7 @@ init_wri2cr(struct nvbios_init *init, u8 index, u8 addr, u8 reg, u8 val) static int init_rdauxr(struct nvbios_init *init, u32 addr) { - struct nouveau_i2c_port *port = init_i2c(init, -1); + struct nouveau_i2c_port *port = init_i2c(init, -2); u8 data; if (port && init_exec(init)) { @@ -274,7 +279,7 @@ init_rdauxr(struct nvbios_init *init, u32 addr) static int init_wrauxr(struct nvbios_init *init, u32 addr, u8 data) { - struct nouveau_i2c_port *port = init_i2c(init, -1); + struct nouveau_i2c_port *port = init_i2c(init, -2); if (port && init_exec(init)) return nv_wraux(port, addr, &data, 1); return -ENODEV; @@ -1816,7 +1821,7 @@ init_ram_restrict_zm_reg_group(struct nvbios_init *init) u8 i, j; trace("RAM_RESTRICT_ZM_REG_GROUP\t" - "R[%08x] 0x%02x 0x%02x\n", addr, incr, num); + "R[0x%08x] 0x%02x 0x%02x\n", addr, incr, num); init->offset += 7; for (i = 0; i < num; i++) { @@ -1849,7 +1854,7 @@ init_copy_zm_reg(struct nvbios_init *init) u32 sreg = nv_ro32(bios, init->offset + 1); u32 dreg = nv_ro32(bios, init->offset + 5); - trace("COPY_ZM_REG\tR[0x%06x] = R[0x%06x]\n", sreg, dreg); + trace("COPY_ZM_REG\tR[0x%06x] = R[0x%06x]\n", dreg, sreg); init->offset += 9; init_wr32(init, dreg, init_rd32(init, sreg)); @@ -1866,7 +1871,7 @@ init_zm_reg_group(struct nvbios_init *init) u32 addr = nv_ro32(bios, init->offset + 1); u8 count = nv_ro08(bios, init->offset + 5); - trace("ZM_REG_GROUP\tR[0x%06x] =\n"); + trace("ZM_REG_GROUP\tR[0x%06x] =\n", addr); init->offset += 6; while (count--) { diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c b/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c index 862a08a2ae27..22a20573ed1b 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c @@ -55,7 +55,7 @@ therm_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *len, u8 *cnt) return therm + nv_ro08(bios, therm + 1); } -u16 +static u16 nvbios_therm_entry(struct nouveau_bios *bios, int idx, u8 *ver, u8 *len) { u8 hdr, cnt; @@ -155,10 +155,15 @@ int nvbios_therm_fan_parse(struct nouveau_bios *bios, struct nvbios_therm_fan *fan) { + struct nouveau_therm_trip_point *cur_trip = NULL; u8 ver, len, i; u16 entry; + uint8_t duty_lut[] = { 0, 0, 25, 0, 40, 0, 50, 0, + 75, 0, 85, 0, 100, 0, 100, 0 }; + i = 0; + fan->nr_fan_trip = 0; while ((entry = nvbios_therm_entry(bios, i++, &ver, &len))) { s16 value = nv_ro16(bios, entry + 1); @@ -167,9 +172,30 @@ nvbios_therm_fan_parse(struct nouveau_bios *bios, fan->min_duty = value & 0xff; fan->max_duty = (value & 0xff00) >> 8; break; + case 0x24: + fan->nr_fan_trip++; + cur_trip = &fan->trip[fan->nr_fan_trip - 1]; + cur_trip->hysteresis = value & 0xf; + cur_trip->temp = (value & 0xff0) >> 4; + cur_trip->fan_duty = duty_lut[(value & 0xf000) >> 12]; + break; + case 0x25: + cur_trip = &fan->trip[fan->nr_fan_trip - 1]; + cur_trip->fan_duty = value; + break; case 0x26: fan->pwm_freq = value; break; + case 0x3b: + fan->bump_period = value; + break; + case 0x3c: + fan->slow_down_period = value; + break; + case 0x46: + fan->linear_min_temp = nv_ro08(bios, entry + 1); + fan->linear_max_temp = nv_ro08(bios, entry + 2); + break; } } diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/xpio.c b/drivers/gpu/drm/nouveau/core/subdev/bios/xpio.c new file mode 100644 index 000000000000..e9b8e5d30a7a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/xpio.c @@ -0,0 +1,76 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/bios.h> +#include <subdev/bios/gpio.h> +#include <subdev/bios/xpio.h> + +static u16 +dcb_xpiod_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len) +{ + u16 data = dcb_gpio_table(bios, ver, hdr, cnt, len); + if (data && *ver >= 0x40 && *hdr >= 0x06) { + u16 xpio = nv_ro16(bios, data + 0x04); + if (xpio) { + *ver = nv_ro08(bios, data + 0x00); + *hdr = nv_ro08(bios, data + 0x01); + *cnt = nv_ro08(bios, data + 0x02); + *len = nv_ro08(bios, data + 0x03); + return xpio; + } + } + return 0x0000; +} + +u16 +dcb_xpio_table(struct nouveau_bios *bios, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len) +{ + u16 data = dcb_xpiod_table(bios, ver, hdr, cnt, len); + if (data && idx < *cnt) { + u16 xpio = nv_ro16(bios, data + *hdr + (idx * *len)); + if (xpio) { + *ver = nv_ro08(bios, data + 0x00); + *hdr = nv_ro08(bios, data + 0x01); + *cnt = nv_ro08(bios, data + 0x02); + *len = nv_ro08(bios, data + 0x03); + return xpio; + } + } + return 0x0000; +} + +u16 +dcb_xpio_parse(struct nouveau_bios *bios, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len, + struct nvbios_xpio *info) +{ + u16 data = dcb_xpio_table(bios, idx, ver, hdr, cnt, len); + if (data && *len >= 6) { + info->type = nv_ro08(bios, data + 0x04); + info->addr = nv_ro08(bios, data + 0x05); + info->flags = nv_ro08(bios, data + 0x06); + } + return 0x0000; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nv04.c new file mode 100644 index 000000000000..8c7f8057a185 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nv04.c @@ -0,0 +1,95 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nv04_bus_priv { + struct nouveau_bus base; +}; + +static void +nv04_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + + if (stat & 0x00000001) { + nv_error(pbus, "BUS ERROR\n"); + stat &= ~0x00000001; + nv_wr32(pbus, 0x001100, 0x00000001); + } + + if (stat & 0x00000110) { + subdev = nouveau_subdev(subdev, NVDEV_SUBDEV_GPIO); + if (subdev && subdev->intr) + subdev->intr(subdev); + stat &= ~0x00000110; + nv_wr32(pbus, 0x001100, 0x00000110); + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0x00000000); + } +} + +static int +nv04_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv04_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nv04_bus_intr; + return 0; +} + +static int +nv04_bus_init(struct nouveau_object *object) +{ + struct nv04_bus_priv *priv = (void *)object; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x00000111); + + return nouveau_bus_init(&priv->base); +} + +struct nouveau_oclass +nv04_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0x04), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv04_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nv04_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nv31.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nv31.c new file mode 100644 index 000000000000..34132aef34e1 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nv31.c @@ -0,0 +1,112 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nv31_bus_priv { + struct nouveau_bus base; +}; + +static void +nv31_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + u32 gpio = nv_rd32(pbus, 0x001104) & nv_rd32(pbus, 0x001144); + + if (gpio) { + subdev = nouveau_subdev(pbus, NVDEV_SUBDEV_GPIO); + if (subdev && subdev->intr) + subdev->intr(subdev); + } + + if (stat & 0x00000008) { /* NV41- */ + u32 addr = nv_rd32(pbus, 0x009084); + u32 data = nv_rd32(pbus, 0x009088); + + nv_error(pbus, "MMIO %s of 0x%08x FAULT at 0x%06x\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc)); + + stat &= ~0x00000008; + nv_wr32(pbus, 0x001100, 0x00000008); + } + + if (stat & 0x00070000) { + subdev = nouveau_subdev(pbus, NVDEV_SUBDEV_THERM); + if (subdev && subdev->intr) + subdev->intr(subdev); + stat &= ~0x00070000; + nv_wr32(pbus, 0x001100, 0x00070000); + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0x00000000); + } +} + +static int +nv31_bus_init(struct nouveau_object *object) +{ + struct nv31_bus_priv *priv = (void *)object; + int ret; + + ret = nouveau_bus_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x00070008); + return 0; +} + +static int +nv31_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv31_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nv31_bus_intr; + return 0; +} + +struct nouveau_oclass +nv31_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0x31), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv31_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nv31_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nv50.c new file mode 100644 index 000000000000..f5b2117fa8c6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nv50.c @@ -0,0 +1,105 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nv50_bus_priv { + struct nouveau_bus base; +}; + +static void +nv50_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + + if (stat & 0x00000008) { + u32 addr = nv_rd32(pbus, 0x009084); + u32 data = nv_rd32(pbus, 0x009088); + + nv_error(pbus, "MMIO %s of 0x%08x FAULT at 0x%06x\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc)); + + stat &= ~0x00000008; + nv_wr32(pbus, 0x001100, 0x00000008); + } + + if (stat & 0x00010000) { + subdev = nouveau_subdev(pbus, NVDEV_SUBDEV_THERM); + if (subdev && subdev->intr) + subdev->intr(subdev); + stat &= ~0x00010000; + nv_wr32(pbus, 0x001100, 0x00010000); + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0); + } +} + +static int +nv50_bus_init(struct nouveau_object *object) +{ + struct nv50_bus_priv *priv = (void *)object; + int ret; + + ret = nouveau_bus_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x00010008); + return 0; +} + +static int +nv50_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nv50_bus_intr; + return 0; +} + +struct nouveau_oclass +nv50_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0x50), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv50_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nv50_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nvc0.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nvc0.c new file mode 100644 index 000000000000..b192d6246363 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nvc0.c @@ -0,0 +1,101 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nvc0_bus_priv { + struct nouveau_bus base; +}; + +static void +nvc0_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + + if (stat & 0x0000000e) { + u32 addr = nv_rd32(pbus, 0x009084); + u32 data = nv_rd32(pbus, 0x009088); + + nv_error(pbus, "MMIO %s of 0x%08x FAULT at 0x%06x [ %s%s%s]\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc), + (stat & 0x00000002) ? "!ENGINE " : "", + (stat & 0x00000004) ? "IBUS " : "", + (stat & 0x00000008) ? "TIMEOUT " : ""); + + nv_wr32(pbus, 0x009084, 0x00000000); + nv_wr32(pbus, 0x001100, (stat & 0x0000000e)); + stat &= ~0x0000000e; + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0x00000000); + } +} + +static int +nvc0_bus_init(struct nouveau_object *object) +{ + struct nvc0_bus_priv *priv = (void *)object; + int ret; + + ret = nouveau_bus_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x0000000e); + return 0; +} + +static int +nvc0_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nvc0_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nvc0_bus_intr; + return 0; +} + +struct nouveau_oclass +nvc0_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0xc0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvc0_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nvc0_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/base.c b/drivers/gpu/drm/nouveau/core/subdev/device/base.c index f8a7ed4166cf..3937ced5c753 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/base.c @@ -66,6 +66,7 @@ static const u64 disable_map[] = { [NVDEV_SUBDEV_CLOCK] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_MXM] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_MC] = NV_DEVICE_DISABLE_CORE, + [NVDEV_SUBDEV_BUS] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_TIMER] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_FB] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_LTCG] = NV_DEVICE_DISABLE_CORE, @@ -103,8 +104,8 @@ nouveau_devobj_ctor(struct nouveau_object *parent, struct nouveau_device *device; struct nouveau_devobj *devobj; struct nv_device_class *args = data; - u64 disable, boot0, strap; - u64 mmio_base, mmio_size; + u32 boot0, strap; + u64 disable, mmio_base, mmio_size; void __iomem *map; int ret, i, c; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c index 8626d0d6cbbc..473c5c03d3c9 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/i2c.h> #include <subdev/clock.h> #include <subdev/devinit.h> @@ -46,10 +47,11 @@ nv04_identify(struct nouveau_device *device) case 0x04: device->cname = "NV04"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv04_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv04_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -63,10 +65,11 @@ nv04_identify(struct nouveau_device *device) case 0x05: device->cname = "NV05"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv05_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv04_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c index 9c40b0fb23f6..d0774f5bebe1 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -48,10 +49,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV10"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -64,10 +66,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV15"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -82,10 +85,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV16"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -100,10 +104,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "nForce"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv1a_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -118,10 +123,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV11"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -136,10 +142,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV17"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -154,10 +161,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "nForce2"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv1a_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -172,10 +180,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV18"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c index 74f88f48e1c2..ab920e0dc45b 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -49,10 +50,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV20"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv20_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -67,10 +69,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV25"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv25_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -85,10 +88,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV28"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv25_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -103,10 +107,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV2A"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv25_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c index 0ac1b2c4f61d..5f2110261b04 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -49,10 +50,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV30"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv30_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -67,10 +69,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV35"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv35_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -85,10 +88,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV31"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv30_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -104,10 +108,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV36"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv36_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -123,10 +128,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV34"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c index 41d59689a021..f3d55efe9ac9 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c @@ -24,6 +24,8 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> +#include <subdev/vm.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -50,11 +52,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV40"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv40_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -70,11 +73,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV41"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv41_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -90,11 +94,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV42"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv41_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -110,11 +115,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV43"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv41_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -130,11 +136,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV45"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv40_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -150,11 +157,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G70"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv47_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -170,11 +178,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G71"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv49_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -190,11 +199,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G73"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv49_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -210,11 +220,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV44"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv44_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -230,11 +241,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G72"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -250,11 +262,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV44A"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv44_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -270,11 +283,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C61"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -290,11 +304,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C51"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv4e_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv4e_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -310,11 +325,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C73"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -330,11 +346,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C67"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -350,11 +367,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C68"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c index 6ccfd8585ba2..5ed2fa51ddc2 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -57,12 +58,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G80"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -79,12 +81,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G84"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -104,12 +107,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G86"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -129,12 +133,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G92"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -154,12 +159,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G94"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -179,12 +185,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G96"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -204,12 +211,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G98"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -229,12 +237,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G200"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -254,12 +263,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "MCP77/MCP78"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -279,12 +289,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "MCP79/MCP7A"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -304,12 +315,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "GT215"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -330,12 +342,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "GT216"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -355,12 +368,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "GT218"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -380,12 +394,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "MCP89"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c b/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c index f0461685a422..4393eb4d6564 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -57,12 +58,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF100"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -85,12 +87,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF104"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -113,12 +116,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF106"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -141,12 +145,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF114"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -169,12 +174,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF116"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -197,12 +203,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF108"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -225,12 +232,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF110"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -253,12 +261,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF119"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -282,4 +291,4 @@ nvc0_identify(struct nouveau_device *device) } return 0; -} + } diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c b/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c index 03a652876e73..5c12391619fd 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -56,13 +57,14 @@ nve0_identify(struct nouveau_device *device) case 0xe4: device->cname = "GK104"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_GPIO ] = &nve0_gpio_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -84,13 +86,14 @@ nve0_identify(struct nouveau_device *device) case 0xe7: device->cname = "GK107"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_GPIO ] = &nve0_gpio_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -112,13 +115,14 @@ nve0_identify(struct nouveau_device *device) case 0xe6: device->cname = "GK106"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_GPIO ] = &nve0_gpio_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c index ae7249b09797..4a8577838417 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c @@ -78,12 +78,13 @@ nv50_devinit_init(struct nouveau_object *object) if (ret) return ret; - /* if we ran the init tables, execute first script pointer for each - * display table output entry that has a matching dcb entry. + /* if we ran the init tables, we have to execute the first script + * pointer of each dcb entry's display encoder table in order + * to properly initialise each encoder. */ - while (priv->base.post && ver) { - u16 data = nvbios_outp_parse(bios, i++, &ver, &hdr, &cnt, &len, &info); - if (data && dcb_outp_match(bios, info.type, info.mask, &ver, &len, &outp)) { + while (priv->base.post && dcb_outp_parse(bios, i, &ver, &hdr, &outp)) { + if (nvbios_outp_match(bios, outp.hasht, outp.hashm, + &ver, &hdr, &cnt, &len, &info)) { struct nvbios_init init = { .subdev = nv_subdev(priv), .bios = bios, @@ -95,7 +96,8 @@ nv50_devinit_init(struct nouveau_object *object) nvbios_exec(&init); } - }; + i++; + } return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c index 487cb8c6c204..a4338d92b02e 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c @@ -22,8 +22,10 @@ * Authors: Ben Skeggs */ -#include <core/object.h> +#include <core/client.h> #include <core/enum.h> +#include <core/engctx.h> +#include <core/object.h> #include <subdev/fb.h> #include <subdev/bios.h> @@ -302,17 +304,18 @@ static const struct nouveau_enum vm_client[] = { }; static const struct nouveau_enum vm_engine[] = { - { 0x00000000, "PGRAPH", NULL }, - { 0x00000001, "PVP", NULL }, + { 0x00000000, "PGRAPH", NULL, NVDEV_ENGINE_GR }, + { 0x00000001, "PVP", NULL, NVDEV_ENGINE_VP }, { 0x00000004, "PEEPHOLE", NULL }, - { 0x00000005, "PFIFO", vm_pfifo_subclients }, + { 0x00000005, "PFIFO", vm_pfifo_subclients, NVDEV_ENGINE_FIFO }, { 0x00000006, "BAR", vm_bar_subclients }, - { 0x00000008, "PPPP", NULL }, - { 0x00000009, "PBSP", NULL }, - { 0x0000000a, "PCRYPT", NULL }, + { 0x00000008, "PPPP", NULL, NVDEV_ENGINE_PPP }, + { 0x00000008, "PMPEG", NULL, NVDEV_ENGINE_MPEG }, + { 0x00000009, "PBSP", NULL, NVDEV_ENGINE_BSP }, + { 0x0000000a, "PCRYPT", NULL, NVDEV_ENGINE_CRYPT }, { 0x0000000b, "PCOUNTER", NULL }, { 0x0000000c, "SEMAPHORE_BG", NULL }, - { 0x0000000d, "PCOPY", NULL }, + { 0x0000000d, "PCOPY", NULL, NVDEV_ENGINE_COPY0 }, { 0x0000000e, "PDAEMON", NULL }, {} }; @@ -334,8 +337,10 @@ static void nv50_fb_intr(struct nouveau_subdev *subdev) { struct nouveau_device *device = nv_device(subdev); + struct nouveau_engine *engine; struct nv50_fb_priv *priv = (void *)subdev; const struct nouveau_enum *en, *cl; + struct nouveau_object *engctx = NULL; u32 trap[6], idx, chan; u8 st0, st1, st2, st3; int i; @@ -366,36 +371,55 @@ nv50_fb_intr(struct nouveau_subdev *subdev) } chan = (trap[2] << 16) | trap[1]; - nv_error(priv, "trapped %s at 0x%02x%04x%04x on channel 0x%08x ", + en = nouveau_enum_find(vm_engine, st0); + + if (en && en->data2) { + const struct nouveau_enum *orig_en = en; + while (en->name && en->value == st0 && en->data2) { + engine = nouveau_engine(subdev, en->data2); + if (engine) { + engctx = nouveau_engctx_get(engine, chan); + if (engctx) + break; + } + en++; + } + if (!engctx) + en = orig_en; + } + + nv_error(priv, "trapped %s at 0x%02x%04x%04x on channel 0x%08x [%s] ", (trap[5] & 0x00000100) ? "read" : "write", - trap[5] & 0xff, trap[4] & 0xffff, trap[3] & 0xffff, chan); + trap[5] & 0xff, trap[4] & 0xffff, trap[3] & 0xffff, chan, + nouveau_client_name(engctx)); + + nouveau_engctx_put(engctx); - en = nouveau_enum_find(vm_engine, st0); if (en) - printk("%s/", en->name); + pr_cont("%s/", en->name); else - printk("%02x/", st0); + pr_cont("%02x/", st0); cl = nouveau_enum_find(vm_client, st2); if (cl) - printk("%s/", cl->name); + pr_cont("%s/", cl->name); else - printk("%02x/", st2); + pr_cont("%02x/", st2); if (cl && cl->data) cl = nouveau_enum_find(cl->data, st3); else if (en && en->data) cl = nouveau_enum_find(en->data, st3); else cl = NULL; if (cl) - printk("%s", cl->name); + pr_cont("%s", cl->name); else - printk("%02x", st3); + pr_cont("%02x", st3); - printk(" reason: "); + pr_cont(" reason: "); en = nouveau_enum_find(vm_fault, st1); if (en) - printk("%s\n", en->name); + pr_cont("%s\n", en->name); else - printk("0x%08x\n", st1); + pr_cont("0x%08x\n", st1); } static int diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c index 9fb0f9b92d49..d422acc9af15 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c @@ -102,135 +102,19 @@ nouveau_gpio_get(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line) return ret; } -static int -nouveau_gpio_irq(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, bool on) -{ - struct dcb_gpio_func func; - int ret; - - ret = nouveau_gpio_find(gpio, idx, tag, line, &func); - if (ret == 0) { - if (idx == 0 && gpio->irq_enable) - gpio->irq_enable(gpio, func.line, on); - else - ret = -ENODEV; - } - - return ret; -} - -struct gpio_isr { - struct nouveau_gpio *gpio; - struct list_head head; - struct work_struct work; - int idx; - struct dcb_gpio_func func; - void (*handler)(void *, int); - void *data; - bool inhibit; -}; - -static void -nouveau_gpio_isr_bh(struct work_struct *work) -{ - struct gpio_isr *isr = container_of(work, struct gpio_isr, work); - struct nouveau_gpio *gpio = isr->gpio; - unsigned long flags; - int state; - - state = nouveau_gpio_get(gpio, isr->idx, isr->func.func, - isr->func.line); - if (state >= 0) - isr->handler(isr->data, state); - - spin_lock_irqsave(&gpio->lock, flags); - isr->inhibit = false; - spin_unlock_irqrestore(&gpio->lock, flags); -} - -static void -nouveau_gpio_isr_run(struct nouveau_gpio *gpio, int idx, u32 line_mask) -{ - struct gpio_isr *isr; - - if (idx != 0) - return; - - spin_lock(&gpio->lock); - list_for_each_entry(isr, &gpio->isr, head) { - if (line_mask & (1 << isr->func.line)) { - if (isr->inhibit) - continue; - isr->inhibit = true; - schedule_work(&isr->work); - } - } - spin_unlock(&gpio->lock); -} - -static int -nouveau_gpio_isr_add(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, - void (*handler)(void *, int), void *data) -{ - struct gpio_isr *isr; - unsigned long flags; - int ret; - - isr = kzalloc(sizeof(*isr), GFP_KERNEL); - if (!isr) - return -ENOMEM; - - ret = nouveau_gpio_find(gpio, idx, tag, line, &isr->func); - if (ret) { - kfree(isr); - return ret; - } - - INIT_WORK(&isr->work, nouveau_gpio_isr_bh); - isr->gpio = gpio; - isr->handler = handler; - isr->data = data; - isr->idx = idx; - - spin_lock_irqsave(&gpio->lock, flags); - list_add(&isr->head, &gpio->isr); - spin_unlock_irqrestore(&gpio->lock, flags); - return 0; -} - -static void -nouveau_gpio_isr_del(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, - void (*handler)(void *, int), void *data) +void +_nouveau_gpio_dtor(struct nouveau_object *object) { - struct gpio_isr *isr, *tmp; - struct dcb_gpio_func func; - unsigned long flags; - LIST_HEAD(tofree); - int ret; - - ret = nouveau_gpio_find(gpio, idx, tag, line, &func); - if (ret == 0) { - spin_lock_irqsave(&gpio->lock, flags); - list_for_each_entry_safe(isr, tmp, &gpio->isr, head) { - if (memcmp(&isr->func, &func, sizeof(func)) || - isr->idx != idx || - isr->handler != handler || isr->data != data) - continue; - list_move_tail(&isr->head, &tofree); - } - spin_unlock_irqrestore(&gpio->lock, flags); - - list_for_each_entry_safe(isr, tmp, &tofree, head) { - flush_work(&isr->work); - kfree(isr); - } - } + struct nouveau_gpio *gpio = (void *)object; + nouveau_event_destroy(&gpio->events); + nouveau_subdev_destroy(&gpio->base); } int nouveau_gpio_create_(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, int length, void **pobject) + struct nouveau_oclass *oclass, int lines, + int length, void **pobject) { struct nouveau_gpio *gpio; int ret; @@ -241,15 +125,13 @@ nouveau_gpio_create_(struct nouveau_object *parent, if (ret) return ret; + ret = nouveau_event_create(lines, &gpio->events); + if (ret) + return ret; + gpio->find = nouveau_gpio_find; gpio->set = nouveau_gpio_set; gpio->get = nouveau_gpio_get; - gpio->irq = nouveau_gpio_irq; - gpio->isr_run = nouveau_gpio_isr_run; - gpio->isr_add = nouveau_gpio_isr_add; - gpio->isr_del = nouveau_gpio_isr_del; - INIT_LIST_HEAD(&gpio->isr); - spin_lock_init(&gpio->lock); return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c index 168d16a9a8e9..76d5d5465ddd 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c @@ -24,7 +24,7 @@ * */ -#include <subdev/gpio.h> +#include "priv.h" struct nv10_gpio_priv { struct nouveau_gpio base; @@ -83,27 +83,36 @@ nv10_gpio_drive(struct nouveau_gpio *gpio, int line, int dir, int out) } static void -nv10_gpio_irq_enable(struct nouveau_gpio *gpio, int line, bool on) -{ - u32 mask = 0x00010001 << line; - - nv_wr32(gpio, 0x001104, mask); - nv_mask(gpio, 0x001144, mask, on ? mask : 0); -} - -static void nv10_gpio_intr(struct nouveau_subdev *subdev) { struct nv10_gpio_priv *priv = (void *)subdev; u32 intr = nv_rd32(priv, 0x001104); u32 hi = (intr & 0x0000ffff) >> 0; u32 lo = (intr & 0xffff0000) >> 16; + int i; - priv->base.isr_run(&priv->base, 0, hi | lo); + for (i = 0; (hi | lo) && i < 32; i++) { + if ((hi | lo) & (1 << i)) + nouveau_event_trigger(priv->base.events, i); + } nv_wr32(priv, 0x001104, intr); } +static void +nv10_gpio_intr_enable(struct nouveau_event *event, int line) +{ + nv_wr32(event->priv, 0x001104, 0x00010001 << line); + nv_mask(event->priv, 0x001144, 0x00010001 << line, 0x00010001 << line); +} + +static void +nv10_gpio_intr_disable(struct nouveau_event *event, int line) +{ + nv_wr32(event->priv, 0x001104, 0x00010001 << line); + nv_mask(event->priv, 0x001144, 0x00010001 << line, 0x00000000); +} + static int nv10_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -112,14 +121,16 @@ nv10_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv10_gpio_priv *priv; int ret; - ret = nouveau_gpio_create(parent, engine, oclass, &priv); + ret = nouveau_gpio_create(parent, engine, oclass, 16, &priv); *pobject = nv_object(priv); if (ret) return ret; priv->base.drive = nv10_gpio_drive; priv->base.sense = nv10_gpio_sense; - priv->base.irq_enable = nv10_gpio_irq_enable; + priv->base.events->priv = priv; + priv->base.events->enable = nv10_gpio_intr_enable; + priv->base.events->disable = nv10_gpio_intr_disable; nv_subdev(priv)->intr = nv10_gpio_intr; return 0; } @@ -141,8 +152,6 @@ nv10_gpio_init(struct nouveau_object *object) if (ret) return ret; - nv_wr32(priv, 0x001140, 0x00000000); - nv_wr32(priv, 0x001100, 0xffffffff); nv_wr32(priv, 0x001144, 0x00000000); nv_wr32(priv, 0x001104, 0xffffffff); return 0; @@ -152,7 +161,6 @@ static int nv10_gpio_fini(struct nouveau_object *object, bool suspend) { struct nv10_gpio_priv *priv = (void *)object; - nv_wr32(priv, 0x001140, 0x00000000); nv_wr32(priv, 0x001144, 0x00000000); return nouveau_gpio_fini(&priv->base, suspend); } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c index bf13a1200f26..bf489dcf46e2 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c @@ -22,7 +22,7 @@ * Authors: Ben Skeggs */ -#include <subdev/gpio.h> +#include "priv.h" struct nv50_gpio_priv { struct nouveau_gpio base; @@ -95,21 +95,12 @@ nv50_gpio_sense(struct nouveau_gpio *gpio, int line) } void -nv50_gpio_irq_enable(struct nouveau_gpio *gpio, int line, bool on) -{ - u32 reg = line < 16 ? 0xe050 : 0xe070; - u32 mask = 0x00010001 << (line & 0xf); - - nv_wr32(gpio, reg + 4, mask); - nv_mask(gpio, reg + 0, mask, on ? mask : 0); -} - -void nv50_gpio_intr(struct nouveau_subdev *subdev) { struct nv50_gpio_priv *priv = (void *)subdev; u32 intr0, intr1 = 0; u32 hi, lo; + int i; intr0 = nv_rd32(priv, 0xe054) & nv_rd32(priv, 0xe050); if (nv_device(priv)->chipset >= 0x90) @@ -117,13 +108,35 @@ nv50_gpio_intr(struct nouveau_subdev *subdev) hi = (intr0 & 0x0000ffff) | (intr1 << 16); lo = (intr0 >> 16) | (intr1 & 0xffff0000); - priv->base.isr_run(&priv->base, 0, hi | lo); + + for (i = 0; (hi | lo) && i < 32; i++) { + if ((hi | lo) & (1 << i)) + nouveau_event_trigger(priv->base.events, i); + } nv_wr32(priv, 0xe054, intr0); if (nv_device(priv)->chipset >= 0x90) nv_wr32(priv, 0xe074, intr1); } +void +nv50_gpio_intr_enable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xe050 : 0xe070; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x04, mask); + nv_mask(event->priv, addr + 0x00, mask, mask); +} + +void +nv50_gpio_intr_disable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xe050 : 0xe070; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x04, mask); + nv_mask(event->priv, addr + 0x00, mask, 0x00000000); +} + static int nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -132,7 +145,9 @@ nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_gpio_priv *priv; int ret; - ret = nouveau_gpio_create(parent, engine, oclass, &priv); + ret = nouveau_gpio_create(parent, engine, oclass, + nv_device(parent)->chipset >= 0x90 ? 32 : 16, + &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -140,7 +155,9 @@ nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->base.reset = nv50_gpio_reset; priv->base.drive = nv50_gpio_drive; priv->base.sense = nv50_gpio_sense; - priv->base.irq_enable = nv50_gpio_irq_enable; + priv->base.events->priv = priv; + priv->base.events->enable = nv50_gpio_intr_enable; + priv->base.events->disable = nv50_gpio_intr_disable; nv_subdev(priv)->intr = nv50_gpio_intr; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c index 83e8b8f16e6a..010431e3acec 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c @@ -22,13 +22,13 @@ * Authors: Ben Skeggs */ -#include <subdev/gpio.h> +#include "priv.h" struct nvd0_gpio_priv { struct nouveau_gpio base; }; -static void +void nvd0_gpio_reset(struct nouveau_gpio *gpio, u8 match) { struct nouveau_bios *bios = nouveau_bios(gpio); @@ -57,7 +57,7 @@ nvd0_gpio_reset(struct nouveau_gpio *gpio, u8 match) } } -static int +int nvd0_gpio_drive(struct nouveau_gpio *gpio, int line, int dir, int out) { u32 data = ((dir ^ 1) << 13) | (out << 12); @@ -66,7 +66,7 @@ nvd0_gpio_drive(struct nouveau_gpio *gpio, int line, int dir, int out) return 0; } -static int +int nvd0_gpio_sense(struct nouveau_gpio *gpio, int line) { return !!(nv_rd32(gpio, 0x00d610 + (line * 4)) & 0x00004000); @@ -80,7 +80,7 @@ nvd0_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nvd0_gpio_priv *priv; int ret; - ret = nouveau_gpio_create(parent, engine, oclass, &priv); + ret = nouveau_gpio_create(parent, engine, oclass, 32, &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -88,7 +88,9 @@ nvd0_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->base.reset = nvd0_gpio_reset; priv->base.drive = nvd0_gpio_drive; priv->base.sense = nvd0_gpio_sense; - priv->base.irq_enable = nv50_gpio_irq_enable; + priv->base.events->priv = priv; + priv->base.events->enable = nv50_gpio_intr_enable; + priv->base.events->disable = nv50_gpio_intr_disable; nv_subdev(priv)->intr = nv50_gpio_intr; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nve0.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nve0.c new file mode 100644 index 000000000000..16b8c5bf5efa --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nve0.c @@ -0,0 +1,131 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "priv.h" + +struct nve0_gpio_priv { + struct nouveau_gpio base; +}; + +void +nve0_gpio_intr(struct nouveau_subdev *subdev) +{ + struct nve0_gpio_priv *priv = (void *)subdev; + u32 intr0 = nv_rd32(priv, 0xdc00) & nv_rd32(priv, 0xdc08); + u32 intr1 = nv_rd32(priv, 0xdc80) & nv_rd32(priv, 0xdc88); + u32 hi = (intr0 & 0x0000ffff) | (intr1 << 16); + u32 lo = (intr0 >> 16) | (intr1 & 0xffff0000); + int i; + + for (i = 0; (hi | lo) && i < 32; i++) { + if ((hi | lo) & (1 << i)) + nouveau_event_trigger(priv->base.events, i); + } + + nv_wr32(priv, 0xdc00, intr0); + nv_wr32(priv, 0xdc88, intr1); +} + +void +nve0_gpio_intr_enable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xdc00 : 0xdc80; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x08, mask); + nv_mask(event->priv, addr + 0x00, mask, mask); +} + +void +nve0_gpio_intr_disable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xdc00 : 0xdc80; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x08, mask); + nv_mask(event->priv, addr + 0x00, mask, 0x00000000); +} + +int +nve0_gpio_fini(struct nouveau_object *object, bool suspend) +{ + struct nve0_gpio_priv *priv = (void *)object; + nv_wr32(priv, 0xdc08, 0x00000000); + nv_wr32(priv, 0xdc88, 0x00000000); + return nouveau_gpio_fini(&priv->base, suspend); +} + +int +nve0_gpio_init(struct nouveau_object *object) +{ + struct nve0_gpio_priv *priv = (void *)object; + int ret; + + ret = nouveau_gpio_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0xdc00, 0xffffffff); + nv_wr32(priv, 0xdc80, 0xffffffff); + return 0; +} + +void +nve0_gpio_dtor(struct nouveau_object *object) +{ + struct nve0_gpio_priv *priv = (void *)object; + nouveau_gpio_destroy(&priv->base); +} + +static int +nve0_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nve0_gpio_priv *priv; + int ret; + + ret = nouveau_gpio_create(parent, engine, oclass, 32, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.reset = nvd0_gpio_reset; + priv->base.drive = nvd0_gpio_drive; + priv->base.sense = nvd0_gpio_sense; + priv->base.events->priv = priv; + priv->base.events->enable = nve0_gpio_intr_enable; + priv->base.events->disable = nve0_gpio_intr_disable; + nv_subdev(priv)->intr = nve0_gpio_intr; + return 0; +} + +struct nouveau_oclass +nve0_gpio_oclass = { + .handle = NV_SUBDEV(GPIO, 0xe0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nve0_gpio_ctor, + .dtor = nv50_gpio_dtor, + .init = nve0_gpio_init, + .fini = nve0_gpio_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/priv.h b/drivers/gpu/drm/nouveau/core/subdev/gpio/priv.h new file mode 100644 index 000000000000..2ee1c895c782 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/priv.h @@ -0,0 +1,17 @@ +#ifndef __NVKM_GPIO_H__ +#define __NVKM_GPIO_H__ + +#include <subdev/gpio.h> + +void nv50_gpio_dtor(struct nouveau_object *); +int nv50_gpio_init(struct nouveau_object *); +int nv50_gpio_fini(struct nouveau_object *, bool); +void nv50_gpio_intr(struct nouveau_subdev *); +void nv50_gpio_intr_enable(struct nouveau_event *, int line); +void nv50_gpio_intr_disable(struct nouveau_event *, int line); + +void nvd0_gpio_reset(struct nouveau_gpio *, u8); +int nvd0_gpio_drive(struct nouveau_gpio *, int, int, int); +int nvd0_gpio_sense(struct nouveau_gpio *, int); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/anx9805.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/anx9805.c new file mode 100644 index 000000000000..dec94e9d776a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/anx9805.c @@ -0,0 +1,279 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ + +#include <subdev/i2c.h> + +struct anx9805_i2c_port { + struct nouveau_i2c_port base; + u32 addr; + u32 ctrl; +}; + +static int +anx9805_train(struct nouveau_i2c_port *port, int link_nr, int link_bw, bool enh) +{ + struct anx9805_i2c_port *chan = (void *)port; + struct nouveau_i2c_port *mast = (void *)nv_object(chan)->parent; + u8 tmp, i; + + nv_wri2cr(mast, chan->addr, 0xa0, link_bw); + nv_wri2cr(mast, chan->addr, 0xa1, link_nr | (enh ? 0x80 : 0x00)); + nv_wri2cr(mast, chan->addr, 0xa2, 0x01); + nv_wri2cr(mast, chan->addr, 0xa8, 0x01); + + i = 0; + while ((tmp = nv_rdi2cr(mast, chan->addr, 0xa8)) & 0x01) { + mdelay(5); + if (i++ == 100) { + nv_error(port, "link training timed out\n"); + return -ETIMEDOUT; + } + } + + if (tmp & 0x70) { + nv_error(port, "link training failed: 0x%02x\n", tmp); + return -EIO; + } + + return 1; +} + +static int +anx9805_aux(struct nouveau_i2c_port *port, u8 type, u32 addr, u8 *data, u8 size) +{ + struct anx9805_i2c_port *chan = (void *)port; + struct nouveau_i2c_port *mast = (void *)nv_object(chan)->parent; + int i, ret = -ETIMEDOUT; + u8 tmp; + + tmp = nv_rdi2cr(mast, chan->ctrl, 0x07) & ~0x04; + nv_wri2cr(mast, chan->ctrl, 0x07, tmp | 0x04); + nv_wri2cr(mast, chan->ctrl, 0x07, tmp); + nv_wri2cr(mast, chan->ctrl, 0xf7, 0x01); + + nv_wri2cr(mast, chan->addr, 0xe4, 0x80); + for (i = 0; !(type & 1) && i < size; i++) + nv_wri2cr(mast, chan->addr, 0xf0 + i, data[i]); + nv_wri2cr(mast, chan->addr, 0xe5, ((size - 1) << 4) | type); + nv_wri2cr(mast, chan->addr, 0xe6, (addr & 0x000ff) >> 0); + nv_wri2cr(mast, chan->addr, 0xe7, (addr & 0x0ff00) >> 8); + nv_wri2cr(mast, chan->addr, 0xe8, (addr & 0xf0000) >> 16); + nv_wri2cr(mast, chan->addr, 0xe9, 0x01); + + i = 0; + while ((tmp = nv_rdi2cr(mast, chan->addr, 0xe9)) & 0x01) { + mdelay(5); + if (i++ == 32) + goto done; + } + + if ((tmp = nv_rdi2cr(mast, chan->ctrl, 0xf7)) & 0x01) { + ret = -EIO; + goto done; + } + + for (i = 0; (type & 1) && i < size; i++) + data[i] = nv_rdi2cr(mast, chan->addr, 0xf0 + i); + ret = 0; +done: + nv_wri2cr(mast, chan->ctrl, 0xf7, 0x01); + return ret; +} + +static const struct nouveau_i2c_func +anx9805_aux_func = { + .aux = anx9805_aux, + .lnk_ctl = anx9805_train, +}; + +static int +anx9805_aux_chan_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct nouveau_i2c_port *mast = (void *)parent; + struct anx9805_i2c_port *chan; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_aux_algo, &chan); + *pobject = nv_object(chan); + if (ret) + return ret; + + switch ((oclass->handle & 0xff00) >> 8) { + case 0x0d: + chan->addr = 0x38; + chan->ctrl = 0x39; + break; + case 0x0e: + chan->addr = 0x3c; + chan->ctrl = 0x3b; + break; + default: + BUG_ON(1); + } + + if (mast->adapter.algo == &i2c_bit_algo) { + struct i2c_algo_bit_data *algo = mast->adapter.algo_data; + algo->udelay = max(algo->udelay, 40); + } + + chan->base.func = &anx9805_aux_func; + return 0; +} + +static struct nouveau_ofuncs +anx9805_aux_ofuncs = { + .ctor = anx9805_aux_chan_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, +}; + +static int +anx9805_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct anx9805_i2c_port *port = adap->algo_data; + struct nouveau_i2c_port *mast = (void *)nv_object(port)->parent; + struct i2c_msg *msg = msgs; + int ret = -ETIMEDOUT; + int i, j, cnt = num; + u8 seg = 0x00, off = 0x00, tmp; + + tmp = nv_rdi2cr(mast, port->ctrl, 0x07) & ~0x10; + nv_wri2cr(mast, port->ctrl, 0x07, tmp | 0x10); + nv_wri2cr(mast, port->ctrl, 0x07, tmp); + nv_wri2cr(mast, port->addr, 0x43, 0x05); + mdelay(5); + + while (cnt--) { + if ( (msg->flags & I2C_M_RD) && msg->addr == 0x50) { + nv_wri2cr(mast, port->addr, 0x40, msg->addr << 1); + nv_wri2cr(mast, port->addr, 0x41, seg); + nv_wri2cr(mast, port->addr, 0x42, off); + nv_wri2cr(mast, port->addr, 0x44, msg->len); + nv_wri2cr(mast, port->addr, 0x45, 0x00); + nv_wri2cr(mast, port->addr, 0x43, 0x01); + for (i = 0; i < msg->len; i++) { + j = 0; + while (nv_rdi2cr(mast, port->addr, 0x46) & 0x10) { + mdelay(5); + if (j++ == 32) + goto done; + } + msg->buf[i] = nv_rdi2cr(mast, port->addr, 0x47); + } + } else + if (!(msg->flags & I2C_M_RD)) { + if (msg->addr == 0x50 && msg->len == 0x01) { + off = msg->buf[0]; + } else + if (msg->addr == 0x30 && msg->len == 0x01) { + seg = msg->buf[0]; + } else + goto done; + } else { + goto done; + } + msg++; + } + + ret = num; +done: + nv_wri2cr(mast, port->addr, 0x43, 0x00); + return ret; +} + +static u32 +anx9805_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm +anx9805_i2c_algo = { + .master_xfer = anx9805_xfer, + .functionality = anx9805_func +}; + +static const struct nouveau_i2c_func +anx9805_i2c_func = { +}; + +static int +anx9805_ddc_port_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct nouveau_i2c_port *mast = (void *)parent; + struct anx9805_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &anx9805_i2c_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + switch ((oclass->handle & 0xff00) >> 8) { + case 0x0d: + port->addr = 0x3d; + port->ctrl = 0x39; + break; + case 0x0e: + port->addr = 0x3f; + port->ctrl = 0x3b; + break; + default: + BUG_ON(1); + } + + if (mast->adapter.algo == &i2c_bit_algo) { + struct i2c_algo_bit_data *algo = mast->adapter.algo_data; + algo->udelay = max(algo->udelay, 40); + } + + port->base.func = &anx9805_i2c_func; + return 0; +} + +static struct nouveau_ofuncs +anx9805_ddc_ofuncs = { + .ctor = anx9805_ddc_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, +}; + +struct nouveau_oclass +nouveau_anx9805_sclass[] = { + { .handle = NV_I2C_TYPE_EXTDDC(0x0d), .ofuncs = &anx9805_ddc_ofuncs }, + { .handle = NV_I2C_TYPE_EXTAUX(0x0d), .ofuncs = &anx9805_aux_ofuncs }, + { .handle = NV_I2C_TYPE_EXTDDC(0x0e), .ofuncs = &anx9805_ddc_ofuncs }, + { .handle = NV_I2C_TYPE_EXTAUX(0x0e), .ofuncs = &anx9805_aux_ofuncs }, + {} +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c index dc27e794a851..5de074ad170b 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c @@ -24,151 +24,40 @@ #include <subdev/i2c.h> -/****************************************************************************** - * aux channel util functions - *****************************************************************************/ -#define AUX_DBG(fmt, args...) nv_debug(aux, "AUXCH(%d): " fmt, ch, ##args) -#define AUX_ERR(fmt, args...) nv_error(aux, "AUXCH(%d): " fmt, ch, ##args) - -static void -auxch_fini(struct nouveau_i2c *aux, int ch) -{ - nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00310000, 0x00000000); -} - -static int -auxch_init(struct nouveau_i2c *aux, int ch) -{ - const u32 unksel = 1; /* nfi which to use, or if it matters.. */ - const u32 ureq = unksel ? 0x00100000 : 0x00200000; - const u32 urep = unksel ? 0x01000000 : 0x02000000; - u32 ctrl, timeout; - - /* wait up to 1ms for any previous transaction to be done... */ - timeout = 1000; - do { - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - udelay(1); - if (!timeout--) { - AUX_ERR("begin idle timeout 0x%08x\n", ctrl); - return -EBUSY; - } - } while (ctrl & 0x03010000); - - /* set some magic, and wait up to 1ms for it to appear */ - nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00300000, ureq); - timeout = 1000; - do { - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - udelay(1); - if (!timeout--) { - AUX_ERR("magic wait 0x%08x\n", ctrl); - auxch_fini(aux, ch); - return -EBUSY; - } - } while ((ctrl & 0x03000000) != urep); - - return 0; -} - -static int -auxch_tx(struct nouveau_i2c *aux, int ch, u8 type, u32 addr, u8 *data, u8 size) -{ - u32 ctrl, stat, timeout, retries; - u32 xbuf[4] = {}; - int ret, i; - - AUX_DBG("%d: 0x%08x %d\n", type, addr, size); - - ret = auxch_init(aux, ch); - if (ret) - goto out; - - stat = nv_rd32(aux, 0x00e4e8 + (ch * 0x50)); - if (!(stat & 0x10000000)) { - AUX_DBG("sink not detected\n"); - ret = -ENXIO; - goto out; - } - - if (!(type & 1)) { - memcpy(xbuf, data, size); - for (i = 0; i < 16; i += 4) { - AUX_DBG("wr 0x%08x\n", xbuf[i / 4]); - nv_wr32(aux, 0x00e4c0 + (ch * 0x50) + i, xbuf[i / 4]); - } - } - - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - ctrl &= ~0x0001f0ff; - ctrl |= type << 12; - ctrl |= size - 1; - nv_wr32(aux, 0x00e4e0 + (ch * 0x50), addr); - - /* retry transaction a number of times on failure... */ - ret = -EREMOTEIO; - for (retries = 0; retries < 32; retries++) { - /* reset, and delay a while if this is a retry */ - nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x80000000 | ctrl); - nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00000000 | ctrl); - if (retries) - udelay(400); - - /* transaction request, wait up to 1ms for it to complete */ - nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00010000 | ctrl); - - timeout = 1000; - do { - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - udelay(1); - if (!timeout--) { - AUX_ERR("tx req timeout 0x%08x\n", ctrl); - goto out; - } - } while (ctrl & 0x00010000); - - /* read status, and check if transaction completed ok */ - stat = nv_mask(aux, 0x00e4e8 + (ch * 0x50), 0, 0); - if (!(stat & 0x000f0f00)) { - ret = 0; - break; - } - - AUX_DBG("%02d 0x%08x 0x%08x\n", retries, ctrl, stat); - } - - if (type & 1) { - for (i = 0; i < 16; i += 4) { - xbuf[i / 4] = nv_rd32(aux, 0x00e4d0 + (ch * 0x50) + i); - AUX_DBG("rd 0x%08x\n", xbuf[i / 4]); - } - memcpy(data, xbuf, size); - } - -out: - auxch_fini(aux, ch); - return ret; -} - int -nv_rdaux(struct nouveau_i2c_port *auxch, u32 addr, u8 *data, u8 size) +nv_rdaux(struct nouveau_i2c_port *port, u32 addr, u8 *data, u8 size) { - return auxch_tx(auxch->i2c, auxch->drive, 9, addr, data, size); + if (port->func->aux) { + if (port->func->acquire) + port->func->acquire(port); + return port->func->aux(port, 9, addr, data, size); + } + return -ENODEV; } int -nv_wraux(struct nouveau_i2c_port *auxch, u32 addr, u8 *data, u8 size) +nv_wraux(struct nouveau_i2c_port *port, u32 addr, u8 *data, u8 size) { - return auxch_tx(auxch->i2c, auxch->drive, 8, addr, data, size); + if (port->func->aux) { + if (port->func->acquire) + port->func->acquire(port); + return port->func->aux(port, 8, addr, data, size); + } + return -ENODEV; } static int aux_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { - struct nouveau_i2c_port *auxch = (struct nouveau_i2c_port *)adap; + struct nouveau_i2c_port *port = adap->algo_data; struct i2c_msg *msg = msgs; int ret, mcnt = num; + if (!port->func->aux) + return -ENODEV; + if ( port->func->acquire) + port->func->acquire(port); + while (mcnt--) { u8 remaining = msg->len; u8 *ptr = msg->buf; @@ -185,8 +74,7 @@ aux_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) if (mcnt || remaining > 16) cmd |= 4; /* MOT */ - ret = auxch_tx(auxch->i2c, auxch->drive, cmd, - msg->addr, ptr, cnt); + ret = port->func->aux(port, cmd, msg->addr, ptr, cnt); if (ret < 0) return ret; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c index dbfc2abf0cfe..a114a0ed7e98 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c @@ -1,5 +1,5 @@ /* - * Copyright 2012 Red Hat Inc. + * Copyright 2013 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -22,64 +22,136 @@ * Authors: Ben Skeggs */ -#include "core/option.h" +#include <core/option.h> -#include "subdev/i2c.h" -#include "subdev/vga.h" +#include <subdev/bios.h> +#include <subdev/bios/dcb.h> +#include <subdev/bios/i2c.h> +#include <subdev/i2c.h> +#include <subdev/vga.h> -int -nv_rdi2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg) +/****************************************************************************** + * interface to linux i2c bit-banging algorithm + *****************************************************************************/ + +#ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT +#define CSTMSEL true +#else +#define CSTMSEL false +#endif + +static int +nouveau_i2c_pre_xfer(struct i2c_adapter *adap) { - u8 val; - struct i2c_msg msgs[] = { - { .addr = addr, .flags = 0, .len = 1, .buf = ® }, - { .addr = addr, .flags = I2C_M_RD, .len = 1, .buf = &val }, - }; + struct i2c_algo_bit_data *bit = adap->algo_data; + struct nouveau_i2c_port *port = bit->data; + if (port->func->acquire) + port->func->acquire(port); + return 0; +} - int ret = i2c_transfer(&port->adapter, msgs, 2); - if (ret != 2) - return -EIO; +static void +nouveau_i2c_setscl(void *data, int state) +{ + struct nouveau_i2c_port *port = data; + port->func->drive_scl(port, state); +} - return val; +static void +nouveau_i2c_setsda(void *data, int state) +{ + struct nouveau_i2c_port *port = data; + port->func->drive_sda(port, state); } -int -nv_wri2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg, u8 val) +static int +nouveau_i2c_getscl(void *data) { - struct i2c_msg msgs[] = { - { .addr = addr, .flags = 0, .len = 1, .buf = ® }, - { .addr = addr, .flags = 0, .len = 1, .buf = &val }, - }; + struct nouveau_i2c_port *port = data; + return port->func->sense_scl(port); +} - int ret = i2c_transfer(&port->adapter, msgs, 2); - if (ret != 2) - return -EIO; +static int +nouveau_i2c_getsda(void *data) +{ + struct nouveau_i2c_port *port = data; + return port->func->sense_sda(port); +} - return 0; +/****************************************************************************** + * base i2c "port" class implementation + *****************************************************************************/ + +void +_nouveau_i2c_port_dtor(struct nouveau_object *object) +{ + struct nouveau_i2c_port *port = (void *)object; + i2c_del_adapter(&port->adapter); + nouveau_object_destroy(&port->base); } -bool -nv_probe_i2c(struct nouveau_i2c_port *port, u8 addr) +int +nouveau_i2c_port_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, u8 index, + const struct i2c_algorithm *algo, + int size, void **pobject) { - u8 buf[] = { 0 }; - struct i2c_msg msgs[] = { - { - .addr = addr, - .flags = 0, - .len = 1, - .buf = buf, - }, - { - .addr = addr, - .flags = I2C_M_RD, - .len = 1, - .buf = buf, - } - }; + struct nouveau_device *device = nv_device(parent); + struct nouveau_i2c *i2c = (void *)engine; + struct nouveau_i2c_port *port; + int ret; - return i2c_transfer(&port->adapter, msgs, 2) == 2; + ret = nouveau_object_create_(parent, engine, oclass, 0, size, pobject); + port = *pobject; + if (ret) + return ret; + + snprintf(port->adapter.name, sizeof(port->adapter.name), + "nouveau-%s-%d", device->name, index); + port->adapter.owner = THIS_MODULE; + port->adapter.dev.parent = &device->pdev->dev; + port->index = index; + i2c_set_adapdata(&port->adapter, i2c); + + if ( algo == &nouveau_i2c_bit_algo && + !nouveau_boolopt(device->cfgopt, "NvI2C", CSTMSEL)) { + struct i2c_algo_bit_data *bit; + + bit = kzalloc(sizeof(*bit), GFP_KERNEL); + if (!bit) + return -ENOMEM; + + bit->udelay = 10; + bit->timeout = usecs_to_jiffies(2200); + bit->data = port; + bit->pre_xfer = nouveau_i2c_pre_xfer; + bit->setsda = nouveau_i2c_setsda; + bit->setscl = nouveau_i2c_setscl; + bit->getsda = nouveau_i2c_getsda; + bit->getscl = nouveau_i2c_getscl; + + port->adapter.algo_data = bit; + ret = i2c_bit_add_bus(&port->adapter); + } else { + port->adapter.algo_data = port; + port->adapter.algo = algo; + ret = i2c_add_adapter(&port->adapter); + } + + /* drop port's i2c subdev refcount, i2c handles this itself */ + if (ret == 0) { + list_add_tail(&port->head, &i2c->ports); + atomic_dec(&engine->refcount); + } + + return ret; } +/****************************************************************************** + * base i2c subdev class implementation + *****************************************************************************/ + static struct nouveau_i2c_port * nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index) { @@ -103,29 +175,23 @@ nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index) list_for_each_entry(port, &i2c->ports, head) { if (port->index == index) - break; + return port; } - if (&port->head == &i2c->ports) - return NULL; + return NULL; +} - if (nv_device(i2c)->card_type >= NV_50 && (port->dcb & 0x00000100)) { - u32 reg = 0x00e500, val; - if (port->type == 6) { - reg += port->drive * 0x50; - val = 0x2002; - } else { - reg += ((port->dcb & 0x1e00) >> 9) * 0x50; - val = 0xe001; - } +static struct nouveau_i2c_port * +nouveau_i2c_find_type(struct nouveau_i2c *i2c, u16 type) +{ + struct nouveau_i2c_port *port; - /* nfi, but neither auxch or i2c work if it's 1 */ - nv_mask(i2c, reg + 0x0c, 0x00000001, 0x00000000); - /* nfi, but switches auxch vs normal i2c */ - nv_mask(i2c, reg + 0x00, 0x0000f003, val); + list_for_each_entry(port, &i2c->ports, head) { + if (nv_hclass(port) == type) + return port; } - return port; + return NULL; } static int @@ -155,109 +221,86 @@ nouveau_i2c_identify(struct nouveau_i2c *i2c, int index, const char *what, return -ENODEV; } -void -nouveau_i2c_drive_scl(void *data, int state) +int +_nouveau_i2c_fini(struct nouveau_object *object, bool suspend) { - struct nouveau_i2c_port *port = data; + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port; + int ret; - if (port->type == DCB_I2C_NV04_BIT) { - u8 val = nv_rdvgac(port->i2c, 0, port->drive); - if (state) val |= 0x20; - else val &= 0xdf; - nv_wrvgac(port->i2c, 0, port->drive, val | 0x01); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - nv_mask(port->i2c, port->drive, 0x2f, state ? 0x21 : 0x01); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (state) port->state |= 0x01; - else port->state &= 0xfe; - nv_wr32(port->i2c, port->drive, 4 | port->state); + list_for_each_entry(port, &i2c->ports, head) { + ret = nv_ofuncs(port)->fini(nv_object(port), suspend); + if (ret && suspend) + goto fail; } -} - -void -nouveau_i2c_drive_sda(void *data, int state) -{ - struct nouveau_i2c_port *port = data; - if (port->type == DCB_I2C_NV04_BIT) { - u8 val = nv_rdvgac(port->i2c, 0, port->drive); - if (state) val |= 0x10; - else val &= 0xef; - nv_wrvgac(port->i2c, 0, port->drive, val | 0x01); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - nv_mask(port->i2c, port->drive, 0x1f, state ? 0x11 : 0x01); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (state) port->state |= 0x02; - else port->state &= 0xfd; - nv_wr32(port->i2c, port->drive, 4 | port->state); + return nouveau_subdev_fini(&i2c->base, suspend); +fail: + list_for_each_entry_continue_reverse(port, &i2c->ports, head) { + nv_ofuncs(port)->init(nv_object(port)); } + + return ret; } int -nouveau_i2c_sense_scl(void *data) +_nouveau_i2c_init(struct nouveau_object *object) { - struct nouveau_i2c_port *port = data; - struct nouveau_device *device = nv_device(port->i2c); - - if (port->type == DCB_I2C_NV04_BIT) { - return !!(nv_rdvgac(port->i2c, 0, port->sense) & 0x04); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - return !!(nv_rd32(port->i2c, port->sense) & 0x00040000); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (device->card_type < NV_D0) - return !!(nv_rd32(port->i2c, port->sense) & 0x01); - else - return !!(nv_rd32(port->i2c, port->sense) & 0x10); + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port; + int ret; + + ret = nouveau_subdev_init(&i2c->base); + if (ret == 0) { + list_for_each_entry(port, &i2c->ports, head) { + ret = nv_ofuncs(port)->init(nv_object(port)); + if (ret) + goto fail; + } } - return 0; + return ret; +fail: + list_for_each_entry_continue_reverse(port, &i2c->ports, head) { + nv_ofuncs(port)->fini(nv_object(port), false); + } + + return ret; } -int -nouveau_i2c_sense_sda(void *data) +void +_nouveau_i2c_dtor(struct nouveau_object *object) { - struct nouveau_i2c_port *port = data; - struct nouveau_device *device = nv_device(port->i2c); - - if (port->type == DCB_I2C_NV04_BIT) { - return !!(nv_rdvgac(port->i2c, 0, port->sense) & 0x08); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - return !!(nv_rd32(port->i2c, port->sense) & 0x00080000); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (device->card_type < NV_D0) - return !!(nv_rd32(port->i2c, port->sense) & 0x02); - else - return !!(nv_rd32(port->i2c, port->sense) & 0x20); + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port, *temp; + + list_for_each_entry_safe(port, temp, &i2c->ports, head) { + nouveau_object_ref(NULL, (struct nouveau_object **)&port); } - return 0; + nouveau_subdev_destroy(&i2c->base); } -static const u32 nv50_i2c_port[] = { - 0x00e138, 0x00e150, 0x00e168, 0x00e180, - 0x00e254, 0x00e274, 0x00e764, 0x00e780, - 0x00e79c, 0x00e7b8 +static struct nouveau_oclass * +nouveau_i2c_extdev_sclass[] = { + nouveau_anx9805_sclass, }; -static int -nouveau_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) +int +nouveau_i2c_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, + struct nouveau_oclass *sclass, + int length, void **pobject) { - struct nouveau_device *device = nv_device(parent); struct nouveau_bios *bios = nouveau_bios(parent); - struct nouveau_i2c_port *port; struct nouveau_i2c *i2c; + struct nouveau_object *object; struct dcb_i2c_entry info; - int ret, i = -1; + int ret, i, j, index = -1; + struct dcb_output outp; + u8 ver, hdr; + u32 data; ret = nouveau_subdev_create(parent, engine, oclass, 0, "I2C", "i2c", &i2c); @@ -266,142 +309,60 @@ nouveau_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, return ret; i2c->find = nouveau_i2c_find; + i2c->find_type = nouveau_i2c_find_type; i2c->identify = nouveau_i2c_identify; INIT_LIST_HEAD(&i2c->ports); - while (!dcb_i2c_parse(bios, ++i, &info)) { + while (!dcb_i2c_parse(bios, ++index, &info)) { if (info.type == DCB_I2C_UNUSED) continue; - port = kzalloc(sizeof(*port), GFP_KERNEL); - if (!port) { - nv_error(i2c, "failed port memory alloc at %d\n", i); - break; - } - - port->type = info.type; - switch (port->type) { - case DCB_I2C_NV04_BIT: - port->drive = info.drive; - port->sense = info.sense; - break; - case DCB_I2C_NV4E_BIT: - port->drive = 0x600800 + info.drive; - port->sense = port->drive; - break; - case DCB_I2C_NVIO_BIT: - port->drive = info.drive & 0x0f; - if (device->card_type < NV_D0) { - if (port->drive >= ARRAY_SIZE(nv50_i2c_port)) - break; - port->drive = nv50_i2c_port[port->drive]; - port->sense = port->drive; - } else { - port->drive = 0x00d014 + (port->drive * 0x20); - port->sense = port->drive; + oclass = sclass; + do { + ret = -EINVAL; + if (oclass->handle == info.type) { + ret = nouveau_object_ctor(*pobject, *pobject, + oclass, &info, + index, &object); } + } while (ret && (++oclass)->handle); + } + + /* in addition to the busses specified in the i2c table, there + * may be ddc/aux channels hiding behind external tmds/dp/etc + * transmitters. + */ + index = ((index + 0x0f) / 0x10) * 0x10; + i = -1; + while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &outp))) { + if (!outp.location || !outp.extdev) + continue; + + switch (outp.type) { + case DCB_OUTPUT_TMDS: + info.type = NV_I2C_TYPE_EXTDDC(outp.extdev); break; - case DCB_I2C_NVIO_AUX: - port->drive = info.drive & 0x0f; - port->sense = port->drive; - port->adapter.algo = &nouveau_i2c_aux_algo; + case DCB_OUTPUT_DP: + info.type = NV_I2C_TYPE_EXTAUX(outp.extdev); break; default: - break; - } - - if (!port->adapter.algo && !port->drive) { - nv_error(i2c, "I2C%d: type %d index %x/%x unknown\n", - i, port->type, port->drive, port->sense); - kfree(port); continue; } - snprintf(port->adapter.name, sizeof(port->adapter.name), - "nouveau-%s-%d", device->name, i); - port->adapter.owner = THIS_MODULE; - port->adapter.dev.parent = &device->pdev->dev; - port->i2c = i2c; - port->index = i; - port->dcb = info.data; - i2c_set_adapdata(&port->adapter, i2c); - - if (port->adapter.algo != &nouveau_i2c_aux_algo) { - nouveau_i2c_drive_scl(port, 0); - nouveau_i2c_drive_sda(port, 1); - nouveau_i2c_drive_scl(port, 1); - -#ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT - if (nouveau_boolopt(device->cfgopt, "NvI2C", true)) { -#else - if (nouveau_boolopt(device->cfgopt, "NvI2C", false)) { -#endif - port->adapter.algo = &nouveau_i2c_bit_algo; - ret = i2c_add_adapter(&port->adapter); - } else { - port->adapter.algo_data = &port->bit; - port->bit.udelay = 10; - port->bit.timeout = usecs_to_jiffies(2200); - port->bit.data = port; - port->bit.setsda = nouveau_i2c_drive_sda; - port->bit.setscl = nouveau_i2c_drive_scl; - port->bit.getsda = nouveau_i2c_sense_sda; - port->bit.getscl = nouveau_i2c_sense_scl; - ret = i2c_bit_add_bus(&port->adapter); - } - } else { - port->adapter.algo = &nouveau_i2c_aux_algo; - ret = i2c_add_adapter(&port->adapter); - } - - if (ret) { - nv_error(i2c, "I2C%d: failed register: %d\n", i, ret); - kfree(port); - continue; + ret = -ENODEV; + j = -1; + while (ret && ++j < ARRAY_SIZE(nouveau_i2c_extdev_sclass)) { + parent = nv_object(i2c->find(i2c, outp.i2c_index)); + oclass = nouveau_i2c_extdev_sclass[j]; + do { + if (oclass->handle != info.type) + continue; + ret = nouveau_object_ctor(parent, *pobject, + oclass, NULL, + index++, &object); + } while (ret && (++oclass)->handle); } - - list_add_tail(&port->head, &i2c->ports); } return 0; } - -static void -nouveau_i2c_dtor(struct nouveau_object *object) -{ - struct nouveau_i2c *i2c = (void *)object; - struct nouveau_i2c_port *port, *temp; - - list_for_each_entry_safe(port, temp, &i2c->ports, head) { - i2c_del_adapter(&port->adapter); - list_del(&port->head); - kfree(port); - } - - nouveau_subdev_destroy(&i2c->base); -} - -static int -nouveau_i2c_init(struct nouveau_object *object) -{ - struct nouveau_i2c *i2c = (void *)object; - return nouveau_subdev_init(&i2c->base); -} - -static int -nouveau_i2c_fini(struct nouveau_object *object, bool suspend) -{ - struct nouveau_i2c *i2c = (void *)object; - return nouveau_subdev_fini(&i2c->base, suspend); -} - -struct nouveau_oclass -nouveau_i2c_oclass = { - .handle = NV_SUBDEV(I2C, 0x00), - .ofuncs = &(struct nouveau_ofuncs) { - .ctor = nouveau_i2c_ctor, - .dtor = nouveau_i2c_dtor, - .init = nouveau_i2c_init, - .fini = nouveau_i2c_fini, - }, -}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c index 1c4c9a5c8e2e..a6e72d3b06b5 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c @@ -32,25 +32,25 @@ static inline void i2c_drive_scl(struct nouveau_i2c_port *port, int state) { - nouveau_i2c_drive_scl(port, state); + port->func->drive_scl(port, state); } static inline void i2c_drive_sda(struct nouveau_i2c_port *port, int state) { - nouveau_i2c_drive_sda(port, state); + port->func->drive_sda(port, state); } static inline int i2c_sense_scl(struct nouveau_i2c_port *port) { - return nouveau_i2c_sense_scl(port); + return port->func->sense_scl(port); } static inline int i2c_sense_sda(struct nouveau_i2c_port *port) { - return nouveau_i2c_sense_sda(port); + return port->func->sense_sda(port); } static void @@ -77,9 +77,8 @@ i2c_start(struct nouveau_i2c_port *port) { int ret = 0; - port->state = i2c_sense_scl(port); - port->state |= i2c_sense_sda(port) << 1; - if (port->state != 3) { + if (!i2c_sense_scl(port) || + !i2c_sense_sda(port)) { i2c_drive_scl(port, 0); i2c_drive_sda(port, 1); if (!i2c_raise_scl(port)) @@ -184,10 +183,13 @@ i2c_addr(struct nouveau_i2c_port *port, struct i2c_msg *msg) static int i2c_bit_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { - struct nouveau_i2c_port *port = (struct nouveau_i2c_port *)adap; + struct nouveau_i2c_port *port = adap->algo_data; struct i2c_msg *msg = msgs; int ret = 0, mcnt = num; + if (port->func->acquire) + port->func->acquire(port); + while (!ret && mcnt--) { u8 remaining = msg->len; u8 *ptr = msg->buf; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv04.c new file mode 100644 index 000000000000..2ad18840fe63 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv04.c @@ -0,0 +1,143 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/i2c.h> +#include <subdev/vga.h> + +struct nv04_i2c_priv { + struct nouveau_i2c base; +}; + +struct nv04_i2c_port { + struct nouveau_i2c_port base; + u8 drive; + u8 sense; +}; + +static void +nv04_i2c_drive_scl(struct nouveau_i2c_port *base, int state) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + u8 val = nv_rdvgac(priv, 0, port->drive); + if (state) val |= 0x20; + else val &= 0xdf; + nv_wrvgac(priv, 0, port->drive, val | 0x01); +} + +static void +nv04_i2c_drive_sda(struct nouveau_i2c_port *base, int state) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + u8 val = nv_rdvgac(priv, 0, port->drive); + if (state) val |= 0x10; + else val &= 0xef; + nv_wrvgac(priv, 0, port->drive, val | 0x01); +} + +static int +nv04_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + return !!(nv_rdvgac(priv, 0, port->sense) & 0x04); +} + +static int +nv04_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + return !!(nv_rdvgac(priv, 0, port->sense) & 0x08); +} + +static const struct nouveau_i2c_func +nv04_i2c_func = { + .drive_scl = nv04_i2c_drive_scl, + .drive_sda = nv04_i2c_drive_sda, + .sense_scl = nv04_i2c_sense_scl, + .sense_sda = nv04_i2c_sense_sda, +}; + +static int +nv04_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv04_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nv04_i2c_func; + port->drive = info->drive; + port->sense = info->sense; + return 0; +} + +static struct nouveau_oclass +nv04_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NV04_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv04_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv04_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv04_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv04_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv04_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x04), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv04_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv4e.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv4e.c new file mode 100644 index 000000000000..f501ae25dbb3 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv4e.c @@ -0,0 +1,135 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/i2c.h> +#include <subdev/vga.h> + +struct nv4e_i2c_priv { + struct nouveau_i2c base; +}; + +struct nv4e_i2c_port { + struct nouveau_i2c_port base; + u32 addr; +}; + +static void +nv4e_i2c_drive_scl(struct nouveau_i2c_port *base, int state) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + nv_mask(priv, port->addr, 0x2f, state ? 0x21 : 0x01); +} + +static void +nv4e_i2c_drive_sda(struct nouveau_i2c_port *base, int state) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + nv_mask(priv, port->addr, 0x1f, state ? 0x11 : 0x01); +} + +static int +nv4e_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00040000); +} + +static int +nv4e_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00080000); +} + +static const struct nouveau_i2c_func +nv4e_i2c_func = { + .drive_scl = nv4e_i2c_drive_scl, + .drive_sda = nv4e_i2c_drive_sda, + .sense_scl = nv4e_i2c_sense_scl, + .sense_sda = nv4e_i2c_sense_sda, +}; + +static int +nv4e_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv4e_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nv4e_i2c_func; + port->addr = 0x600800 + info->drive; + return 0; +} + +static struct nouveau_oclass +nv4e_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NV4E_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv4e_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv4e_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv4e_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv4e_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv4e_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x4e), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv4e_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.c new file mode 100644 index 000000000000..378dfa324e5f --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.c @@ -0,0 +1,149 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "nv50.h" + +void +nv50_i2c_drive_scl(struct nouveau_i2c_port *base, int state) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + if (state) port->state |= 0x01; + else port->state &= 0xfe; + nv_wr32(priv, port->addr, port->state); +} + +void +nv50_i2c_drive_sda(struct nouveau_i2c_port *base, int state) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + if (state) port->state |= 0x02; + else port->state &= 0xfd; + nv_wr32(priv, port->addr, port->state); +} + +int +nv50_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000001); +} + +int +nv50_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000002); +} + +static const struct nouveau_i2c_func +nv50_i2c_func = { + .drive_scl = nv50_i2c_drive_scl, + .drive_sda = nv50_i2c_drive_sda, + .sense_scl = nv50_i2c_sense_scl, + .sense_sda = nv50_i2c_sense_sda, +}; + +const u32 nv50_i2c_addr[] = { + 0x00e138, 0x00e150, 0x00e168, 0x00e180, + 0x00e254, 0x00e274, 0x00e764, 0x00e780, + 0x00e79c, 0x00e7b8 +}; +const int nv50_i2c_addr_nr = ARRAY_SIZE(nv50_i2c_addr); + +static int +nv50_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + if (info->drive >= nv50_i2c_addr_nr) + return -EINVAL; + + port->base.func = &nv50_i2c_func; + port->state = 0x00000007; + port->addr = nv50_i2c_addr[info->drive]; + return 0; +} + +int +nv50_i2c_port_init(struct nouveau_object *object) +{ + struct nv50_i2c_priv *priv = (void *)object->engine; + struct nv50_i2c_port *port = (void *)object; + nv_wr32(priv, port->addr, port->state); + return nouveau_i2c_port_init(&port->base); +} + +static struct nouveau_oclass +nv50_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv50_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = nv50_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv50_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv50_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv50_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x50), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv50_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.h b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.h new file mode 100644 index 000000000000..4e5ba48ebf5a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.h @@ -0,0 +1,32 @@ +#ifndef __NV50_I2C_H__ +#define __NV50_I2C_H__ + +#include <subdev/i2c.h> + +struct nv50_i2c_priv { + struct nouveau_i2c base; +}; + +struct nv50_i2c_port { + struct nouveau_i2c_port base; + u32 addr; + u32 ctrl; + u32 data; + u32 state; +}; + +extern const u32 nv50_i2c_addr[]; +extern const int nv50_i2c_addr_nr; +int nv50_i2c_port_init(struct nouveau_object *); +int nv50_i2c_sense_scl(struct nouveau_i2c_port *); +int nv50_i2c_sense_sda(struct nouveau_i2c_port *); +void nv50_i2c_drive_scl(struct nouveau_i2c_port *, int state); +void nv50_i2c_drive_sda(struct nouveau_i2c_port *, int state); + +int nv94_aux_port_ctor(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, void *, u32, + struct nouveau_object **); +void nv94_i2c_acquire(struct nouveau_i2c_port *); +void nv94_i2c_release(struct nouveau_i2c_port *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv94.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv94.c new file mode 100644 index 000000000000..61b771670bfe --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv94.c @@ -0,0 +1,285 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "nv50.h" + +#define AUX_DBG(fmt, args...) nv_debug(aux, "AUXCH(%d): " fmt, ch, ##args) +#define AUX_ERR(fmt, args...) nv_error(aux, "AUXCH(%d): " fmt, ch, ##args) + +static void +auxch_fini(struct nouveau_i2c *aux, int ch) +{ + nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00310000, 0x00000000); +} + +static int +auxch_init(struct nouveau_i2c *aux, int ch) +{ + const u32 unksel = 1; /* nfi which to use, or if it matters.. */ + const u32 ureq = unksel ? 0x00100000 : 0x00200000; + const u32 urep = unksel ? 0x01000000 : 0x02000000; + u32 ctrl, timeout; + + /* wait up to 1ms for any previous transaction to be done... */ + timeout = 1000; + do { + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("begin idle timeout 0x%08x\n", ctrl); + return -EBUSY; + } + } while (ctrl & 0x03010000); + + /* set some magic, and wait up to 1ms for it to appear */ + nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00300000, ureq); + timeout = 1000; + do { + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("magic wait 0x%08x\n", ctrl); + auxch_fini(aux, ch); + return -EBUSY; + } + } while ((ctrl & 0x03000000) != urep); + + return 0; +} + +int +nv94_aux(struct nouveau_i2c_port *base, u8 type, u32 addr, u8 *data, u8 size) +{ + struct nouveau_i2c *aux = nouveau_i2c(base); + struct nv50_i2c_port *port = (void *)base; + u32 ctrl, stat, timeout, retries; + u32 xbuf[4] = {}; + int ch = port->addr; + int ret, i; + + AUX_DBG("%d: 0x%08x %d\n", type, addr, size); + + ret = auxch_init(aux, ch); + if (ret) + goto out; + + stat = nv_rd32(aux, 0x00e4e8 + (ch * 0x50)); + if (!(stat & 0x10000000)) { + AUX_DBG("sink not detected\n"); + ret = -ENXIO; + goto out; + } + + if (!(type & 1)) { + memcpy(xbuf, data, size); + for (i = 0; i < 16; i += 4) { + AUX_DBG("wr 0x%08x\n", xbuf[i / 4]); + nv_wr32(aux, 0x00e4c0 + (ch * 0x50) + i, xbuf[i / 4]); + } + } + + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + ctrl &= ~0x0001f0ff; + ctrl |= type << 12; + ctrl |= size - 1; + nv_wr32(aux, 0x00e4e0 + (ch * 0x50), addr); + + /* retry transaction a number of times on failure... */ + ret = -EREMOTEIO; + for (retries = 0; retries < 32; retries++) { + /* reset, and delay a while if this is a retry */ + nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x80000000 | ctrl); + nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00000000 | ctrl); + if (retries) + udelay(400); + + /* transaction request, wait up to 1ms for it to complete */ + nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00010000 | ctrl); + + timeout = 1000; + do { + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("tx req timeout 0x%08x\n", ctrl); + goto out; + } + } while (ctrl & 0x00010000); + + /* read status, and check if transaction completed ok */ + stat = nv_mask(aux, 0x00e4e8 + (ch * 0x50), 0, 0); + if (!(stat & 0x000f0f00)) { + ret = 0; + break; + } + + AUX_DBG("%02d 0x%08x 0x%08x\n", retries, ctrl, stat); + } + + if (type & 1) { + for (i = 0; i < 16; i += 4) { + xbuf[i / 4] = nv_rd32(aux, 0x00e4d0 + (ch * 0x50) + i); + AUX_DBG("rd 0x%08x\n", xbuf[i / 4]); + } + memcpy(data, xbuf, size); + } + +out: + auxch_fini(aux, ch); + return ret; +} + +void +nv94_i2c_acquire(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + if (port->ctrl) { + nv_mask(priv, port->ctrl + 0x0c, 0x00000001, 0x00000000); + nv_mask(priv, port->ctrl + 0x00, 0x0000f003, port->data); + } +} + +void +nv94_i2c_release(struct nouveau_i2c_port *base) +{ +} + +static const struct nouveau_i2c_func +nv94_i2c_func = { + .acquire = nv94_i2c_acquire, + .release = nv94_i2c_release, + .drive_scl = nv50_i2c_drive_scl, + .drive_sda = nv50_i2c_drive_sda, + .sense_scl = nv50_i2c_sense_scl, + .sense_sda = nv50_i2c_sense_sda, +}; + +static int +nv94_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + if (info->drive >= nv50_i2c_addr_nr) + return -EINVAL; + + port->base.func = &nv94_i2c_func; + port->state = 7; + port->addr = nv50_i2c_addr[info->drive]; + if (info->share != DCB_I2C_UNUSED) { + port->ctrl = 0x00e500 + (info->share * 0x50); + port->data = 0x0000e001; + } + return 0; +} + +static const struct nouveau_i2c_func +nv94_aux_func = { + .acquire = nv94_i2c_acquire, + .release = nv94_i2c_release, + .aux = nv94_aux, +}; + +int +nv94_aux_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_aux_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nv94_aux_func; + port->addr = info->drive; + if (info->share != DCB_I2C_UNUSED) { + port->ctrl = 0x00e500 + (info->drive * 0x50); + port->data = 0x00002002; + } + + return 0; +} + +static struct nouveau_oclass +nv94_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = nv50_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_AUX), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_aux_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv94_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv94_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv94_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x94), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nvd0.c new file mode 100644 index 000000000000..f761b8a610f1 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nvd0.c @@ -0,0 +1,124 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "nv50.h" + +static int +nvd0_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000010); +} + +static int +nvd0_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000020); +} + +static const struct nouveau_i2c_func +nvd0_i2c_func = { + .acquire = nv94_i2c_acquire, + .release = nv94_i2c_release, + .drive_scl = nv50_i2c_drive_scl, + .drive_sda = nv50_i2c_drive_sda, + .sense_scl = nvd0_i2c_sense_scl, + .sense_sda = nvd0_i2c_sense_sda, +}; + +static int +nvd0_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nvd0_i2c_func; + port->state = 0x00000007; + port->addr = 0x00d014 + (info->drive * 0x20); + if (info->share != DCB_I2C_UNUSED) { + port->ctrl = 0x00e500 + (info->share * 0x50); + port->data = 0x0000e001; + } + return 0; +} + +static struct nouveau_oclass +nvd0_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvd0_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = nv50_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_AUX), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_aux_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nvd0_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nvd0_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nvd0_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0xd0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvd0_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c index 23ebe477a6f0..89da8fa7ea0f 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c @@ -37,7 +37,7 @@ nv04_mc_intr[] = { { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x01000000, NVDEV_ENGINE_DISP }, /* NV04- PCRTC0 */ { 0x02000000, NVDEV_ENGINE_DISP }, /* NV11- PCRTC1 */ - { 0x10000000, NVDEV_SUBDEV_GPIO }, /* PBUS */ + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x80000000, NVDEV_ENGINE_SW }, {} }; diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c index 8d759f830323..5965add6daee 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c @@ -38,6 +38,7 @@ nv50_mc_intr[] = { { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x00200000, NVDEV_SUBDEV_GPIO }, { 0x04000000, NVDEV_ENGINE_DISP }, + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x80000000, NVDEV_ENGINE_SW }, { 0x0000d101, NVDEV_SUBDEV_FB }, {}, diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c index ceb5c83f9459..3a80b29dce0f 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c @@ -35,10 +35,12 @@ nv98_mc_intr[] = { { 0x00001000, NVDEV_ENGINE_GR }, { 0x00004000, NVDEV_ENGINE_CRYPT }, /* NV84:NVA3 */ { 0x00008000, NVDEV_ENGINE_BSP }, + { 0x00080000, NVDEV_SUBDEV_THERM }, /* NVA3:NVC0 */ { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x00200000, NVDEV_SUBDEV_GPIO }, { 0x00400000, NVDEV_ENGINE_COPY0 }, /* NVA3- */ { 0x04000000, NVDEV_ENGINE_DISP }, + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x80000000, NVDEV_ENGINE_SW }, { 0x0040d101, NVDEV_SUBDEV_FB }, {}, diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c index 92796682722d..42bbf72023a8 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c @@ -36,11 +36,13 @@ nvc0_mc_intr[] = { { 0x00000100, NVDEV_ENGINE_FIFO }, { 0x00001000, NVDEV_ENGINE_GR }, { 0x00008000, NVDEV_ENGINE_BSP }, + { 0x00040000, NVDEV_SUBDEV_THERM }, { 0x00020000, NVDEV_ENGINE_VP }, { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x00200000, NVDEV_SUBDEV_GPIO }, { 0x02000000, NVDEV_SUBDEV_LTCG }, { 0x04000000, NVDEV_ENGINE_DISP }, + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x40000000, NVDEV_SUBDEV_IBUS }, { 0x80000000, NVDEV_ENGINE_SW }, {}, diff --git a/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c b/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c index 839ca1edc132..4bde7f7f7b81 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c @@ -156,15 +156,15 @@ mxms_foreach(struct nouveau_mxm *mxm, u8 types, nv_debug(mxm, "%4s: ", mxms_desc_name[type]); for (j = headerlen - 1; j >= 0; j--) - printk("%02x", dump[j]); - printk("\n"); + pr_cont("%02x", dump[j]); + pr_cont("\n"); dump += headerlen; for (i = 0; i < entries; i++, dump += recordlen) { nv_debug(mxm, " "); for (j = recordlen - 1; j >= 0; j--) - printk("%02x", dump[j]); - printk("\n"); + pr_cont("%02x", dump[j]); + pr_cont("\n"); } } diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/base.c b/drivers/gpu/drm/nouveau/core/subdev/therm/base.c index 1674c74a76c8..f794dc89a3b2 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/base.c @@ -29,6 +29,134 @@ #include "priv.h" +static int +nouveau_therm_update_trip(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nouveau_therm_trip_point *trip = priv->fan->bios.trip, + *cur_trip = NULL, + *last_trip = priv->last_trip; + u8 temp = therm->temp_get(therm); + u16 duty, i; + + /* look for the trip point corresponding to the current temperature */ + cur_trip = NULL; + for (i = 0; i < priv->fan->bios.nr_fan_trip; i++) { + if (temp >= trip[i].temp) + cur_trip = &trip[i]; + } + + /* account for the hysteresis cycle */ + if (last_trip && temp <= (last_trip->temp) && + temp > (last_trip->temp - last_trip->hysteresis)) + cur_trip = last_trip; + + if (cur_trip) { + duty = cur_trip->fan_duty; + priv->last_trip = cur_trip; + } else { + duty = 0; + priv->last_trip = NULL; + } + + return duty; +} + +static int +nouveau_therm_update_linear(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + u8 linear_min_temp = priv->fan->bios.linear_min_temp; + u8 linear_max_temp = priv->fan->bios.linear_max_temp; + u8 temp = therm->temp_get(therm); + u16 duty; + + /* handle the non-linear part first */ + if (temp < linear_min_temp) + return priv->fan->bios.min_duty; + else if (temp > linear_max_temp) + return priv->fan->bios.max_duty; + + /* we are in the linear zone */ + duty = (temp - linear_min_temp); + duty *= (priv->fan->bios.max_duty - priv->fan->bios.min_duty); + duty /= (linear_max_temp - linear_min_temp); + duty += priv->fan->bios.min_duty; + + return duty; +} + +static void +nouveau_therm_update(struct nouveau_therm *therm, int mode) +{ + struct nouveau_timer *ptimer = nouveau_timer(therm); + struct nouveau_therm_priv *priv = (void *)therm; + unsigned long flags; + int duty; + + spin_lock_irqsave(&priv->lock, flags); + if (mode < 0) + mode = priv->mode; + priv->mode = mode; + + switch (mode) { + case NOUVEAU_THERM_CTRL_MANUAL: + duty = nouveau_therm_fan_get(therm); + if (duty < 0) + duty = 100; + break; + case NOUVEAU_THERM_CTRL_AUTO: + if (priv->fan->bios.nr_fan_trip) + duty = nouveau_therm_update_trip(therm); + else + duty = nouveau_therm_update_linear(therm); + break; + case NOUVEAU_THERM_CTRL_NONE: + default: + goto done; + } + + nv_debug(therm, "FAN target request: %d%%\n", duty); + nouveau_therm_fan_set(therm, (mode != NOUVEAU_THERM_CTRL_AUTO), duty); + +done: + if (list_empty(&priv->alarm.head) && (mode == NOUVEAU_THERM_CTRL_AUTO)) + ptimer->alarm(ptimer, 1000000000ULL, &priv->alarm); + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void +nouveau_therm_alarm(struct nouveau_alarm *alarm) +{ + struct nouveau_therm_priv *priv = + container_of(alarm, struct nouveau_therm_priv, alarm); + nouveau_therm_update(&priv->base, -1); +} + +int +nouveau_therm_mode(struct nouveau_therm *therm, int mode) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nouveau_device *device = nv_device(therm); + static const char *name[] = { + "disabled", + "manual", + "automatic" + }; + + /* The default PDAEMON ucode interferes with fan management */ + if ((mode >= ARRAY_SIZE(name)) || + (mode != NOUVEAU_THERM_CTRL_NONE && device->card_type >= NV_C0)) + return -EINVAL; + + if (priv->mode == mode) + return 0; + + nv_info(therm, "Thermal management: %s\n", name[mode]); + nouveau_therm_update(therm, mode); + return 0; +} + int nouveau_therm_attr_get(struct nouveau_therm *therm, enum nouveau_therm_attr_type type) @@ -37,11 +165,11 @@ nouveau_therm_attr_get(struct nouveau_therm *therm, switch (type) { case NOUVEAU_THERM_ATTR_FAN_MIN_DUTY: - return priv->bios_fan.min_duty; + return priv->fan->bios.min_duty; case NOUVEAU_THERM_ATTR_FAN_MAX_DUTY: - return priv->bios_fan.max_duty; + return priv->fan->bios.max_duty; case NOUVEAU_THERM_ATTR_FAN_MODE: - return priv->fan.mode; + return priv->mode; case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST: return priv->bios_sensor.thrs_fan_boost.temp; case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST: @@ -73,42 +201,50 @@ nouveau_therm_attr_set(struct nouveau_therm *therm, case NOUVEAU_THERM_ATTR_FAN_MIN_DUTY: if (value < 0) value = 0; - if (value > priv->bios_fan.max_duty) - value = priv->bios_fan.max_duty; - priv->bios_fan.min_duty = value; + if (value > priv->fan->bios.max_duty) + value = priv->fan->bios.max_duty; + priv->fan->bios.min_duty = value; return 0; case NOUVEAU_THERM_ATTR_FAN_MAX_DUTY: if (value < 0) value = 0; - if (value < priv->bios_fan.min_duty) - value = priv->bios_fan.min_duty; - priv->bios_fan.max_duty = value; + if (value < priv->fan->bios.min_duty) + value = priv->fan->bios.min_duty; + priv->fan->bios.max_duty = value; return 0; case NOUVEAU_THERM_ATTR_FAN_MODE: - return nouveau_therm_fan_set_mode(therm, value); + return nouveau_therm_mode(therm, value); case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST: priv->bios_sensor.thrs_fan_boost.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST: priv->bios_sensor.thrs_fan_boost.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK: priv->bios_sensor.thrs_down_clock.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST: priv->bios_sensor.thrs_down_clock.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_CRITICAL: priv->bios_sensor.thrs_critical.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST: priv->bios_sensor.thrs_critical.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN: priv->bios_sensor.thrs_shutdown.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST: priv->bios_sensor.thrs_shutdown.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; } @@ -116,7 +252,7 @@ nouveau_therm_attr_set(struct nouveau_therm *therm, } int -nouveau_therm_init(struct nouveau_object *object) +_nouveau_therm_init(struct nouveau_object *object) { struct nouveau_therm *therm = (void *)object; struct nouveau_therm_priv *priv = (void *)therm; @@ -126,19 +262,69 @@ nouveau_therm_init(struct nouveau_object *object) if (ret) return ret; - if (priv->fan.percent >= 0) - therm->fan_set(therm, priv->fan.percent); - + if (priv->suspend >= 0) + nouveau_therm_mode(therm, priv->mode); + priv->sensor.program_alarms(therm); return 0; } int -nouveau_therm_fini(struct nouveau_object *object, bool suspend) +_nouveau_therm_fini(struct nouveau_object *object, bool suspend) { struct nouveau_therm *therm = (void *)object; struct nouveau_therm_priv *priv = (void *)therm; - priv->fan.percent = therm->fan_get(therm); + if (suspend) { + priv->suspend = priv->mode; + priv->mode = NOUVEAU_THERM_CTRL_NONE; + } return nouveau_subdev_fini(&therm->base, suspend); } + +int +nouveau_therm_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, + int length, void **pobject) +{ + struct nouveau_therm_priv *priv; + int ret; + + ret = nouveau_subdev_create_(parent, engine, oclass, 0, "PTHERM", + "therm", length, pobject); + priv = *pobject; + if (ret) + return ret; + + nouveau_alarm_init(&priv->alarm, nouveau_therm_alarm); + spin_lock_init(&priv->lock); + spin_lock_init(&priv->sensor.alarm_program_lock); + + priv->base.fan_get = nouveau_therm_fan_user_get; + priv->base.fan_set = nouveau_therm_fan_user_set; + priv->base.fan_sense = nouveau_therm_fan_sense; + priv->base.attr_get = nouveau_therm_attr_get; + priv->base.attr_set = nouveau_therm_attr_set; + priv->mode = priv->suspend = -1; /* undefined */ + return 0; +} + +int +nouveau_therm_preinit(struct nouveau_therm *therm) +{ + nouveau_therm_ic_ctor(therm); + nouveau_therm_sensor_ctor(therm); + nouveau_therm_fan_ctor(therm); + + nouveau_therm_mode(therm, NOUVEAU_THERM_CTRL_NONE); + return 0; +} + +void +_nouveau_therm_dtor(struct nouveau_object *object) +{ + struct nouveau_therm_priv *priv = (void *)object; + kfree(priv->fan); + nouveau_subdev_destroy(&priv->base.base); +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c index 523178685180..c728380d3d62 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c @@ -27,90 +27,107 @@ #include <core/object.h> #include <core/device.h> + #include <subdev/gpio.h> #include <subdev/timer.h> -int -nouveau_therm_fan_get(struct nouveau_therm *therm) +static int +nouveau_fan_update(struct nouveau_fan *fan, bool immediate, int target) { + struct nouveau_therm *therm = fan->parent; struct nouveau_therm_priv *priv = (void *)therm; - struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; - int card_type = nv_device(therm)->card_type; - u32 divs, duty; - int ret; - - if (!priv->fan.pwm_get) - return -ENODEV; + struct nouveau_timer *ptimer = nouveau_timer(priv); + unsigned long flags; + int ret = 0; + int duty; + + /* update target fan speed, restricting to allowed range */ + spin_lock_irqsave(&fan->lock, flags); + if (target < 0) + target = fan->percent; + target = max_t(u8, target, fan->bios.min_duty); + target = min_t(u8, target, fan->bios.max_duty); + if (fan->percent != target) { + nv_debug(therm, "FAN target: %d\n", target); + fan->percent = target; + } - ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func); - if (ret == 0) { - ret = priv->fan.pwm_get(therm, func.line, &divs, &duty); - if (ret == 0 && divs) { - divs = max(divs, duty); - if (card_type <= NV_40 || (func.log[0] & 1)) - duty = divs - duty; - return (duty * 100) / divs; - } + /* check that we're not already at the target duty cycle */ + duty = fan->get(therm); + if (duty == target) + goto done; + + /* smooth out the fanspeed increase/decrease */ + if (!immediate && duty >= 0) { + /* the constant "3" is a rough approximation taken from + * nvidia's behaviour. + * it is meant to bump the fan speed more incrementally + */ + if (duty < target) + duty = min(duty + 3, target); + else if (duty > target) + duty = max(duty - 3, target); + } else { + duty = target; + } - return gpio->get(gpio, 0, func.func, func.line) * 100; + nv_debug(therm, "FAN update: %d\n", duty); + ret = fan->set(therm, duty); + if (ret) + goto done; + + /* schedule next fan update, if not at target speed already */ + if (list_empty(&fan->alarm.head) && target != duty) { + u16 bump_period = fan->bios.bump_period; + u16 slow_down_period = fan->bios.slow_down_period; + u64 delay; + + if (duty > target) + delay = slow_down_period; + else if (duty == target) + delay = min(bump_period, slow_down_period) ; + else + delay = bump_period; + + ptimer->alarm(ptimer, delay * 1000 * 1000, &fan->alarm); } - return -ENODEV; +done: + spin_unlock_irqrestore(&fan->lock, flags); + return ret; +} + +static void +nouveau_fan_alarm(struct nouveau_alarm *alarm) +{ + struct nouveau_fan *fan = container_of(alarm, struct nouveau_fan, alarm); + nouveau_fan_update(fan, false, -1); } int -nouveau_therm_fan_set(struct nouveau_therm *therm, int percent) +nouveau_therm_fan_get(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; - int card_type = nv_device(therm)->card_type; - u32 divs, duty; - int ret; - - if (priv->fan.mode == FAN_CONTROL_NONE) - return -EINVAL; - - if (!priv->fan.pwm_set) - return -ENODEV; - - if (percent < priv->bios_fan.min_duty) - percent = priv->bios_fan.min_duty; - if (percent > priv->bios_fan.max_duty) - percent = priv->bios_fan.max_duty; - - ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func); - if (ret == 0) { - divs = priv->bios_perf_fan.pwm_divisor; - if (priv->bios_fan.pwm_freq) { - divs = 1; - if (priv->fan.pwm_clock) - divs = priv->fan.pwm_clock(therm); - divs /= priv->bios_fan.pwm_freq; - } - - duty = ((divs * percent) + 99) / 100; - if (card_type <= NV_40 || (func.log[0] & 1)) - duty = divs - duty; - - ret = priv->fan.pwm_set(therm, func.line, divs, duty); - return ret; - } + return priv->fan->get(therm); +} - return -ENODEV; +int +nouveau_therm_fan_set(struct nouveau_therm *therm, bool immediate, int percent) +{ + struct nouveau_therm_priv *priv = (void *)therm; + return nouveau_fan_update(priv->fan, immediate, percent); } int nouveau_therm_fan_sense(struct nouveau_therm *therm) { + struct nouveau_therm_priv *priv = (void *)therm; struct nouveau_timer *ptimer = nouveau_timer(therm); struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; u32 cycles, cur, prev; u64 start, end, tach; - if (gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &func)) + if (priv->fan->tach.func == DCB_GPIO_UNUSED) return -ENODEV; /* Time a complete rotation and extrapolate to RPM: @@ -118,12 +135,12 @@ nouveau_therm_fan_sense(struct nouveau_therm *therm) * We get 4 changes (0 -> 1 -> 0 -> 1) per complete rotation. */ start = ptimer->read(ptimer); - prev = gpio->get(gpio, 0, func.func, func.line); + prev = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); cycles = 0; do { usleep_range(500, 1000); /* supports 0 < rpm < 7500 */ - cur = gpio->get(gpio, 0, func.func, func.line); + cur = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); if (prev != cur) { if (!start) start = ptimer->read(ptimer); @@ -142,34 +159,6 @@ nouveau_therm_fan_sense(struct nouveau_therm *therm) } int -nouveau_therm_fan_set_mode(struct nouveau_therm *therm, - enum nouveau_therm_fan_mode mode) -{ - struct nouveau_therm_priv *priv = (void *)therm; - - if (priv->fan.mode == mode) - return 0; - - if (mode < FAN_CONTROL_NONE || mode >= FAN_CONTROL_NR) - return -EINVAL; - - switch (mode) - { - case FAN_CONTROL_NONE: - nv_info(therm, "switch fan to no-control mode\n"); - break; - case FAN_CONTROL_MANUAL: - nv_info(therm, "switch fan to manual mode\n"); - break; - case FAN_CONTROL_NR: - break; - } - - priv->fan.mode = mode; - return 0; -} - -int nouveau_therm_fan_user_get(struct nouveau_therm *therm) { return nouveau_therm_fan_get(therm); @@ -180,55 +169,86 @@ nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent) { struct nouveau_therm_priv *priv = (void *)therm; - if (priv->fan.mode != FAN_CONTROL_MANUAL) + if (priv->mode != NOUVEAU_THERM_CTRL_MANUAL) return -EINVAL; - return nouveau_therm_fan_set(therm, percent); + return nouveau_therm_fan_set(therm, true, percent); } -void +static void nouveau_therm_fan_set_defaults(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - priv->bios_fan.pwm_freq = 0; - priv->bios_fan.min_duty = 0; - priv->bios_fan.max_duty = 100; + priv->fan->bios.pwm_freq = 0; + priv->fan->bios.min_duty = 0; + priv->fan->bios.max_duty = 100; + priv->fan->bios.bump_period = 500; + priv->fan->bios.slow_down_period = 2000; + priv->fan->bios.linear_min_temp = 40; + priv->fan->bios.linear_max_temp = 85; } - static void nouveau_therm_fan_safety_checks(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - if (priv->bios_fan.min_duty > 100) - priv->bios_fan.min_duty = 100; - if (priv->bios_fan.max_duty > 100) - priv->bios_fan.max_duty = 100; + if (priv->fan->bios.min_duty > 100) + priv->fan->bios.min_duty = 100; + if (priv->fan->bios.max_duty > 100) + priv->fan->bios.max_duty = 100; - if (priv->bios_fan.min_duty > priv->bios_fan.max_duty) - priv->bios_fan.min_duty = priv->bios_fan.max_duty; -} - -int nouveau_fan_pwm_clock_dummy(struct nouveau_therm *therm) -{ - return 1; + if (priv->fan->bios.min_duty > priv->fan->bios.max_duty) + priv->fan->bios.min_duty = priv->fan->bios.max_duty; } int nouveau_therm_fan_ctor(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; + struct nouveau_gpio *gpio = nouveau_gpio(therm); struct nouveau_bios *bios = nouveau_bios(therm); + struct dcb_gpio_func func; + int ret; + /* attempt to locate a drivable fan, and determine control method */ + ret = gpio->find(gpio, 0, DCB_GPIO_FAN, 0xff, &func); + if (ret == 0) { + if (func.log[0] & DCB_GPIO_LOG_DIR_IN) { + nv_debug(therm, "GPIO_FAN is in input mode\n"); + ret = -EINVAL; + } else { + ret = nouveau_fanpwm_create(therm, &func); + if (ret != 0) + ret = nouveau_fantog_create(therm, &func); + } + } + + /* no controllable fan found, create a dummy fan module */ + if (ret != 0) { + ret = nouveau_fannil_create(therm); + if (ret) + return ret; + } + + nv_info(therm, "FAN control: %s\n", priv->fan->type); + + /* attempt to detect a tachometer connection */ + ret = gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &priv->fan->tach); + if (ret) + priv->fan->tach.func = DCB_GPIO_UNUSED; + + /* initialise fan bump/slow update handling */ + priv->fan->parent = therm; + nouveau_alarm_init(&priv->fan->alarm, nouveau_fan_alarm); + spin_lock_init(&priv->fan->lock); + + /* other random init... */ nouveau_therm_fan_set_defaults(therm); - nvbios_perf_fan_parse(bios, &priv->bios_perf_fan); - if (nvbios_therm_fan_parse(bios, &priv->bios_fan)) + nvbios_perf_fan_parse(bios, &priv->fan->perf); + if (nvbios_therm_fan_parse(bios, &priv->fan->bios)) nv_error(therm, "parsing the thermal table failed\n"); nouveau_therm_fan_safety_checks(therm); - - nouveau_therm_fan_set_mode(therm, FAN_CONTROL_NONE); - return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fannil.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fannil.c new file mode 100644 index 000000000000..b78c182e1d51 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fannil.c @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "priv.h" + +static int +nouveau_fannil_get(struct nouveau_therm *therm) +{ + return -ENODEV; +} + +static int +nouveau_fannil_set(struct nouveau_therm *therm, int percent) +{ + return -ENODEV; +} + +int +nouveau_fannil_create(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fan *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = priv; + if (!priv) + return -ENOMEM; + + priv->type = "none / external"; + priv->get = nouveau_fannil_get; + priv->set = nouveau_fannil_set; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fanpwm.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fanpwm.c new file mode 100644 index 000000000000..5f71db8e8992 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fanpwm.c @@ -0,0 +1,107 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + * Martin Peres + */ + +#include <core/option.h> +#include <subdev/gpio.h> + +#include "priv.h" + +struct nouveau_fanpwm_priv { + struct nouveau_fan base; + struct dcb_gpio_func func; +}; + +static int +nouveau_fanpwm_get(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fanpwm_priv *priv = (void *)tpriv->fan; + struct nouveau_gpio *gpio = nouveau_gpio(therm); + int card_type = nv_device(therm)->card_type; + u32 divs, duty; + int ret; + + ret = therm->pwm_get(therm, priv->func.line, &divs, &duty); + if (ret == 0 && divs) { + divs = max(divs, duty); + if (card_type <= NV_40 || (priv->func.log[0] & 1)) + duty = divs - duty; + return (duty * 100) / divs; + } + + return gpio->get(gpio, 0, priv->func.func, priv->func.line) * 100; +} + +static int +nouveau_fanpwm_set(struct nouveau_therm *therm, int percent) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fanpwm_priv *priv = (void *)tpriv->fan; + int card_type = nv_device(therm)->card_type; + u32 divs, duty; + int ret; + + divs = priv->base.perf.pwm_divisor; + if (priv->base.bios.pwm_freq) { + divs = 1; + if (therm->pwm_clock) + divs = therm->pwm_clock(therm); + divs /= priv->base.bios.pwm_freq; + } + + duty = ((divs * percent) + 99) / 100; + if (card_type <= NV_40 || (priv->func.log[0] & 1)) + duty = divs - duty; + + ret = therm->pwm_set(therm, priv->func.line, divs, duty); + if (ret == 0) + ret = therm->pwm_ctrl(therm, priv->func.line, true); + return ret; +} + +int +nouveau_fanpwm_create(struct nouveau_therm *therm, struct dcb_gpio_func *func) +{ + struct nouveau_device *device = nv_device(therm); + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fanpwm_priv *priv; + u32 divs, duty; + + if (!nouveau_boolopt(device->cfgopt, "NvFanPWM", func->param) || + !therm->pwm_ctrl || + therm->pwm_get(therm, func->line, &divs, &duty) == -ENODEV) + return -ENODEV; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = &priv->base; + if (!priv) + return -ENOMEM; + + priv->base.type = "PWM"; + priv->base.get = nouveau_fanpwm_get; + priv->base.set = nouveau_fanpwm_set; + priv->func = *func; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fantog.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fantog.c new file mode 100644 index 000000000000..e601773ee475 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fantog.c @@ -0,0 +1,115 @@ +/* + * Copyright 2012 The Nouveau community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ + +#include "priv.h" + +#include <core/object.h> +#include <core/device.h> + +#include <subdev/gpio.h> +#include <subdev/timer.h> + +struct nouveau_fantog_priv { + struct nouveau_fan base; + struct nouveau_alarm alarm; + spinlock_t lock; + u32 period_us; + u32 percent; + struct dcb_gpio_func func; +}; + +static void +nouveau_fantog_update(struct nouveau_fantog_priv *priv, int percent) +{ + struct nouveau_therm_priv *tpriv = (void *)priv->base.parent; + struct nouveau_timer *ptimer = nouveau_timer(tpriv); + struct nouveau_gpio *gpio = nouveau_gpio(tpriv); + unsigned long flags; + int duty; + + spin_lock_irqsave(&priv->lock, flags); + if (percent < 0) + percent = priv->percent; + priv->percent = percent; + + duty = !gpio->get(gpio, 0, DCB_GPIO_FAN, 0xff); + gpio->set(gpio, 0, DCB_GPIO_FAN, 0xff, duty); + + if (list_empty(&priv->alarm.head) && percent != (duty * 100)) { + u64 next_change = (percent * priv->period_us) / 100; + if (!duty) + next_change = priv->period_us - next_change; + ptimer->alarm(ptimer, next_change * 1000, &priv->alarm); + } + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void +nouveau_fantog_alarm(struct nouveau_alarm *alarm) +{ + struct nouveau_fantog_priv *priv = + container_of(alarm, struct nouveau_fantog_priv, alarm); + nouveau_fantog_update(priv, -1); +} + +static int +nouveau_fantog_get(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fantog_priv *priv = (void *)tpriv->fan; + return priv->percent; +} + +static int +nouveau_fantog_set(struct nouveau_therm *therm, int percent) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fantog_priv *priv = (void *)tpriv->fan; + if (therm->pwm_ctrl) + therm->pwm_ctrl(therm, priv->func.line, false); + nouveau_fantog_update(priv, percent); + return 0; +} + +int +nouveau_fantog_create(struct nouveau_therm *therm, struct dcb_gpio_func *func) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fantog_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = &priv->base; + if (!priv) + return -ENOMEM; + + priv->base.type = "toggle"; + priv->base.get = nouveau_fantog_get; + priv->base.set = nouveau_fantog_set; + nouveau_alarm_init(&priv->alarm, nouveau_fantog_alarm); + priv->period_us = 100000; /* 10Hz */ + priv->percent = 100; + priv->func = *func; + spin_lock_init(&priv->lock); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c b/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c index e512ff0aae60..e24090bac195 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c @@ -31,7 +31,7 @@ static bool probe_monitoring_device(struct nouveau_i2c_port *i2c, struct i2c_board_info *info) { - struct nouveau_therm_priv *priv = (void *)nouveau_therm(i2c->i2c); + struct nouveau_therm_priv *priv = (void *)nouveau_therm(i2c); struct i2c_client *client; request_module("%s%s", I2C_MODULE_PREFIX, info->type); @@ -53,6 +53,31 @@ probe_monitoring_device(struct nouveau_i2c_port *i2c, return true; } +static struct i2c_board_info +nv_board_infos[] = { + { I2C_BOARD_INFO("w83l785ts", 0x2d) }, + { I2C_BOARD_INFO("w83781d", 0x2d) }, + { I2C_BOARD_INFO("adt7473", 0x2e) }, + { I2C_BOARD_INFO("adt7473", 0x2d) }, + { I2C_BOARD_INFO("adt7473", 0x2c) }, + { I2C_BOARD_INFO("f75375", 0x2e) }, + { I2C_BOARD_INFO("lm99", 0x4c) }, + { I2C_BOARD_INFO("lm90", 0x4c) }, + { I2C_BOARD_INFO("lm90", 0x4d) }, + { I2C_BOARD_INFO("adm1021", 0x18) }, + { I2C_BOARD_INFO("adm1021", 0x19) }, + { I2C_BOARD_INFO("adm1021", 0x1a) }, + { I2C_BOARD_INFO("adm1021", 0x29) }, + { I2C_BOARD_INFO("adm1021", 0x2a) }, + { I2C_BOARD_INFO("adm1021", 0x2b) }, + { I2C_BOARD_INFO("adm1021", 0x4c) }, + { I2C_BOARD_INFO("adm1021", 0x4d) }, + { I2C_BOARD_INFO("adm1021", 0x4e) }, + { I2C_BOARD_INFO("lm63", 0x18) }, + { I2C_BOARD_INFO("lm63", 0x4e) }, + { } +}; + void nouveau_therm_ic_ctor(struct nouveau_therm *therm) { @@ -60,29 +85,6 @@ nouveau_therm_ic_ctor(struct nouveau_therm *therm) struct nouveau_bios *bios = nouveau_bios(therm); struct nouveau_i2c *i2c = nouveau_i2c(therm); struct nvbios_extdev_func extdev_entry; - struct i2c_board_info info[] = { - { I2C_BOARD_INFO("w83l785ts", 0x2d) }, - { I2C_BOARD_INFO("w83781d", 0x2d) }, - { I2C_BOARD_INFO("adt7473", 0x2e) }, - { I2C_BOARD_INFO("adt7473", 0x2d) }, - { I2C_BOARD_INFO("adt7473", 0x2c) }, - { I2C_BOARD_INFO("f75375", 0x2e) }, - { I2C_BOARD_INFO("lm99", 0x4c) }, - { I2C_BOARD_INFO("lm90", 0x4c) }, - { I2C_BOARD_INFO("lm90", 0x4d) }, - { I2C_BOARD_INFO("adm1021", 0x18) }, - { I2C_BOARD_INFO("adm1021", 0x19) }, - { I2C_BOARD_INFO("adm1021", 0x1a) }, - { I2C_BOARD_INFO("adm1021", 0x29) }, - { I2C_BOARD_INFO("adm1021", 0x2a) }, - { I2C_BOARD_INFO("adm1021", 0x2b) }, - { I2C_BOARD_INFO("adm1021", 0x4c) }, - { I2C_BOARD_INFO("adm1021", 0x4d) }, - { I2C_BOARD_INFO("adm1021", 0x4e) }, - { I2C_BOARD_INFO("lm63", 0x18) }, - { I2C_BOARD_INFO("lm63", 0x4e) }, - { } - }; if (!nvbios_extdev_find(bios, NVBIOS_EXTDEV_LM89, &extdev_entry)) { struct i2c_board_info board[] = { @@ -111,6 +113,6 @@ nouveau_therm_ic_ctor(struct nouveau_therm *therm) /* The vbios doesn't provide the address of an exisiting monitoring device. Let's try our static list. */ - i2c->identify(i2c, NV_I2C_DEFAULT(0), "monitoring device", info, - probe_monitoring_device); + i2c->identify(i2c, NV_I2C_DEFAULT(0), "monitoring device", + nv_board_infos, probe_monitoring_device); } diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c index fcf2cfe731d6..0f5363edb964 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c @@ -25,6 +25,10 @@ #include "priv.h" +struct nv40_therm_priv { + struct nouveau_therm_priv base; +}; + static int nv40_sensor_setup(struct nouveau_therm *therm) { @@ -34,6 +38,7 @@ nv40_sensor_setup(struct nouveau_therm *therm) if (device->chipset >= 0x46) { nv_mask(therm, 0x15b8, 0x80000000, 0); nv_wr32(therm, 0x15b0, 0x80003fff); + mdelay(10); /* wait for the temperature to stabilize */ return nv_rd32(therm, 0x15b4) & 0x3fff; } else { nv_wr32(therm, 0x15b0, 0xff); @@ -75,7 +80,20 @@ nv40_temp_get(struct nouveau_therm *therm) return core_temp; } -int +static int +nv40_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable) +{ + u32 mask = enable ? 0x80000000 : 0x0000000; + if (line == 2) nv_mask(therm, 0x0010f0, 0x80000000, mask); + else if (line == 9) nv_mask(therm, 0x0015f4, 0x80000000, mask); + else { + nv_error(therm, "unknown pwm ctrl for gpio %d\n", line); + return -ENODEV; + } + return 0; +} + +static int nv40_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) { if (line == 2) { @@ -101,15 +119,15 @@ nv40_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) return -EINVAL; } -int +static int nv40_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) { if (line == 2) { - nv_wr32(therm, 0x0010f0, 0x80000000 | (duty << 16) | divs); + nv_mask(therm, 0x0010f0, 0x7fff7fff, (duty << 16) | divs); } else if (line == 9) { nv_wr32(therm, 0x0015f8, divs); - nv_wr32(therm, 0x0015f4, duty | 0x80000000); + nv_mask(therm, 0x0015f4, 0x7fffffff, duty); } else { nv_error(therm, "unknown pwm ctrl for gpio %d\n", line); return -ENODEV; @@ -118,37 +136,51 @@ nv40_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) return 0; } +static void +nv40_therm_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_therm *therm = nouveau_therm(subdev); + uint32_t stat = nv_rd32(therm, 0x1100); + + /* traitement */ + + /* ack all IRQs */ + nv_wr32(therm, 0x1100, 0x70000); + + nv_error(therm, "THERM received an IRQ: stat = %x\n", stat); +} + static int nv40_therm_ctor(struct nouveau_object *parent, - struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { - struct nouveau_therm_priv *priv; - struct nouveau_therm *therm; + struct nv40_therm_priv *priv; int ret; ret = nouveau_therm_create(parent, engine, oclass, &priv); *pobject = nv_object(priv); - therm = (void *) priv; if (ret) return ret; - nouveau_therm_ic_ctor(therm); - nouveau_therm_sensor_ctor(therm); - nouveau_therm_fan_ctor(therm); + priv->base.base.pwm_ctrl = nv40_fan_pwm_ctrl; + priv->base.base.pwm_get = nv40_fan_pwm_get; + priv->base.base.pwm_set = nv40_fan_pwm_set; + priv->base.base.temp_get = nv40_temp_get; + priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; + nv_subdev(priv)->intr = nv40_therm_intr; + return nouveau_therm_preinit(&priv->base.base); +} - priv->fan.pwm_get = nv40_fan_pwm_get; - priv->fan.pwm_set = nv40_fan_pwm_set; +static int +nv40_therm_init(struct nouveau_object *object) +{ + struct nouveau_therm *therm = (void *)object; - therm->temp_get = nv40_temp_get; - therm->fan_get = nouveau_therm_fan_user_get; - therm->fan_set = nouveau_therm_fan_user_set; - therm->fan_sense = nouveau_therm_fan_sense; - therm->attr_get = nouveau_therm_attr_get; - therm->attr_set = nouveau_therm_attr_set; + nv40_sensor_setup(therm); - return 0; + return _nouveau_therm_init(object); } struct nouveau_oclass @@ -157,7 +189,7 @@ nv40_therm_oclass = { .ofuncs = &(struct nouveau_ofuncs) { .ctor = nv40_therm_ctor, .dtor = _nouveau_therm_dtor, - .init = nouveau_therm_init, - .fini = nouveau_therm_fini, + .init = nv40_therm_init, + .fini = _nouveau_therm_fini, }, -};
\ No newline at end of file +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c index 9360ddd469e7..86632cbd65ce 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c @@ -25,6 +25,10 @@ #include "priv.h" +struct nv50_therm_priv { + struct nouveau_therm_priv base; +}; + static int pwm_info(struct nouveau_therm *therm, int *line, int *ctrl, int *indx) { @@ -51,6 +55,16 @@ pwm_info(struct nouveau_therm *therm, int *line, int *ctrl, int *indx) } int +nv50_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable) +{ + u32 data = enable ? 0x00000001 : 0x00000000; + int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id); + if (ret == 0) + nv_mask(therm, ctrl, 0x00010001 << line, data << line); + return ret; +} + +int nv50_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) { int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id); @@ -73,7 +87,6 @@ nv50_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) if (ret) return ret; - nv_mask(therm, ctrl, 0x00010001 << line, 0x00000001 << line); nv_wr32(therm, 0x00e114 + (id * 8), divs); nv_wr32(therm, 0x00e118 + (id * 8), duty | 0x80000000); return 0; @@ -111,38 +124,178 @@ nv50_temp_get(struct nouveau_therm *therm) return nv_rd32(therm, 0x20400); } +static void +nv50_therm_program_alarms(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + unsigned long flags; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + /* enable RISING and FALLING IRQs for shutdown, THRS 0, 1, 2 and 4 */ + nv_wr32(therm, 0x20000, 0x000003ff); + + /* shutdown: The computer should be shutdown when reached */ + nv_wr32(therm, 0x20484, sensor->thrs_shutdown.hysteresis); + nv_wr32(therm, 0x20480, sensor->thrs_shutdown.temp); + + /* THRS_1 : fan boost*/ + nv_wr32(therm, 0x204c4, sensor->thrs_fan_boost.temp); + + /* THRS_2 : critical */ + nv_wr32(therm, 0x204c0, sensor->thrs_critical.temp); + + /* THRS_4 : down clock */ + nv_wr32(therm, 0x20414, sensor->thrs_down_clock.temp); + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); + + nv_info(therm, + "Programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", + sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, + sensor->thrs_down_clock.temp, + sensor->thrs_down_clock.hysteresis, + sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, + sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); + +} + +/* must be called with alarm_program_lock taken ! */ +static void +nv50_therm_threshold_hyst_emulation(struct nouveau_therm *therm, + uint32_t thrs_reg, u8 status_bit, + const struct nvbios_therm_threshold *thrs, + enum nouveau_therm_thrs thrs_name) +{ + enum nouveau_therm_thrs_direction direction; + enum nouveau_therm_thrs_state prev_state, new_state; + int temp, cur; + + prev_state = nouveau_therm_sensor_get_threshold_state(therm, thrs_name); + temp = nv_rd32(therm, thrs_reg); + + /* program the next threshold */ + if (temp == thrs->temp) { + nv_wr32(therm, thrs_reg, thrs->temp - thrs->hysteresis); + new_state = NOUVEAU_THERM_THRS_HIGHER; + } else { + nv_wr32(therm, thrs_reg, thrs->temp); + new_state = NOUVEAU_THERM_THRS_LOWER; + } + + /* fix the state (in case someone reprogrammed the alarms) */ + cur = therm->temp_get(therm); + if (new_state == NOUVEAU_THERM_THRS_LOWER && cur > thrs->temp) + new_state = NOUVEAU_THERM_THRS_HIGHER; + else if (new_state == NOUVEAU_THERM_THRS_HIGHER && + cur < thrs->temp - thrs->hysteresis) + new_state = NOUVEAU_THERM_THRS_LOWER; + nouveau_therm_sensor_set_threshold_state(therm, thrs_name, new_state); + + /* find the direction */ + if (prev_state < new_state) + direction = NOUVEAU_THERM_THRS_RISING; + else if (prev_state > new_state) + direction = NOUVEAU_THERM_THRS_FALLING; + else + return; + + /* advertise a change in direction */ + nouveau_therm_sensor_event(therm, thrs_name, direction); +} + +static void +nv50_therm_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_therm *therm = nouveau_therm(subdev); + struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + unsigned long flags; + uint32_t intr; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + intr = nv_rd32(therm, 0x20100); + + /* THRS_4: downclock */ + if (intr & 0x002) { + nv50_therm_threshold_hyst_emulation(therm, 0x20414, 24, + &sensor->thrs_down_clock, + NOUVEAU_THERM_THRS_DOWNCLOCK); + intr &= ~0x002; + } + + /* shutdown */ + if (intr & 0x004) { + nv50_therm_threshold_hyst_emulation(therm, 0x20480, 20, + &sensor->thrs_shutdown, + NOUVEAU_THERM_THRS_SHUTDOWN); + intr &= ~0x004; + } + + /* THRS_1 : fan boost */ + if (intr & 0x008) { + nv50_therm_threshold_hyst_emulation(therm, 0x204c4, 21, + &sensor->thrs_fan_boost, + NOUVEAU_THERM_THRS_FANBOOST); + intr &= ~0x008; + } + + /* THRS_2 : critical */ + if (intr & 0x010) { + nv50_therm_threshold_hyst_emulation(therm, 0x204c0, 22, + &sensor->thrs_critical, + NOUVEAU_THERM_THRS_CRITICAL); + intr &= ~0x010; + } + + if (intr) + nv_error(therm, "unhandled intr 0x%08x\n", intr); + + /* ACK everything */ + nv_wr32(therm, 0x20100, 0xffffffff); + nv_wr32(therm, 0x1100, 0x10000); /* PBUS */ + + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); +} + static int nv50_therm_ctor(struct nouveau_object *parent, - struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { - struct nouveau_therm_priv *priv; - struct nouveau_therm *therm; + struct nv50_therm_priv *priv; int ret; ret = nouveau_therm_create(parent, engine, oclass, &priv); *pobject = nv_object(priv); - therm = (void *) priv; if (ret) return ret; - nouveau_therm_ic_ctor(therm); - nouveau_therm_sensor_ctor(therm); - nouveau_therm_fan_ctor(therm); + priv->base.base.pwm_ctrl = nv50_fan_pwm_ctrl; + priv->base.base.pwm_get = nv50_fan_pwm_get; + priv->base.base.pwm_set = nv50_fan_pwm_set; + priv->base.base.pwm_clock = nv50_fan_pwm_clock; + priv->base.base.temp_get = nv50_temp_get; + priv->base.sensor.program_alarms = nv50_therm_program_alarms; + nv_subdev(priv)->intr = nv50_therm_intr; - priv->fan.pwm_get = nv50_fan_pwm_get; - priv->fan.pwm_set = nv50_fan_pwm_set; - priv->fan.pwm_clock = nv50_fan_pwm_clock; + /* init the thresholds */ + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_SHUTDOWN, + NOUVEAU_THERM_THRS_LOWER); + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_FANBOOST, + NOUVEAU_THERM_THRS_LOWER); + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_CRITICAL, + NOUVEAU_THERM_THRS_LOWER); + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_DOWNCLOCK, + NOUVEAU_THERM_THRS_LOWER); - therm->temp_get = nv50_temp_get; - therm->fan_get = nouveau_therm_fan_user_get; - therm->fan_set = nouveau_therm_fan_user_set; - therm->fan_sense = nouveau_therm_fan_sense; - therm->attr_get = nouveau_therm_attr_get; - therm->attr_set = nouveau_therm_attr_set; - - return 0; + return nouveau_therm_preinit(&priv->base.base); } struct nouveau_oclass @@ -151,7 +304,7 @@ nv50_therm_oclass = { .ofuncs = &(struct nouveau_ofuncs) { .ctor = nv50_therm_ctor, .dtor = _nouveau_therm_dtor, - .init = nouveau_therm_init, - .fini = nouveau_therm_fini, + .init = _nouveau_therm_init, + .fini = _nouveau_therm_fini, }, }; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c new file mode 100644 index 000000000000..2dcc5437116a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c @@ -0,0 +1,99 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/gpio.h> + +#include "priv.h" + +struct nva3_therm_priv { + struct nouveau_therm_priv base; +}; + +int +nva3_therm_fan_sense(struct nouveau_therm *therm) +{ + u32 tach = nv_rd32(therm, 0x00e728) & 0x0000ffff; + u32 ctrl = nv_rd32(therm, 0x00e720); + if (ctrl & 0x00000001) + return tach * 60; + return -ENODEV; +} + +static int +nva3_therm_init(struct nouveau_object *object) +{ + struct nva3_therm_priv *priv = (void *)object; + struct dcb_gpio_func *tach = &priv->base.fan->tach; + int ret; + + ret = nouveau_therm_init(&priv->base.base); + if (ret) + return ret; + + /* enable fan tach, count revolutions per-second */ + nv_mask(priv, 0x00e720, 0x00000003, 0x00000002); + if (tach->func != DCB_GPIO_UNUSED) { + nv_wr32(priv, 0x00e724, nv_device(priv)->crystal * 1000); + nv_mask(priv, 0x00e720, 0x001f0000, tach->line << 16); + nv_mask(priv, 0x00e720, 0x00000001, 0x00000001); + } + nv_mask(priv, 0x00e720, 0x00000002, 0x00000000); + + return 0; +} + +static int +nva3_therm_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nva3_therm_priv *priv; + int ret; + + ret = nouveau_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nv50_fan_pwm_ctrl; + priv->base.base.pwm_get = nv50_fan_pwm_get; + priv->base.base.pwm_set = nv50_fan_pwm_set; + priv->base.base.pwm_clock = nv50_fan_pwm_clock; + priv->base.base.temp_get = nv50_temp_get; + priv->base.base.fan_sense = nva3_therm_fan_sense; + priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; + return nouveau_therm_preinit(&priv->base.base); +} + +struct nouveau_oclass +nva3_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0xa3), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nva3_therm_ctor, + .dtor = _nouveau_therm_dtor, + .init = nva3_therm_init, + .fini = _nouveau_therm_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c new file mode 100644 index 000000000000..d7d30ee8332e --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c @@ -0,0 +1,153 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "priv.h" + +struct nvd0_therm_priv { + struct nouveau_therm_priv base; +}; + +static int +pwm_info(struct nouveau_therm *therm, int line) +{ + u32 gpio = nv_rd32(therm, 0x00d610 + (line * 0x04)); + switch (gpio & 0x000000c0) { + case 0x00000000: /* normal mode, possibly pwm forced off by us */ + case 0x00000040: /* nvio special */ + switch (gpio & 0x0000001f) { + case 0x19: return 1; + case 0x1c: return 0; + default: + break; + } + default: + break; + } + + nv_error(therm, "GPIO %d unknown PWM: 0x%08x\n", line, gpio); + return -ENODEV; +} + +static int +nvd0_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable) +{ + u32 data = enable ? 0x00000040 : 0x00000000; + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + + nv_mask(therm, 0x00d610 + (line * 0x04), 0x000000c0, data); + return 0; +} + +static int +nvd0_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) +{ + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + + if (nv_rd32(therm, 0x00d610 + (line * 0x04)) & 0x00000040) { + *divs = nv_rd32(therm, 0x00e114 + (indx * 8)); + *duty = nv_rd32(therm, 0x00e118 + (indx * 8)); + return 0; + } + + return -EINVAL; +} + +static int +nvd0_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) +{ + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + + nv_wr32(therm, 0x00e114 + (indx * 8), divs); + nv_wr32(therm, 0x00e118 + (indx * 8), duty | 0x80000000); + return 0; +} + +static int +nvd0_fan_pwm_clock(struct nouveau_therm *therm) +{ + return (nv_device(therm)->crystal * 1000) / 20; +} + +static int +nvd0_therm_init(struct nouveau_object *object) +{ + struct nvd0_therm_priv *priv = (void *)object; + int ret; + + ret = nouveau_therm_init(&priv->base.base); + if (ret) + return ret; + + /* enable fan tach, count revolutions per-second */ + nv_mask(priv, 0x00e720, 0x00000003, 0x00000002); + if (priv->base.fan->tach.func != DCB_GPIO_UNUSED) { + nv_mask(priv, 0x00d79c, 0x000000ff, priv->base.fan->tach.line); + nv_wr32(priv, 0x00e724, nv_device(priv)->crystal * 1000); + nv_mask(priv, 0x00e720, 0x00000001, 0x00000001); + } + nv_mask(priv, 0x00e720, 0x00000002, 0x00000000); + + return 0; +} + +static int +nvd0_therm_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nvd0_therm_priv *priv; + int ret; + + ret = nouveau_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nvd0_fan_pwm_ctrl; + priv->base.base.pwm_get = nvd0_fan_pwm_get; + priv->base.base.pwm_set = nvd0_fan_pwm_set; + priv->base.base.pwm_clock = nvd0_fan_pwm_clock; + priv->base.base.temp_get = nv50_temp_get; + priv->base.base.fan_sense = nva3_therm_fan_sense; + priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; + return nouveau_therm_preinit(&priv->base.base); +} + +struct nouveau_oclass +nvd0_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0xd0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvd0_therm_ctor, + .dtor = _nouveau_therm_dtor, + .init = nvd0_therm_init, + .fini = _nouveau_therm_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h index 1c3cd6abc36e..06b98706b3fc 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h @@ -1,3 +1,6 @@ +#ifndef __NVTHERM_PRIV_H__ +#define __NVTHERM_PRIV_H__ + /* * Copyright 2012 The Nouveau community * @@ -25,33 +28,81 @@ #include <subdev/therm.h> #include <subdev/bios/extdev.h> +#include <subdev/bios/gpio.h> #include <subdev/bios/perf.h> #include <subdev/bios/therm.h> +#include <subdev/timer.h> + +struct nouveau_fan { + struct nouveau_therm *parent; + const char *type; + + struct nvbios_therm_fan bios; + struct nvbios_perf_fan perf; + + struct nouveau_alarm alarm; + spinlock_t lock; + int percent; + + int (*get)(struct nouveau_therm *therm); + int (*set)(struct nouveau_therm *therm, int percent); + + struct dcb_gpio_func tach; +}; + +enum nouveau_therm_thrs_direction { + NOUVEAU_THERM_THRS_FALLING = 0, + NOUVEAU_THERM_THRS_RISING = 1 +}; + +enum nouveau_therm_thrs_state { + NOUVEAU_THERM_THRS_LOWER = 0, + NOUVEAU_THERM_THRS_HIGHER = 1 +}; + +enum nouveau_therm_thrs { + NOUVEAU_THERM_THRS_FANBOOST = 0, + NOUVEAU_THERM_THRS_DOWNCLOCK = 1, + NOUVEAU_THERM_THRS_CRITICAL = 2, + NOUVEAU_THERM_THRS_SHUTDOWN = 3, + NOUVEAU_THERM_THRS_NR +}; struct nouveau_therm_priv { struct nouveau_therm base; + /* automatic thermal management */ + struct nouveau_alarm alarm; + spinlock_t lock; + struct nouveau_therm_trip_point *last_trip; + int mode; + int suspend; + /* bios */ struct nvbios_therm_sensor bios_sensor; - struct nvbios_therm_fan bios_fan; - struct nvbios_perf_fan bios_perf_fan; /* fan priv */ + struct nouveau_fan *fan; + + /* alarms priv */ struct { - enum nouveau_therm_fan_mode mode; - int percent; + spinlock_t alarm_program_lock; + struct nouveau_alarm therm_poll_alarm; + enum nouveau_therm_thrs_state alarm_state[NOUVEAU_THERM_THRS_NR]; + void (*program_alarms)(struct nouveau_therm *); + } sensor; - int (*pwm_get)(struct nouveau_therm *, int line, u32*, u32*); - int (*pwm_set)(struct nouveau_therm *, int line, u32, u32); - int (*pwm_clock)(struct nouveau_therm *); - } fan; + /* what should be done if the card overheats */ + struct { + void (*downclock)(struct nouveau_therm *, bool active); + void (*pause)(struct nouveau_therm *, bool active); + } emergency; /* ic */ struct i2c_client *ic; }; -int nouveau_therm_init(struct nouveau_object *object); -int nouveau_therm_fini(struct nouveau_object *object, bool suspend); +int nouveau_therm_mode(struct nouveau_therm *therm, int mode); int nouveau_therm_attr_get(struct nouveau_therm *therm, enum nouveau_therm_attr_type type); int nouveau_therm_attr_set(struct nouveau_therm *therm, @@ -63,11 +114,35 @@ int nouveau_therm_sensor_ctor(struct nouveau_therm *therm); int nouveau_therm_fan_ctor(struct nouveau_therm *therm); int nouveau_therm_fan_get(struct nouveau_therm *therm); -int nouveau_therm_fan_set(struct nouveau_therm *therm, int percent); +int nouveau_therm_fan_set(struct nouveau_therm *therm, bool now, int percent); int nouveau_therm_fan_user_get(struct nouveau_therm *therm); int nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent); -int nouveau_therm_fan_set_mode(struct nouveau_therm *therm, - enum nouveau_therm_fan_mode mode); - int nouveau_therm_fan_sense(struct nouveau_therm *therm); + +int nouveau_therm_preinit(struct nouveau_therm *); + +void nouveau_therm_sensor_set_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_state st); +enum nouveau_therm_thrs_state +nouveau_therm_sensor_get_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs); +void nouveau_therm_sensor_event(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_direction dir); +void nouveau_therm_program_alarms_polling(struct nouveau_therm *therm); + +int nv50_fan_pwm_ctrl(struct nouveau_therm *, int, bool); +int nv50_fan_pwm_get(struct nouveau_therm *, int, u32 *, u32 *); +int nv50_fan_pwm_set(struct nouveau_therm *, int, u32, u32); +int nv50_fan_pwm_clock(struct nouveau_therm *); +int nv50_temp_get(struct nouveau_therm *therm); + +int nva3_therm_fan_sense(struct nouveau_therm *); + +int nouveau_fanpwm_create(struct nouveau_therm *, struct dcb_gpio_func *); +int nouveau_fantog_create(struct nouveau_therm *, struct dcb_gpio_func *); +int nouveau_fannil_create(struct nouveau_therm *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c b/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c index 204282301fb1..b37624af8297 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c @@ -58,11 +58,171 @@ static void nouveau_therm_temp_safety_checks(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *s = &priv->bios_sensor; if (!priv->bios_sensor.slope_div) priv->bios_sensor.slope_div = 1; if (!priv->bios_sensor.offset_den) priv->bios_sensor.offset_den = 1; + + /* enforce a minimum hysteresis on thresholds */ + s->thrs_fan_boost.hysteresis = max_t(u8, s->thrs_fan_boost.hysteresis, 2); + s->thrs_down_clock.hysteresis = max_t(u8, s->thrs_down_clock.hysteresis, 2); + s->thrs_critical.hysteresis = max_t(u8, s->thrs_critical.hysteresis, 2); + s->thrs_shutdown.hysteresis = max_t(u8, s->thrs_shutdown.hysteresis, 2); +} + +/* must be called with alarm_program_lock taken ! */ +void nouveau_therm_sensor_set_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_state st) +{ + struct nouveau_therm_priv *priv = (void *)therm; + priv->sensor.alarm_state[thrs] = st; +} + +/* must be called with alarm_program_lock taken ! */ +enum nouveau_therm_thrs_state +nouveau_therm_sensor_get_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs) +{ + struct nouveau_therm_priv *priv = (void *)therm; + return priv->sensor.alarm_state[thrs]; +} + +static void +nv_poweroff_work(struct work_struct *work) +{ + orderly_poweroff(true); + kfree(work); +} + +void nouveau_therm_sensor_event(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_direction dir) +{ + struct nouveau_therm_priv *priv = (void *)therm; + bool active; + const char *thresolds[] = { + "fanboost", "downclock", "critical", "shutdown" + }; + uint8_t temperature = therm->temp_get(therm); + + if (thrs < 0 || thrs > 3) + return; + + if (dir == NOUVEAU_THERM_THRS_FALLING) + nv_info(therm, "temperature (%u C) went below the '%s' threshold\n", + temperature, thresolds[thrs]); + else + nv_info(therm, "temperature (%u C) hit the '%s' threshold\n", + temperature, thresolds[thrs]); + + active = (dir == NOUVEAU_THERM_THRS_RISING); + switch (thrs) { + case NOUVEAU_THERM_THRS_FANBOOST: + if (active) { + nouveau_therm_fan_set(therm, true, 100); + nouveau_therm_mode(therm, NOUVEAU_THERM_CTRL_AUTO); + } + break; + case NOUVEAU_THERM_THRS_DOWNCLOCK: + if (priv->emergency.downclock) + priv->emergency.downclock(therm, active); + break; + case NOUVEAU_THERM_THRS_CRITICAL: + if (priv->emergency.pause) + priv->emergency.pause(therm, active); + break; + case NOUVEAU_THERM_THRS_SHUTDOWN: + if (active) { + struct work_struct *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK(work, nv_poweroff_work); + schedule_work(work); + } + } + break; + case NOUVEAU_THERM_THRS_NR: + break; + } + +} + +/* must be called with alarm_program_lock taken ! */ +static void +nouveau_therm_threshold_hyst_polling(struct nouveau_therm *therm, + const struct nvbios_therm_threshold *thrs, + enum nouveau_therm_thrs thrs_name) +{ + enum nouveau_therm_thrs_direction direction; + enum nouveau_therm_thrs_state prev_state, new_state; + int temp = therm->temp_get(therm); + + prev_state = nouveau_therm_sensor_get_threshold_state(therm, thrs_name); + + if (temp >= thrs->temp && prev_state == NOUVEAU_THERM_THRS_LOWER) { + direction = NOUVEAU_THERM_THRS_RISING; + new_state = NOUVEAU_THERM_THRS_HIGHER; + } else if (temp <= thrs->temp - thrs->hysteresis && + prev_state == NOUVEAU_THERM_THRS_HIGHER) { + direction = NOUVEAU_THERM_THRS_FALLING; + new_state = NOUVEAU_THERM_THRS_LOWER; + } else + return; /* nothing to do */ + + nouveau_therm_sensor_set_threshold_state(therm, thrs_name, new_state); + nouveau_therm_sensor_event(therm, thrs_name, direction); +} + +static void +alarm_timer_callback(struct nouveau_alarm *alarm) +{ + struct nouveau_therm_priv *priv = + container_of(alarm, struct nouveau_therm_priv, sensor.therm_poll_alarm); + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + struct nouveau_timer *ptimer = nouveau_timer(priv); + struct nouveau_therm *therm = &priv->base; + unsigned long flags; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_fan_boost, + NOUVEAU_THERM_THRS_FANBOOST); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_down_clock, + NOUVEAU_THERM_THRS_DOWNCLOCK); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_critical, + NOUVEAU_THERM_THRS_CRITICAL); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_shutdown, + NOUVEAU_THERM_THRS_SHUTDOWN); + + /* schedule the next poll in one second */ + if (list_empty(&alarm->head)) + ptimer->alarm(ptimer, 1000 * 1000 * 1000, alarm); + + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); +} + +void +nouveau_therm_program_alarms_polling(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + + nv_info(therm, + "programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", + sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, + sensor->thrs_down_clock.temp, + sensor->thrs_down_clock.hysteresis, + sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, + sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); + + alarm_timer_callback(&priv->sensor.therm_poll_alarm); } int @@ -71,6 +231,8 @@ nouveau_therm_sensor_ctor(struct nouveau_therm *therm) struct nouveau_therm_priv *priv = (void *)therm; struct nouveau_bios *bios = nouveau_bios(therm); + nouveau_alarm_init(&priv->sensor.therm_poll_alarm, alarm_timer_callback); + nouveau_therm_temp_set_defaults(therm); if (nvbios_therm_sensor_parse(bios, NVBIOS_THERM_DOMAIN_CORE, &priv->bios_sensor)) diff --git a/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c index c26ca9bef671..8e1bae4f12e8 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c +++ b/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c @@ -79,7 +79,7 @@ nv04_timer_alarm_trigger(struct nouveau_timer *ptimer) /* execute any pending alarm handlers */ list_for_each_entry_safe(alarm, atemp, &exec, head) { - list_del(&alarm->head); + list_del_init(&alarm->head); alarm->func(alarm); } } diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.h b/drivers/gpu/drm/nouveau/nouveau_acpi.h index d0da230d7706..74acf0f87785 100644 --- a/drivers/gpu/drm/nouveau/nouveau_acpi.h +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.h @@ -3,7 +3,7 @@ #define ROM_BIOS_PAGE 4096 -#if defined(CONFIG_ACPI) +#if defined(CONFIG_ACPI) && defined(CONFIG_X86) bool nouveau_is_optimus(void); bool nouveau_is_v1_dsm(void); void nouveau_register_dsm_handler(void); diff --git a/drivers/gpu/drm/nouveau/nouveau_backlight.c b/drivers/gpu/drm/nouveau/nouveau_backlight.c index f65b20a375f6..5d940302d2aa 100644 --- a/drivers/gpu/drm/nouveau/nouveau_backlight.c +++ b/drivers/gpu/drm/nouveau/nouveau_backlight.c @@ -84,6 +84,8 @@ nv40_backlight_init(struct drm_connector *connector) props.max_brightness = 31; bd = backlight_device_register("nv_backlight", &connector->kdev, drm, &nv40_bl_ops, &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); drm->backlight = bd; bd->props.brightness = nv40_get_intensity(bd); backlight_update_status(bd); diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c b/drivers/gpu/drm/nouveau/nouveau_bios.c index 865eddfa30a7..50a6dd02f7c5 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bios.c +++ b/drivers/gpu/drm/nouveau/nouveau_bios.c @@ -678,23 +678,6 @@ int run_tmds_table(struct drm_device *dev, struct dcb_output *dcbent, int head, return 0; } -static void parse_bios_version(struct drm_device *dev, struct nvbios *bios, uint16_t offset) -{ - /* - * offset + 0 (8 bits): Micro version - * offset + 1 (8 bits): Minor version - * offset + 2 (8 bits): Chip version - * offset + 3 (8 bits): Major version - */ - struct nouveau_drm *drm = nouveau_drm(dev); - - bios->major_version = bios->data[offset + 3]; - bios->chip_version = bios->data[offset + 2]; - NV_INFO(drm, "Bios version %02x.%02x.%02x.%02x\n", - bios->data[offset + 3], bios->data[offset + 2], - bios->data[offset + 1], bios->data[offset]); -} - static void parse_script_table_pointers(struct nvbios *bios, uint16_t offset) { /* @@ -710,12 +693,6 @@ static void parse_script_table_pointers(struct nvbios *bios, uint16_t offset) */ bios->init_script_tbls_ptr = ROM16(bios->data[offset]); - bios->macro_index_tbl_ptr = ROM16(bios->data[offset + 2]); - bios->macro_tbl_ptr = ROM16(bios->data[offset + 4]); - bios->condition_tbl_ptr = ROM16(bios->data[offset + 6]); - bios->io_condition_tbl_ptr = ROM16(bios->data[offset + 8]); - bios->io_flag_condition_tbl_ptr = ROM16(bios->data[offset + 10]); - bios->init_function_tbl_ptr = ROM16(bios->data[offset + 12]); } static int parse_bit_A_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) @@ -765,25 +742,6 @@ static int parse_bit_A_tbl_entry(struct drm_device *dev, struct nvbios *bios, st return 0; } -static int parse_bit_C_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) -{ - /* - * offset + 8 (16 bits): PLL limits table pointer - * - * There's more in here, but that's unknown. - */ - struct nouveau_drm *drm = nouveau_drm(dev); - - if (bitentry->length < 10) { - NV_ERROR(drm, "Do not understand BIT C table\n"); - return -EINVAL; - } - - bios->pll_limit_tbl_ptr = ROM16(bios->data[bitentry->offset + 8]); - - return 0; -} - static int parse_bit_display_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) { /* @@ -821,12 +779,6 @@ static int parse_bit_init_tbl_entry(struct drm_device *dev, struct nvbios *bios, } parse_script_table_pointers(bios, bitentry->offset); - - if (bitentry->length >= 16) - bios->some_script_ptr = ROM16(bios->data[bitentry->offset + 14]); - if (bitentry->length >= 18) - bios->init96_tbl_ptr = ROM16(bios->data[bitentry->offset + 16]); - return 0; } @@ -852,8 +804,6 @@ static int parse_bit_i_tbl_entry(struct drm_device *dev, struct nvbios *bios, st return -EINVAL; } - parse_bios_version(dev, bios, bitentry->offset); - /* * bit 4 seems to indicate a mobile bios (doesn't suffer from BMP's * Quadro identity crisis), other bits possibly as for BMP feature byte @@ -1078,9 +1028,6 @@ parse_bit_structure(struct nvbios *bios, const uint16_t bitoffset) return ret; if (bios->major_version >= 0x60) /* g80+ */ parse_bit_table(bios, bitoffset, &BIT_TABLE('A', A)); - ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('C', C)); - if (ret) - return ret; parse_bit_table(bios, bitoffset, &BIT_TABLE('D', display)); ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('I', init)); if (ret) @@ -1228,8 +1175,6 @@ static int parse_bmp_structure(struct drm_device *dev, struct nvbios *bios, unsi */ bios->feature_byte = bmp[9]; - parse_bios_version(dev, bios, offset + 10); - if (bmp_version_major < 5 || bmp_version_minor < 0x10) bios->old_style_init = true; legacy_scripts_offset = 18; @@ -1276,8 +1221,10 @@ static int parse_bmp_structure(struct drm_device *dev, struct nvbios *bios, unsi bios->fp.lvdsmanufacturerpointer = ROM16(bmp[117]); bios->fp.fpxlatemanufacturertableptr = ROM16(bmp[119]); } +#if 0 if (bmplength > 143) bios->pll_limit_tbl_ptr = ROM16(bmp[142]); +#endif if (bmplength > 157) bios->fp.duallink_transition_clk = ROM16(bmp[156]) * 10; @@ -1522,6 +1469,7 @@ parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb, } case DCB_OUTPUT_DP: entry->dpconf.sor.link = (conf & 0x00000030) >> 4; + entry->extdev = (conf & 0x0000ff00) >> 8; switch ((conf & 0x00e00000) >> 21) { case 0: entry->dpconf.link_bw = 162000; @@ -1543,8 +1491,10 @@ parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb, } break; case DCB_OUTPUT_TMDS: - if (dcb->version >= 0x40) + if (dcb->version >= 0x40) { entry->tmdsconf.sor.link = (conf & 0x00000030) >> 4; + entry->extdev = (conf & 0x0000ff00) >> 8; + } else if (dcb->version >= 0x30) entry->tmdsconf.slave_addr = (conf & 0x00000700) >> 8; else if (dcb->version >= 0x22) @@ -1937,9 +1887,9 @@ parse_dcb_table(struct drm_device *dev, struct nvbios *bios) if (conn[0] != 0xff) { NV_INFO(drm, "DCB conn %02d: ", idx); if (olddcb_conntab(dev)[3] < 4) - printk("%04x\n", ROM16(conn[0])); + pr_cont("%04x\n", ROM16(conn[0])); else - printk("%08x\n", ROM32(conn[0])); + pr_cont("%08x\n", ROM32(conn[0])); } } dcb_fake_connectors(bios); @@ -2052,45 +2002,29 @@ uint8_t *nouveau_bios_embedded_edid(struct drm_device *dev) static bool NVInitVBIOS(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); - struct nvbios *bios = &drm->vbios; - - memset(bios, 0, sizeof(struct nvbios)); - spin_lock_init(&bios->lock); - bios->dev = dev; - - bios->data = nouveau_bios(drm->device)->data; - bios->length = nouveau_bios(drm->device)->size; - return true; -} + struct nouveau_bios *bios = nouveau_bios(drm->device); + struct nvbios *legacy = &drm->vbios; + + memset(legacy, 0, sizeof(struct nvbios)); + spin_lock_init(&legacy->lock); + legacy->dev = dev; + + legacy->data = bios->data; + legacy->length = bios->size; + legacy->major_version = bios->version.major; + legacy->chip_version = bios->version.chip; + if (bios->bit_offset) { + legacy->type = NVBIOS_BIT; + legacy->offset = bios->bit_offset; + return !parse_bit_structure(legacy, legacy->offset + 6); + } else + if (bios->bmp_offset) { + legacy->type = NVBIOS_BMP; + legacy->offset = bios->bmp_offset; + return !parse_bmp_structure(dev, legacy, legacy->offset); + } -static int nouveau_parse_vbios_struct(struct drm_device *dev) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct nvbios *bios = &drm->vbios; - const uint8_t bit_signature[] = { 0xff, 0xb8, 'B', 'I', 'T' }; - const uint8_t bmp_signature[] = { 0xff, 0x7f, 'N', 'V', 0x0 }; - int offset; - - offset = findstr(bios->data, bios->length, - bit_signature, sizeof(bit_signature)); - if (offset) { - NV_INFO(drm, "BIT BIOS found\n"); - bios->type = NVBIOS_BIT; - bios->offset = offset; - return parse_bit_structure(bios, offset + 6); - } - - offset = findstr(bios->data, bios->length, - bmp_signature, sizeof(bmp_signature)); - if (offset) { - NV_INFO(drm, "BMP BIOS found\n"); - bios->type = NVBIOS_BMP; - bios->offset = offset; - return parse_bmp_structure(dev, bios, offset); - } - - NV_ERROR(drm, "No known BIOS signature found\n"); - return -ENODEV; + return false; } int @@ -2146,10 +2080,6 @@ nouveau_bios_init(struct drm_device *dev) if (!NVInitVBIOS(dev)) return -ENODEV; - ret = nouveau_parse_vbios_struct(dev); - if (ret) - return ret; - ret = parse_dcb_table(dev, bios); if (ret) return ret; diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.h b/drivers/gpu/drm/nouveau/nouveau_bios.h index f68c54ca422f..7ccd28f11adf 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bios.h +++ b/drivers/gpu/drm/nouveau/nouveau_bios.h @@ -107,20 +107,10 @@ struct nvbios { bool old_style_init; uint16_t init_script_tbls_ptr; uint16_t extra_init_script_tbl_ptr; - uint16_t macro_index_tbl_ptr; - uint16_t macro_tbl_ptr; - uint16_t condition_tbl_ptr; - uint16_t io_condition_tbl_ptr; - uint16_t io_flag_condition_tbl_ptr; - uint16_t init_function_tbl_ptr; - - uint16_t pll_limit_tbl_ptr; + uint16_t ram_restrict_tbl_ptr; uint8_t ram_restrict_group_count; - uint16_t some_script_ptr; /* BIT I + 14 */ - uint16_t init96_tbl_ptr; /* BIT I + 16 */ - struct dcb_table dcb; struct { diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c index 2f2741483b51..11ca82148edc 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bo.c +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c @@ -562,7 +562,7 @@ nouveau_bo_move_accel_cleanup(struct nouveau_channel *chan, struct nouveau_fence *fence = NULL; int ret; - ret = nouveau_fence_new(chan, &fence); + ret = nouveau_fence_new(chan, false, &fence); if (ret) return ret; diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c index 174300b6a02e..eaa80a2b81ee 100644 --- a/drivers/gpu/drm/nouveau/nouveau_chan.c +++ b/drivers/gpu/drm/nouveau/nouveau_chan.c @@ -51,14 +51,15 @@ nouveau_channel_idle(struct nouveau_channel *chan) struct nouveau_fence *fence = NULL; int ret; - ret = nouveau_fence_new(chan, &fence); + ret = nouveau_fence_new(chan, false, &fence); if (!ret) { ret = nouveau_fence_wait(fence, false, false); nouveau_fence_unref(&fence); } if (ret) - NV_ERROR(cli, "failed to idle channel 0x%08x\n", chan->handle); + NV_ERROR(cli, "failed to idle channel 0x%08x [%s]\n", + chan->handle, cli->base.name); return ret; } diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index e620ba8271b4..4dd7ae2ac6c6 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -55,8 +55,6 @@ MODULE_PARM_DESC(duallink, "Allow dual-link TMDS (default: enabled)"); static int nouveau_duallink = 1; module_param_named(duallink, nouveau_duallink, int, 0400); -static void nouveau_connector_hotplug(void *, int); - struct nouveau_encoder * find_encoder(struct drm_connector *connector, int type) { @@ -100,22 +98,6 @@ static void nouveau_connector_destroy(struct drm_connector *connector) { struct nouveau_connector *nv_connector = nouveau_connector(connector); - struct nouveau_gpio *gpio; - struct nouveau_drm *drm; - struct drm_device *dev; - - if (!nv_connector) - return; - - dev = nv_connector->base.dev; - drm = nouveau_drm(dev); - gpio = nouveau_gpio(drm->device); - - if (gpio && nv_connector->hpd != DCB_GPIO_UNUSED) { - gpio->isr_del(gpio, 0, nv_connector->hpd, 0xff, - nouveau_connector_hotplug, connector); - } - kfree(nv_connector->edid); drm_sysfs_connector_remove(connector); drm_connector_cleanup(connector); @@ -130,7 +112,6 @@ nouveau_connector_ddc_detect(struct drm_connector *connector, struct nouveau_connector *nv_connector = nouveau_connector(connector); struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_gpio *gpio = nouveau_gpio(drm->device); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_i2c_port *port = NULL; int i, panel = -ENODEV; @@ -160,8 +141,7 @@ nouveau_connector_ddc_detect(struct drm_connector *connector, continue; nv_encoder = nouveau_encoder(obj_to_encoder(obj)); - if (nv_encoder->dcb->i2c_index < 0xf) - port = i2c->find(i2c, nv_encoder->dcb->i2c_index); + port = nv_encoder->i2c; if (port && nv_probe_i2c(port, 0x50)) { *pnv_encoder = nv_encoder; break; @@ -399,9 +379,10 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force) struct edid *edid = (struct edid *)nouveau_bios_embedded_edid(dev); if (edid) { - nv_connector->edid = kmalloc(EDID_LENGTH, GFP_KERNEL); - *(nv_connector->edid) = *edid; - status = connector_status_connected; + nv_connector->edid = + kmemdup(edid, EDID_LENGTH, GFP_KERNEL); + if (nv_connector->edid) + status = connector_status_connected; } } @@ -911,6 +892,37 @@ nouveau_connector_funcs_lvds = { .force = nouveau_connector_force }; +static void +nouveau_connector_hotplug_work(struct work_struct *work) +{ + struct nouveau_connector *nv_connector = + container_of(work, struct nouveau_connector, hpd_work); + struct drm_connector *connector = &nv_connector->base; + struct drm_device *dev = connector->dev; + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_gpio *gpio = nouveau_gpio(drm->device); + bool plugged = gpio->get(gpio, 0, nv_connector->hpd.func, 0xff); + + NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", + drm_get_connector_name(connector)); + + if (plugged) + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); + else + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + + drm_helper_hpd_irq_event(dev); +} + +static int +nouveau_connector_hotplug(struct nouveau_eventh *event, int index) +{ + struct nouveau_connector *nv_connector = + container_of(event, struct nouveau_connector, hpd_func); + schedule_work(&nv_connector->hpd_work); + return NVKM_EVENT_KEEP; +} + static int drm_conntype_from_dcb(enum dcb_connector_type dcb) { @@ -961,6 +973,7 @@ nouveau_connector_create(struct drm_device *dev, int index) return ERR_PTR(-ENOMEM); connector = &nv_connector->base; + INIT_WORK(&nv_connector->hpd_work, nouveau_connector_hotplug_work); nv_connector->index = index; /* attempt to parse vbios connector type and hotplug gpio */ @@ -975,8 +988,11 @@ nouveau_connector_create(struct drm_device *dev, int index) if (olddcb_conntab(dev)[3] >= 4) entry |= (u32)ROM16(nv_connector->dcb[2]) << 16; - nv_connector->hpd = ffs((entry & 0x07033000) >> 12); - nv_connector->hpd = hpd[nv_connector->hpd]; + ret = gpio->find(gpio, 0, hpd[ffs((entry & 0x07033000) >> 12)], + DCB_GPIO_UNUSED, &nv_connector->hpd); + nv_connector->hpd_func.func = nouveau_connector_hotplug; + if (ret) + nv_connector->hpd.func = DCB_GPIO_UNUSED; nv_connector->type = nv_connector->dcb[0]; if (drm_conntype_from_dcb(nv_connector->type) == @@ -999,7 +1015,7 @@ nouveau_connector_create(struct drm_device *dev, int index) } } else { nv_connector->type = DCB_CONNECTOR_NONE; - nv_connector->hpd = DCB_GPIO_UNUSED; + nv_connector->hpd.func = DCB_GPIO_UNUSED; } /* no vbios data, or an unknown dcb connector type - attempt to @@ -1126,31 +1142,9 @@ nouveau_connector_create(struct drm_device *dev, int index) } connector->polled = DRM_CONNECTOR_POLL_CONNECT; - if (gpio && nv_connector->hpd != DCB_GPIO_UNUSED) { - ret = gpio->isr_add(gpio, 0, nv_connector->hpd, 0xff, - nouveau_connector_hotplug, connector); - if (ret == 0) - connector->polled = DRM_CONNECTOR_POLL_HPD; - } + if (nv_connector->hpd.func != DCB_GPIO_UNUSED) + connector->polled = DRM_CONNECTOR_POLL_HPD; drm_sysfs_connector_add(connector); return connector; } - -static void -nouveau_connector_hotplug(void *data, int plugged) -{ - struct drm_connector *connector = data; - struct drm_device *dev = connector->dev; - struct nouveau_drm *drm = nouveau_drm(dev); - - NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", - drm_get_connector_name(connector)); - - if (plugged) - drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); - else - drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); - - drm_helper_hpd_irq_event(dev); -} diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h index 20eb84cce9e6..6e399aad491a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.h +++ b/drivers/gpu/drm/nouveau/nouveau_connector.h @@ -30,6 +30,11 @@ #include <drm/drm_edid.h> #include "nouveau_crtc.h" +#include <core/event.h> + +#include <subdev/bios.h> +#include <subdev/bios/gpio.h> + struct nouveau_i2c_port; enum nouveau_underscan_type { @@ -61,7 +66,10 @@ struct nouveau_connector { enum dcb_connector_type type; u8 index; u8 *dcb; - u8 hpd; + + struct dcb_gpio_func hpd; + struct work_struct hpd_work; + struct nouveau_eventh hpd_func; int dithering_mode; int dithering_depth; diff --git a/drivers/gpu/drm/nouveau/nouveau_debugfs.c b/drivers/gpu/drm/nouveau/nouveau_debugfs.c new file mode 100644 index 000000000000..5392e07edfc6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_debugfs.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2009 Red Hat <bskeggs@redhat.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* + * Authors: + * Ben Skeggs <bskeggs@redhat.com> + */ + +#include "nouveau_debugfs.h" +#include "nouveau_drm.h" + +static int +nouveau_debugfs_vbios_image(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct nouveau_drm *drm = nouveau_drm(node->minor->dev); + int i; + + for (i = 0; i < drm->vbios.length; i++) + seq_printf(m, "%c", drm->vbios.data[i]); + return 0; +} + +static struct drm_info_list nouveau_debugfs_list[] = { + { "vbios.rom", nouveau_debugfs_vbios_image, 0, NULL }, +}; +#define NOUVEAU_DEBUGFS_ENTRIES ARRAY_SIZE(nouveau_debugfs_list) + +int +nouveau_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES, + minor->debugfs_root, minor); + return 0; +} + +void +nouveau_debugfs_takedown(struct drm_minor *minor) +{ + drm_debugfs_remove_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES, + minor); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_debugfs.h b/drivers/gpu/drm/nouveau/nouveau_debugfs.h new file mode 100644 index 000000000000..a62af6fb5f99 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_debugfs.h @@ -0,0 +1,22 @@ +#ifndef __NOUVEAU_DEBUGFS_H__ +#define __NOUVEAU_DEBUGFS_H__ + +#include <drm/drmP.h> + +#if defined(CONFIG_DEBUG_FS) +extern int nouveau_debugfs_init(struct drm_minor *); +extern void nouveau_debugfs_takedown(struct drm_minor *); +#else +static inline int +nouveau_debugfs_init(struct drm_minor *minor) +{ + return 0; +} + +static inline void nouveau_debugfs_takedown(struct drm_minor *minor) +{ +} + +#endif + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index d42c9e860c16..4610c3a29bbe 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -41,6 +41,8 @@ #include <subdev/gpio.h> #include <engine/disp.h> +#include <core/class.h> + static void nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb) { @@ -231,8 +233,10 @@ nouveau_display_init(struct drm_device *dev) /* enable hotplug interrupts */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { struct nouveau_connector *conn = nouveau_connector(connector); - if (gpio) - gpio->irq(gpio, 0, conn->hpd, 0xff, true); + if (gpio && conn->hpd.func != DCB_GPIO_UNUSED) { + nouveau_event_get(gpio->events, conn->hpd.line, + &conn->hpd_func); + } } return ret; @@ -249,37 +253,20 @@ nouveau_display_fini(struct drm_device *dev) /* disable hotplug interrupts */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { struct nouveau_connector *conn = nouveau_connector(connector); - if (gpio) - gpio->irq(gpio, 0, conn->hpd, 0xff, false); + if (gpio && conn->hpd.func != DCB_GPIO_UNUSED) { + nouveau_event_put(gpio->events, conn->hpd.line, + &conn->hpd_func); + } } drm_kms_helper_poll_disable(dev); disp->fini(dev); } -static void -nouveau_display_vblank_notify(void *data, int crtc) -{ - drm_handle_vblank(data, crtc); -} - -static void -nouveau_display_vblank_get(void *data, int crtc) -{ - drm_vblank_get(data, crtc); -} - -static void -nouveau_display_vblank_put(void *data, int crtc) -{ - drm_vblank_put(data, crtc); -} - int nouveau_display_create(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); - struct nouveau_disp *pdisp = nouveau_disp(drm->device); struct nouveau_display *disp; u32 pclass = dev->pdev->class >> 8; int ret, gen; @@ -288,11 +275,6 @@ nouveau_display_create(struct drm_device *dev) if (!disp) return -ENOMEM; - pdisp->vblank.data = dev; - pdisp->vblank.notify = nouveau_display_vblank_notify; - pdisp->vblank.get = nouveau_display_vblank_get; - pdisp->vblank.put = nouveau_display_vblank_put; - drm_mode_config_init(dev); drm_mode_create_scaling_mode_property(dev); drm_mode_create_dvi_i_properties(dev); @@ -316,17 +298,13 @@ nouveau_display_create(struct drm_device *dev) drm_property_create_range(dev, 0, "underscan vborder", 0, 128); if (gen >= 1) { + /* -90..+90 */ disp->vibrant_hue_property = - drm_property_create(dev, DRM_MODE_PROP_RANGE, - "vibrant hue", 2); - disp->vibrant_hue_property->values[0] = 0; - disp->vibrant_hue_property->values[1] = 180; /* -90..+90 */ + drm_property_create_range(dev, 0, "vibrant hue", 0, 180); + /* -100..+100 */ disp->color_vibrance_property = - drm_property_create(dev, DRM_MODE_PROP_RANGE, - "color vibrance", 2); - disp->color_vibrance_property->values[0] = 0; - disp->color_vibrance_property->values[1] = 200; /* -100..+100 */ + drm_property_create_range(dev, 0, "color vibrance", 0, 200); } dev->mode_config.funcs = &nouveau_mode_config_funcs; @@ -478,39 +456,6 @@ nouveau_display_resume(struct drm_device *dev) } } -int -nouveau_vblank_enable(struct drm_device *dev, int crtc) -{ - struct nouveau_device *device = nouveau_dev(dev); - - if (device->card_type >= NV_D0) - nv_mask(device, 0x6100c0 + (crtc * 0x800), 1, 1); - else - if (device->card_type >= NV_50) - nv_mask(device, NV50_PDISPLAY_INTR_EN_1, 0, - NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_(crtc)); - else - NVWriteCRTC(dev, crtc, NV_PCRTC_INTR_EN_0, - NV_PCRTC_INTR_0_VBLANK); - - return 0; -} - -void -nouveau_vblank_disable(struct drm_device *dev, int crtc) -{ - struct nouveau_device *device = nouveau_dev(dev); - - if (device->card_type >= NV_D0) - nv_mask(device, 0x6100c0 + (crtc * 0x800), 1, 0); - else - if (device->card_type >= NV_50) - nv_mask(device, NV50_PDISPLAY_INTR_EN_1, - NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_(crtc), 0); - else - NVWriteCRTC(dev, crtc, NV_PCRTC_INTR_EN_0, 0); -} - static int nouveau_page_flip_reserve(struct nouveau_bo *old_bo, struct nouveau_bo *new_bo) @@ -595,7 +540,7 @@ nouveau_page_flip_emit(struct nouveau_channel *chan, } FIRE_RING (chan); - ret = nouveau_fence_new(chan, pfence); + ret = nouveau_fence_new(chan, false, pfence); if (ret) goto fail; diff --git a/drivers/gpu/drm/nouveau/nouveau_display.h b/drivers/gpu/drm/nouveau/nouveau_display.h index 722548bb3bd3..1ea3e4734b62 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.h +++ b/drivers/gpu/drm/nouveau/nouveau_display.h @@ -59,9 +59,6 @@ void nouveau_display_fini(struct drm_device *dev); int nouveau_display_suspend(struct drm_device *dev); void nouveau_display_resume(struct drm_device *dev); -int nouveau_vblank_enable(struct drm_device *dev, int crtc); -void nouveau_vblank_disable(struct drm_device *dev, int crtc); - int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event); int nouveau_finish_page_flip(struct nouveau_channel *, diff --git a/drivers/gpu/drm/nouveau/nouveau_dma.h b/drivers/gpu/drm/nouveau/nouveau_dma.h index 5c2e22932d1c..690d5930ce32 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dma.h +++ b/drivers/gpu/drm/nouveau/nouveau_dma.h @@ -191,7 +191,7 @@ WIND_RING(struct nouveau_channel *chan) #define NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG 0x00000002 #define NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_GEQUAL 0x00000004 #define NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD 0x00001000 -#define NV84_SUBCHAN_NOTIFY_INTR 0x00000020 +#define NV84_SUBCHAN_UEVENT 0x00000020 #define NV84_SUBCHAN_WRCACHE_FLUSH 0x00000024 #define NV10_SUBCHAN_REF_CNT 0x00000050 #define NVSW_SUBCHAN_PAGE_FLIP 0x00000054 diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c index 59838651ee8f..36fd22500569 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dp.c +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -35,300 +35,6 @@ #include <subdev/gpio.h> #include <subdev/i2c.h> -/****************************************************************************** - * link training - *****************************************************************************/ -struct dp_state { - struct nouveau_i2c_port *auxch; - struct nouveau_object *core; - struct dcb_output *dcb; - int crtc; - u8 *dpcd; - int link_nr; - u32 link_bw; - u8 stat[6]; - u8 conf[4]; -}; - -static void -dp_set_link_config(struct drm_device *dev, struct dp_state *dp) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - u8 sink[2]; - u32 data; - - NV_DEBUG(drm, "%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw); - - /* set desired link configuration on the source */ - data = ((dp->link_bw / 27000) << 8) | dp->link_nr; - if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP) - data |= NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH; - - nv_call(dp->core, NV94_DISP_SOR_DP_LNKCTL + moff, data); - - /* inform the sink of the new configuration */ - sink[0] = dp->link_bw / 27000; - sink[1] = dp->link_nr; - if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP) - sink[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; - - nv_wraux(dp->auxch, DP_LINK_BW_SET, sink, 2); -} - -static void -dp_set_training_pattern(struct drm_device *dev, struct dp_state *dp, u8 pattern) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - u8 sink_tp; - - NV_DEBUG(drm, "training pattern %d\n", pattern); - - nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, pattern); - - nv_rdaux(dp->auxch, DP_TRAINING_PATTERN_SET, &sink_tp, 1); - sink_tp &= ~DP_TRAINING_PATTERN_MASK; - sink_tp |= pattern; - nv_wraux(dp->auxch, DP_TRAINING_PATTERN_SET, &sink_tp, 1); -} - -static int -dp_link_train_commit(struct drm_device *dev, struct dp_state *dp) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - int i; - - for (i = 0; i < dp->link_nr; i++) { - u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf; - u8 lpre = (lane & 0x0c) >> 2; - u8 lvsw = (lane & 0x03) >> 0; - - dp->conf[i] = (lpre << 3) | lvsw; - if (lvsw == DP_TRAIN_VOLTAGE_SWING_1200) - dp->conf[i] |= DP_TRAIN_MAX_SWING_REACHED; - if ((lpre << 3) == DP_TRAIN_PRE_EMPHASIS_9_5) - dp->conf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; - - NV_DEBUG(drm, "config lane %d %02x\n", i, dp->conf[i]); - - nv_call(dp->core, NV94_DISP_SOR_DP_DRVCTL(i) + moff, (lvsw << 8) | lpre); - } - - return nv_wraux(dp->auxch, DP_TRAINING_LANE0_SET, dp->conf, 4); -} - -static int -dp_link_train_update(struct drm_device *dev, struct dp_state *dp, u32 delay) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - int ret; - - udelay(delay); - - ret = nv_rdaux(dp->auxch, DP_LANE0_1_STATUS, dp->stat, 6); - if (ret) - return ret; - - NV_DEBUG(drm, "status %*ph\n", 6, dp->stat); - return 0; -} - -static int -dp_link_train_cr(struct drm_device *dev, struct dp_state *dp) -{ - bool cr_done = false, abort = false; - int voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK; - int tries = 0, i; - - dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_1); - - do { - if (dp_link_train_commit(dev, dp) || - dp_link_train_update(dev, dp, 100)) - break; - - cr_done = true; - for (i = 0; i < dp->link_nr; i++) { - u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; - if (!(lane & DP_LANE_CR_DONE)) { - cr_done = false; - if (dp->conf[i] & DP_TRAIN_MAX_SWING_REACHED) - abort = true; - break; - } - } - - if ((dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK) != voltage) { - voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK; - tries = 0; - } - } while (!cr_done && !abort && ++tries < 5); - - return cr_done ? 0 : -1; -} - -static int -dp_link_train_eq(struct drm_device *dev, struct dp_state *dp) -{ - bool eq_done, cr_done = true; - int tries = 0, i; - - dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_2); - - do { - if (dp_link_train_update(dev, dp, 400)) - break; - - eq_done = !!(dp->stat[2] & DP_INTERLANE_ALIGN_DONE); - for (i = 0; i < dp->link_nr && eq_done; i++) { - u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; - if (!(lane & DP_LANE_CR_DONE)) - cr_done = false; - if (!(lane & DP_LANE_CHANNEL_EQ_DONE) || - !(lane & DP_LANE_SYMBOL_LOCKED)) - eq_done = false; - } - - if (dp_link_train_commit(dev, dp)) - break; - } while (!eq_done && cr_done && ++tries <= 5); - - return eq_done ? 0 : -1; -} - -static void -dp_link_train_init(struct drm_device *dev, struct dp_state *dp, bool spread) -{ - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - - nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, (spread ? - NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON : - NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_OFF) | - NV94_DISP_SOR_DP_TRAIN_OP_INIT); -} - -static void -dp_link_train_fini(struct drm_device *dev, struct dp_state *dp) -{ - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - - nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, - NV94_DISP_SOR_DP_TRAIN_OP_FINI); -} - -static bool -nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate, - struct nouveau_object *core) -{ - struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); - struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); - struct nouveau_connector *nv_connector = - nouveau_encoder_connector_get(nv_encoder); - struct drm_device *dev = encoder->dev; - struct nouveau_drm *drm = nouveau_drm(dev); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); - struct nouveau_gpio *gpio = nouveau_gpio(drm->device); - const u32 bw_list[] = { 270000, 162000, 0 }; - const u32 *link_bw = bw_list; - struct dp_state dp; - - dp.auxch = i2c->find(i2c, nv_encoder->dcb->i2c_index); - if (!dp.auxch) - return false; - - dp.core = core; - dp.dcb = nv_encoder->dcb; - dp.crtc = nv_crtc->index; - dp.dpcd = nv_encoder->dp.dpcd; - - /* adjust required bandwidth for 8B/10B coding overhead */ - datarate = (datarate / 8) * 10; - - /* some sinks toggle hotplug in response to some of the actions - * we take during link training (DP_SET_POWER is one), we need - * to ignore them for the moment to avoid races. - */ - gpio->irq(gpio, 0, nv_connector->hpd, 0xff, false); - - /* enable down-spreading and execute pre-train script from vbios */ - dp_link_train_init(dev, &dp, nv_encoder->dp.dpcd[3] & 1); - - /* start off at highest link rate supported by encoder and display */ - while (*link_bw > nv_encoder->dp.link_bw) - link_bw++; - - while (link_bw[0]) { - /* find minimum required lane count at this link rate */ - dp.link_nr = nv_encoder->dp.link_nr; - while ((dp.link_nr >> 1) * link_bw[0] > datarate) - dp.link_nr >>= 1; - - /* drop link rate to minimum with this lane count */ - while ((link_bw[1] * dp.link_nr) > datarate) - link_bw++; - dp.link_bw = link_bw[0]; - - /* program selected link configuration */ - dp_set_link_config(dev, &dp); - - /* attempt to train the link at this configuration */ - memset(dp.stat, 0x00, sizeof(dp.stat)); - if (!dp_link_train_cr(dev, &dp) && - !dp_link_train_eq(dev, &dp)) - break; - - /* retry at lower rate */ - link_bw++; - } - - /* finish link training */ - dp_set_training_pattern(dev, &dp, DP_TRAINING_PATTERN_DISABLE); - - /* execute post-train script from vbios */ - dp_link_train_fini(dev, &dp); - - /* re-enable hotplug detect */ - gpio->irq(gpio, 0, nv_connector->hpd, 0xff, true); - return true; -} - -void -nouveau_dp_dpms(struct drm_encoder *encoder, int mode, u32 datarate, - struct nouveau_object *core) -{ - struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); - struct nouveau_drm *drm = nouveau_drm(encoder->dev); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); - struct nouveau_i2c_port *auxch; - u8 status; - - auxch = i2c->find(i2c, nv_encoder->dcb->i2c_index); - if (!auxch) - return; - - if (mode == DRM_MODE_DPMS_ON) - status = DP_SET_POWER_D0; - else - status = DP_SET_POWER_D3; - - nv_wraux(auxch, DP_SET_POWER, &status, 1); - - if (mode == DRM_MODE_DPMS_ON) - nouveau_dp_link_train(encoder, datarate, core); -} - static void nouveau_dp_probe_oui(struct drm_device *dev, struct nouveau_i2c_port *auxch, u8 *dpcd) @@ -355,12 +61,11 @@ nouveau_dp_detect(struct drm_encoder *encoder) struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); struct drm_device *dev = encoder->dev; struct nouveau_drm *drm = nouveau_drm(dev); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_i2c_port *auxch; u8 *dpcd = nv_encoder->dp.dpcd; int ret; - auxch = i2c->find(i2c, nv_encoder->dcb->i2c_index); + auxch = nv_encoder->i2c; if (!auxch) return false; diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 8e8e8ce75528..8a03c58ae988 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -34,6 +34,8 @@ #include <subdev/device.h> #include <subdev/vm.h> +#include <engine/disp.h> + #include "nouveau_drm.h" #include "nouveau_irq.h" #include "nouveau_dma.h" @@ -48,6 +50,7 @@ #include "nouveau_abi16.h" #include "nouveau_fbcon.h" #include "nouveau_fence.h" +#include "nouveau_debugfs.h" MODULE_PARM_DESC(config, "option string to pass to driver core"); static char *nouveau_config; @@ -68,6 +71,32 @@ module_param_named(modeset, nouveau_modeset, int, 0400); static struct drm_driver driver; +static int +nouveau_drm_vblank_enable(struct drm_device *dev, int head) +{ + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_disp *pdisp = nouveau_disp(drm->device); + nouveau_event_get(pdisp->vblank, head, &drm->vblank); + return 0; +} + +static void +nouveau_drm_vblank_disable(struct drm_device *dev, int head) +{ + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_disp *pdisp = nouveau_disp(drm->device); + nouveau_event_put(pdisp->vblank, head, &drm->vblank); +} + +static int +nouveau_drm_vblank_handler(struct nouveau_eventh *event, int head) +{ + struct nouveau_drm *drm = + container_of(event, struct nouveau_drm, vblank); + drm_handle_vblank(drm->dev, head); + return NVKM_EVENT_KEEP; +} + static u64 nouveau_name(struct pci_dev *pdev) { @@ -132,7 +161,8 @@ nouveau_accel_init(struct nouveau_drm *drm) /* initialise synchronisation routines */ if (device->card_type < NV_10) ret = nv04_fence_create(drm); - else if (device->card_type < NV_50) ret = nv10_fence_create(drm); + else if (device->chipset < 0x17) ret = nv10_fence_create(drm); + else if (device->card_type < NV_50) ret = nv17_fence_create(drm); else if (device->chipset < 0x84) ret = nv50_fence_create(drm); else if (device->card_type < NV_C0) ret = nv84_fence_create(drm); else ret = nvc0_fence_create(drm); @@ -259,6 +289,7 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags) dev->dev_private = drm; drm->dev = dev; + drm->vblank.func = nouveau_drm_vblank_handler; INIT_LIST_HEAD(&drm->clients); spin_lock_init(&drm->tile.lock); @@ -398,7 +429,7 @@ nouveau_drm_remove(struct pci_dev *pdev) nouveau_object_debug(); } -int +static int nouveau_do_suspend(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); @@ -469,7 +500,7 @@ int nouveau_pmops_suspend(struct device *dev) return 0; } -int +static int nouveau_do_resume(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); @@ -543,10 +574,11 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) struct pci_dev *pdev = dev->pdev; struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_cli *cli; - char name[16]; + char name[32], tmpname[TASK_COMM_LEN]; int ret; - snprintf(name, sizeof(name), "%d", pid_nr(fpriv->pid)); + get_task_comm(tmpname, current); + snprintf(name, sizeof(name), "%s[%d]", tmpname, pid_nr(fpriv->pid)); ret = nouveau_cli_create(pdev, name, sizeof(*cli), (void **)&cli); if (ret) @@ -636,14 +668,19 @@ driver = { .postclose = nouveau_drm_postclose, .lastclose = nouveau_vga_lastclose, +#if defined(CONFIG_DEBUG_FS) + .debugfs_init = nouveau_debugfs_init, + .debugfs_cleanup = nouveau_debugfs_takedown, +#endif + .irq_preinstall = nouveau_irq_preinstall, .irq_postinstall = nouveau_irq_postinstall, .irq_uninstall = nouveau_irq_uninstall, .irq_handler = nouveau_irq_handler, .get_vblank_counter = drm_vblank_count, - .enable_vblank = nouveau_vblank_enable, - .disable_vblank = nouveau_vblank_disable, + .enable_vblank = nouveau_drm_vblank_enable, + .disable_vblank = nouveau_drm_vblank_disable, .ioctls = nouveau_ioctls, .fops = &nouveau_driver_fops, diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.h b/drivers/gpu/drm/nouveau/nouveau_drm.h index aa89eb938b47..b25df374c901 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.h +++ b/drivers/gpu/drm/nouveau/nouveau_drm.h @@ -13,6 +13,7 @@ #define DRIVER_PATCHLEVEL 0 #include <core/client.h> +#include <core/event.h> #include <subdev/vm.h> @@ -112,6 +113,7 @@ struct nouveau_drm { struct nvbios vbios; struct nouveau_display *display; struct backlight_device *backlight; + struct nouveau_eventh vblank; /* power management */ struct nouveau_pm *pm; diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h index d0d95bd511ab..e24341229d5e 100644 --- a/drivers/gpu/drm/nouveau/nouveau_encoder.h +++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h @@ -36,19 +36,12 @@ struct nouveau_i2c_port; -struct dp_train_func { - void (*link_set)(struct drm_device *, struct dcb_output *, int crtc, - int nr, u32 bw, bool enhframe); - void (*train_set)(struct drm_device *, struct dcb_output *, u8 pattern); - void (*train_adj)(struct drm_device *, struct dcb_output *, - u8 lane, u8 swing, u8 preem); -}; - struct nouveau_encoder { struct drm_encoder_slave base; struct dcb_output *dcb; int or; + struct nouveau_i2c_port *i2c; /* different to drm_encoder.crtc, this reflects what's * actually programmed on the hw, not the proposed crtc */ diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c index 1d049be79f74..6c946837a0aa 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.c +++ b/drivers/gpu/drm/nouveau/nouveau_fence.c @@ -33,14 +33,14 @@ #include "nouveau_dma.h" #include "nouveau_fence.h" +#include <engine/fifo.h> + void nouveau_fence_context_del(struct nouveau_fence_chan *fctx) { struct nouveau_fence *fence, *fnext; spin_lock(&fctx->lock); list_for_each_entry_safe(fence, fnext, &fctx->pending, head) { - if (fence->work) - fence->work(fence->priv, false); fence->channel = NULL; list_del(&fence->head); nouveau_fence_unref(&fence); @@ -59,17 +59,14 @@ nouveau_fence_context_new(struct nouveau_fence_chan *fctx) static void nouveau_fence_update(struct nouveau_channel *chan) { - struct nouveau_fence_priv *priv = chan->drm->fence; struct nouveau_fence_chan *fctx = chan->fence; struct nouveau_fence *fence, *fnext; spin_lock(&fctx->lock); list_for_each_entry_safe(fence, fnext, &fctx->pending, head) { - if (priv->read(chan) < fence->sequence) + if (fctx->read(chan) < fence->sequence) break; - if (fence->work) - fence->work(fence->priv, true); fence->channel = NULL; list_del(&fence->head); nouveau_fence_unref(&fence); @@ -80,7 +77,6 @@ nouveau_fence_update(struct nouveau_channel *chan) int nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan) { - struct nouveau_fence_priv *priv = chan->drm->fence; struct nouveau_fence_chan *fctx = chan->fence; int ret; @@ -88,7 +84,7 @@ nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan) fence->timeout = jiffies + (3 * DRM_HZ); fence->sequence = ++fctx->sequence; - ret = priv->emit(fence); + ret = fctx->emit(fence); if (!ret) { kref_get(&fence->kref); spin_lock(&fctx->lock); @@ -107,13 +103,87 @@ nouveau_fence_done(struct nouveau_fence *fence) return !fence->channel; } +struct nouveau_fence_uevent { + struct nouveau_eventh handler; + struct nouveau_fence_priv *priv; +}; + +static int +nouveau_fence_wait_uevent_handler(struct nouveau_eventh *event, int index) +{ + struct nouveau_fence_uevent *uevent = + container_of(event, struct nouveau_fence_uevent, handler); + wake_up_all(&uevent->priv->waiting); + return NVKM_EVENT_KEEP; +} + +static int +nouveau_fence_wait_uevent(struct nouveau_fence *fence, bool intr) + +{ + struct nouveau_channel *chan = fence->channel; + struct nouveau_fifo *pfifo = nouveau_fifo(chan->drm->device); + struct nouveau_fence_priv *priv = chan->drm->fence; + struct nouveau_fence_uevent uevent = { + .handler.func = nouveau_fence_wait_uevent_handler, + .priv = priv, + }; + int ret = 0; + + nouveau_event_get(pfifo->uevent, 0, &uevent.handler); + + if (fence->timeout) { + unsigned long timeout = fence->timeout - jiffies; + + if (time_before(jiffies, fence->timeout)) { + if (intr) { + ret = wait_event_interruptible_timeout( + priv->waiting, + nouveau_fence_done(fence), + timeout); + } else { + ret = wait_event_timeout(priv->waiting, + nouveau_fence_done(fence), + timeout); + } + } + + if (ret >= 0) { + fence->timeout = jiffies + ret; + if (time_after_eq(jiffies, fence->timeout)) + ret = -EBUSY; + } + } else { + if (intr) { + ret = wait_event_interruptible(priv->waiting, + nouveau_fence_done(fence)); + } else { + wait_event(priv->waiting, nouveau_fence_done(fence)); + } + } + + nouveau_event_put(pfifo->uevent, 0, &uevent.handler); + if (unlikely(ret < 0)) + return ret; + + return 0; +} + int nouveau_fence_wait(struct nouveau_fence *fence, bool lazy, bool intr) { + struct nouveau_channel *chan = fence->channel; + struct nouveau_fence_priv *priv = chan ? chan->drm->fence : NULL; unsigned long sleep_time = NSEC_PER_MSEC / 1000; ktime_t t; int ret = 0; + while (priv && priv->uevent && lazy && !nouveau_fence_done(fence)) { + ret = nouveau_fence_wait_uevent(fence, intr); + if (ret < 0) + return ret; + } + while (!nouveau_fence_done(fence)) { if (fence->timeout && time_after_eq(jiffies, fence->timeout)) { ret = -EBUSY; @@ -143,14 +213,14 @@ nouveau_fence_wait(struct nouveau_fence *fence, bool lazy, bool intr) int nouveau_fence_sync(struct nouveau_fence *fence, struct nouveau_channel *chan) { - struct nouveau_fence_priv *priv = chan->drm->fence; + struct nouveau_fence_chan *fctx = chan->fence; struct nouveau_channel *prev; int ret = 0; prev = fence ? fence->channel : NULL; if (prev) { if (unlikely(prev != chan && !nouveau_fence_done(fence))) { - ret = priv->sync(fence, prev, chan); + ret = fctx->sync(fence, prev, chan); if (unlikely(ret)) ret = nouveau_fence_wait(fence, true, false); } @@ -182,7 +252,8 @@ nouveau_fence_ref(struct nouveau_fence *fence) } int -nouveau_fence_new(struct nouveau_channel *chan, struct nouveau_fence **pfence) +nouveau_fence_new(struct nouveau_channel *chan, bool sysmem, + struct nouveau_fence **pfence) { struct nouveau_fence *fence; int ret = 0; @@ -193,13 +264,13 @@ nouveau_fence_new(struct nouveau_channel *chan, struct nouveau_fence **pfence) fence = kzalloc(sizeof(*fence), GFP_KERNEL); if (!fence) return -ENOMEM; + + fence->sysmem = sysmem; kref_init(&fence->kref); - if (chan) { - ret = nouveau_fence_emit(fence, chan); - if (ret) - nouveau_fence_unref(&fence); - } + ret = nouveau_fence_emit(fence, chan); + if (ret) + nouveau_fence_unref(&fence); *pfence = fence; return ret; diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.h b/drivers/gpu/drm/nouveau/nouveau_fence.h index cdb83acdffe2..c89943407b52 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.h +++ b/drivers/gpu/drm/nouveau/nouveau_fence.h @@ -7,15 +7,15 @@ struct nouveau_fence { struct list_head head; struct kref kref; + bool sysmem; + struct nouveau_channel *channel; unsigned long timeout; u32 sequence; - - void (*work)(void *priv, bool signalled); - void *priv; }; -int nouveau_fence_new(struct nouveau_channel *, struct nouveau_fence **); +int nouveau_fence_new(struct nouveau_channel *, bool sysmem, + struct nouveau_fence **); struct nouveau_fence * nouveau_fence_ref(struct nouveau_fence *); void nouveau_fence_unref(struct nouveau_fence **); @@ -29,6 +29,13 @@ struct nouveau_fence_chan { struct list_head pending; struct list_head flip; + int (*emit)(struct nouveau_fence *); + int (*sync)(struct nouveau_fence *, struct nouveau_channel *, + struct nouveau_channel *); + u32 (*read)(struct nouveau_channel *); + int (*emit32)(struct nouveau_channel *, u64, u32); + int (*sync32)(struct nouveau_channel *, u64, u32); + spinlock_t lock; u32 sequence; }; @@ -39,10 +46,9 @@ struct nouveau_fence_priv { void (*resume)(struct nouveau_drm *); int (*context_new)(struct nouveau_channel *); void (*context_del)(struct nouveau_channel *); - int (*emit)(struct nouveau_fence *); - int (*sync)(struct nouveau_fence *, struct nouveau_channel *, - struct nouveau_channel *); - u32 (*read)(struct nouveau_channel *); + + wait_queue_head_t waiting; + bool uevent; }; #define nouveau_fence(drm) ((struct nouveau_fence_priv *)(drm)->fence) @@ -60,13 +66,31 @@ u32 nv10_fence_read(struct nouveau_channel *); void nv10_fence_context_del(struct nouveau_channel *); void nv10_fence_destroy(struct nouveau_drm *); int nv10_fence_create(struct nouveau_drm *); + +int nv17_fence_create(struct nouveau_drm *); void nv17_fence_resume(struct nouveau_drm *drm); int nv50_fence_create(struct nouveau_drm *); int nv84_fence_create(struct nouveau_drm *); int nvc0_fence_create(struct nouveau_drm *); -u64 nvc0_fence_crtc(struct nouveau_channel *, int crtc); int nouveau_flip_complete(void *chan); +struct nv84_fence_chan { + struct nouveau_fence_chan base; + struct nouveau_vma vma; + struct nouveau_vma vma_gart; + struct nouveau_vma dispc_vma[4]; +}; + +struct nv84_fence_priv { + struct nouveau_fence_priv base; + struct nouveau_bo *bo; + struct nouveau_bo *bo_gart; + u32 *suspend; +}; + +u64 nv84_fence_crtc(struct nouveau_channel *, int); +int nv84_fence_context_new(struct nouveau_channel *); + #endif diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c index d98bee012cab..b4b4d0c1f4af 100644 --- a/drivers/gpu/drm/nouveau/nouveau_gem.c +++ b/drivers/gpu/drm/nouveau/nouveau_gem.c @@ -203,6 +203,7 @@ nouveau_gem_ioctl_new(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_cli *cli = nouveau_cli(file_priv); struct nouveau_fb *pfb = nouveau_fb(drm->device); struct drm_nouveau_gem_new *req = data; struct nouveau_bo *nvbo = NULL; @@ -211,7 +212,7 @@ nouveau_gem_ioctl_new(struct drm_device *dev, void *data, drm->ttm.bdev.dev_mapping = drm->dev->dev_mapping; if (!pfb->memtype_valid(pfb, req->info.tile_flags)) { - NV_ERROR(drm, "bad page flags: 0x%08x\n", req->info.tile_flags); + NV_ERROR(cli, "bad page flags: 0x%08x\n", req->info.tile_flags); return -EINVAL; } @@ -313,6 +314,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, struct drm_nouveau_gem_pushbuf_bo *pbbo, int nr_buffers, struct validate_op *op) { + struct nouveau_cli *cli = nouveau_cli(file_priv); struct drm_device *dev = chan->drm->dev; struct nouveau_drm *drm = nouveau_drm(dev); uint32_t sequence; @@ -323,7 +325,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, sequence = atomic_add_return(1, &drm->ttm.validate_sequence); retry: if (++trycnt > 100000) { - NV_ERROR(drm, "%s failed and gave up.\n", __func__); + NV_ERROR(cli, "%s failed and gave up.\n", __func__); return -EINVAL; } @@ -334,7 +336,7 @@ retry: gem = drm_gem_object_lookup(dev, file_priv, b->handle); if (!gem) { - NV_ERROR(drm, "Unknown handle 0x%08x\n", b->handle); + NV_ERROR(cli, "Unknown handle 0x%08x\n", b->handle); validate_fini(op, NULL); return -ENOENT; } @@ -346,7 +348,7 @@ retry: } if (nvbo->reserved_by && nvbo->reserved_by == file_priv) { - NV_ERROR(drm, "multiple instances of buffer %d on " + NV_ERROR(cli, "multiple instances of buffer %d on " "validation list\n", b->handle); drm_gem_object_unreference_unlocked(gem); validate_fini(op, NULL); @@ -366,7 +368,7 @@ retry: if (unlikely(ret)) { drm_gem_object_unreference_unlocked(gem); if (ret != -ERESTARTSYS) - NV_ERROR(drm, "fail reserve\n"); + NV_ERROR(cli, "fail reserve\n"); return ret; } } @@ -384,7 +386,7 @@ retry: if (b->valid_domains & NOUVEAU_GEM_DOMAIN_GART) list_add_tail(&nvbo->entry, &op->gart_list); else { - NV_ERROR(drm, "invalid valid domains: 0x%08x\n", + NV_ERROR(cli, "invalid valid domains: 0x%08x\n", b->valid_domains); list_add_tail(&nvbo->entry, &op->both_list); validate_fini(op, NULL); @@ -417,8 +419,9 @@ validate_sync(struct nouveau_channel *chan, struct nouveau_bo *nvbo) } static int -validate_list(struct nouveau_channel *chan, struct list_head *list, - struct drm_nouveau_gem_pushbuf_bo *pbbo, uint64_t user_pbbo_ptr) +validate_list(struct nouveau_channel *chan, struct nouveau_cli *cli, + struct list_head *list, struct drm_nouveau_gem_pushbuf_bo *pbbo, + uint64_t user_pbbo_ptr) { struct nouveau_drm *drm = chan->drm; struct drm_nouveau_gem_pushbuf_bo __user *upbbo = @@ -431,7 +434,7 @@ validate_list(struct nouveau_channel *chan, struct list_head *list, ret = validate_sync(chan, nvbo); if (unlikely(ret)) { - NV_ERROR(drm, "fail pre-validate sync\n"); + NV_ERROR(cli, "fail pre-validate sync\n"); return ret; } @@ -439,20 +442,20 @@ validate_list(struct nouveau_channel *chan, struct list_head *list, b->write_domains, b->valid_domains); if (unlikely(ret)) { - NV_ERROR(drm, "fail set_domain\n"); + NV_ERROR(cli, "fail set_domain\n"); return ret; } ret = nouveau_bo_validate(nvbo, true, false); if (unlikely(ret)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "fail ttm_validate\n"); + NV_ERROR(cli, "fail ttm_validate\n"); return ret; } ret = validate_sync(chan, nvbo); if (unlikely(ret)) { - NV_ERROR(drm, "fail post-validate sync\n"); + NV_ERROR(cli, "fail post-validate sync\n"); return ret; } @@ -488,7 +491,7 @@ nouveau_gem_pushbuf_validate(struct nouveau_channel *chan, uint64_t user_buffers, int nr_buffers, struct validate_op *op, int *apply_relocs) { - struct nouveau_drm *drm = chan->drm; + struct nouveau_cli *cli = nouveau_cli(file_priv); int ret, relocs = 0; INIT_LIST_HEAD(&op->vram_list); @@ -501,32 +504,32 @@ nouveau_gem_pushbuf_validate(struct nouveau_channel *chan, ret = validate_init(chan, file_priv, pbbo, nr_buffers, op); if (unlikely(ret)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate_init\n"); + NV_ERROR(cli, "validate_init\n"); return ret; } - ret = validate_list(chan, &op->vram_list, pbbo, user_buffers); + ret = validate_list(chan, cli, &op->vram_list, pbbo, user_buffers); if (unlikely(ret < 0)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate vram_list\n"); + NV_ERROR(cli, "validate vram_list\n"); validate_fini(op, NULL); return ret; } relocs += ret; - ret = validate_list(chan, &op->gart_list, pbbo, user_buffers); + ret = validate_list(chan, cli, &op->gart_list, pbbo, user_buffers); if (unlikely(ret < 0)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate gart_list\n"); + NV_ERROR(cli, "validate gart_list\n"); validate_fini(op, NULL); return ret; } relocs += ret; - ret = validate_list(chan, &op->both_list, pbbo, user_buffers); + ret = validate_list(chan, cli, &op->both_list, pbbo, user_buffers); if (unlikely(ret < 0)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate both_list\n"); + NV_ERROR(cli, "validate both_list\n"); validate_fini(op, NULL); return ret; } @@ -555,11 +558,10 @@ u_memcpya(uint64_t user, unsigned nmemb, unsigned size) } static int -nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, +nouveau_gem_pushbuf_reloc_apply(struct nouveau_cli *cli, struct drm_nouveau_gem_pushbuf *req, struct drm_nouveau_gem_pushbuf_bo *bo) { - struct nouveau_drm *drm = nouveau_drm(dev); struct drm_nouveau_gem_pushbuf_reloc *reloc = NULL; int ret = 0; unsigned i; @@ -575,7 +577,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, uint32_t data; if (unlikely(r->bo_index > req->nr_buffers)) { - NV_ERROR(drm, "reloc bo index invalid\n"); + NV_ERROR(cli, "reloc bo index invalid\n"); ret = -EINVAL; break; } @@ -585,7 +587,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, continue; if (unlikely(r->reloc_bo_index > req->nr_buffers)) { - NV_ERROR(drm, "reloc container bo index invalid\n"); + NV_ERROR(cli, "reloc container bo index invalid\n"); ret = -EINVAL; break; } @@ -593,7 +595,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, if (unlikely(r->reloc_bo_offset + 4 > nvbo->bo.mem.num_pages << PAGE_SHIFT)) { - NV_ERROR(drm, "reloc outside of bo\n"); + NV_ERROR(cli, "reloc outside of bo\n"); ret = -EINVAL; break; } @@ -602,7 +604,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, ret = ttm_bo_kmap(&nvbo->bo, 0, nvbo->bo.mem.num_pages, &nvbo->kmap); if (ret) { - NV_ERROR(drm, "failed kmap for reloc\n"); + NV_ERROR(cli, "failed kmap for reloc\n"); break; } nvbo->validate_mapped = true; @@ -627,7 +629,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, ret = ttm_bo_wait(&nvbo->bo, false, false, false); spin_unlock(&nvbo->bo.bdev->fence_lock); if (ret) { - NV_ERROR(drm, "reloc wait_idle failed: %d\n", ret); + NV_ERROR(cli, "reloc wait_idle failed: %d\n", ret); break; } @@ -643,6 +645,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct nouveau_abi16 *abi16 = nouveau_abi16_get(file_priv, dev); + struct nouveau_cli *cli = nouveau_cli(file_priv); struct nouveau_abi16_chan *temp; struct nouveau_drm *drm = nouveau_drm(dev); struct drm_nouveau_gem_pushbuf *req = data; @@ -672,19 +675,19 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, goto out_next; if (unlikely(req->nr_push > NOUVEAU_GEM_MAX_PUSH)) { - NV_ERROR(drm, "pushbuf push count exceeds limit: %d max %d\n", + NV_ERROR(cli, "pushbuf push count exceeds limit: %d max %d\n", req->nr_push, NOUVEAU_GEM_MAX_PUSH); return nouveau_abi16_put(abi16, -EINVAL); } if (unlikely(req->nr_buffers > NOUVEAU_GEM_MAX_BUFFERS)) { - NV_ERROR(drm, "pushbuf bo count exceeds limit: %d max %d\n", + NV_ERROR(cli, "pushbuf bo count exceeds limit: %d max %d\n", req->nr_buffers, NOUVEAU_GEM_MAX_BUFFERS); return nouveau_abi16_put(abi16, -EINVAL); } if (unlikely(req->nr_relocs > NOUVEAU_GEM_MAX_RELOCS)) { - NV_ERROR(drm, "pushbuf reloc count exceeds limit: %d max %d\n", + NV_ERROR(cli, "pushbuf reloc count exceeds limit: %d max %d\n", req->nr_relocs, NOUVEAU_GEM_MAX_RELOCS); return nouveau_abi16_put(abi16, -EINVAL); } @@ -702,7 +705,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, /* Ensure all push buffers are on validate list */ for (i = 0; i < req->nr_push; i++) { if (push[i].bo_index >= req->nr_buffers) { - NV_ERROR(drm, "push %d buffer not in list\n", i); + NV_ERROR(cli, "push %d buffer not in list\n", i); ret = -EINVAL; goto out_prevalid; } @@ -713,15 +716,15 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, req->nr_buffers, &op, &do_reloc); if (ret) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate: %d\n", ret); + NV_ERROR(cli, "validate: %d\n", ret); goto out_prevalid; } /* Apply any relocations that are required */ if (do_reloc) { - ret = nouveau_gem_pushbuf_reloc_apply(dev, req, bo); + ret = nouveau_gem_pushbuf_reloc_apply(cli, req, bo); if (ret) { - NV_ERROR(drm, "reloc apply: %d\n", ret); + NV_ERROR(cli, "reloc apply: %d\n", ret); goto out; } } @@ -729,7 +732,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, if (chan->dma.ib_max) { ret = nouveau_dma_wait(chan, req->nr_push + 1, 16); if (ret) { - NV_ERROR(drm, "nv50cal_space: %d\n", ret); + NV_ERROR(cli, "nv50cal_space: %d\n", ret); goto out; } @@ -744,7 +747,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, if (nv_device(drm->device)->chipset >= 0x25) { ret = RING_SPACE(chan, req->nr_push * 2); if (ret) { - NV_ERROR(drm, "cal_space: %d\n", ret); + NV_ERROR(cli, "cal_space: %d\n", ret); goto out; } @@ -758,7 +761,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, } else { ret = RING_SPACE(chan, req->nr_push * (2 + NOUVEAU_DMA_SKIPS)); if (ret) { - NV_ERROR(drm, "jmp_space: %d\n", ret); + NV_ERROR(cli, "jmp_space: %d\n", ret); goto out; } @@ -794,9 +797,9 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, } } - ret = nouveau_fence_new(chan, &fence); + ret = nouveau_fence_new(chan, false, &fence); if (ret) { - NV_ERROR(drm, "error fencing pushbuf: %d\n", ret); + NV_ERROR(cli, "error fencing pushbuf: %d\n", ret); WIND_RING(chan); goto out; } diff --git a/drivers/gpu/drm/nouveau/nouveau_pm.c b/drivers/gpu/drm/nouveau/nouveau_pm.c index a701ff5ffa5b..bb54098c6d97 100644 --- a/drivers/gpu/drm/nouveau/nouveau_pm.c +++ b/drivers/gpu/drm/nouveau/nouveau_pm.c @@ -409,6 +409,81 @@ static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, nouveau_hwmon_show_temp, NULL, 0); static ssize_t +nouveau_hwmon_show_temp1_auto_point1_pwm(struct device *d, + struct device_attribute *a, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", 100); +} +static SENSOR_DEVICE_ATTR(temp1_auto_point1_pwm, S_IRUGO, + nouveau_hwmon_show_temp1_auto_point1_pwm, NULL, 0); + +static ssize_t +nouveau_hwmon_temp1_auto_point1_temp(struct device *d, + struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST) * 1000); +} +static ssize_t +nouveau_hwmon_set_temp1_auto_point1_temp(struct device *d, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO | S_IWUSR, + nouveau_hwmon_temp1_auto_point1_temp, + nouveau_hwmon_set_temp1_auto_point1_temp, 0); + +static ssize_t +nouveau_hwmon_temp1_auto_point1_temp_hyst(struct device *d, + struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_temp1_auto_point1_temp_hyst(struct device *d, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_temp1_auto_point1_temp_hyst, + nouveau_hwmon_set_temp1_auto_point1_temp_hyst, 0); + +static ssize_t nouveau_hwmon_max_temp(struct device *d, struct device_attribute *a, char *buf) { struct drm_device *dev = dev_get_drvdata(d); @@ -439,6 +514,38 @@ static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, nouveau_hwmon_max_temp, 0); static ssize_t +nouveau_hwmon_max_temp_hyst(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_max_temp_hyst(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_max_temp_hyst, + nouveau_hwmon_set_max_temp_hyst, 0); + +static ssize_t nouveau_hwmon_critical_temp(struct device *d, struct device_attribute *a, char *buf) { @@ -471,6 +578,107 @@ static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, nouveau_hwmon_set_critical_temp, 0); +static ssize_t +nouveau_hwmon_critical_temp_hyst(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_critical_temp_hyst(struct device *d, + struct device_attribute *a, + const char *buf, + size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_critical_temp_hyst, + nouveau_hwmon_set_critical_temp_hyst, 0); +static ssize_t +nouveau_hwmon_emergency_temp(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN) * 1000); +} +static ssize_t +nouveau_hwmon_set_emergency_temp(struct device *d, struct device_attribute *a, + const char *buf, + size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN, value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO | S_IWUSR, + nouveau_hwmon_emergency_temp, + nouveau_hwmon_set_emergency_temp, + 0); + +static ssize_t +nouveau_hwmon_emergency_temp_hyst(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_emergency_temp_hyst(struct device *d, + struct device_attribute *a, + const char *buf, + size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_emergency_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_emergency_temp_hyst, + nouveau_hwmon_set_emergency_temp_hyst, + 0); + static ssize_t nouveau_hwmon_show_name(struct device *dev, struct device_attribute *attr, char *buf) @@ -490,7 +698,7 @@ static SENSOR_DEVICE_ATTR(update_rate, S_IRUGO, NULL, 0); static ssize_t -nouveau_hwmon_show_fan0_input(struct device *d, struct device_attribute *attr, +nouveau_hwmon_show_fan1_input(struct device *d, struct device_attribute *attr, char *buf) { struct drm_device *dev = dev_get_drvdata(d); @@ -499,7 +707,7 @@ nouveau_hwmon_show_fan0_input(struct device *d, struct device_attribute *attr, return snprintf(buf, PAGE_SIZE, "%d\n", therm->fan_sense(therm)); } -static SENSOR_DEVICE_ATTR(fan0_input, S_IRUGO, nouveau_hwmon_show_fan0_input, +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, nouveau_hwmon_show_fan1_input, NULL, 0); static ssize_t @@ -665,14 +873,21 @@ static SENSOR_DEVICE_ATTR(pwm1_max, S_IRUGO | S_IWUSR, static struct attribute *hwmon_attributes[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp_hyst.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_emergency.dev_attr.attr, + &sensor_dev_attr_temp1_emergency_hyst.dev_attr.attr, &sensor_dev_attr_name.dev_attr.attr, &sensor_dev_attr_update_rate.dev_attr.attr, NULL }; static struct attribute *hwmon_fan_rpm_attributes[] = { - &sensor_dev_attr_fan0_input.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, NULL }; static struct attribute *hwmon_pwm_fan_attributes[] = { @@ -717,7 +932,7 @@ nouveau_hwmon_init(struct drm_device *dev) dev_set_drvdata(hwmon_dev, dev); /* default sysfs entries */ - ret = sysfs_create_group(&dev->pdev->dev.kobj, &hwmon_attrgroup); + ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_attrgroup); if (ret) { if (ret) goto error; @@ -728,7 +943,7 @@ nouveau_hwmon_init(struct drm_device *dev) * the gpio entries for pwm fan control even when there's no * actual fan connected to it... therm table? */ if (therm->fan_get && therm->fan_get(therm) >= 0) { - ret = sysfs_create_group(&dev->pdev->dev.kobj, + ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_pwm_fan_attrgroup); if (ret) goto error; @@ -736,7 +951,7 @@ nouveau_hwmon_init(struct drm_device *dev) /* if the card can read the fan rpm */ if (therm->fan_sense(therm) >= 0) { - ret = sysfs_create_group(&dev->pdev->dev.kobj, + ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_fan_rpm_attrgroup); if (ret) goto error; @@ -764,10 +979,10 @@ nouveau_hwmon_fini(struct drm_device *dev) struct nouveau_pm *pm = nouveau_pm(dev); if (pm->hwmon) { - sysfs_remove_group(&dev->pdev->dev.kobj, &hwmon_attrgroup); - sysfs_remove_group(&dev->pdev->dev.kobj, + sysfs_remove_group(&pm->hwmon->kobj, &hwmon_attrgroup); + sysfs_remove_group(&pm->hwmon->kobj, &hwmon_pwm_fan_attrgroup); - sysfs_remove_group(&dev->pdev->dev.kobj, + sysfs_remove_group(&pm->hwmon->kobj, &hwmon_fan_rpm_attrgroup); hwmon_device_unregister(pm->hwmon); diff --git a/drivers/gpu/drm/nouveau/nv04_dfp.c b/drivers/gpu/drm/nouveau/nv04_dfp.c index 39ffc07f906b..7e24cdf1cb39 100644 --- a/drivers/gpu/drm/nouveau/nv04_dfp.c +++ b/drivers/gpu/drm/nouveau/nv04_dfp.c @@ -490,8 +490,8 @@ static void nv04_dfp_update_backlight(struct drm_encoder *encoder, int mode) /* BIOS scripts usually take care of the backlight, thanks * Apple for your consistency. */ - if (dev->pci_device == 0x0179 || dev->pci_device == 0x0189 || - dev->pci_device == 0x0329) { + if (dev->pci_device == 0x0174 || dev->pci_device == 0x0179 || + dev->pci_device == 0x0189 || dev->pci_device == 0x0329) { if (mode == DRM_MODE_DPMS_ON) { nv_mask(device, NV_PBUS_DEBUG_DUALHEAD_CTL, 0, 1 << 31); nv_mask(device, NV_PCRTC_GPIO_EXT, 3, 1); diff --git a/drivers/gpu/drm/nouveau/nv04_display.c b/drivers/gpu/drm/nouveau/nv04_display.c index 4c6e9f83fe82..ad48444c385c 100644 --- a/drivers/gpu/drm/nouveau/nv04_display.c +++ b/drivers/gpu/drm/nouveau/nv04_display.c @@ -22,6 +22,9 @@ * Author: Ben Skeggs */ +#include <core/object.h> +#include <core/class.h> + #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -31,6 +34,8 @@ #include "nouveau_encoder.h" #include "nouveau_connector.h" +#include <subdev/i2c.h> + int nv04_display_early_init(struct drm_device *dev) { @@ -53,6 +58,7 @@ int nv04_display_create(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct dcb_table *dcb = &drm->vbios.dcb; struct drm_connector *connector, *ct; struct drm_encoder *encoder; @@ -71,6 +77,11 @@ nv04_display_create(struct drm_device *dev) nouveau_hw_save_vga_fonts(dev, 1); + ret = nouveau_object_new(nv_object(drm), NVDRM_DEVICE, 0xd1500000, + NV04_DISP_CLASS, NULL, 0, &disp->core); + if (ret) + return ret; + nv04_crtc_create(dev, 0); if (nv_two_heads(dev)) nv04_crtc_create(dev, 1); @@ -114,6 +125,11 @@ nv04_display_create(struct drm_device *dev) } } + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + nv_encoder->i2c = i2c->find(i2c, nv_encoder->dcb->i2c_index); + } + /* Save previous state */ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) crtc->funcs->save(crtc); diff --git a/drivers/gpu/drm/nouveau/nv04_display.h b/drivers/gpu/drm/nouveau/nv04_display.h index 45322802e37d..a0a031dad13f 100644 --- a/drivers/gpu/drm/nouveau/nv04_display.h +++ b/drivers/gpu/drm/nouveau/nv04_display.h @@ -80,6 +80,7 @@ struct nv04_display { struct nv04_mode_state saved_reg; uint32_t saved_vga_font[4][16384]; uint32_t dac_users[4]; + struct nouveau_object *core; }; static inline struct nv04_display * diff --git a/drivers/gpu/drm/nouveau/nv04_fence.c b/drivers/gpu/drm/nouveau/nv04_fence.c index a220b94ba9f2..94eadd1dd10a 100644 --- a/drivers/gpu/drm/nouveau/nv04_fence.c +++ b/drivers/gpu/drm/nouveau/nv04_fence.c @@ -78,6 +78,9 @@ nv04_fence_context_new(struct nouveau_channel *chan) struct nv04_fence_chan *fctx = kzalloc(sizeof(*fctx), GFP_KERNEL); if (fctx) { nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv04_fence_emit; + fctx->base.sync = nv04_fence_sync; + fctx->base.read = nv04_fence_read; chan->fence = fctx; return 0; } @@ -104,8 +107,5 @@ nv04_fence_create(struct nouveau_drm *drm) priv->base.dtor = nv04_fence_destroy; priv->base.context_new = nv04_fence_context_new; priv->base.context_del = nv04_fence_context_del; - priv->base.emit = nv04_fence_emit; - priv->base.sync = nv04_fence_sync; - priv->base.read = nv04_fence_read; return 0; } diff --git a/drivers/gpu/drm/nouveau/nv10_fence.c b/drivers/gpu/drm/nouveau/nv10_fence.c index 03017f24d593..06f434f03fba 100644 --- a/drivers/gpu/drm/nouveau/nv10_fence.c +++ b/drivers/gpu/drm/nouveau/nv10_fence.c @@ -27,18 +27,7 @@ #include "nouveau_drm.h" #include "nouveau_dma.h" -#include "nouveau_fence.h" - -struct nv10_fence_chan { - struct nouveau_fence_chan base; -}; - -struct nv10_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_bo *bo; - spinlock_t lock; - u32 sequence; -}; +#include "nv10_fence.h" int nv10_fence_emit(struct nouveau_fence *fence) @@ -61,45 +50,6 @@ nv10_fence_sync(struct nouveau_fence *fence, return -ENODEV; } -int -nv17_fence_sync(struct nouveau_fence *fence, - struct nouveau_channel *prev, struct nouveau_channel *chan) -{ - struct nv10_fence_priv *priv = chan->drm->fence; - u32 value; - int ret; - - if (!mutex_trylock(&prev->cli->mutex)) - return -EBUSY; - - spin_lock(&priv->lock); - value = priv->sequence; - priv->sequence += 2; - spin_unlock(&priv->lock); - - ret = RING_SPACE(prev, 5); - if (!ret) { - BEGIN_NV04(prev, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); - OUT_RING (prev, NvSema); - OUT_RING (prev, 0); - OUT_RING (prev, value + 0); - OUT_RING (prev, value + 1); - FIRE_RING (prev); - } - - if (!ret && !(ret = RING_SPACE(chan, 5))) { - BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); - OUT_RING (chan, NvSema); - OUT_RING (chan, 0); - OUT_RING (chan, value + 1); - OUT_RING (chan, value + 2); - FIRE_RING (chan); - } - - mutex_unlock(&prev->cli->mutex); - return 0; -} - u32 nv10_fence_read(struct nouveau_channel *chan) { @@ -115,39 +65,20 @@ nv10_fence_context_del(struct nouveau_channel *chan) kfree(fctx); } -static int +int nv10_fence_context_new(struct nouveau_channel *chan) { - struct nv10_fence_priv *priv = chan->drm->fence; struct nv10_fence_chan *fctx; - int ret = 0; fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); if (!fctx) return -ENOMEM; nouveau_fence_context_new(&fctx->base); - - if (priv->bo) { - struct ttm_mem_reg *mem = &priv->bo->bo.mem; - struct nouveau_object *object; - u32 start = mem->start * PAGE_SIZE; - u32 limit = mem->start + mem->size - 1; - - ret = nouveau_object_new(nv_object(chan->cli), chan->handle, - NvSema, 0x0002, - &(struct nv_dma_class) { - .flags = NV_DMA_TARGET_VRAM | - NV_DMA_ACCESS_RDWR, - .start = start, - .limit = limit, - }, sizeof(struct nv_dma_class), - &object); - } - - if (ret) - nv10_fence_context_del(chan); - return ret; + fctx->base.emit = nv10_fence_emit; + fctx->base.read = nv10_fence_read; + fctx->base.sync = nv10_fence_sync; + return 0; } void @@ -162,18 +93,10 @@ nv10_fence_destroy(struct nouveau_drm *drm) kfree(priv); } -void nv17_fence_resume(struct nouveau_drm *drm) -{ - struct nv10_fence_priv *priv = drm->fence; - - nouveau_bo_wr32(priv->bo, 0, priv->sequence); -} - int nv10_fence_create(struct nouveau_drm *drm) { struct nv10_fence_priv *priv; - int ret = 0; priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) @@ -182,33 +105,6 @@ nv10_fence_create(struct nouveau_drm *drm) priv->base.dtor = nv10_fence_destroy; priv->base.context_new = nv10_fence_context_new; priv->base.context_del = nv10_fence_context_del; - priv->base.emit = nv10_fence_emit; - priv->base.read = nv10_fence_read; - priv->base.sync = nv10_fence_sync; spin_lock_init(&priv->lock); - - if (nv_device(drm->device)->chipset >= 0x17) { - ret = nouveau_bo_new(drm->dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, - 0, 0x0000, NULL, &priv->bo); - if (!ret) { - ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); - if (!ret) { - ret = nouveau_bo_map(priv->bo); - if (ret) - nouveau_bo_unpin(priv->bo); - } - if (ret) - nouveau_bo_ref(NULL, &priv->bo); - } - - if (ret == 0) { - nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); - priv->base.sync = nv17_fence_sync; - priv->base.resume = nv17_fence_resume; - } - } - - if (ret) - nv10_fence_destroy(drm); - return ret; + return 0; } diff --git a/drivers/gpu/drm/nouveau/nv10_fence.h b/drivers/gpu/drm/nouveau/nv10_fence.h new file mode 100644 index 000000000000..e5d9204826c2 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv10_fence.h @@ -0,0 +1,19 @@ +#ifndef __NV10_FENCE_H_ +#define __NV10_FENCE_H_ + +#include <core/os.h> +#include "nouveau_fence.h" +#include "nouveau_bo.h" + +struct nv10_fence_chan { + struct nouveau_fence_chan base; +}; + +struct nv10_fence_priv { + struct nouveau_fence_priv base; + struct nouveau_bo *bo; + spinlock_t lock; + u32 sequence; +}; + +#endif diff --git a/drivers/gpu/drm/nouveau/nv17_fence.c b/drivers/gpu/drm/nouveau/nv17_fence.c new file mode 100644 index 000000000000..8e47a9bae8c3 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv17_fence.c @@ -0,0 +1,149 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ + +#include <core/object.h> +#include <core/class.h> + +#include "nouveau_drm.h" +#include "nouveau_dma.h" +#include "nv10_fence.h" + +int +nv17_fence_sync(struct nouveau_fence *fence, + struct nouveau_channel *prev, struct nouveau_channel *chan) +{ + struct nv10_fence_priv *priv = chan->drm->fence; + u32 value; + int ret; + + if (!mutex_trylock(&prev->cli->mutex)) + return -EBUSY; + + spin_lock(&priv->lock); + value = priv->sequence; + priv->sequence += 2; + spin_unlock(&priv->lock); + + ret = RING_SPACE(prev, 5); + if (!ret) { + BEGIN_NV04(prev, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); + OUT_RING (prev, NvSema); + OUT_RING (prev, 0); + OUT_RING (prev, value + 0); + OUT_RING (prev, value + 1); + FIRE_RING (prev); + } + + if (!ret && !(ret = RING_SPACE(chan, 5))) { + BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); + OUT_RING (chan, NvSema); + OUT_RING (chan, 0); + OUT_RING (chan, value + 1); + OUT_RING (chan, value + 2); + FIRE_RING (chan); + } + + mutex_unlock(&prev->cli->mutex); + return 0; +} + +static int +nv17_fence_context_new(struct nouveau_channel *chan) +{ + struct nv10_fence_priv *priv = chan->drm->fence; + struct nv10_fence_chan *fctx; + struct ttm_mem_reg *mem = &priv->bo->bo.mem; + struct nouveau_object *object; + u32 start = mem->start * PAGE_SIZE; + u32 limit = mem->start + mem->size - 1; + int ret = 0; + + fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); + if (!fctx) + return -ENOMEM; + + nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv10_fence_emit; + fctx->base.read = nv10_fence_read; + fctx->base.sync = nv17_fence_sync; + + ret = nouveau_object_new(nv_object(chan->cli), chan->handle, + NvSema, 0x0002, + &(struct nv_dma_class) { + .flags = NV_DMA_TARGET_VRAM | + NV_DMA_ACCESS_RDWR, + .start = start, + .limit = limit, + }, sizeof(struct nv_dma_class), + &object); + if (ret) + nv10_fence_context_del(chan); + return ret; +} + +void +nv17_fence_resume(struct nouveau_drm *drm) +{ + struct nv10_fence_priv *priv = drm->fence; + + nouveau_bo_wr32(priv->bo, 0, priv->sequence); +} + +int +nv17_fence_create(struct nouveau_drm *drm) +{ + struct nv10_fence_priv *priv; + int ret = 0; + + priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base.dtor = nv10_fence_destroy; + priv->base.resume = nv17_fence_resume; + priv->base.context_new = nv17_fence_context_new; + priv->base.context_del = nv10_fence_context_del; + spin_lock_init(&priv->lock); + + ret = nouveau_bo_new(drm->dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, + 0, 0x0000, NULL, &priv->bo); + if (!ret) { + ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); + if (!ret) { + ret = nouveau_bo_map(priv->bo); + if (ret) + nouveau_bo_unpin(priv->bo); + } + if (ret) + nouveau_bo_ref(NULL, &priv->bo); + } + + if (ret) { + nv10_fence_destroy(drm); + return ret; + } + + nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c index d4cbea19b890..a6237c9cbbc3 100644 --- a/drivers/gpu/drm/nouveau/nv50_display.c +++ b/drivers/gpu/drm/nouveau/nv50_display.c @@ -43,6 +43,7 @@ #include <subdev/timer.h> #include <subdev/bar.h> #include <subdev/fb.h> +#include <subdev/i2c.h> #define EVO_DMA_NR 9 @@ -433,7 +434,10 @@ evo_kick(u32 *push, void *evoc) static bool evo_sync_wait(void *data) { - return nouveau_bo_rd32(data, EVO_MAST_NTFY) != 0x00000000; + if (nouveau_bo_rd32(data, EVO_MAST_NTFY) != 0x00000000) + return true; + usleep_range(1, 2); + return false; } static int @@ -512,7 +516,7 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb, if (ret) return ret; - if (nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) { + if (nv_mclass(chan->object) < NV84_CHANNEL_IND_CLASS) { BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 2); OUT_RING (chan, NvEvoSema0 + nv_crtc->index); OUT_RING (chan, sync->sem.offset); @@ -522,24 +526,36 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb, OUT_RING (chan, sync->sem.offset ^ 0x10); OUT_RING (chan, 0x74b1e000); BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); - if (nv_mclass(chan->object) < NV84_CHANNEL_DMA_CLASS) - OUT_RING (chan, NvSema); - else - OUT_RING (chan, chan->vram); + OUT_RING (chan, NvSema); + } else + if (nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) { + u64 offset = nv84_fence_crtc(chan, nv_crtc->index); + offset += sync->sem.offset; + + BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 0xf00d0000 | sync->sem.value); + OUT_RING (chan, 0x00000002); + BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset ^ 0x10)); + OUT_RING (chan, 0x74b1e000); + OUT_RING (chan, 0x00000001); } else { - u64 offset = nvc0_fence_crtc(chan, nv_crtc->index); + u64 offset = nv84_fence_crtc(chan, nv_crtc->index); offset += sync->sem.offset; BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); OUT_RING (chan, upper_32_bits(offset)); OUT_RING (chan, lower_32_bits(offset)); OUT_RING (chan, 0xf00d0000 | sync->sem.value); - OUT_RING (chan, 0x1002); + OUT_RING (chan, 0x00001002); BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); OUT_RING (chan, upper_32_bits(offset)); OUT_RING (chan, lower_32_bits(offset ^ 0x10)); OUT_RING (chan, 0x74b1e000); - OUT_RING (chan, 0x1001); + OUT_RING (chan, 0x00001001); } FIRE_RING (chan); @@ -1503,9 +1519,6 @@ nv50_dac_disconnect(struct drm_encoder *encoder) evo_mthd(push, 0x0180 + (or * 0x020), 1); evo_data(push, 0x00000000); } - - evo_mthd(push, 0x0080, 1); - evo_data(push, 0x00000000); evo_kick(push, mast); } } @@ -1552,20 +1565,23 @@ static const struct drm_encoder_funcs nv50_dac_func = { static int nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe) { - struct drm_device *dev = connector->dev; + struct nouveau_drm *drm = nouveau_drm(connector->dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_encoder *nv_encoder; struct drm_encoder *encoder; + int type = DRM_MODE_ENCODER_DAC; nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); if (!nv_encoder) return -ENOMEM; nv_encoder->dcb = dcbe; nv_encoder->or = ffs(dcbe->or) - 1; + nv_encoder->i2c = i2c->find(i2c, dcbe->i2c_index); encoder = to_drm_encoder(nv_encoder); encoder->possible_crtcs = dcbe->heads; encoder->possible_clones = 0; - drm_encoder_init(dev, encoder, &nv50_dac_func, DRM_MODE_ENCODER_DAC); + drm_encoder_init(connector->dev, encoder, &nv50_dac_func, type); drm_encoder_helper_add(encoder, &nv50_dac_hfunc); drm_mode_connector_attach_encoder(connector, encoder); @@ -1674,9 +1690,6 @@ nv50_sor_dpms(struct drm_encoder *encoder, int mode) } nv_call(disp->core, NV50_DISP_SOR_PWR + or, (mode == DRM_MODE_DPMS_ON)); - - if (nv_encoder->dcb->type == DCB_OUTPUT_DP) - nouveau_dp_dpms(encoder, mode, nv_encoder->dp.datarate, disp->core); } static bool @@ -1719,9 +1732,6 @@ nv50_sor_disconnect(struct drm_encoder *encoder) evo_mthd(push, 0x0200 + (or * 0x20), 1); evo_data(push, 0x00000000); } - - evo_mthd(push, 0x0080, 1); - evo_data(push, 0x00000000); evo_kick(push, mast); } @@ -1733,14 +1743,6 @@ nv50_sor_disconnect(struct drm_encoder *encoder) } static void -nv50_sor_prepare(struct drm_encoder *encoder) -{ - nv50_sor_disconnect(encoder); - if (nouveau_encoder(encoder)->dcb->type == DCB_OUTPUT_DP) - evo_sync(encoder->dev); -} - -static void nv50_sor_commit(struct drm_encoder *encoder) { } @@ -1835,8 +1837,13 @@ nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode, push = evo_wait(nv50_mast(dev), 8); if (push) { if (nv50_vers(mast) < NVD0_DISP_CLASS) { + u32 ctrl = (depth << 16) | (proto << 8) | owner; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl |= 0x00001000; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl |= 0x00002000; evo_mthd(push, 0x0600 + (nv_encoder->or * 0x040), 1); - evo_data(push, (depth << 16) | (proto << 8) | owner); + evo_data(push, ctrl); } else { u32 magic = 0x31ec6000 | (nv_crtc->index << 25); u32 syncs = 0x00000001; @@ -1872,7 +1879,7 @@ nv50_sor_destroy(struct drm_encoder *encoder) static const struct drm_encoder_helper_funcs nv50_sor_hfunc = { .dpms = nv50_sor_dpms, .mode_fixup = nv50_sor_mode_fixup, - .prepare = nv50_sor_prepare, + .prepare = nv50_sor_disconnect, .commit = nv50_sor_commit, .mode_set = nv50_sor_mode_set, .disable = nv50_sor_disconnect, @@ -1886,21 +1893,33 @@ static const struct drm_encoder_funcs nv50_sor_func = { static int nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe) { - struct drm_device *dev = connector->dev; + struct nouveau_drm *drm = nouveau_drm(connector->dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_encoder *nv_encoder; struct drm_encoder *encoder; + int type; + + switch (dcbe->type) { + case DCB_OUTPUT_LVDS: type = DRM_MODE_ENCODER_LVDS; break; + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_DP: + default: + type = DRM_MODE_ENCODER_TMDS; + break; + } nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); if (!nv_encoder) return -ENOMEM; nv_encoder->dcb = dcbe; nv_encoder->or = ffs(dcbe->or) - 1; + nv_encoder->i2c = i2c->find(i2c, dcbe->i2c_index); nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; encoder = to_drm_encoder(nv_encoder); encoder->possible_crtcs = dcbe->heads; encoder->possible_clones = 0; - drm_encoder_init(dev, encoder, &nv50_sor_func, DRM_MODE_ENCODER_TMDS); + drm_encoder_init(connector->dev, encoder, &nv50_sor_func, type); drm_encoder_helper_add(encoder, &nv50_sor_hfunc); drm_mode_connector_attach_encoder(connector, encoder); @@ -1908,6 +1927,181 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe) } /****************************************************************************** + * PIOR + *****************************************************************************/ + +static void +nv50_pior_dpms(struct drm_encoder *encoder, int mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nv50_disp *disp = nv50_disp(encoder->dev); + u32 mthd = (nv_encoder->dcb->type << 12) | nv_encoder->or; + u32 ctrl = (mode == DRM_MODE_DPMS_ON); + nv_call(disp->core, NV50_DISP_PIOR_PWR + mthd, ctrl); +} + +static bool +nv50_pior_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (nv_connector && nv_connector->native_mode) { + if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) { + int id = adjusted_mode->base.id; + *adjusted_mode = *nv_connector->native_mode; + adjusted_mode->base.id = id; + } + } + + adjusted_mode->clock *= 2; + return true; +} + +static void +nv50_pior_commit(struct drm_encoder *encoder) +{ +} + +static void +nv50_pior_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nv50_mast *mast = nv50_mast(encoder->dev); + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nouveau_connector *nv_connector; + u8 owner = 1 << nv_crtc->index; + u8 proto, depth; + u32 *push; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + switch (nv_connector->base.display_info.bpc) { + case 10: depth = 0x6; break; + case 8: depth = 0x5; break; + case 6: depth = 0x2; break; + default: depth = 0x0; break; + } + + switch (nv_encoder->dcb->type) { + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_DP: + proto = 0x0; + break; + default: + BUG_ON(1); + break; + } + + nv50_pior_dpms(encoder, DRM_MODE_DPMS_ON); + + push = evo_wait(mast, 8); + if (push) { + if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { + u32 ctrl = (depth << 16) | (proto << 8) | owner; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl |= 0x00001000; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl |= 0x00002000; + evo_mthd(push, 0x0700 + (nv_encoder->or * 0x040), 1); + evo_data(push, ctrl); + } + + evo_kick(push, mast); + } + + nv_encoder->crtc = encoder->crtc; +} + +static void +nv50_pior_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nv50_mast *mast = nv50_mast(encoder->dev); + const int or = nv_encoder->or; + u32 *push; + + if (nv_encoder->crtc) { + nv50_crtc_prepare(nv_encoder->crtc); + + push = evo_wait(mast, 4); + if (push) { + if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { + evo_mthd(push, 0x0700 + (or * 0x040), 1); + evo_data(push, 0x00000000); + } + evo_kick(push, mast); + } + } + + nv_encoder->crtc = NULL; +} + +static void +nv50_pior_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_helper_funcs nv50_pior_hfunc = { + .dpms = nv50_pior_dpms, + .mode_fixup = nv50_pior_mode_fixup, + .prepare = nv50_pior_disconnect, + .commit = nv50_pior_commit, + .mode_set = nv50_pior_mode_set, + .disable = nv50_pior_disconnect, + .get_crtc = nv50_display_crtc_get, +}; + +static const struct drm_encoder_funcs nv50_pior_func = { + .destroy = nv50_pior_destroy, +}; + +static int +nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe) +{ + struct nouveau_drm *drm = nouveau_drm(connector->dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); + struct nouveau_i2c_port *ddc = NULL; + struct nouveau_encoder *nv_encoder; + struct drm_encoder *encoder; + int type; + + switch (dcbe->type) { + case DCB_OUTPUT_TMDS: + ddc = i2c->find_type(i2c, NV_I2C_TYPE_EXTDDC(dcbe->extdev)); + type = DRM_MODE_ENCODER_TMDS; + break; + case DCB_OUTPUT_DP: + ddc = i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(dcbe->extdev)); + type = DRM_MODE_ENCODER_TMDS; + break; + default: + return -ENODEV; + } + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + nv_encoder->dcb = dcbe; + nv_encoder->or = ffs(dcbe->or) - 1; + nv_encoder->i2c = ddc; + + encoder = to_drm_encoder(nv_encoder); + encoder->possible_crtcs = dcbe->heads; + encoder->possible_clones = 0; + drm_encoder_init(connector->dev, encoder, &nv50_pior_func, type); + drm_encoder_helper_add(encoder, &nv50_pior_hfunc); + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} + +/****************************************************************************** * Init *****************************************************************************/ void @@ -1923,7 +2117,7 @@ nv50_display_init(struct drm_device *dev) evo_mthd(push, 0x0088, 1); evo_data(push, NvEvoSync); evo_kick(push, nv50_mast(dev)); - return evo_sync(dev); + return 0; } return -EBUSY; @@ -2029,25 +2223,28 @@ nv50_display_create(struct drm_device *dev) if (IS_ERR(connector)) continue; - if (dcbe->location != DCB_LOC_ON_CHIP) { - NV_WARN(drm, "skipping off-chip encoder %d/%d\n", - dcbe->type, ffs(dcbe->or) - 1); - continue; + if (dcbe->location == DCB_LOC_ON_CHIP) { + switch (dcbe->type) { + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_LVDS: + case DCB_OUTPUT_DP: + ret = nv50_sor_create(connector, dcbe); + break; + case DCB_OUTPUT_ANALOG: + ret = nv50_dac_create(connector, dcbe); + break; + default: + ret = -ENODEV; + break; + } + } else { + ret = nv50_pior_create(connector, dcbe); } - switch (dcbe->type) { - case DCB_OUTPUT_TMDS: - case DCB_OUTPUT_LVDS: - case DCB_OUTPUT_DP: - nv50_sor_create(connector, dcbe); - break; - case DCB_OUTPUT_ANALOG: - nv50_dac_create(connector, dcbe); - break; - default: - NV_WARN(drm, "skipping unsupported encoder %d/%d\n", - dcbe->type, ffs(dcbe->or) - 1); - continue; + if (ret) { + NV_WARN(drm, "failed to create encoder %d/%d/%d: %d\n", + dcbe->location, dcbe->type, + ffs(dcbe->or) - 1, ret); } } diff --git a/drivers/gpu/drm/nouveau/nv50_fence.c b/drivers/gpu/drm/nouveau/nv50_fence.c index d889f3ac0d41..f9701e567db8 100644 --- a/drivers/gpu/drm/nouveau/nv50_fence.c +++ b/drivers/gpu/drm/nouveau/nv50_fence.c @@ -27,27 +27,16 @@ #include "nouveau_drm.h" #include "nouveau_dma.h" -#include "nouveau_fence.h" +#include "nv10_fence.h" #include "nv50_display.h" -struct nv50_fence_chan { - struct nouveau_fence_chan base; -}; - -struct nv50_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_bo *bo; - spinlock_t lock; - u32 sequence; -}; - static int nv50_fence_context_new(struct nouveau_channel *chan) { struct drm_device *dev = chan->drm->dev; - struct nv50_fence_priv *priv = chan->drm->fence; - struct nv50_fence_chan *fctx; + struct nv10_fence_priv *priv = chan->drm->fence; + struct nv10_fence_chan *fctx; struct ttm_mem_reg *mem = &priv->bo->bo.mem; struct nouveau_object *object; int ret, i; @@ -57,6 +46,9 @@ nv50_fence_context_new(struct nouveau_channel *chan) return -ENOMEM; nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv10_fence_emit; + fctx->base.read = nv10_fence_read; + fctx->base.sync = nv17_fence_sync; ret = nouveau_object_new(nv_object(chan->cli), chan->handle, NvSema, 0x0002, @@ -91,7 +83,7 @@ nv50_fence_context_new(struct nouveau_channel *chan) int nv50_fence_create(struct nouveau_drm *drm) { - struct nv50_fence_priv *priv; + struct nv10_fence_priv *priv; int ret = 0; priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); @@ -99,11 +91,9 @@ nv50_fence_create(struct nouveau_drm *drm) return -ENOMEM; priv->base.dtor = nv10_fence_destroy; + priv->base.resume = nv17_fence_resume; priv->base.context_new = nv50_fence_context_new; priv->base.context_del = nv10_fence_context_del; - priv->base.emit = nv10_fence_emit; - priv->base.read = nv10_fence_read; - priv->base.sync = nv17_fence_sync; spin_lock_init(&priv->lock); ret = nouveau_bo_new(drm->dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, @@ -119,13 +109,11 @@ nv50_fence_create(struct nouveau_drm *drm) nouveau_bo_ref(NULL, &priv->bo); } - if (ret == 0) { - nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); - priv->base.sync = nv17_fence_sync; - priv->base.resume = nv17_fence_resume; + if (ret) { + nv10_fence_destroy(drm); + return ret; } - if (ret) - nv10_fence_destroy(drm); + nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); return ret; } diff --git a/drivers/gpu/drm/nouveau/nv84_fence.c b/drivers/gpu/drm/nouveau/nv84_fence.c index c686650584b6..9fd475c89820 100644 --- a/drivers/gpu/drm/nouveau/nv84_fence.c +++ b/drivers/gpu/drm/nouveau/nv84_fence.c @@ -23,6 +23,7 @@ */ #include <core/object.h> +#include <core/client.h> #include <core/class.h> #include <engine/fifo.h> @@ -33,79 +34,115 @@ #include "nv50_display.h" -struct nv84_fence_chan { - struct nouveau_fence_chan base; -}; - -struct nv84_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_gpuobj *mem; -}; +u64 +nv84_fence_crtc(struct nouveau_channel *chan, int crtc) +{ + struct nv84_fence_chan *fctx = chan->fence; + return fctx->dispc_vma[crtc].offset; +} static int -nv84_fence_emit(struct nouveau_fence *fence) +nv84_fence_emit32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nouveau_channel *chan = fence->channel; - struct nouveau_fifo_chan *fifo = (void *)chan->object; - int ret = RING_SPACE(chan, 7); + int ret = RING_SPACE(chan, 8); if (ret == 0) { BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); - OUT_RING (chan, NvSema); - BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(fifo->chid * 16)); - OUT_RING (chan, lower_32_bits(fifo->chid * 16)); - OUT_RING (chan, fence->sequence); + OUT_RING (chan, chan->vram); + BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 5); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG); + OUT_RING (chan, 0x00000000); FIRE_RING (chan); } return ret; } - static int -nv84_fence_sync(struct nouveau_fence *fence, - struct nouveau_channel *prev, struct nouveau_channel *chan) +nv84_fence_sync32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nouveau_fifo_chan *fifo = (void *)prev->object; int ret = RING_SPACE(chan, 7); if (ret == 0) { BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); - OUT_RING (chan, NvSema); + OUT_RING (chan, chan->vram); BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(fifo->chid * 16)); - OUT_RING (chan, lower_32_bits(fifo->chid * 16)); - OUT_RING (chan, fence->sequence); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_GEQUAL); FIRE_RING (chan); } return ret; } +static int +nv84_fence_emit(struct nouveau_fence *fence) +{ + struct nouveau_channel *chan = fence->channel; + struct nv84_fence_chan *fctx = chan->fence; + struct nouveau_fifo_chan *fifo = (void *)chan->object; + u64 addr = fifo->chid * 16; + + if (fence->sysmem) + addr += fctx->vma_gart.offset; + else + addr += fctx->vma.offset; + + return fctx->base.emit32(chan, addr, fence->sequence); +} + +static int +nv84_fence_sync(struct nouveau_fence *fence, + struct nouveau_channel *prev, struct nouveau_channel *chan) +{ + struct nv84_fence_chan *fctx = chan->fence; + struct nouveau_fifo_chan *fifo = (void *)prev->object; + u64 addr = fifo->chid * 16; + + if (fence->sysmem) + addr += fctx->vma_gart.offset; + else + addr += fctx->vma.offset; + + return fctx->base.sync32(chan, addr, fence->sequence); +} + static u32 nv84_fence_read(struct nouveau_channel *chan) { struct nouveau_fifo_chan *fifo = (void *)chan->object; struct nv84_fence_priv *priv = chan->drm->fence; - return nv_ro32(priv->mem, fifo->chid * 16); + return nouveau_bo_rd32(priv->bo, fifo->chid * 16/4); } static void nv84_fence_context_del(struct nouveau_channel *chan) { + struct drm_device *dev = chan->drm->dev; + struct nv84_fence_priv *priv = chan->drm->fence; struct nv84_fence_chan *fctx = chan->fence; + int i; + + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct nouveau_bo *bo = nv50_display_crtc_sema(dev, i); + nouveau_bo_vma_del(bo, &fctx->dispc_vma[i]); + } + + nouveau_bo_vma_del(priv->bo, &fctx->vma_gart); + nouveau_bo_vma_del(priv->bo, &fctx->vma); nouveau_fence_context_del(&fctx->base); chan->fence = NULL; kfree(fctx); } -static int +int nv84_fence_context_new(struct nouveau_channel *chan) { - struct drm_device *dev = chan->drm->dev; struct nouveau_fifo_chan *fifo = (void *)chan->object; + struct nouveau_client *client = nouveau_client(fifo); struct nv84_fence_priv *priv = chan->drm->fence; struct nv84_fence_chan *fctx; - struct nouveau_object *object; int ret, i; fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); @@ -113,44 +150,74 @@ nv84_fence_context_new(struct nouveau_channel *chan) return -ENOMEM; nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv84_fence_emit; + fctx->base.sync = nv84_fence_sync; + fctx->base.read = nv84_fence_read; + fctx->base.emit32 = nv84_fence_emit32; + fctx->base.sync32 = nv84_fence_sync32; - ret = nouveau_object_new(nv_object(chan->cli), chan->handle, - NvSema, 0x0002, - &(struct nv_dma_class) { - .flags = NV_DMA_TARGET_VRAM | - NV_DMA_ACCESS_RDWR, - .start = priv->mem->addr, - .limit = priv->mem->addr + - priv->mem->size - 1, - }, sizeof(struct nv_dma_class), - &object); - - /* dma objects for display sync channel semaphore blocks */ - for (i = 0; !ret && i < dev->mode_config.num_crtc; i++) { - struct nouveau_bo *bo = nv50_display_crtc_sema(dev, i); + ret = nouveau_bo_vma_add(priv->bo, client->vm, &fctx->vma); + if (ret == 0) { + ret = nouveau_bo_vma_add(priv->bo_gart, client->vm, + &fctx->vma_gart); + } - ret = nouveau_object_new(nv_object(chan->cli), chan->handle, - NvEvoSema0 + i, 0x003d, - &(struct nv_dma_class) { - .flags = NV_DMA_TARGET_VRAM | - NV_DMA_ACCESS_RDWR, - .start = bo->bo.offset, - .limit = bo->bo.offset + 0xfff, - }, sizeof(struct nv_dma_class), - &object); + /* map display semaphore buffers into channel's vm */ + for (i = 0; !ret && i < chan->drm->dev->mode_config.num_crtc; i++) { + struct nouveau_bo *bo = nv50_display_crtc_sema(chan->drm->dev, i); + ret = nouveau_bo_vma_add(bo, client->vm, &fctx->dispc_vma[i]); } + nouveau_bo_wr32(priv->bo, fifo->chid * 16/4, 0x00000000); + if (ret) nv84_fence_context_del(chan); - nv_wo32(priv->mem, fifo->chid * 16, 0x00000000); return ret; } +static bool +nv84_fence_suspend(struct nouveau_drm *drm) +{ + struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); + struct nv84_fence_priv *priv = drm->fence; + int i; + + priv->suspend = vmalloc((pfifo->max + 1) * sizeof(u32)); + if (priv->suspend) { + for (i = 0; i <= pfifo->max; i++) + priv->suspend[i] = nouveau_bo_rd32(priv->bo, i*4); + } + + return priv->suspend != NULL; +} + +static void +nv84_fence_resume(struct nouveau_drm *drm) +{ + struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); + struct nv84_fence_priv *priv = drm->fence; + int i; + + if (priv->suspend) { + for (i = 0; i <= pfifo->max; i++) + nouveau_bo_wr32(priv->bo, i*4, priv->suspend[i]); + vfree(priv->suspend); + priv->suspend = NULL; + } +} + static void nv84_fence_destroy(struct nouveau_drm *drm) { struct nv84_fence_priv *priv = drm->fence; - nouveau_gpuobj_ref(NULL, &priv->mem); + nouveau_bo_unmap(priv->bo_gart); + if (priv->bo_gart) + nouveau_bo_unpin(priv->bo_gart); + nouveau_bo_ref(NULL, &priv->bo_gart); + nouveau_bo_unmap(priv->bo); + if (priv->bo) + nouveau_bo_unpin(priv->bo); + nouveau_bo_ref(NULL, &priv->bo); drm->fence = NULL; kfree(priv); } @@ -160,7 +227,6 @@ nv84_fence_create(struct nouveau_drm *drm) { struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); struct nv84_fence_priv *priv; - u32 chan = pfifo->max + 1; int ret; priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); @@ -168,14 +234,42 @@ nv84_fence_create(struct nouveau_drm *drm) return -ENOMEM; priv->base.dtor = nv84_fence_destroy; + priv->base.suspend = nv84_fence_suspend; + priv->base.resume = nv84_fence_resume; priv->base.context_new = nv84_fence_context_new; priv->base.context_del = nv84_fence_context_del; - priv->base.emit = nv84_fence_emit; - priv->base.sync = nv84_fence_sync; - priv->base.read = nv84_fence_read; - ret = nouveau_gpuobj_new(drm->device, NULL, chan * 16, 0x1000, 0, - &priv->mem); + init_waitqueue_head(&priv->base.waiting); + priv->base.uevent = true; + + ret = nouveau_bo_new(drm->dev, 16 * (pfifo->max + 1), 0, + TTM_PL_FLAG_VRAM, 0, 0, NULL, &priv->bo); + if (ret == 0) { + ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); + if (ret == 0) { + ret = nouveau_bo_map(priv->bo); + if (ret) + nouveau_bo_unpin(priv->bo); + } + if (ret) + nouveau_bo_ref(NULL, &priv->bo); + } + + if (ret == 0) + ret = nouveau_bo_new(drm->dev, 16 * (pfifo->max + 1), 0, + TTM_PL_FLAG_TT, 0, 0, NULL, + &priv->bo_gart); + if (ret == 0) { + ret = nouveau_bo_pin(priv->bo_gart, TTM_PL_FLAG_TT); + if (ret == 0) { + ret = nouveau_bo_map(priv->bo_gart); + if (ret) + nouveau_bo_unpin(priv->bo_gart); + } + if (ret) + nouveau_bo_ref(NULL, &priv->bo_gart); + } + if (ret) nv84_fence_destroy(drm); return ret; diff --git a/drivers/gpu/drm/nouveau/nvc0_fence.c b/drivers/gpu/drm/nouveau/nvc0_fence.c index 2a56b1b551cb..9566267fbc42 100644 --- a/drivers/gpu/drm/nouveau/nvc0_fence.c +++ b/drivers/gpu/drm/nouveau/nvc0_fence.c @@ -34,203 +34,57 @@ #include "nv50_display.h" -struct nvc0_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_bo *bo; - u32 *suspend; -}; - -struct nvc0_fence_chan { - struct nouveau_fence_chan base; - struct nouveau_vma vma; - struct nouveau_vma dispc_vma[4]; -}; - -u64 -nvc0_fence_crtc(struct nouveau_channel *chan, int crtc) -{ - struct nvc0_fence_chan *fctx = chan->fence; - return fctx->dispc_vma[crtc].offset; -} - static int -nvc0_fence_emit(struct nouveau_fence *fence) +nvc0_fence_emit32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nouveau_channel *chan = fence->channel; - struct nvc0_fence_chan *fctx = chan->fence; - struct nouveau_fifo_chan *fifo = (void *)chan->object; - u64 addr = fctx->vma.offset + fifo->chid * 16; - int ret; - - ret = RING_SPACE(chan, 5); + int ret = RING_SPACE(chan, 6); if (ret == 0) { - BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(addr)); - OUT_RING (chan, lower_32_bits(addr)); - OUT_RING (chan, fence->sequence); + BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 5); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG); + OUT_RING (chan, 0x00000000); FIRE_RING (chan); } - return ret; } static int -nvc0_fence_sync(struct nouveau_fence *fence, - struct nouveau_channel *prev, struct nouveau_channel *chan) +nvc0_fence_sync32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nvc0_fence_chan *fctx = chan->fence; - struct nouveau_fifo_chan *fifo = (void *)prev->object; - u64 addr = fctx->vma.offset + fifo->chid * 16; - int ret; - - ret = RING_SPACE(chan, 5); + int ret = RING_SPACE(chan, 5); if (ret == 0) { BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(addr)); - OUT_RING (chan, lower_32_bits(addr)); - OUT_RING (chan, fence->sequence); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_GEQUAL | NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD); FIRE_RING (chan); } - return ret; } -static u32 -nvc0_fence_read(struct nouveau_channel *chan) -{ - struct nouveau_fifo_chan *fifo = (void *)chan->object; - struct nvc0_fence_priv *priv = chan->drm->fence; - return nouveau_bo_rd32(priv->bo, fifo->chid * 16/4); -} - -static void -nvc0_fence_context_del(struct nouveau_channel *chan) -{ - struct drm_device *dev = chan->drm->dev; - struct nvc0_fence_priv *priv = chan->drm->fence; - struct nvc0_fence_chan *fctx = chan->fence; - int i; - - for (i = 0; i < dev->mode_config.num_crtc; i++) { - struct nouveau_bo *bo = nv50_display_crtc_sema(dev, i); - nouveau_bo_vma_del(bo, &fctx->dispc_vma[i]); - } - - nouveau_bo_vma_del(priv->bo, &fctx->vma); - nouveau_fence_context_del(&fctx->base); - chan->fence = NULL; - kfree(fctx); -} - static int nvc0_fence_context_new(struct nouveau_channel *chan) { - struct nouveau_fifo_chan *fifo = (void *)chan->object; - struct nouveau_client *client = nouveau_client(fifo); - struct nvc0_fence_priv *priv = chan->drm->fence; - struct nvc0_fence_chan *fctx; - int ret, i; - - fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); - if (!fctx) - return -ENOMEM; - - nouveau_fence_context_new(&fctx->base); - - ret = nouveau_bo_vma_add(priv->bo, client->vm, &fctx->vma); - if (ret) - nvc0_fence_context_del(chan); - - /* map display semaphore buffers into channel's vm */ - for (i = 0; !ret && i < chan->drm->dev->mode_config.num_crtc; i++) { - struct nouveau_bo *bo = nv50_display_crtc_sema(chan->drm->dev, i); - ret = nouveau_bo_vma_add(bo, client->vm, &fctx->dispc_vma[i]); + int ret = nv84_fence_context_new(chan); + if (ret == 0) { + struct nv84_fence_chan *fctx = chan->fence; + fctx->base.emit32 = nvc0_fence_emit32; + fctx->base.sync32 = nvc0_fence_sync32; } - - nouveau_bo_wr32(priv->bo, fifo->chid * 16/4, 0x00000000); return ret; } -static bool -nvc0_fence_suspend(struct nouveau_drm *drm) -{ - struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); - struct nvc0_fence_priv *priv = drm->fence; - int i; - - priv->suspend = vmalloc((pfifo->max + 1) * sizeof(u32)); - if (priv->suspend) { - for (i = 0; i <= pfifo->max; i++) - priv->suspend[i] = nouveau_bo_rd32(priv->bo, i); - } - - return priv->suspend != NULL; -} - -static void -nvc0_fence_resume(struct nouveau_drm *drm) -{ - struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); - struct nvc0_fence_priv *priv = drm->fence; - int i; - - if (priv->suspend) { - for (i = 0; i <= pfifo->max; i++) - nouveau_bo_wr32(priv->bo, i, priv->suspend[i]); - vfree(priv->suspend); - priv->suspend = NULL; - } -} - -static void -nvc0_fence_destroy(struct nouveau_drm *drm) -{ - struct nvc0_fence_priv *priv = drm->fence; - nouveau_bo_unmap(priv->bo); - if (priv->bo) - nouveau_bo_unpin(priv->bo); - nouveau_bo_ref(NULL, &priv->bo); - drm->fence = NULL; - kfree(priv); -} - int nvc0_fence_create(struct nouveau_drm *drm) { - struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); - struct nvc0_fence_priv *priv; - int ret; - - priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->base.dtor = nvc0_fence_destroy; - priv->base.suspend = nvc0_fence_suspend; - priv->base.resume = nvc0_fence_resume; - priv->base.context_new = nvc0_fence_context_new; - priv->base.context_del = nvc0_fence_context_del; - priv->base.emit = nvc0_fence_emit; - priv->base.sync = nvc0_fence_sync; - priv->base.read = nvc0_fence_read; - - ret = nouveau_bo_new(drm->dev, 16 * (pfifo->max + 1), 0, - TTM_PL_FLAG_VRAM, 0, 0, NULL, &priv->bo); + int ret = nv84_fence_create(drm); if (ret == 0) { - ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); - if (ret == 0) { - ret = nouveau_bo_map(priv->bo); - if (ret) - nouveau_bo_unpin(priv->bo); - } - if (ret) - nouveau_bo_ref(NULL, &priv->bo); + struct nv84_fence_priv *priv = drm->fence; + priv->base.context_new = nvc0_fence_context_new; } - - if (ret) - nvc0_fence_destroy(drm); return ret; } diff --git a/drivers/gpu/drm/omapdrm/Kconfig b/drivers/gpu/drm/omapdrm/Kconfig new file mode 100644 index 000000000000..b724a4131435 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/Kconfig @@ -0,0 +1,25 @@ + +config DRM_OMAP + tristate "OMAP DRM" + depends on DRM && !CONFIG_FB_OMAP2 + depends on ARCH_OMAP2PLUS || ARCH_MULTIPLATFORM + select DRM_KMS_HELPER + select OMAP2_DSS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + default n + help + DRM display driver for OMAP2/3/4 based boards. + +config DRM_OMAP_NUM_CRTCS + int "Number of CRTCs" + range 1 10 + default 1 if ARCH_OMAP2 || ARCH_OMAP3 + default 2 if ARCH_OMAP4 + depends on DRM_OMAP + help + Select the number of video overlays which can be used as framebuffers. + The remaining overlays are reserved for video. + diff --git a/drivers/gpu/drm/omapdrm/Makefile b/drivers/gpu/drm/omapdrm/Makefile new file mode 100644 index 000000000000..d85e058f2845 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/Makefile @@ -0,0 +1,24 @@ +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) +# + +ccflags-y := -Iinclude/drm -Werror +omapdrm-y := omap_drv.o \ + omap_irq.o \ + omap_debugfs.o \ + omap_crtc.o \ + omap_plane.o \ + omap_encoder.o \ + omap_connector.o \ + omap_fb.o \ + omap_fbdev.o \ + omap_gem.o \ + omap_gem_dmabuf.o \ + omap_dmm_tiler.o \ + tcm-sita.o + +# temporary: +omapdrm-y += omap_gem_helpers.o + +obj-$(CONFIG_DRM_OMAP) += omapdrm.o diff --git a/drivers/gpu/drm/omapdrm/TODO b/drivers/gpu/drm/omapdrm/TODO new file mode 100644 index 000000000000..4d8c18aa5dd7 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/TODO @@ -0,0 +1,23 @@ +TODO +. Where should we do eviction (detatch_pages())? We aren't necessarily + accessing the pages via a GART, so maybe we need some other threshold + to put a cap on the # of pages that can be pin'd. + . Use mm_shrinker to trigger unpinning pages. + . This is mainly theoretical since most of these devices don't actually + have swap or harddrive. +. GEM/shmem backed pages can have existing mappings (kernel linear map, + etc..), which isn't really ideal. +. Revisit GEM sync object infrastructure.. TTM has some framework for this + already. Possibly this could be refactored out and made more common? + There should be some way to do this with less wheel-reinvention. + . This can be handled by the dma-buf fence/reservation stuff when it + lands + +Userspace: +. git://anongit.freedesktop.org/xorg/driver/xf86-video-omap + +Currently tested on +. OMAP3530 beagleboard +. OMAP4430 pandaboard +. OMAP4460 pandaboard +. OMAP5432 uEVM diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c new file mode 100644 index 000000000000..44284fd981fc --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_connector.c @@ -0,0 +1,298 @@ +/* + * drivers/gpu/drm/omapdrm/omap_connector.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +/* + * connector funcs + */ + +#define to_omap_connector(x) container_of(x, struct omap_connector, base) + +struct omap_connector { + struct drm_connector base; + struct omap_dss_device *dssdev; + struct drm_encoder *encoder; +}; + +void copy_timings_omap_to_drm(struct drm_display_mode *mode, + struct omap_video_timings *timings) +{ + mode->clock = timings->pixel_clock; + + mode->hdisplay = timings->x_res; + mode->hsync_start = mode->hdisplay + timings->hfp; + mode->hsync_end = mode->hsync_start + timings->hsw; + mode->htotal = mode->hsync_end + timings->hbp; + + mode->vdisplay = timings->y_res; + mode->vsync_start = mode->vdisplay + timings->vfp; + mode->vsync_end = mode->vsync_start + timings->vsw; + mode->vtotal = mode->vsync_end + timings->vbp; + + mode->flags = 0; + + if (timings->interlace) + mode->flags |= DRM_MODE_FLAG_INTERLACE; + + if (timings->hsync_level == OMAPDSS_SIG_ACTIVE_HIGH) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + else + mode->flags |= DRM_MODE_FLAG_NHSYNC; + + if (timings->vsync_level == OMAPDSS_SIG_ACTIVE_HIGH) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + else + mode->flags |= DRM_MODE_FLAG_NVSYNC; +} + +void copy_timings_drm_to_omap(struct omap_video_timings *timings, + struct drm_display_mode *mode) +{ + timings->pixel_clock = mode->clock; + + timings->x_res = mode->hdisplay; + timings->hfp = mode->hsync_start - mode->hdisplay; + timings->hsw = mode->hsync_end - mode->hsync_start; + timings->hbp = mode->htotal - mode->hsync_end; + + timings->y_res = mode->vdisplay; + timings->vfp = mode->vsync_start - mode->vdisplay; + timings->vsw = mode->vsync_end - mode->vsync_start; + timings->vbp = mode->vtotal - mode->vsync_end; + + timings->interlace = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + timings->hsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + else + timings->hsync_level = OMAPDSS_SIG_ACTIVE_LOW; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + timings->vsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + else + timings->vsync_level = OMAPDSS_SIG_ACTIVE_LOW; + + timings->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + timings->de_level = OMAPDSS_SIG_ACTIVE_HIGH; + timings->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; +} + +static enum drm_connector_status omap_connector_detect( + struct drm_connector *connector, bool force) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + enum drm_connector_status ret; + + if (dssdrv->detect) { + if (dssdrv->detect(dssdev)) + ret = connector_status_connected; + else + ret = connector_status_disconnected; + } else { + ret = connector_status_unknown; + } + + VERB("%s: %d (force=%d)", omap_connector->dssdev->name, ret, force); + + return ret; +} + +static void omap_connector_destroy(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + + DBG("%s", omap_connector->dssdev->name); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(omap_connector); + + omap_dss_put_device(dssdev); +} + +#define MAX_EDID 512 + +static int omap_connector_get_modes(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct drm_device *dev = connector->dev; + int n = 0; + + DBG("%s", omap_connector->dssdev->name); + + /* if display exposes EDID, then we parse that in the normal way to + * build table of supported modes.. otherwise (ie. fixed resolution + * LCD panels) we just return a single mode corresponding to the + * currently configured timings: + */ + if (dssdrv->read_edid) { + void *edid = kzalloc(MAX_EDID, GFP_KERNEL); + + if ((dssdrv->read_edid(dssdev, edid, MAX_EDID) > 0) && + drm_edid_is_valid(edid)) { + drm_mode_connector_update_edid_property( + connector, edid); + n = drm_add_edid_modes(connector, edid); + } else { + drm_mode_connector_update_edid_property( + connector, NULL); + } + kfree(edid); + } else { + struct drm_display_mode *mode = drm_mode_create(dev); + struct omap_video_timings timings = {0}; + + dssdrv->get_timings(dssdev, &timings); + + copy_timings_omap_to_drm(mode, &timings); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + n = 1; + } + + return n; +} + +static int omap_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omap_video_timings timings = {0}; + struct drm_device *dev = connector->dev; + struct drm_display_mode *new_mode; + int ret = MODE_BAD; + + copy_timings_drm_to_omap(&timings, mode); + mode->vrefresh = drm_mode_vrefresh(mode); + + if (!dssdrv->check_timings(dssdev, &timings)) { + /* check if vrefresh is still valid */ + new_mode = drm_mode_duplicate(dev, mode); + new_mode->clock = timings.pixel_clock; + new_mode->vrefresh = 0; + if (mode->vrefresh == drm_mode_vrefresh(new_mode)) + ret = MODE_OK; + drm_mode_destroy(dev, new_mode); + } + + DBG("connector: mode %s: " + "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + (ret == MODE_OK) ? "valid" : "invalid", + mode->base.id, mode->name, mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + + return ret; +} + +struct drm_encoder *omap_connector_attached_encoder( + struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + return omap_connector->encoder; +} + +static const struct drm_connector_funcs omap_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = omap_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = omap_connector_destroy, +}; + +static const struct drm_connector_helper_funcs omap_connector_helper_funcs = { + .get_modes = omap_connector_get_modes, + .mode_valid = omap_connector_mode_valid, + .best_encoder = omap_connector_attached_encoder, +}; + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +void omap_connector_flush(struct drm_connector *connector, + int x, int y, int w, int h) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + + /* TODO: enable when supported in dss */ + VERB("%s: %d,%d, %dx%d", omap_connector->dssdev->name, x, y, w, h); +} + +/* initialize connector */ +struct drm_connector *omap_connector_init(struct drm_device *dev, + int connector_type, struct omap_dss_device *dssdev, + struct drm_encoder *encoder) +{ + struct drm_connector *connector = NULL; + struct omap_connector *omap_connector; + + DBG("%s", dssdev->name); + + omap_dss_get_device(dssdev); + + omap_connector = kzalloc(sizeof(struct omap_connector), GFP_KERNEL); + if (!omap_connector) { + dev_err(dev->dev, "could not allocate connector\n"); + goto fail; + } + + omap_connector->dssdev = dssdev; + omap_connector->encoder = encoder; + + connector = &omap_connector->base; + + drm_connector_init(dev, connector, &omap_connector_funcs, + connector_type); + drm_connector_helper_add(connector, &omap_connector_helper_funcs); + +#if 0 /* enable when dss2 supports hotplug */ + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_HPD) + connector->polled = 0; + else +#endif + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 1; + connector->doublescan_allowed = 0; + + drm_sysfs_connector_add(connector); + + return connector; + +fail: + if (connector) + omap_connector_destroy(connector); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_crtc.c b/drivers/gpu/drm/omapdrm/omap_crtc.c new file mode 100644 index 000000000000..ac2258f59805 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_crtc.c @@ -0,0 +1,657 @@ +/* + * drivers/gpu/drm/omapdrm/omap_crtc.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include <drm/drm_mode.h> +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#define to_omap_crtc(x) container_of(x, struct omap_crtc, base) + +struct omap_crtc { + struct drm_crtc base; + struct drm_plane *plane; + + const char *name; + int pipe; + enum omap_channel channel; + struct omap_overlay_manager_info info; + + /* + * Temporary: eventually this will go away, but it is needed + * for now to keep the output's happy. (They only need + * mgr->id.) Eventually this will be replaced w/ something + * more common-panel-framework-y + */ + struct omap_overlay_manager mgr; + + struct omap_video_timings timings; + bool enabled; + bool full_update; + + struct omap_drm_apply apply; + + struct omap_drm_irq apply_irq; + struct omap_drm_irq error_irq; + + /* list of in-progress apply's: */ + struct list_head pending_applies; + + /* list of queued apply's: */ + struct list_head queued_applies; + + /* for handling queued and in-progress applies: */ + struct work_struct apply_work; + + /* if there is a pending flip, these will be non-null: */ + struct drm_pending_vblank_event *event; + struct drm_framebuffer *old_fb; + + /* for handling page flips without caring about what + * the callback is called from. Possibly we should just + * make omap_gem always call the cb from the worker so + * we don't have to care about this.. + * + * XXX maybe fold into apply_work?? + */ + struct work_struct page_flip_work; +}; + +/* + * Manager-ops, callbacks from output when they need to configure + * the upstream part of the video pipe. + * + * Most of these we can ignore until we add support for command-mode + * panels.. for video-mode the crtc-helpers already do an adequate + * job of sequencing the setup of the video pipe in the proper order + */ + +/* we can probably ignore these until we support command-mode panels: */ +static void omap_crtc_start_update(struct omap_overlay_manager *mgr) +{ +} + +static int omap_crtc_enable(struct omap_overlay_manager *mgr) +{ + return 0; +} + +static void omap_crtc_disable(struct omap_overlay_manager *mgr) +{ +} + +static void omap_crtc_set_timings(struct omap_overlay_manager *mgr, + const struct omap_video_timings *timings) +{ + struct omap_crtc *omap_crtc = container_of(mgr, struct omap_crtc, mgr); + DBG("%s", omap_crtc->name); + omap_crtc->timings = *timings; + omap_crtc->full_update = true; +} + +static void omap_crtc_set_lcd_config(struct omap_overlay_manager *mgr, + const struct dss_lcd_mgr_config *config) +{ + struct omap_crtc *omap_crtc = container_of(mgr, struct omap_crtc, mgr); + DBG("%s", omap_crtc->name); + dispc_mgr_set_lcd_config(omap_crtc->channel, config); +} + +static int omap_crtc_register_framedone_handler( + struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ + return 0; +} + +static void omap_crtc_unregister_framedone_handler( + struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ +} + +static const struct dss_mgr_ops mgr_ops = { + .start_update = omap_crtc_start_update, + .enable = omap_crtc_enable, + .disable = omap_crtc_disable, + .set_timings = omap_crtc_set_timings, + .set_lcd_config = omap_crtc_set_lcd_config, + .register_framedone_handler = omap_crtc_register_framedone_handler, + .unregister_framedone_handler = omap_crtc_unregister_framedone_handler, +}; + +/* + * CRTC funcs: + */ + +static void omap_crtc_destroy(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + DBG("%s", omap_crtc->name); + + WARN_ON(omap_crtc->apply_irq.registered); + omap_irq_unregister(crtc->dev, &omap_crtc->error_irq); + + omap_crtc->plane->funcs->destroy(omap_crtc->plane); + drm_crtc_cleanup(crtc); + + kfree(omap_crtc); +} + +static void omap_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct omap_drm_private *priv = crtc->dev->dev_private; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + bool enabled = (mode == DRM_MODE_DPMS_ON); + int i; + + DBG("%s: %d", omap_crtc->name, mode); + + if (enabled != omap_crtc->enabled) { + omap_crtc->enabled = enabled; + omap_crtc->full_update = true; + omap_crtc_apply(crtc, &omap_crtc->apply); + + /* also enable our private plane: */ + WARN_ON(omap_plane_dpms(omap_crtc->plane, mode)); + + /* and any attached overlay planes: */ + for (i = 0; i < priv->num_planes; i++) { + struct drm_plane *plane = priv->planes[i]; + if (plane->crtc == crtc) + WARN_ON(omap_plane_dpms(plane, mode)); + } + } +} + +static bool omap_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int omap_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + mode = adjusted_mode; + + DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + omap_crtc->name, mode->base.id, mode->name, + mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, + mode->type, mode->flags); + + copy_timings_drm_to_omap(&omap_crtc->timings, mode); + omap_crtc->full_update = true; + + return omap_plane_mode_set(omap_crtc->plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + x << 16, y << 16, + mode->hdisplay << 16, mode->vdisplay << 16, + NULL, NULL); +} + +static void omap_crtc_prepare(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->name); + omap_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void omap_crtc_commit(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->name); + omap_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static int omap_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_plane *plane = omap_crtc->plane; + struct drm_display_mode *mode = &crtc->mode; + + return omap_plane_mode_set(plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + x << 16, y << 16, + mode->hdisplay << 16, mode->vdisplay << 16, + NULL, NULL); +} + +static void omap_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static void vblank_cb(void *arg) +{ + struct drm_crtc *crtc = arg; + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + + /* wakeup userspace */ + if (omap_crtc->event) + drm_send_vblank_event(dev, omap_crtc->pipe, omap_crtc->event); + + omap_crtc->event = NULL; + omap_crtc->old_fb = NULL; + + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void page_flip_worker(struct work_struct *work) +{ + struct omap_crtc *omap_crtc = + container_of(work, struct omap_crtc, page_flip_work); + struct drm_crtc *crtc = &omap_crtc->base; + struct drm_display_mode *mode = &crtc->mode; + struct drm_gem_object *bo; + + mutex_lock(&crtc->mutex); + omap_plane_mode_set(omap_crtc->plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + crtc->x << 16, crtc->y << 16, + mode->hdisplay << 16, mode->vdisplay << 16, + vblank_cb, crtc); + mutex_unlock(&crtc->mutex); + + bo = omap_framebuffer_bo(crtc->fb, 0); + drm_gem_object_unreference_unlocked(bo); +} + +static void page_flip_cb(void *arg) +{ + struct drm_crtc *crtc = arg; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct omap_drm_private *priv = crtc->dev->dev_private; + + /* avoid assumptions about what ctxt we are called from: */ + queue_work(priv->wq, &omap_crtc->page_flip_work); +} + +static int omap_crtc_page_flip_locked(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_gem_object *bo; + + DBG("%d -> %d (event=%p)", crtc->fb ? crtc->fb->base.id : -1, + fb->base.id, event); + + if (omap_crtc->old_fb) { + dev_err(dev->dev, "already a pending flip\n"); + return -EINVAL; + } + + omap_crtc->event = event; + crtc->fb = fb; + + /* + * Hold a reference temporarily until the crtc is updated + * and takes the reference to the bo. This avoids it + * getting freed from under us: + */ + bo = omap_framebuffer_bo(fb, 0); + drm_gem_object_reference(bo); + + omap_gem_op_async(bo, OMAP_GEM_READ, page_flip_cb, crtc); + + return 0; +} + +static int omap_crtc_set_property(struct drm_crtc *crtc, + struct drm_property *property, uint64_t val) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct omap_drm_private *priv = crtc->dev->dev_private; + + if (property == priv->rotation_prop) { + crtc->invert_dimensions = + !!(val & ((1LL << DRM_ROTATE_90) | (1LL << DRM_ROTATE_270))); + } + + return omap_plane_set_property(omap_crtc->plane, property, val); +} + +static const struct drm_crtc_funcs omap_crtc_funcs = { + .set_config = drm_crtc_helper_set_config, + .destroy = omap_crtc_destroy, + .page_flip = omap_crtc_page_flip_locked, + .set_property = omap_crtc_set_property, +}; + +static const struct drm_crtc_helper_funcs omap_crtc_helper_funcs = { + .dpms = omap_crtc_dpms, + .mode_fixup = omap_crtc_mode_fixup, + .mode_set = omap_crtc_mode_set, + .prepare = omap_crtc_prepare, + .commit = omap_crtc_commit, + .mode_set_base = omap_crtc_mode_set_base, + .load_lut = omap_crtc_load_lut, +}; + +const struct omap_video_timings *omap_crtc_timings(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + return &omap_crtc->timings; +} + +enum omap_channel omap_crtc_channel(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + return omap_crtc->channel; +} + +static void omap_crtc_error_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_crtc *omap_crtc = + container_of(irq, struct omap_crtc, error_irq); + struct drm_crtc *crtc = &omap_crtc->base; + DRM_ERROR("%s: errors: %08x\n", omap_crtc->name, irqstatus); + /* avoid getting in a flood, unregister the irq until next vblank */ + omap_irq_unregister(crtc->dev, &omap_crtc->error_irq); +} + +static void omap_crtc_apply_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_crtc *omap_crtc = + container_of(irq, struct omap_crtc, apply_irq); + struct drm_crtc *crtc = &omap_crtc->base; + + if (!omap_crtc->error_irq.registered) + omap_irq_register(crtc->dev, &omap_crtc->error_irq); + + if (!dispc_mgr_go_busy(omap_crtc->channel)) { + struct omap_drm_private *priv = + crtc->dev->dev_private; + DBG("%s: apply done", omap_crtc->name); + omap_irq_unregister(crtc->dev, &omap_crtc->apply_irq); + queue_work(priv->wq, &omap_crtc->apply_work); + } +} + +static void apply_worker(struct work_struct *work) +{ + struct omap_crtc *omap_crtc = + container_of(work, struct omap_crtc, apply_work); + struct drm_crtc *crtc = &omap_crtc->base; + struct drm_device *dev = crtc->dev; + struct omap_drm_apply *apply, *n; + bool need_apply; + + /* + * Synchronize everything on mode_config.mutex, to keep + * the callbacks and list modification all serialized + * with respect to modesetting ioctls from userspace. + */ + mutex_lock(&crtc->mutex); + dispc_runtime_get(); + + /* + * If we are still pending a previous update, wait.. when the + * pending update completes, we get kicked again. + */ + if (omap_crtc->apply_irq.registered) + goto out; + + /* finish up previous apply's: */ + list_for_each_entry_safe(apply, n, + &omap_crtc->pending_applies, pending_node) { + apply->post_apply(apply); + list_del(&apply->pending_node); + } + + need_apply = !list_empty(&omap_crtc->queued_applies); + + /* then handle the next round of of queued apply's: */ + list_for_each_entry_safe(apply, n, + &omap_crtc->queued_applies, queued_node) { + apply->pre_apply(apply); + list_del(&apply->queued_node); + apply->queued = false; + list_add_tail(&apply->pending_node, + &omap_crtc->pending_applies); + } + + if (need_apply) { + enum omap_channel channel = omap_crtc->channel; + + DBG("%s: GO", omap_crtc->name); + + if (dispc_mgr_is_enabled(channel)) { + omap_irq_register(dev, &omap_crtc->apply_irq); + dispc_mgr_go(channel); + } else { + struct omap_drm_private *priv = dev->dev_private; + queue_work(priv->wq, &omap_crtc->apply_work); + } + } + +out: + dispc_runtime_put(); + mutex_unlock(&crtc->mutex); +} + +int omap_crtc_apply(struct drm_crtc *crtc, + struct omap_drm_apply *apply) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + WARN_ON(!mutex_is_locked(&crtc->mutex)); + + /* no need to queue it again if it is already queued: */ + if (apply->queued) + return 0; + + apply->queued = true; + list_add_tail(&apply->queued_node, &omap_crtc->queued_applies); + + /* + * If there are no currently pending updates, then go ahead and + * kick the worker immediately, otherwise it will run again when + * the current update finishes. + */ + if (list_empty(&omap_crtc->pending_applies)) { + struct omap_drm_private *priv = crtc->dev->dev_private; + queue_work(priv->wq, &omap_crtc->apply_work); + } + + return 0; +} + +/* called only from apply */ +static void set_enabled(struct drm_crtc *crtc, bool enable) +{ + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + enum omap_channel channel = omap_crtc->channel; + struct omap_irq_wait *wait = NULL; + + if (dispc_mgr_is_enabled(channel) == enable) + return; + + /* ignore sync-lost irqs during enable/disable */ + omap_irq_unregister(crtc->dev, &omap_crtc->error_irq); + + if (dispc_mgr_get_framedone_irq(channel)) { + if (!enable) { + wait = omap_irq_wait_init(dev, + dispc_mgr_get_framedone_irq(channel), 1); + } + } else { + /* + * When we disable digit output, we need to wait until fields + * are done. Otherwise the DSS is still working, and turning + * off the clocks prevents DSS from going to OFF mode. And when + * enabling, we need to wait for the extra sync losts + */ + wait = omap_irq_wait_init(dev, + dispc_mgr_get_vsync_irq(channel), 2); + } + + dispc_mgr_enable(channel, enable); + + if (wait) { + int ret = omap_irq_wait(dev, wait, msecs_to_jiffies(100)); + if (ret) { + dev_err(dev->dev, "%s: timeout waiting for %s\n", + omap_crtc->name, enable ? "enable" : "disable"); + } + } + + omap_irq_register(crtc->dev, &omap_crtc->error_irq); +} + +static void omap_crtc_pre_apply(struct omap_drm_apply *apply) +{ + struct omap_crtc *omap_crtc = + container_of(apply, struct omap_crtc, apply); + struct drm_crtc *crtc = &omap_crtc->base; + struct drm_encoder *encoder = NULL; + + DBG("%s: enabled=%d, full=%d", omap_crtc->name, + omap_crtc->enabled, omap_crtc->full_update); + + if (omap_crtc->full_update) { + struct omap_drm_private *priv = crtc->dev->dev_private; + int i; + for (i = 0; i < priv->num_encoders; i++) { + if (priv->encoders[i]->crtc == crtc) { + encoder = priv->encoders[i]; + break; + } + } + } + + if (!omap_crtc->enabled) { + set_enabled(&omap_crtc->base, false); + if (encoder) + omap_encoder_set_enabled(encoder, false); + } else { + if (encoder) { + omap_encoder_set_enabled(encoder, false); + omap_encoder_update(encoder, &omap_crtc->mgr, + &omap_crtc->timings); + omap_encoder_set_enabled(encoder, true); + omap_crtc->full_update = false; + } + + dispc_mgr_setup(omap_crtc->channel, &omap_crtc->info); + dispc_mgr_set_timings(omap_crtc->channel, + &omap_crtc->timings); + set_enabled(&omap_crtc->base, true); + } + + omap_crtc->full_update = false; +} + +static void omap_crtc_post_apply(struct omap_drm_apply *apply) +{ + /* nothing needed for post-apply */ +} + +static const char *channel_names[] = { + [OMAP_DSS_CHANNEL_LCD] = "lcd", + [OMAP_DSS_CHANNEL_DIGIT] = "tv", + [OMAP_DSS_CHANNEL_LCD2] = "lcd2", +}; + +/* initialize crtc */ +struct drm_crtc *omap_crtc_init(struct drm_device *dev, + struct drm_plane *plane, enum omap_channel channel, int id) +{ + struct drm_crtc *crtc = NULL; + struct omap_crtc *omap_crtc; + struct omap_overlay_manager_info *info; + + DBG("%s", channel_names[channel]); + + omap_crtc = kzalloc(sizeof(*omap_crtc), GFP_KERNEL); + + if (!omap_crtc) { + dev_err(dev->dev, "could not allocate CRTC\n"); + goto fail; + } + + crtc = &omap_crtc->base; + + INIT_WORK(&omap_crtc->page_flip_work, page_flip_worker); + INIT_WORK(&omap_crtc->apply_work, apply_worker); + + INIT_LIST_HEAD(&omap_crtc->pending_applies); + INIT_LIST_HEAD(&omap_crtc->queued_applies); + + omap_crtc->apply.pre_apply = omap_crtc_pre_apply; + omap_crtc->apply.post_apply = omap_crtc_post_apply; + + omap_crtc->apply_irq.irqmask = pipe2vbl(id); + omap_crtc->apply_irq.irq = omap_crtc_apply_irq; + + omap_crtc->error_irq.irqmask = + dispc_mgr_get_sync_lost_irq(channel); + omap_crtc->error_irq.irq = omap_crtc_error_irq; + omap_irq_register(dev, &omap_crtc->error_irq); + + omap_crtc->channel = channel; + omap_crtc->plane = plane; + omap_crtc->plane->crtc = crtc; + omap_crtc->name = channel_names[channel]; + omap_crtc->pipe = id; + + /* temporary: */ + omap_crtc->mgr.id = channel; + + dss_install_mgr_ops(&mgr_ops); + + /* TODO: fix hard-coded setup.. add properties! */ + info = &omap_crtc->info; + info->default_color = 0x00000000; + info->trans_key = 0x00000000; + info->trans_key_type = OMAP_DSS_COLOR_KEY_GFX_DST; + info->trans_enabled = false; + + drm_crtc_init(dev, crtc, &omap_crtc_funcs); + drm_crtc_helper_add(crtc, &omap_crtc_helper_funcs); + + omap_plane_install_properties(omap_crtc->plane, &crtc->base); + + return crtc; + +fail: + if (crtc) + omap_crtc_destroy(crtc); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_debugfs.c b/drivers/gpu/drm/omapdrm/omap_debugfs.c new file mode 100644 index 000000000000..c27f59da7f29 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_debugfs.c @@ -0,0 +1,125 @@ +/* + * drivers/gpu/drm/omapdrm/omap_debugfs.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +#include "drm_fb_helper.h" + + +#ifdef CONFIG_DEBUG_FS + +static int gem_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct omap_drm_private *priv = dev->dev_private; + int ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + seq_printf(m, "All Objects:\n"); + omap_gem_describe_objects(&priv->obj_list, m); + + mutex_unlock(&dev->struct_mutex); + + return 0; +} + +static int mm_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + return drm_mm_dump_table(m, dev->mm_private); +} + +static int fb_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct omap_drm_private *priv = dev->dev_private; + struct drm_framebuffer *fb; + + seq_printf(m, "fbcon "); + omap_framebuffer_describe(priv->fbdev->fb, m); + + mutex_lock(&dev->mode_config.fb_lock); + list_for_each_entry(fb, &dev->mode_config.fb_list, head) { + if (fb == priv->fbdev->fb) + continue; + + seq_printf(m, "user "); + omap_framebuffer_describe(fb, m); + } + mutex_unlock(&dev->mode_config.fb_lock); + + return 0; +} + +/* list of debufs files that are applicable to all devices */ +static struct drm_info_list omap_debugfs_list[] = { + {"gem", gem_show, 0}, + {"mm", mm_show, 0}, + {"fb", fb_show, 0}, +}; + +/* list of debugfs files that are specific to devices with dmm/tiler */ +static struct drm_info_list omap_dmm_debugfs_list[] = { + {"tiler_map", tiler_map_show, 0}, +}; + +int omap_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *dev = minor->dev; + int ret; + + ret = drm_debugfs_create_files(omap_debugfs_list, + ARRAY_SIZE(omap_debugfs_list), + minor->debugfs_root, minor); + + if (ret) { + dev_err(dev->dev, "could not install omap_debugfs_list\n"); + return ret; + } + + if (dmm_is_available()) + ret = drm_debugfs_create_files(omap_dmm_debugfs_list, + ARRAY_SIZE(omap_dmm_debugfs_list), + minor->debugfs_root, minor); + + if (ret) { + dev_err(dev->dev, "could not install omap_dmm_debugfs_list\n"); + return ret; + } + + return ret; +} + +void omap_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(omap_debugfs_list, + ARRAY_SIZE(omap_debugfs_list), minor); + if (dmm_is_available()) + drm_debugfs_remove_files(omap_dmm_debugfs_list, + ARRAY_SIZE(omap_dmm_debugfs_list), minor); +} + +#endif diff --git a/drivers/gpu/drm/omapdrm/omap_dmm_priv.h b/drivers/gpu/drm/omapdrm/omap_dmm_priv.h new file mode 100644 index 000000000000..58bcd6ae0255 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_dmm_priv.h @@ -0,0 +1,188 @@ +/* + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Rob Clark <rob@ti.com> + * Andy Gross <andy.gross@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef OMAP_DMM_PRIV_H +#define OMAP_DMM_PRIV_H + +#define DMM_REVISION 0x000 +#define DMM_HWINFO 0x004 +#define DMM_LISA_HWINFO 0x008 +#define DMM_DMM_SYSCONFIG 0x010 +#define DMM_LISA_LOCK 0x01C +#define DMM_LISA_MAP__0 0x040 +#define DMM_LISA_MAP__1 0x044 +#define DMM_TILER_HWINFO 0x208 +#define DMM_TILER_OR__0 0x220 +#define DMM_TILER_OR__1 0x224 +#define DMM_PAT_HWINFO 0x408 +#define DMM_PAT_GEOMETRY 0x40C +#define DMM_PAT_CONFIG 0x410 +#define DMM_PAT_VIEW__0 0x420 +#define DMM_PAT_VIEW__1 0x424 +#define DMM_PAT_VIEW_MAP__0 0x440 +#define DMM_PAT_VIEW_MAP_BASE 0x460 +#define DMM_PAT_IRQ_EOI 0x478 +#define DMM_PAT_IRQSTATUS_RAW 0x480 +#define DMM_PAT_IRQSTATUS 0x490 +#define DMM_PAT_IRQENABLE_SET 0x4A0 +#define DMM_PAT_IRQENABLE_CLR 0x4B0 +#define DMM_PAT_STATUS__0 0x4C0 +#define DMM_PAT_STATUS__1 0x4C4 +#define DMM_PAT_STATUS__2 0x4C8 +#define DMM_PAT_STATUS__3 0x4CC +#define DMM_PAT_DESCR__0 0x500 +#define DMM_PAT_DESCR__1 0x510 +#define DMM_PAT_DESCR__2 0x520 +#define DMM_PAT_DESCR__3 0x530 +#define DMM_PEG_HWINFO 0x608 +#define DMM_PEG_PRIO 0x620 +#define DMM_PEG_PRIO_PAT 0x640 + +#define DMM_IRQSTAT_DST (1<<0) +#define DMM_IRQSTAT_LST (1<<1) +#define DMM_IRQSTAT_ERR_INV_DSC (1<<2) +#define DMM_IRQSTAT_ERR_INV_DATA (1<<3) +#define DMM_IRQSTAT_ERR_UPD_AREA (1<<4) +#define DMM_IRQSTAT_ERR_UPD_CTRL (1<<5) +#define DMM_IRQSTAT_ERR_UPD_DATA (1<<6) +#define DMM_IRQSTAT_ERR_LUT_MISS (1<<7) + +#define DMM_IRQSTAT_ERR_MASK (DMM_IRQ_STAT_ERR_INV_DSC | \ + DMM_IRQ_STAT_ERR_INV_DATA | \ + DMM_IRQ_STAT_ERR_UPD_AREA | \ + DMM_IRQ_STAT_ERR_UPD_CTRL | \ + DMM_IRQ_STAT_ERR_UPD_DATA | \ + DMM_IRQ_STAT_ERR_LUT_MISS) + +#define DMM_PATSTATUS_READY (1<<0) +#define DMM_PATSTATUS_VALID (1<<1) +#define DMM_PATSTATUS_RUN (1<<2) +#define DMM_PATSTATUS_DONE (1<<3) +#define DMM_PATSTATUS_LINKED (1<<4) +#define DMM_PATSTATUS_BYPASSED (1<<7) +#define DMM_PATSTATUS_ERR_INV_DESCR (1<<10) +#define DMM_PATSTATUS_ERR_INV_DATA (1<<11) +#define DMM_PATSTATUS_ERR_UPD_AREA (1<<12) +#define DMM_PATSTATUS_ERR_UPD_CTRL (1<<13) +#define DMM_PATSTATUS_ERR_UPD_DATA (1<<14) +#define DMM_PATSTATUS_ERR_ACCESS (1<<15) + +/* note: don't treat DMM_PATSTATUS_ERR_ACCESS as an error */ +#define DMM_PATSTATUS_ERR (DMM_PATSTATUS_ERR_INV_DESCR | \ + DMM_PATSTATUS_ERR_INV_DATA | \ + DMM_PATSTATUS_ERR_UPD_AREA | \ + DMM_PATSTATUS_ERR_UPD_CTRL | \ + DMM_PATSTATUS_ERR_UPD_DATA) + + + +enum { + PAT_STATUS, + PAT_DESCR +}; + +struct pat_ctrl { + u32 start:4; + u32 dir:4; + u32 lut_id:8; + u32 sync:12; + u32 ini:4; +}; + +struct pat { + uint32_t next_pa; + struct pat_area area; + struct pat_ctrl ctrl; + uint32_t data_pa; +}; + +#define DMM_FIXED_RETRY_COUNT 1000 + +/* create refill buffer big enough to refill all slots, plus 3 descriptors.. + * 3 descriptors is probably the worst-case for # of 2d-slices in a 1d area, + * but I guess you don't hit that worst case at the same time as full area + * refill + */ +#define DESCR_SIZE 128 +#define REFILL_BUFFER_SIZE ((4 * 128 * 256) + (3 * DESCR_SIZE)) + +/* For OMAP5, a fixed offset is added to all Y coordinates for 1D buffers. + * This is used in programming to address the upper portion of the LUT +*/ +#define OMAP5_LUT_OFFSET 128 + +struct dmm; + +struct dmm_txn { + void *engine_handle; + struct tcm *tcm; + + uint8_t *current_va; + dma_addr_t current_pa; + + struct pat *last_pat; +}; + +struct refill_engine { + int id; + struct dmm *dmm; + struct tcm *tcm; + + uint8_t *refill_va; + dma_addr_t refill_pa; + + /* only one trans per engine for now */ + struct dmm_txn txn; + + bool async; + + wait_queue_head_t wait_for_refill; + + struct list_head idle_node; +}; + +struct dmm { + struct device *dev; + void __iomem *base; + int irq; + + struct page *dummy_page; + dma_addr_t dummy_pa; + + void *refill_va; + dma_addr_t refill_pa; + + /* refill engines */ + wait_queue_head_t engine_queue; + struct list_head idle_head; + struct refill_engine *engines; + int num_engines; + atomic_t engine_counter; + + /* container information */ + int container_width; + int container_height; + int lut_width; + int lut_height; + int num_lut; + + /* array of LUT - TCM containers */ + struct tcm **tcm; + + /* allocation list and lock */ + struct list_head alloc_head; +}; + +#endif diff --git a/drivers/gpu/drm/omapdrm/omap_dmm_tiler.c b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.c new file mode 100644 index 000000000000..391021537105 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.c @@ -0,0 +1,991 @@ +/* + * DMM IOMMU driver support functions for TI OMAP processors. + * + * Author: Rob Clark <rob@ti.com> + * Andy Gross <andy.gross@ti.com> + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> /* platform_device() */ +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/time.h> +#include <linux/list.h> + +#include "omap_dmm_tiler.h" +#include "omap_dmm_priv.h" + +#define DMM_DRIVER_NAME "dmm" + +/* mappings for associating views to luts */ +static struct tcm *containers[TILFMT_NFORMATS]; +static struct dmm *omap_dmm; + +/* global spinlock for protecting lists */ +static DEFINE_SPINLOCK(list_lock); + +/* Geometry table */ +#define GEOM(xshift, yshift, bytes_per_pixel) { \ + .x_shft = (xshift), \ + .y_shft = (yshift), \ + .cpp = (bytes_per_pixel), \ + .slot_w = 1 << (SLOT_WIDTH_BITS - (xshift)), \ + .slot_h = 1 << (SLOT_HEIGHT_BITS - (yshift)), \ + } + +static const struct { + uint32_t x_shft; /* unused X-bits (as part of bpp) */ + uint32_t y_shft; /* unused Y-bits (as part of bpp) */ + uint32_t cpp; /* bytes/chars per pixel */ + uint32_t slot_w; /* width of each slot (in pixels) */ + uint32_t slot_h; /* height of each slot (in pixels) */ +} geom[TILFMT_NFORMATS] = { + [TILFMT_8BIT] = GEOM(0, 0, 1), + [TILFMT_16BIT] = GEOM(0, 1, 2), + [TILFMT_32BIT] = GEOM(1, 1, 4), + [TILFMT_PAGE] = GEOM(SLOT_WIDTH_BITS, SLOT_HEIGHT_BITS, 1), +}; + + +/* lookup table for registers w/ per-engine instances */ +static const uint32_t reg[][4] = { + [PAT_STATUS] = {DMM_PAT_STATUS__0, DMM_PAT_STATUS__1, + DMM_PAT_STATUS__2, DMM_PAT_STATUS__3}, + [PAT_DESCR] = {DMM_PAT_DESCR__0, DMM_PAT_DESCR__1, + DMM_PAT_DESCR__2, DMM_PAT_DESCR__3}, +}; + +/* simple allocator to grab next 16 byte aligned memory from txn */ +static void *alloc_dma(struct dmm_txn *txn, size_t sz, dma_addr_t *pa) +{ + void *ptr; + struct refill_engine *engine = txn->engine_handle; + + /* dmm programming requires 16 byte aligned addresses */ + txn->current_pa = round_up(txn->current_pa, 16); + txn->current_va = (void *)round_up((long)txn->current_va, 16); + + ptr = txn->current_va; + *pa = txn->current_pa; + + txn->current_pa += sz; + txn->current_va += sz; + + BUG_ON((txn->current_va - engine->refill_va) > REFILL_BUFFER_SIZE); + + return ptr; +} + +/* check status and spin until wait_mask comes true */ +static int wait_status(struct refill_engine *engine, uint32_t wait_mask) +{ + struct dmm *dmm = engine->dmm; + uint32_t r = 0, err, i; + + i = DMM_FIXED_RETRY_COUNT; + while (true) { + r = readl(dmm->base + reg[PAT_STATUS][engine->id]); + err = r & DMM_PATSTATUS_ERR; + if (err) + return -EFAULT; + + if ((r & wait_mask) == wait_mask) + break; + + if (--i == 0) + return -ETIMEDOUT; + + udelay(1); + } + + return 0; +} + +static void release_engine(struct refill_engine *engine) +{ + unsigned long flags; + + spin_lock_irqsave(&list_lock, flags); + list_add(&engine->idle_node, &omap_dmm->idle_head); + spin_unlock_irqrestore(&list_lock, flags); + + atomic_inc(&omap_dmm->engine_counter); + wake_up_interruptible(&omap_dmm->engine_queue); +} + +static irqreturn_t omap_dmm_irq_handler(int irq, void *arg) +{ + struct dmm *dmm = arg; + uint32_t status = readl(dmm->base + DMM_PAT_IRQSTATUS); + int i; + + /* ack IRQ */ + writel(status, dmm->base + DMM_PAT_IRQSTATUS); + + for (i = 0; i < dmm->num_engines; i++) { + if (status & DMM_IRQSTAT_LST) { + wake_up_interruptible(&dmm->engines[i].wait_for_refill); + + if (dmm->engines[i].async) + release_engine(&dmm->engines[i]); + } + + status >>= 8; + } + + return IRQ_HANDLED; +} + +/** + * Get a handle for a DMM transaction + */ +static struct dmm_txn *dmm_txn_init(struct dmm *dmm, struct tcm *tcm) +{ + struct dmm_txn *txn = NULL; + struct refill_engine *engine = NULL; + int ret; + unsigned long flags; + + + /* wait until an engine is available */ + ret = wait_event_interruptible(omap_dmm->engine_queue, + atomic_add_unless(&omap_dmm->engine_counter, -1, 0)); + if (ret) + return ERR_PTR(ret); + + /* grab an idle engine */ + spin_lock_irqsave(&list_lock, flags); + if (!list_empty(&dmm->idle_head)) { + engine = list_entry(dmm->idle_head.next, struct refill_engine, + idle_node); + list_del(&engine->idle_node); + } + spin_unlock_irqrestore(&list_lock, flags); + + BUG_ON(!engine); + + txn = &engine->txn; + engine->tcm = tcm; + txn->engine_handle = engine; + txn->last_pat = NULL; + txn->current_va = engine->refill_va; + txn->current_pa = engine->refill_pa; + + return txn; +} + +/** + * Add region to DMM transaction. If pages or pages[i] is NULL, then the + * corresponding slot is cleared (ie. dummy_pa is programmed) + */ +static void dmm_txn_append(struct dmm_txn *txn, struct pat_area *area, + struct page **pages, uint32_t npages, uint32_t roll) +{ + dma_addr_t pat_pa = 0; + uint32_t *data; + struct pat *pat; + struct refill_engine *engine = txn->engine_handle; + int columns = (1 + area->x1 - area->x0); + int rows = (1 + area->y1 - area->y0); + int i = columns*rows; + + pat = alloc_dma(txn, sizeof(struct pat), &pat_pa); + + if (txn->last_pat) + txn->last_pat->next_pa = (uint32_t)pat_pa; + + pat->area = *area; + + /* adjust Y coordinates based off of container parameters */ + pat->area.y0 += engine->tcm->y_offset; + pat->area.y1 += engine->tcm->y_offset; + + pat->ctrl = (struct pat_ctrl){ + .start = 1, + .lut_id = engine->tcm->lut_id, + }; + + data = alloc_dma(txn, 4*i, &pat->data_pa); + + while (i--) { + int n = i + roll; + if (n >= npages) + n -= npages; + data[i] = (pages && pages[n]) ? + page_to_phys(pages[n]) : engine->dmm->dummy_pa; + } + + txn->last_pat = pat; + + return; +} + +/** + * Commit the DMM transaction. + */ +static int dmm_txn_commit(struct dmm_txn *txn, bool wait) +{ + int ret = 0; + struct refill_engine *engine = txn->engine_handle; + struct dmm *dmm = engine->dmm; + + if (!txn->last_pat) { + dev_err(engine->dmm->dev, "need at least one txn\n"); + ret = -EINVAL; + goto cleanup; + } + + txn->last_pat->next_pa = 0; + + /* write to PAT_DESCR to clear out any pending transaction */ + writel(0x0, dmm->base + reg[PAT_DESCR][engine->id]); + + /* wait for engine ready: */ + ret = wait_status(engine, DMM_PATSTATUS_READY); + if (ret) { + ret = -EFAULT; + goto cleanup; + } + + /* mark whether it is async to denote list management in IRQ handler */ + engine->async = wait ? false : true; + + /* kick reload */ + writel(engine->refill_pa, + dmm->base + reg[PAT_DESCR][engine->id]); + + if (wait) { + if (wait_event_interruptible_timeout(engine->wait_for_refill, + wait_status(engine, DMM_PATSTATUS_READY) == 0, + msecs_to_jiffies(1)) <= 0) { + dev_err(dmm->dev, "timed out waiting for done\n"); + ret = -ETIMEDOUT; + } + } + +cleanup: + /* only place engine back on list if we are done with it */ + if (ret || wait) + release_engine(engine); + + return ret; +} + +/* + * DMM programming + */ +static int fill(struct tcm_area *area, struct page **pages, + uint32_t npages, uint32_t roll, bool wait) +{ + int ret = 0; + struct tcm_area slice, area_s; + struct dmm_txn *txn; + + txn = dmm_txn_init(omap_dmm, area->tcm); + if (IS_ERR_OR_NULL(txn)) + return -ENOMEM; + + tcm_for_each_slice(slice, *area, area_s) { + struct pat_area p_area = { + .x0 = slice.p0.x, .y0 = slice.p0.y, + .x1 = slice.p1.x, .y1 = slice.p1.y, + }; + + dmm_txn_append(txn, &p_area, pages, npages, roll); + + roll += tcm_sizeof(slice); + } + + ret = dmm_txn_commit(txn, wait); + + return ret; +} + +/* + * Pin/unpin + */ + +/* note: slots for which pages[i] == NULL are filled w/ dummy page + */ +int tiler_pin(struct tiler_block *block, struct page **pages, + uint32_t npages, uint32_t roll, bool wait) +{ + int ret; + + ret = fill(&block->area, pages, npages, roll, wait); + + if (ret) + tiler_unpin(block); + + return ret; +} + +int tiler_unpin(struct tiler_block *block) +{ + return fill(&block->area, NULL, 0, 0, false); +} + +/* + * Reserve/release + */ +struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w, + uint16_t h, uint16_t align) +{ + struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL); + u32 min_align = 128; + int ret; + unsigned long flags; + + BUG_ON(!validfmt(fmt)); + + /* convert width/height to slots */ + w = DIV_ROUND_UP(w, geom[fmt].slot_w); + h = DIV_ROUND_UP(h, geom[fmt].slot_h); + + /* convert alignment to slots */ + min_align = max(min_align, (geom[fmt].slot_w * geom[fmt].cpp)); + align = ALIGN(align, min_align); + align /= geom[fmt].slot_w * geom[fmt].cpp; + + block->fmt = fmt; + + ret = tcm_reserve_2d(containers[fmt], w, h, align, &block->area); + if (ret) { + kfree(block); + return ERR_PTR(-ENOMEM); + } + + /* add to allocation list */ + spin_lock_irqsave(&list_lock, flags); + list_add(&block->alloc_node, &omap_dmm->alloc_head); + spin_unlock_irqrestore(&list_lock, flags); + + return block; +} + +struct tiler_block *tiler_reserve_1d(size_t size) +{ + struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL); + int num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + unsigned long flags; + + if (!block) + return ERR_PTR(-ENOMEM); + + block->fmt = TILFMT_PAGE; + + if (tcm_reserve_1d(containers[TILFMT_PAGE], num_pages, + &block->area)) { + kfree(block); + return ERR_PTR(-ENOMEM); + } + + spin_lock_irqsave(&list_lock, flags); + list_add(&block->alloc_node, &omap_dmm->alloc_head); + spin_unlock_irqrestore(&list_lock, flags); + + return block; +} + +/* note: if you have pin'd pages, you should have already unpin'd first! */ +int tiler_release(struct tiler_block *block) +{ + int ret = tcm_free(&block->area); + unsigned long flags; + + if (block->area.tcm) + dev_err(omap_dmm->dev, "failed to release block\n"); + + spin_lock_irqsave(&list_lock, flags); + list_del(&block->alloc_node); + spin_unlock_irqrestore(&list_lock, flags); + + kfree(block); + return ret; +} + +/* + * Utils + */ + +/* calculate the tiler space address of a pixel in a view orientation... + * below description copied from the display subsystem section of TRM: + * + * When the TILER is addressed, the bits: + * [28:27] = 0x0 for 8-bit tiled + * 0x1 for 16-bit tiled + * 0x2 for 32-bit tiled + * 0x3 for page mode + * [31:29] = 0x0 for 0-degree view + * 0x1 for 180-degree view + mirroring + * 0x2 for 0-degree view + mirroring + * 0x3 for 180-degree view + * 0x4 for 270-degree view + mirroring + * 0x5 for 270-degree view + * 0x6 for 90-degree view + * 0x7 for 90-degree view + mirroring + * Otherwise the bits indicated the corresponding bit address to access + * the SDRAM. + */ +static u32 tiler_get_address(enum tiler_fmt fmt, u32 orient, u32 x, u32 y) +{ + u32 x_bits, y_bits, tmp, x_mask, y_mask, alignment; + + x_bits = CONT_WIDTH_BITS - geom[fmt].x_shft; + y_bits = CONT_HEIGHT_BITS - geom[fmt].y_shft; + alignment = geom[fmt].x_shft + geom[fmt].y_shft; + + /* validate coordinate */ + x_mask = MASK(x_bits); + y_mask = MASK(y_bits); + + if (x < 0 || x > x_mask || y < 0 || y > y_mask) { + DBG("invalid coords: %u < 0 || %u > %u || %u < 0 || %u > %u", + x, x, x_mask, y, y, y_mask); + return 0; + } + + /* account for mirroring */ + if (orient & MASK_X_INVERT) + x ^= x_mask; + if (orient & MASK_Y_INVERT) + y ^= y_mask; + + /* get coordinate address */ + if (orient & MASK_XY_FLIP) + tmp = ((x << y_bits) + y); + else + tmp = ((y << x_bits) + x); + + return TIL_ADDR((tmp << alignment), orient, fmt); +} + +dma_addr_t tiler_ssptr(struct tiler_block *block) +{ + BUG_ON(!validfmt(block->fmt)); + + return TILVIEW_8BIT + tiler_get_address(block->fmt, 0, + block->area.p0.x * geom[block->fmt].slot_w, + block->area.p0.y * geom[block->fmt].slot_h); +} + +dma_addr_t tiler_tsptr(struct tiler_block *block, uint32_t orient, + uint32_t x, uint32_t y) +{ + struct tcm_pt *p = &block->area.p0; + BUG_ON(!validfmt(block->fmt)); + + return tiler_get_address(block->fmt, orient, + (p->x * geom[block->fmt].slot_w) + x, + (p->y * geom[block->fmt].slot_h) + y); +} + +void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h) +{ + BUG_ON(!validfmt(fmt)); + *w = round_up(*w, geom[fmt].slot_w); + *h = round_up(*h, geom[fmt].slot_h); +} + +uint32_t tiler_stride(enum tiler_fmt fmt, uint32_t orient) +{ + BUG_ON(!validfmt(fmt)); + + if (orient & MASK_XY_FLIP) + return 1 << (CONT_HEIGHT_BITS + geom[fmt].x_shft); + else + return 1 << (CONT_WIDTH_BITS + geom[fmt].y_shft); +} + +size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h) +{ + tiler_align(fmt, &w, &h); + return geom[fmt].cpp * w * h; +} + +size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h) +{ + BUG_ON(!validfmt(fmt)); + return round_up(geom[fmt].cpp * w, PAGE_SIZE) * h; +} + +bool dmm_is_available(void) +{ + return omap_dmm ? true : false; +} + +static int omap_dmm_remove(struct platform_device *dev) +{ + struct tiler_block *block, *_block; + int i; + unsigned long flags; + + if (omap_dmm) { + /* free all area regions */ + spin_lock_irqsave(&list_lock, flags); + list_for_each_entry_safe(block, _block, &omap_dmm->alloc_head, + alloc_node) { + list_del(&block->alloc_node); + kfree(block); + } + spin_unlock_irqrestore(&list_lock, flags); + + for (i = 0; i < omap_dmm->num_lut; i++) + if (omap_dmm->tcm && omap_dmm->tcm[i]) + omap_dmm->tcm[i]->deinit(omap_dmm->tcm[i]); + kfree(omap_dmm->tcm); + + kfree(omap_dmm->engines); + if (omap_dmm->refill_va) + dma_free_writecombine(omap_dmm->dev, + REFILL_BUFFER_SIZE * omap_dmm->num_engines, + omap_dmm->refill_va, + omap_dmm->refill_pa); + if (omap_dmm->dummy_page) + __free_page(omap_dmm->dummy_page); + + if (omap_dmm->irq > 0) + free_irq(omap_dmm->irq, omap_dmm); + + iounmap(omap_dmm->base); + kfree(omap_dmm); + omap_dmm = NULL; + } + + return 0; +} + +static int omap_dmm_probe(struct platform_device *dev) +{ + int ret = -EFAULT, i; + struct tcm_area area = {0}; + u32 hwinfo, pat_geom; + struct resource *mem; + + omap_dmm = kzalloc(sizeof(*omap_dmm), GFP_KERNEL); + if (!omap_dmm) { + dev_err(&dev->dev, "failed to allocate driver data section\n"); + goto fail; + } + + /* initialize lists */ + INIT_LIST_HEAD(&omap_dmm->alloc_head); + INIT_LIST_HEAD(&omap_dmm->idle_head); + + init_waitqueue_head(&omap_dmm->engine_queue); + + /* lookup hwmod data - base address and irq */ + mem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&dev->dev, "failed to get base address resource\n"); + goto fail; + } + + omap_dmm->base = ioremap(mem->start, SZ_2K); + + if (!omap_dmm->base) { + dev_err(&dev->dev, "failed to get dmm base address\n"); + goto fail; + } + + omap_dmm->irq = platform_get_irq(dev, 0); + if (omap_dmm->irq < 0) { + dev_err(&dev->dev, "failed to get IRQ resource\n"); + goto fail; + } + + omap_dmm->dev = &dev->dev; + + hwinfo = readl(omap_dmm->base + DMM_PAT_HWINFO); + omap_dmm->num_engines = (hwinfo >> 24) & 0x1F; + omap_dmm->num_lut = (hwinfo >> 16) & 0x1F; + omap_dmm->container_width = 256; + omap_dmm->container_height = 128; + + atomic_set(&omap_dmm->engine_counter, omap_dmm->num_engines); + + /* read out actual LUT width and height */ + pat_geom = readl(omap_dmm->base + DMM_PAT_GEOMETRY); + omap_dmm->lut_width = ((pat_geom >> 16) & 0xF) << 5; + omap_dmm->lut_height = ((pat_geom >> 24) & 0xF) << 5; + + /* increment LUT by one if on OMAP5 */ + /* LUT has twice the height, and is split into a separate container */ + if (omap_dmm->lut_height != omap_dmm->container_height) + omap_dmm->num_lut++; + + /* initialize DMM registers */ + writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__0); + writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__1); + writel(0x80808080, omap_dmm->base + DMM_PAT_VIEW_MAP__0); + writel(0x80000000, omap_dmm->base + DMM_PAT_VIEW_MAP_BASE); + writel(0x88888888, omap_dmm->base + DMM_TILER_OR__0); + writel(0x88888888, omap_dmm->base + DMM_TILER_OR__1); + + ret = request_irq(omap_dmm->irq, omap_dmm_irq_handler, IRQF_SHARED, + "omap_dmm_irq_handler", omap_dmm); + + if (ret) { + dev_err(&dev->dev, "couldn't register IRQ %d, error %d\n", + omap_dmm->irq, ret); + omap_dmm->irq = -1; + goto fail; + } + + /* Enable all interrupts for each refill engine except + * ERR_LUT_MISS<n> (which is just advisory, and we don't care + * about because we want to be able to refill live scanout + * buffers for accelerated pan/scroll) and FILL_DSC<n> which + * we just generally don't care about. + */ + writel(0x7e7e7e7e, omap_dmm->base + DMM_PAT_IRQENABLE_SET); + + omap_dmm->dummy_page = alloc_page(GFP_KERNEL | __GFP_DMA32); + if (!omap_dmm->dummy_page) { + dev_err(&dev->dev, "could not allocate dummy page\n"); + ret = -ENOMEM; + goto fail; + } + + /* set dma mask for device */ + /* NOTE: this is a workaround for the hwmod not initializing properly */ + dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + omap_dmm->dummy_pa = page_to_phys(omap_dmm->dummy_page); + + /* alloc refill memory */ + omap_dmm->refill_va = dma_alloc_writecombine(&dev->dev, + REFILL_BUFFER_SIZE * omap_dmm->num_engines, + &omap_dmm->refill_pa, GFP_KERNEL); + if (!omap_dmm->refill_va) { + dev_err(&dev->dev, "could not allocate refill memory\n"); + goto fail; + } + + /* alloc engines */ + omap_dmm->engines = kzalloc( + omap_dmm->num_engines * sizeof(struct refill_engine), + GFP_KERNEL); + if (!omap_dmm->engines) { + dev_err(&dev->dev, "could not allocate engines\n"); + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < omap_dmm->num_engines; i++) { + omap_dmm->engines[i].id = i; + omap_dmm->engines[i].dmm = omap_dmm; + omap_dmm->engines[i].refill_va = omap_dmm->refill_va + + (REFILL_BUFFER_SIZE * i); + omap_dmm->engines[i].refill_pa = omap_dmm->refill_pa + + (REFILL_BUFFER_SIZE * i); + init_waitqueue_head(&omap_dmm->engines[i].wait_for_refill); + + list_add(&omap_dmm->engines[i].idle_node, &omap_dmm->idle_head); + } + + omap_dmm->tcm = kzalloc(omap_dmm->num_lut * sizeof(*omap_dmm->tcm), + GFP_KERNEL); + if (!omap_dmm->tcm) { + dev_err(&dev->dev, "failed to allocate lut ptrs\n"); + ret = -ENOMEM; + goto fail; + } + + /* init containers */ + /* Each LUT is associated with a TCM (container manager). We use the + lut_id to denote the lut_id used to identify the correct LUT for + programming during reill operations */ + for (i = 0; i < omap_dmm->num_lut; i++) { + omap_dmm->tcm[i] = sita_init(omap_dmm->container_width, + omap_dmm->container_height, + NULL); + + if (!omap_dmm->tcm[i]) { + dev_err(&dev->dev, "failed to allocate container\n"); + ret = -ENOMEM; + goto fail; + } + + omap_dmm->tcm[i]->lut_id = i; + } + + /* assign access mode containers to applicable tcm container */ + /* OMAP 4 has 1 container for all 4 views */ + /* OMAP 5 has 2 containers, 1 for 2D and 1 for 1D */ + containers[TILFMT_8BIT] = omap_dmm->tcm[0]; + containers[TILFMT_16BIT] = omap_dmm->tcm[0]; + containers[TILFMT_32BIT] = omap_dmm->tcm[0]; + + if (omap_dmm->container_height != omap_dmm->lut_height) { + /* second LUT is used for PAGE mode. Programming must use + y offset that is added to all y coordinates. LUT id is still + 0, because it is the same LUT, just the upper 128 lines */ + containers[TILFMT_PAGE] = omap_dmm->tcm[1]; + omap_dmm->tcm[1]->y_offset = OMAP5_LUT_OFFSET; + omap_dmm->tcm[1]->lut_id = 0; + } else { + containers[TILFMT_PAGE] = omap_dmm->tcm[0]; + } + + area = (struct tcm_area) { + .tcm = NULL, + .p1.x = omap_dmm->container_width - 1, + .p1.y = omap_dmm->container_height - 1, + }; + + /* initialize all LUTs to dummy page entries */ + for (i = 0; i < omap_dmm->num_lut; i++) { + area.tcm = omap_dmm->tcm[i]; + if (fill(&area, NULL, 0, 0, true)) + dev_err(omap_dmm->dev, "refill failed"); + } + + dev_info(omap_dmm->dev, "initialized all PAT entries\n"); + + return 0; + +fail: + if (omap_dmm_remove(dev)) + dev_err(&dev->dev, "cleanup failed\n"); + return ret; +} + +/* + * debugfs support + */ + +#ifdef CONFIG_DEBUG_FS + +static const char *alphabet = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; +static const char *special = ".,:;'\"`~!^-+"; + +static void fill_map(char **map, int xdiv, int ydiv, struct tcm_area *a, + char c, bool ovw) +{ + int x, y; + for (y = a->p0.y / ydiv; y <= a->p1.y / ydiv; y++) + for (x = a->p0.x / xdiv; x <= a->p1.x / xdiv; x++) + if (map[y][x] == ' ' || ovw) + map[y][x] = c; +} + +static void fill_map_pt(char **map, int xdiv, int ydiv, struct tcm_pt *p, + char c) +{ + map[p->y / ydiv][p->x / xdiv] = c; +} + +static char read_map_pt(char **map, int xdiv, int ydiv, struct tcm_pt *p) +{ + return map[p->y / ydiv][p->x / xdiv]; +} + +static int map_width(int xdiv, int x0, int x1) +{ + return (x1 / xdiv) - (x0 / xdiv) + 1; +} + +static void text_map(char **map, int xdiv, char *nice, int yd, int x0, int x1) +{ + char *p = map[yd] + (x0 / xdiv); + int w = (map_width(xdiv, x0, x1) - strlen(nice)) / 2; + if (w >= 0) { + p += w; + while (*nice) + *p++ = *nice++; + } +} + +static void map_1d_info(char **map, int xdiv, int ydiv, char *nice, + struct tcm_area *a) +{ + sprintf(nice, "%dK", tcm_sizeof(*a) * 4); + if (a->p0.y + 1 < a->p1.y) { + text_map(map, xdiv, nice, (a->p0.y + a->p1.y) / 2 / ydiv, 0, + 256 - 1); + } else if (a->p0.y < a->p1.y) { + if (strlen(nice) < map_width(xdiv, a->p0.x, 256 - 1)) + text_map(map, xdiv, nice, a->p0.y / ydiv, + a->p0.x + xdiv, 256 - 1); + else if (strlen(nice) < map_width(xdiv, 0, a->p1.x)) + text_map(map, xdiv, nice, a->p1.y / ydiv, + 0, a->p1.y - xdiv); + } else if (strlen(nice) + 1 < map_width(xdiv, a->p0.x, a->p1.x)) { + text_map(map, xdiv, nice, a->p0.y / ydiv, a->p0.x, a->p1.x); + } +} + +static void map_2d_info(char **map, int xdiv, int ydiv, char *nice, + struct tcm_area *a) +{ + sprintf(nice, "(%d*%d)", tcm_awidth(*a), tcm_aheight(*a)); + if (strlen(nice) + 1 < map_width(xdiv, a->p0.x, a->p1.x)) + text_map(map, xdiv, nice, (a->p0.y + a->p1.y) / 2 / ydiv, + a->p0.x, a->p1.x); +} + +int tiler_map_show(struct seq_file *s, void *arg) +{ + int xdiv = 2, ydiv = 1; + char **map = NULL, *global_map; + struct tiler_block *block; + struct tcm_area a, p; + int i; + const char *m2d = alphabet; + const char *a2d = special; + const char *m2dp = m2d, *a2dp = a2d; + char nice[128]; + int h_adj; + int w_adj; + unsigned long flags; + int lut_idx; + + + if (!omap_dmm) { + /* early return if dmm/tiler device is not initialized */ + return 0; + } + + h_adj = omap_dmm->container_height / ydiv; + w_adj = omap_dmm->container_width / xdiv; + + map = kmalloc(h_adj * sizeof(*map), GFP_KERNEL); + global_map = kmalloc((w_adj + 1) * h_adj, GFP_KERNEL); + + if (!map || !global_map) + goto error; + + for (lut_idx = 0; lut_idx < omap_dmm->num_lut; lut_idx++) { + memset(map, 0, sizeof(h_adj * sizeof(*map))); + memset(global_map, ' ', (w_adj + 1) * h_adj); + + for (i = 0; i < omap_dmm->container_height; i++) { + map[i] = global_map + i * (w_adj + 1); + map[i][w_adj] = 0; + } + + spin_lock_irqsave(&list_lock, flags); + + list_for_each_entry(block, &omap_dmm->alloc_head, alloc_node) { + if (block->area.tcm == omap_dmm->tcm[lut_idx]) { + if (block->fmt != TILFMT_PAGE) { + fill_map(map, xdiv, ydiv, &block->area, + *m2dp, true); + if (!*++a2dp) + a2dp = a2d; + if (!*++m2dp) + m2dp = m2d; + map_2d_info(map, xdiv, ydiv, nice, + &block->area); + } else { + bool start = read_map_pt(map, xdiv, + ydiv, &block->area.p0) == ' '; + bool end = read_map_pt(map, xdiv, ydiv, + &block->area.p1) == ' '; + + tcm_for_each_slice(a, block->area, p) + fill_map(map, xdiv, ydiv, &a, + '=', true); + fill_map_pt(map, xdiv, ydiv, + &block->area.p0, + start ? '<' : 'X'); + fill_map_pt(map, xdiv, ydiv, + &block->area.p1, + end ? '>' : 'X'); + map_1d_info(map, xdiv, ydiv, nice, + &block->area); + } + } + } + + spin_unlock_irqrestore(&list_lock, flags); + + if (s) { + seq_printf(s, "CONTAINER %d DUMP BEGIN\n", lut_idx); + for (i = 0; i < 128; i++) + seq_printf(s, "%03d:%s\n", i, map[i]); + seq_printf(s, "CONTAINER %d DUMP END\n", lut_idx); + } else { + dev_dbg(omap_dmm->dev, "CONTAINER %d DUMP BEGIN\n", + lut_idx); + for (i = 0; i < 128; i++) + dev_dbg(omap_dmm->dev, "%03d:%s\n", i, map[i]); + dev_dbg(omap_dmm->dev, "CONTAINER %d DUMP END\n", + lut_idx); + } + } + +error: + kfree(map); + kfree(global_map); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int omap_dmm_resume(struct device *dev) +{ + struct tcm_area area; + int i; + + if (!omap_dmm) + return -ENODEV; + + area = (struct tcm_area) { + .tcm = NULL, + .p1.x = omap_dmm->container_width - 1, + .p1.y = omap_dmm->container_height - 1, + }; + + /* initialize all LUTs to dummy page entries */ + for (i = 0; i < omap_dmm->num_lut; i++) { + area.tcm = omap_dmm->tcm[i]; + if (fill(&area, NULL, 0, 0, true)) + dev_err(dev, "refill failed"); + } + + return 0; +} + +static const struct dev_pm_ops omap_dmm_pm_ops = { + .resume = omap_dmm_resume, +}; +#endif + +struct platform_driver omap_dmm_driver = { + .probe = omap_dmm_probe, + .remove = omap_dmm_remove, + .driver = { + .owner = THIS_MODULE, + .name = DMM_DRIVER_NAME, +#ifdef CONFIG_PM + .pm = &omap_dmm_pm_ops, +#endif + }, +}; + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Andy Gross <andy.gross@ti.com>"); +MODULE_DESCRIPTION("OMAP DMM/Tiler Driver"); +MODULE_ALIAS("platform:" DMM_DRIVER_NAME); diff --git a/drivers/gpu/drm/omapdrm/omap_dmm_tiler.h b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.h new file mode 100644 index 000000000000..4fdd61e54bd2 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.h @@ -0,0 +1,141 @@ +/* + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Rob Clark <rob@ti.com> + * Andy Gross <andy.gross@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef OMAP_DMM_TILER_H +#define OMAP_DMM_TILER_H + +#include "omap_drv.h" +#include "tcm.h" + +enum tiler_fmt { + TILFMT_8BIT = 0, + TILFMT_16BIT, + TILFMT_32BIT, + TILFMT_PAGE, + TILFMT_NFORMATS +}; + +struct pat_area { + u32 x0:8; + u32 y0:8; + u32 x1:8; + u32 y1:8; +}; + +struct tiler_block { + struct list_head alloc_node; /* node for global block list */ + struct tcm_area area; /* area */ + enum tiler_fmt fmt; /* format */ +}; + +/* bits representing the same slot in DMM-TILER hw-block */ +#define SLOT_WIDTH_BITS 6 +#define SLOT_HEIGHT_BITS 6 + +/* bits reserved to describe coordinates in DMM-TILER hw-block */ +#define CONT_WIDTH_BITS 14 +#define CONT_HEIGHT_BITS 13 + +/* calculated constants */ +#define TILER_PAGE (1 << (SLOT_WIDTH_BITS + SLOT_HEIGHT_BITS)) +#define TILER_WIDTH (1 << (CONT_WIDTH_BITS - SLOT_WIDTH_BITS)) +#define TILER_HEIGHT (1 << (CONT_HEIGHT_BITS - SLOT_HEIGHT_BITS)) + +/* +Table 15-11. Coding and Description of TILER Orientations +S Y X Description Alternate description +0 0 0 0-degree view Natural view +0 0 1 0-degree view with vertical mirror 180-degree view with horizontal mirror +0 1 0 0-degree view with horizontal mirror 180-degree view with vertical mirror +0 1 1 180-degree view +1 0 0 90-degree view with vertical mirror 270-degree view with horizontal mirror +1 0 1 270-degree view +1 1 0 90-degree view +1 1 1 90-degree view with horizontal mirror 270-degree view with vertical mirror + */ +#define MASK_XY_FLIP (1 << 31) +#define MASK_Y_INVERT (1 << 30) +#define MASK_X_INVERT (1 << 29) +#define SHIFT_ACC_MODE 27 +#define MASK_ACC_MODE 3 + +#define MASK(bits) ((1 << (bits)) - 1) + +#define TILVIEW_8BIT 0x60000000u +#define TILVIEW_16BIT (TILVIEW_8BIT + VIEW_SIZE) +#define TILVIEW_32BIT (TILVIEW_16BIT + VIEW_SIZE) +#define TILVIEW_PAGE (TILVIEW_32BIT + VIEW_SIZE) +#define TILVIEW_END (TILVIEW_PAGE + VIEW_SIZE) + +/* create tsptr by adding view orientation and access mode */ +#define TIL_ADDR(x, orient, a)\ + ((u32) (x) | (orient) | ((a) << SHIFT_ACC_MODE)) + +#ifdef CONFIG_DEBUG_FS +int tiler_map_show(struct seq_file *s, void *arg); +#endif + +/* pin/unpin */ +int tiler_pin(struct tiler_block *block, struct page **pages, + uint32_t npages, uint32_t roll, bool wait); +int tiler_unpin(struct tiler_block *block); + +/* reserve/release */ +struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w, uint16_t h, + uint16_t align); +struct tiler_block *tiler_reserve_1d(size_t size); +int tiler_release(struct tiler_block *block); + +/* utilities */ +dma_addr_t tiler_ssptr(struct tiler_block *block); +dma_addr_t tiler_tsptr(struct tiler_block *block, uint32_t orient, + uint32_t x, uint32_t y); +uint32_t tiler_stride(enum tiler_fmt fmt, uint32_t orient); +size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h); +size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h); +void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h); +bool dmm_is_available(void); + +extern struct platform_driver omap_dmm_driver; + +/* GEM bo flags -> tiler fmt */ +static inline enum tiler_fmt gem2fmt(uint32_t flags) +{ + switch (flags & OMAP_BO_TILED) { + case OMAP_BO_TILED_8: + return TILFMT_8BIT; + case OMAP_BO_TILED_16: + return TILFMT_16BIT; + case OMAP_BO_TILED_32: + return TILFMT_32BIT; + default: + return TILFMT_PAGE; + } +} + +static inline bool validfmt(enum tiler_fmt fmt) +{ + switch (fmt) { + case TILFMT_8BIT: + case TILFMT_16BIT: + case TILFMT_32BIT: + case TILFMT_PAGE: + return true; + default: + return false; + } +} + +#endif diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c new file mode 100644 index 000000000000..9083538bd16a --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_drv.c @@ -0,0 +1,610 @@ +/* + * drivers/gpu/drm/omapdrm/omap_drv.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc_helper.h" +#include "drm_fb_helper.h" +#include "omap_dmm_tiler.h" + +#define DRIVER_NAME MODULE_NAME +#define DRIVER_DESC "OMAP DRM" +#define DRIVER_DATE "20110917" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +static int num_crtc = CONFIG_DRM_OMAP_NUM_CRTCS; + +MODULE_PARM_DESC(num_crtc, "Number of overlays to use as CRTCs"); +module_param(num_crtc, int, 0600); + +/* + * mode config funcs + */ + +/* Notes about mapping DSS and DRM entities: + * CRTC: overlay + * encoder: manager.. with some extension to allow one primary CRTC + * and zero or more video CRTC's to be mapped to one encoder? + * connector: dssdev.. manager can be attached/detached from different + * devices + */ + +static void omap_fb_output_poll_changed(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + DBG("dev=%p", dev); + if (priv->fbdev) + drm_fb_helper_hotplug_event(priv->fbdev); +} + +static const struct drm_mode_config_funcs omap_mode_config_funcs = { + .fb_create = omap_framebuffer_create, + .output_poll_changed = omap_fb_output_poll_changed, +}; + +static int get_connector_type(struct omap_dss_device *dssdev) +{ + switch (dssdev->type) { + case OMAP_DISPLAY_TYPE_HDMI: + return DRM_MODE_CONNECTOR_HDMIA; + case OMAP_DISPLAY_TYPE_DPI: + if (!strcmp(dssdev->name, "dvi")) + return DRM_MODE_CONNECTOR_DVID; + /* fallthrough */ + default: + return DRM_MODE_CONNECTOR_Unknown; + } +} + +static int omap_modeset_init(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_dss_device *dssdev = NULL; + int num_ovls = dss_feat_get_num_ovls(); + int id; + + drm_mode_config_init(dev); + + omap_drm_irq_install(dev); + + /* + * Create private planes and CRTCs for the last NUM_CRTCs overlay + * plus manager: + */ + for (id = 0; id < min(num_crtc, num_ovls); id++) { + struct drm_plane *plane; + struct drm_crtc *crtc; + + plane = omap_plane_init(dev, id, true); + crtc = omap_crtc_init(dev, plane, pipe2chan(id), id); + + BUG_ON(priv->num_crtcs >= ARRAY_SIZE(priv->crtcs)); + priv->crtcs[id] = crtc; + priv->num_crtcs++; + + priv->planes[id] = plane; + priv->num_planes++; + } + + /* + * Create normal planes for the remaining overlays: + */ + for (; id < num_ovls; id++) { + struct drm_plane *plane = omap_plane_init(dev, id, false); + + BUG_ON(priv->num_planes >= ARRAY_SIZE(priv->planes)); + priv->planes[priv->num_planes++] = plane; + } + + for_each_dss_dev(dssdev) { + struct drm_connector *connector; + struct drm_encoder *encoder; + + if (!dssdev->driver) { + dev_warn(dev->dev, "%s has no driver.. skipping it\n", + dssdev->name); + return 0; + } + + if (!(dssdev->driver->get_timings || + dssdev->driver->read_edid)) { + dev_warn(dev->dev, "%s driver does not support " + "get_timings or read_edid.. skipping it!\n", + dssdev->name); + return 0; + } + + encoder = omap_encoder_init(dev, dssdev); + + if (!encoder) { + dev_err(dev->dev, "could not create encoder: %s\n", + dssdev->name); + return -ENOMEM; + } + + connector = omap_connector_init(dev, + get_connector_type(dssdev), dssdev, encoder); + + if (!connector) { + dev_err(dev->dev, "could not create connector: %s\n", + dssdev->name); + return -ENOMEM; + } + + BUG_ON(priv->num_encoders >= ARRAY_SIZE(priv->encoders)); + BUG_ON(priv->num_connectors >= ARRAY_SIZE(priv->connectors)); + + priv->encoders[priv->num_encoders++] = encoder; + priv->connectors[priv->num_connectors++] = connector; + + drm_mode_connector_attach_encoder(connector, encoder); + + /* figure out which crtc's we can connect the encoder to: */ + encoder->possible_crtcs = 0; + for (id = 0; id < priv->num_crtcs; id++) { + enum omap_dss_output_id supported_outputs = + dss_feat_get_supported_outputs(pipe2chan(id)); + if (supported_outputs & dssdev->output->id) + encoder->possible_crtcs |= (1 << id); + } + } + + dev->mode_config.min_width = 32; + dev->mode_config.min_height = 32; + + /* note: eventually will need some cpu_is_omapXYZ() type stuff here + * to fill in these limits properly on different OMAP generations.. + */ + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + + dev->mode_config.funcs = &omap_mode_config_funcs; + + return 0; +} + +static void omap_modeset_free(struct drm_device *dev) +{ + drm_mode_config_cleanup(dev); +} + +/* + * drm ioctl funcs + */ + + +static int ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_omap_param *args = data; + + DBG("%p: param=%llu", dev, args->param); + + switch (args->param) { + case OMAP_PARAM_CHIPSET_ID: + args->value = priv->omaprev; + break; + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_set_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_param *args = data; + + switch (args->param) { + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_gem_new(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_new *args = data; + VERB("%p:%p: size=0x%08x, flags=%08x", dev, file_priv, + args->size.bytes, args->flags); + return omap_gem_new_handle(dev, file_priv, args->size, + args->flags, &args->handle); +} + +static int ioctl_gem_cpu_prep(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_prep *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d, op=%x", dev, file_priv, args->handle, args->op); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) + return -ENOENT; + + ret = omap_gem_op_sync(obj, args->op); + + if (!ret) + ret = omap_gem_op_start(obj, args->op); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_cpu_fini(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_fini *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) + return -ENOENT; + + /* XXX flushy, flushy */ + ret = 0; + + if (!ret) + ret = omap_gem_op_finish(obj, args->op); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_info(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_info *args = data; + struct drm_gem_object *obj; + int ret = 0; + + VERB("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) + return -ENOENT; + + args->size = omap_gem_mmap_size(obj); + args->offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = { + DRM_IOCTL_DEF_DRV(OMAP_GET_PARAM, ioctl_get_param, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_SET_PARAM, ioctl_set_param, DRM_UNLOCKED|DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), + DRM_IOCTL_DEF_DRV(OMAP_GEM_NEW, ioctl_gem_new, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_PREP, ioctl_gem_cpu_prep, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_FINI, ioctl_gem_cpu_fini, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_INFO, ioctl_gem_info, DRM_UNLOCKED|DRM_AUTH), +}; + +/* + * drm driver funcs + */ + +/** + * load - setup chip and create an initial config + * @dev: DRM device + * @flags: startup flags + * + * The driver load routine has to do several things: + * - initialize the memory manager + * - allocate initial config memory + * - setup the DRM framebuffer with the allocated memory + */ +static int dev_load(struct drm_device *dev, unsigned long flags) +{ + struct omap_drm_platform_data *pdata = dev->dev->platform_data; + struct omap_drm_private *priv; + int ret; + + DBG("load: dev=%p", dev); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev->dev, "could not allocate priv\n"); + return -ENOMEM; + } + + priv->omaprev = pdata->omaprev; + + dev->dev_private = priv; + + priv->wq = alloc_ordered_workqueue("omapdrm", 0); + + INIT_LIST_HEAD(&priv->obj_list); + + omap_gem_init(dev); + + ret = omap_modeset_init(dev); + if (ret) { + dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret); + dev->dev_private = NULL; + kfree(priv); + return ret; + } + + ret = drm_vblank_init(dev, priv->num_crtcs); + if (ret) + dev_warn(dev->dev, "could not init vblank\n"); + + priv->fbdev = omap_fbdev_init(dev); + if (!priv->fbdev) { + dev_warn(dev->dev, "omap_fbdev_init failed\n"); + /* well, limp along without an fbdev.. maybe X11 will work? */ + } + + /* store off drm_device for use in pm ops */ + dev_set_drvdata(dev->dev, dev); + + drm_kms_helper_poll_init(dev); + + return 0; +} + +static int dev_unload(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + + DBG("unload: dev=%p", dev); + + drm_kms_helper_poll_fini(dev); + drm_vblank_cleanup(dev); + omap_drm_irq_uninstall(dev); + + omap_fbdev_free(dev); + omap_modeset_free(dev); + omap_gem_deinit(dev); + + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + kfree(dev->dev_private); + dev->dev_private = NULL; + + dev_set_drvdata(dev->dev, NULL); + + return 0; +} + +static int dev_open(struct drm_device *dev, struct drm_file *file) +{ + file->driver_priv = NULL; + + DBG("open: dev=%p, file=%p", dev, file); + + return 0; +} + +static int dev_firstopen(struct drm_device *dev) +{ + DBG("firstopen: dev=%p", dev); + return 0; +} + +/** + * lastclose - clean up after all DRM clients have exited + * @dev: DRM device + * + * Take care of cleaning up after all DRM clients have exited. In the + * mode setting case, we want to restore the kernel's initial mode (just + * in case the last client left us in a bad state). + */ +static void dev_lastclose(struct drm_device *dev) +{ + int i; + + /* we don't support vga-switcheroo.. so just make sure the fbdev + * mode is active + */ + struct omap_drm_private *priv = dev->dev_private; + int ret; + + DBG("lastclose: dev=%p", dev); + + if (priv->rotation_prop) { + /* need to restore default rotation state.. not sure + * if there is a cleaner way to restore properties to + * default state? Maybe a flag that properties should + * automatically be restored to default state on + * lastclose? + */ + for (i = 0; i < priv->num_crtcs; i++) { + drm_object_property_set_value(&priv->crtcs[i]->base, + priv->rotation_prop, 0); + } + + for (i = 0; i < priv->num_planes; i++) { + drm_object_property_set_value(&priv->planes[i]->base, + priv->rotation_prop, 0); + } + } + + drm_modeset_lock_all(dev); + ret = drm_fb_helper_restore_fbdev_mode(priv->fbdev); + drm_modeset_unlock_all(dev); + if (ret) + DBG("failed to restore crtc mode"); +} + +static void dev_preclose(struct drm_device *dev, struct drm_file *file) +{ + DBG("preclose: dev=%p", dev); +} + +static void dev_postclose(struct drm_device *dev, struct drm_file *file) +{ + DBG("postclose: dev=%p, file=%p", dev, file); +} + +static const struct vm_operations_struct omap_gem_vm_ops = { + .fault = omap_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct file_operations omapdriver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, + .mmap = omap_gem_mmap, + .poll = drm_poll, + .fasync = drm_fasync, + .read = drm_read, + .llseek = noop_llseek, +}; + +static struct drm_driver omap_drm_driver = { + .driver_features = + DRIVER_HAVE_IRQ | DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, + .load = dev_load, + .unload = dev_unload, + .open = dev_open, + .firstopen = dev_firstopen, + .lastclose = dev_lastclose, + .preclose = dev_preclose, + .postclose = dev_postclose, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = omap_irq_enable_vblank, + .disable_vblank = omap_irq_disable_vblank, + .irq_preinstall = omap_irq_preinstall, + .irq_postinstall = omap_irq_postinstall, + .irq_uninstall = omap_irq_uninstall, + .irq_handler = omap_irq_handler, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = omap_debugfs_init, + .debugfs_cleanup = omap_debugfs_cleanup, +#endif + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = omap_gem_prime_export, + .gem_prime_import = omap_gem_prime_import, + .gem_init_object = omap_gem_init_object, + .gem_free_object = omap_gem_free_object, + .gem_vm_ops = &omap_gem_vm_ops, + .dumb_create = omap_gem_dumb_create, + .dumb_map_offset = omap_gem_dumb_map_offset, + .dumb_destroy = omap_gem_dumb_destroy, + .ioctls = ioctls, + .num_ioctls = DRM_OMAP_NUM_IOCTLS, + .fops = &omapdriver_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +static int pdev_suspend(struct platform_device *pDevice, pm_message_t state) +{ + DBG(""); + return 0; +} + +static int pdev_resume(struct platform_device *device) +{ + DBG(""); + return 0; +} + +static void pdev_shutdown(struct platform_device *device) +{ + DBG(""); +} + +static int pdev_probe(struct platform_device *device) +{ + DBG("%s", device->name); + return drm_platform_init(&omap_drm_driver, device); +} + +static int pdev_remove(struct platform_device *device) +{ + DBG(""); + drm_platform_exit(&omap_drm_driver, device); + + platform_driver_unregister(&omap_dmm_driver); + return 0; +} + +#ifdef CONFIG_PM +static const struct dev_pm_ops omapdrm_pm_ops = { + .resume = omap_gem_resume, +}; +#endif + +struct platform_driver pdev = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &omapdrm_pm_ops, +#endif + }, + .probe = pdev_probe, + .remove = pdev_remove, + .suspend = pdev_suspend, + .resume = pdev_resume, + .shutdown = pdev_shutdown, +}; + +static int __init omap_drm_init(void) +{ + DBG("init"); + if (platform_driver_register(&omap_dmm_driver)) { + /* we can continue on without DMM.. so not fatal */ + dev_err(NULL, "DMM registration failed\n"); + } + return platform_driver_register(&pdev); +} + +static void __exit omap_drm_fini(void) +{ + DBG("fini"); + platform_driver_unregister(&pdev); +} + +/* need late_initcall() so we load after dss_driver's are loaded */ +late_initcall(omap_drm_init); +module_exit(omap_drm_fini); + +MODULE_AUTHOR("Rob Clark <rob@ti.com>"); +MODULE_DESCRIPTION("OMAP DRM Display Driver"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/omapdrm/omap_drv.h b/drivers/gpu/drm/omapdrm/omap_drv.h new file mode 100644 index 000000000000..d4f997bb4ac0 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_drv.h @@ -0,0 +1,333 @@ +/* + * drivers/gpu/drm/omapdrm/omap_drv.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP_DRV_H__ +#define __OMAP_DRV_H__ + +#include <video/omapdss.h> +#include <linux/module.h> +#include <linux/types.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/omap_drm.h> +#include <linux/platform_data/omap_drm.h> + + +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +#define VERB(fmt, ...) if (0) DRM_DEBUG(fmt, ##__VA_ARGS__) /* verbose debug */ + +#define MODULE_NAME "omapdrm" + +/* max # of mapper-id's that can be assigned.. todo, come up with a better + * (but still inexpensive) way to store/access per-buffer mapper private + * data.. + */ +#define MAX_MAPPERS 2 + +/* parameters which describe (unrotated) coordinates of scanout within a fb: */ +struct omap_drm_window { + uint32_t rotation; + int32_t crtc_x, crtc_y; /* signed because can be offscreen */ + uint32_t crtc_w, crtc_h; + uint32_t src_x, src_y; + uint32_t src_w, src_h; +}; + +/* Once GO bit is set, we can't make further updates to shadowed registers + * until the GO bit is cleared. So various parts in the kms code that need + * to update shadowed registers queue up a pair of callbacks, pre_apply + * which is called before setting GO bit, and post_apply that is called + * after GO bit is cleared. The crtc manages the queuing, and everyone + * else goes thru omap_crtc_apply() using these callbacks so that the + * code which has to deal w/ GO bit state is centralized. + */ +struct omap_drm_apply { + struct list_head pending_node, queued_node; + bool queued; + void (*pre_apply)(struct omap_drm_apply *apply); + void (*post_apply)(struct omap_drm_apply *apply); +}; + +/* For transiently registering for different DSS irqs that various parts + * of the KMS code need during setup/configuration. We these are not + * necessarily the same as what drm_vblank_get/put() are requesting, and + * the hysteresis in drm_vblank_put() is not necessarily desirable for + * internal housekeeping related irq usage. + */ +struct omap_drm_irq { + struct list_head node; + uint32_t irqmask; + bool registered; + void (*irq)(struct omap_drm_irq *irq, uint32_t irqstatus); +}; + +/* For KMS code that needs to wait for a certain # of IRQs: + */ +struct omap_irq_wait; +struct omap_irq_wait * omap_irq_wait_init(struct drm_device *dev, + uint32_t irqmask, int count); +int omap_irq_wait(struct drm_device *dev, struct omap_irq_wait *wait, + unsigned long timeout); + +struct omap_drm_private { + uint32_t omaprev; + + unsigned int num_crtcs; + struct drm_crtc *crtcs[8]; + + unsigned int num_planes; + struct drm_plane *planes[8]; + + unsigned int num_encoders; + struct drm_encoder *encoders[8]; + + unsigned int num_connectors; + struct drm_connector *connectors[8]; + + struct drm_fb_helper *fbdev; + + struct workqueue_struct *wq; + + /* list of GEM objects: */ + struct list_head obj_list; + + bool has_dmm; + + /* properties: */ + struct drm_property *rotation_prop; + struct drm_property *zorder_prop; + + /* irq handling: */ + struct list_head irq_list; /* list of omap_drm_irq */ + uint32_t vblank_mask; /* irq bits set for userspace vblank */ + struct omap_drm_irq error_handler; +}; + +/* this should probably be in drm-core to standardize amongst drivers */ +#define DRM_ROTATE_0 0 +#define DRM_ROTATE_90 1 +#define DRM_ROTATE_180 2 +#define DRM_ROTATE_270 3 +#define DRM_REFLECT_X 4 +#define DRM_REFLECT_Y 5 + +#ifdef CONFIG_DEBUG_FS +int omap_debugfs_init(struct drm_minor *minor); +void omap_debugfs_cleanup(struct drm_minor *minor); +void omap_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m); +void omap_gem_describe(struct drm_gem_object *obj, struct seq_file *m); +void omap_gem_describe_objects(struct list_head *list, struct seq_file *m); +#endif + +#ifdef CONFIG_PM +int omap_gem_resume(struct device *dev); +#endif + +int omap_irq_enable_vblank(struct drm_device *dev, int crtc); +void omap_irq_disable_vblank(struct drm_device *dev, int crtc); +irqreturn_t omap_irq_handler(DRM_IRQ_ARGS); +void omap_irq_preinstall(struct drm_device *dev); +int omap_irq_postinstall(struct drm_device *dev); +void omap_irq_uninstall(struct drm_device *dev); +void omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq); +void omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq); +int omap_drm_irq_uninstall(struct drm_device *dev); +int omap_drm_irq_install(struct drm_device *dev); + +struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev); +void omap_fbdev_free(struct drm_device *dev); + +const struct omap_video_timings *omap_crtc_timings(struct drm_crtc *crtc); +enum omap_channel omap_crtc_channel(struct drm_crtc *crtc); +int omap_crtc_apply(struct drm_crtc *crtc, + struct omap_drm_apply *apply); +struct drm_crtc *omap_crtc_init(struct drm_device *dev, + struct drm_plane *plane, enum omap_channel channel, int id); + +struct drm_plane *omap_plane_init(struct drm_device *dev, + int plane_id, bool private_plane); +int omap_plane_dpms(struct drm_plane *plane, int mode); +int omap_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h, + void (*fxn)(void *), void *arg); +void omap_plane_install_properties(struct drm_plane *plane, + struct drm_mode_object *obj); +int omap_plane_set_property(struct drm_plane *plane, + struct drm_property *property, uint64_t val); + +struct drm_encoder *omap_encoder_init(struct drm_device *dev, + struct omap_dss_device *dssdev); +int omap_encoder_set_enabled(struct drm_encoder *encoder, bool enabled); +int omap_encoder_update(struct drm_encoder *encoder, + struct omap_overlay_manager *mgr, + struct omap_video_timings *timings); + +struct drm_connector *omap_connector_init(struct drm_device *dev, + int connector_type, struct omap_dss_device *dssdev, + struct drm_encoder *encoder); +struct drm_encoder *omap_connector_attached_encoder( + struct drm_connector *connector); +void omap_connector_flush(struct drm_connector *connector, + int x, int y, int w, int h); + +void copy_timings_omap_to_drm(struct drm_display_mode *mode, + struct omap_video_timings *timings); +void copy_timings_drm_to_omap(struct omap_video_timings *timings, + struct drm_display_mode *mode); + +uint32_t omap_framebuffer_get_formats(uint32_t *pixel_formats, + uint32_t max_formats, enum omap_color_mode supported_modes); +struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd); +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos); +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb, int p); +int omap_framebuffer_replace(struct drm_framebuffer *a, + struct drm_framebuffer *b, void *arg, + void (*unpin)(void *arg, struct drm_gem_object *bo)); +void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, + struct omap_drm_window *win, struct omap_overlay_info *info); +struct drm_connector *omap_framebuffer_get_next_connector( + struct drm_framebuffer *fb, struct drm_connector *from); +void omap_framebuffer_flush(struct drm_framebuffer *fb, + int x, int y, int w, int h); + +void omap_gem_init(struct drm_device *dev); +void omap_gem_deinit(struct drm_device *dev); + +struct drm_gem_object *omap_gem_new(struct drm_device *dev, + union omap_gem_size gsize, uint32_t flags); +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, + union omap_gem_size gsize, uint32_t flags, uint32_t *handle); +void omap_gem_free_object(struct drm_gem_object *obj); +int omap_gem_init_object(struct drm_gem_object *obj); +void *omap_gem_vaddr(struct drm_gem_object *obj); +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, + uint32_t handle, uint64_t *offset); +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, + uint32_t handle); +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma); +int omap_gem_mmap_obj(struct drm_gem_object *obj, + struct vm_area_struct *vma); +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, + void (*fxn)(void *arg), void *arg); +int omap_gem_roll(struct drm_gem_object *obj, uint32_t roll); +void omap_gem_cpu_sync(struct drm_gem_object *obj, int pgoff); +void omap_gem_dma_sync(struct drm_gem_object *obj, + enum dma_data_direction dir); +int omap_gem_get_paddr(struct drm_gem_object *obj, + dma_addr_t *paddr, bool remap); +int omap_gem_put_paddr(struct drm_gem_object *obj); +int omap_gem_get_pages(struct drm_gem_object *obj, struct page ***pages, + bool remap); +int omap_gem_put_pages(struct drm_gem_object *obj); +uint32_t omap_gem_flags(struct drm_gem_object *obj); +int omap_gem_rotated_paddr(struct drm_gem_object *obj, uint32_t orient, + int x, int y, dma_addr_t *paddr); +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj); +size_t omap_gem_mmap_size(struct drm_gem_object *obj); +int omap_gem_tiled_size(struct drm_gem_object *obj, uint16_t *w, uint16_t *h); +int omap_gem_tiled_stride(struct drm_gem_object *obj, uint32_t orient); + +struct dma_buf *omap_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, int flags); +struct drm_gem_object *omap_gem_prime_import(struct drm_device *dev, + struct dma_buf *buffer); + +static inline int align_pitch(int pitch, int width, int bpp) +{ + int bytespp = (bpp + 7) / 8; + /* in case someone tries to feed us a completely bogus stride: */ + pitch = max(pitch, width * bytespp); + /* PVR needs alignment to 8 pixels.. right now that is the most + * restrictive stride requirement.. + */ + return ALIGN(pitch, 8 * bytespp); +} + +static inline enum omap_channel pipe2chan(int pipe) +{ + int num_mgrs = dss_feat_get_num_mgrs(); + + /* + * We usually don't want to create a CRTC for each manager, + * at least not until we have a way to expose private planes + * to userspace. Otherwise there would not be enough video + * pipes left for drm planes. The higher #'d managers tend + * to have more features so start in reverse order. + */ + return num_mgrs - pipe - 1; +} + +/* map crtc to vblank mask */ +static inline uint32_t pipe2vbl(int crtc) +{ + enum omap_channel channel = pipe2chan(crtc); + return dispc_mgr_get_vsync_irq(channel); +} + +static inline int crtc2pipe(struct drm_device *dev, struct drm_crtc *crtc) +{ + struct omap_drm_private *priv = dev->dev_private; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->crtcs); i++) + if (priv->crtcs[i] == crtc) + return i; + + BUG(); /* bogus CRTC ptr */ + return -1; +} + +/* should these be made into common util helpers? + */ + +static inline int objects_lookup(struct drm_device *dev, + struct drm_file *filp, uint32_t pixel_format, + struct drm_gem_object **bos, uint32_t *handles) +{ + int i, n = drm_format_num_planes(pixel_format); + + for (i = 0; i < n; i++) { + bos[i] = drm_gem_object_lookup(dev, filp, handles[i]); + if (!bos[i]) + goto fail; + + } + + return 0; + +fail: + while (--i > 0) + drm_gem_object_unreference_unlocked(bos[i]); + + return -ENOENT; +} + +#endif /* __OMAP_DRV_H__ */ diff --git a/drivers/gpu/drm/omapdrm/omap_encoder.c b/drivers/gpu/drm/omapdrm/omap_encoder.c new file mode 100644 index 000000000000..7e1f2ab65372 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_encoder.c @@ -0,0 +1,170 @@ +/* + * drivers/gpu/drm/omapdrm/omap_encoder.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#include <linux/list.h> + + +/* + * encoder funcs + */ + +#define to_omap_encoder(x) container_of(x, struct omap_encoder, base) + +/* The encoder and connector both map to same dssdev.. the encoder + * handles the 'active' parts, ie. anything the modifies the state + * of the hw, and the connector handles the 'read-only' parts, like + * detecting connection and reading edid. + */ +struct omap_encoder { + struct drm_encoder base; + struct omap_dss_device *dssdev; +}; + +static void omap_encoder_destroy(struct drm_encoder *encoder) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + drm_encoder_cleanup(encoder); + kfree(omap_encoder); +} + +static const struct drm_encoder_funcs omap_encoder_funcs = { + .destroy = omap_encoder_destroy, +}; + +/* + * The CRTC drm_crtc_helper_set_mode() doesn't really give us the right + * order.. the easiest way to work around this for now is to make all + * the encoder-helper's no-op's and have the omap_crtc code take care + * of the sequencing and call us in the right points. + * + * Eventually to handle connecting CRTCs to different encoders properly, + * either the CRTC helpers need to change or we need to replace + * drm_crtc_helper_set_mode(), but lets wait until atomic-modeset for + * that. + */ + +static void omap_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool omap_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void omap_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static void omap_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void omap_encoder_commit(struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_helper_funcs omap_encoder_helper_funcs = { + .dpms = omap_encoder_dpms, + .mode_fixup = omap_encoder_mode_fixup, + .mode_set = omap_encoder_mode_set, + .prepare = omap_encoder_prepare, + .commit = omap_encoder_commit, +}; + +/* + * Instead of relying on the helpers for modeset, the omap_crtc code + * calls these functions in the proper sequence. + */ + +int omap_encoder_set_enabled(struct drm_encoder *encoder, bool enabled) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct omap_dss_device *dssdev = omap_encoder->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + + if (enabled) { + return dssdrv->enable(dssdev); + } else { + dssdrv->disable(dssdev); + return 0; + } +} + +int omap_encoder_update(struct drm_encoder *encoder, + struct omap_overlay_manager *mgr, + struct omap_video_timings *timings) +{ + struct drm_device *dev = encoder->dev; + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct omap_dss_device *dssdev = omap_encoder->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + int ret; + + dssdev->output->manager = mgr; + + ret = dssdrv->check_timings(dssdev, timings); + if (ret) { + dev_err(dev->dev, "could not set timings: %d\n", ret); + return ret; + } + + dssdrv->set_timings(dssdev, timings); + + return 0; +} + +/* initialize encoder */ +struct drm_encoder *omap_encoder_init(struct drm_device *dev, + struct omap_dss_device *dssdev) +{ + struct drm_encoder *encoder = NULL; + struct omap_encoder *omap_encoder; + + omap_encoder = kzalloc(sizeof(*omap_encoder), GFP_KERNEL); + if (!omap_encoder) { + dev_err(dev->dev, "could not allocate encoder\n"); + goto fail; + } + + omap_encoder->dssdev = dssdev; + + encoder = &omap_encoder->base; + + drm_encoder_init(dev, encoder, &omap_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(encoder, &omap_encoder_helper_funcs); + + return encoder; + +fail: + if (encoder) + omap_encoder_destroy(encoder); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_fb.c b/drivers/gpu/drm/omapdrm/omap_fb.c new file mode 100644 index 000000000000..9d5f6f696c72 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_fb.c @@ -0,0 +1,472 @@ +/* + * drivers/gpu/drm/omapdrm/omap_fb.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +/* + * framebuffer funcs + */ + +/* per-format info: */ +struct format { + enum omap_color_mode dss_format; + uint32_t pixel_format; + struct { + int stride_bpp; /* this times width is stride */ + int sub_y; /* sub-sample in y dimension */ + } planes[4]; + bool yuv; +}; + +static const struct format formats[] = { + /* 16bpp [A]RGB: */ + { OMAP_DSS_COLOR_RGB16, DRM_FORMAT_RGB565, {{2, 1}}, false }, /* RGB16-565 */ + { OMAP_DSS_COLOR_RGB12U, DRM_FORMAT_RGBX4444, {{2, 1}}, false }, /* RGB12x-4444 */ + { OMAP_DSS_COLOR_RGBX16, DRM_FORMAT_XRGB4444, {{2, 1}}, false }, /* xRGB12-4444 */ + { OMAP_DSS_COLOR_RGBA16, DRM_FORMAT_RGBA4444, {{2, 1}}, false }, /* RGBA12-4444 */ + { OMAP_DSS_COLOR_ARGB16, DRM_FORMAT_ARGB4444, {{2, 1}}, false }, /* ARGB16-4444 */ + { OMAP_DSS_COLOR_XRGB16_1555, DRM_FORMAT_XRGB1555, {{2, 1}}, false }, /* xRGB15-1555 */ + { OMAP_DSS_COLOR_ARGB16_1555, DRM_FORMAT_ARGB1555, {{2, 1}}, false }, /* ARGB16-1555 */ + /* 24bpp RGB: */ + { OMAP_DSS_COLOR_RGB24P, DRM_FORMAT_RGB888, {{3, 1}}, false }, /* RGB24-888 */ + /* 32bpp [A]RGB: */ + { OMAP_DSS_COLOR_RGBX32, DRM_FORMAT_RGBX8888, {{4, 1}}, false }, /* RGBx24-8888 */ + { OMAP_DSS_COLOR_RGB24U, DRM_FORMAT_XRGB8888, {{4, 1}}, false }, /* xRGB24-8888 */ + { OMAP_DSS_COLOR_RGBA32, DRM_FORMAT_RGBA8888, {{4, 1}}, false }, /* RGBA32-8888 */ + { OMAP_DSS_COLOR_ARGB32, DRM_FORMAT_ARGB8888, {{4, 1}}, false }, /* ARGB32-8888 */ + /* YUV: */ + { OMAP_DSS_COLOR_NV12, DRM_FORMAT_NV12, {{1, 1}, {1, 2}}, true }, + { OMAP_DSS_COLOR_YUV2, DRM_FORMAT_YUYV, {{2, 1}}, true }, + { OMAP_DSS_COLOR_UYVY, DRM_FORMAT_UYVY, {{2, 1}}, true }, +}; + +/* convert from overlay's pixel formats bitmask to an array of fourcc's */ +uint32_t omap_framebuffer_get_formats(uint32_t *pixel_formats, + uint32_t max_formats, enum omap_color_mode supported_modes) +{ + uint32_t nformats = 0; + int i = 0; + + for (i = 0; i < ARRAY_SIZE(formats) && nformats < max_formats; i++) + if (formats[i].dss_format & supported_modes) + pixel_formats[nformats++] = formats[i].pixel_format; + + return nformats; +} + +/* per-plane info for the fb: */ +struct plane { + struct drm_gem_object *bo; + uint32_t pitch; + uint32_t offset; + dma_addr_t paddr; +}; + +#define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) + +struct omap_framebuffer { + struct drm_framebuffer base; + const struct format *format; + struct plane planes[4]; +}; + +static int omap_framebuffer_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + return drm_gem_handle_create(file_priv, + omap_fb->planes[0].bo, handle); +} + +static void omap_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + int i, n = drm_format_num_planes(fb->pixel_format); + + DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); + + drm_framebuffer_cleanup(fb); + + for (i = 0; i < n; i++) { + struct plane *plane = &omap_fb->planes[i]; + if (plane->bo) + drm_gem_object_unreference_unlocked(plane->bo); + } + + kfree(omap_fb); +} + +static int omap_framebuffer_dirty(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned flags, unsigned color, + struct drm_clip_rect *clips, unsigned num_clips) +{ + int i; + + for (i = 0; i < num_clips; i++) { + omap_framebuffer_flush(fb, clips[i].x1, clips[i].y1, + clips[i].x2 - clips[i].x1, + clips[i].y2 - clips[i].y1); + } + + return 0; +} + +static const struct drm_framebuffer_funcs omap_framebuffer_funcs = { + .create_handle = omap_framebuffer_create_handle, + .destroy = omap_framebuffer_destroy, + .dirty = omap_framebuffer_dirty, +}; + +static uint32_t get_linear_addr(struct plane *plane, + const struct format *format, int n, int x, int y) +{ + uint32_t offset; + + offset = plane->offset + + (x * format->planes[n].stride_bpp) + + (y * plane->pitch / format->planes[n].sub_y); + + return plane->paddr + offset; +} + +/* update ovl info for scanout, handles cases of multi-planar fb's, etc. + */ +void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, + struct omap_drm_window *win, struct omap_overlay_info *info) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + const struct format *format = omap_fb->format; + struct plane *plane = &omap_fb->planes[0]; + uint32_t x, y, orient = 0; + + info->color_mode = format->dss_format; + + info->pos_x = win->crtc_x; + info->pos_y = win->crtc_y; + info->out_width = win->crtc_w; + info->out_height = win->crtc_h; + info->width = win->src_w; + info->height = win->src_h; + + x = win->src_x; + y = win->src_y; + + if (omap_gem_flags(plane->bo) & OMAP_BO_TILED) { + uint32_t w = win->src_w; + uint32_t h = win->src_h; + + switch (win->rotation & 0xf) { + default: + dev_err(fb->dev->dev, "invalid rotation: %02x", + (uint32_t)win->rotation); + /* fallthru to default to no rotation */ + case 0: + case BIT(DRM_ROTATE_0): + orient = 0; + break; + case BIT(DRM_ROTATE_90): + orient = MASK_XY_FLIP | MASK_X_INVERT; + break; + case BIT(DRM_ROTATE_180): + orient = MASK_X_INVERT | MASK_Y_INVERT; + break; + case BIT(DRM_ROTATE_270): + orient = MASK_XY_FLIP | MASK_Y_INVERT; + break; + } + + if (win->rotation & BIT(DRM_REFLECT_X)) + orient ^= MASK_X_INVERT; + + if (win->rotation & BIT(DRM_REFLECT_Y)) + orient ^= MASK_Y_INVERT; + + /* adjust x,y offset for flip/invert: */ + if (orient & MASK_XY_FLIP) + swap(w, h); + if (orient & MASK_Y_INVERT) + y += h - 1; + if (orient & MASK_X_INVERT) + x += w - 1; + + omap_gem_rotated_paddr(plane->bo, orient, x, y, &info->paddr); + info->rotation_type = OMAP_DSS_ROT_TILER; + info->screen_width = omap_gem_tiled_stride(plane->bo, orient); + } else { + info->paddr = get_linear_addr(plane, format, 0, x, y); + info->rotation_type = OMAP_DSS_ROT_DMA; + info->screen_width = plane->pitch; + } + + /* convert to pixels: */ + info->screen_width /= format->planes[0].stride_bpp; + + if (format->dss_format == OMAP_DSS_COLOR_NV12) { + plane = &omap_fb->planes[1]; + + if (info->rotation_type == OMAP_DSS_ROT_TILER) { + WARN_ON(!(omap_gem_flags(plane->bo) & OMAP_BO_TILED)); + omap_gem_rotated_paddr(plane->bo, orient, + x/2, y/2, &info->p_uv_addr); + } else { + info->p_uv_addr = get_linear_addr(plane, format, 1, x, y); + } + } else { + info->p_uv_addr = 0; + } +} + +/* Call for unpin 'a' (if not NULL), and pin 'b' (if not NULL). Although + * buffers to unpin are just pushed to the unpin fifo so that the + * caller can defer unpin until vblank. + * + * Note if this fails (ie. something went very wrong!), all buffers are + * unpinned, and the caller disables the overlay. We could have tried + * to revert back to the previous set of pinned buffers but if things are + * hosed there is no guarantee that would succeed. + */ +int omap_framebuffer_replace(struct drm_framebuffer *a, + struct drm_framebuffer *b, void *arg, + void (*unpin)(void *arg, struct drm_gem_object *bo)) +{ + int ret = 0, i, na, nb; + struct omap_framebuffer *ofba = to_omap_framebuffer(a); + struct omap_framebuffer *ofbb = to_omap_framebuffer(b); + uint32_t pinned_mask = 0; + + na = a ? drm_format_num_planes(a->pixel_format) : 0; + nb = b ? drm_format_num_planes(b->pixel_format) : 0; + + for (i = 0; i < max(na, nb); i++) { + struct plane *pa, *pb; + + pa = (i < na) ? &ofba->planes[i] : NULL; + pb = (i < nb) ? &ofbb->planes[i] : NULL; + + if (pa) + unpin(arg, pa->bo); + + if (pb && !ret) { + ret = omap_gem_get_paddr(pb->bo, &pb->paddr, true); + if (!ret) { + omap_gem_dma_sync(pb->bo, DMA_TO_DEVICE); + pinned_mask |= (1 << i); + } + } + } + + if (ret) { + /* something went wrong.. unpin what has been pinned */ + for (i = 0; i < nb; i++) { + if (pinned_mask & (1 << i)) { + struct plane *pb = &ofba->planes[i]; + unpin(arg, pb->bo); + } + } + } + + return ret; +} + +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb, int p) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + if (p >= drm_format_num_planes(fb->pixel_format)) + return NULL; + return omap_fb->planes[p].bo; +} + +/* iterate thru all the connectors, returning ones that are attached + * to the same fb.. + */ +struct drm_connector *omap_framebuffer_get_next_connector( + struct drm_framebuffer *fb, struct drm_connector *from) +{ + struct drm_device *dev = fb->dev; + struct list_head *connector_list = &dev->mode_config.connector_list; + struct drm_connector *connector = from; + + if (!from) + return list_first_entry(connector_list, typeof(*from), head); + + list_for_each_entry_from(connector, connector_list, head) { + if (connector != from) { + struct drm_encoder *encoder = connector->encoder; + struct drm_crtc *crtc = encoder ? encoder->crtc : NULL; + if (crtc && crtc->fb == fb) + return connector; + + } + } + + return NULL; +} + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +void omap_framebuffer_flush(struct drm_framebuffer *fb, + int x, int y, int w, int h) +{ + struct drm_connector *connector = NULL; + + VERB("flush: %d,%d %dx%d, fb=%p", x, y, w, h, fb); + + while ((connector = omap_framebuffer_get_next_connector(fb, connector))) { + /* only consider connectors that are part of a chain */ + if (connector->encoder && connector->encoder->crtc) { + /* TODO: maybe this should propagate thru the crtc who + * could do the coordinate translation.. + */ + struct drm_crtc *crtc = connector->encoder->crtc; + int cx = max(0, x - crtc->x); + int cy = max(0, y - crtc->y); + int cw = w + (x - crtc->x) - cx; + int ch = h + (y - crtc->y) - cy; + + omap_connector_flush(connector, cx, cy, cw, ch); + } + } +} + +#ifdef CONFIG_DEBUG_FS +void omap_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + int i, n = drm_format_num_planes(fb->pixel_format); + + seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height, + (char *)&fb->pixel_format); + + for (i = 0; i < n; i++) { + struct plane *plane = &omap_fb->planes[i]; + seq_printf(m, " %d: offset=%d pitch=%d, obj: ", + i, plane->offset, plane->pitch); + omap_gem_describe(plane->bo, m); + } +} +#endif + +struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *bos[4]; + struct drm_framebuffer *fb; + int ret; + + ret = objects_lookup(dev, file, mode_cmd->pixel_format, + bos, mode_cmd->handles); + if (ret) + return ERR_PTR(ret); + + fb = omap_framebuffer_init(dev, mode_cmd, bos); + if (IS_ERR(fb)) { + int i, n = drm_format_num_planes(mode_cmd->pixel_format); + for (i = 0; i < n; i++) + drm_gem_object_unreference_unlocked(bos[i]); + return fb; + } + return fb; +} + +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos) +{ + struct omap_framebuffer *omap_fb; + struct drm_framebuffer *fb = NULL; + const struct format *format = NULL; + int ret, i, n = drm_format_num_planes(mode_cmd->pixel_format); + + DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%4.4s)", + dev, mode_cmd, mode_cmd->width, mode_cmd->height, + (char *)&mode_cmd->pixel_format); + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixel_format == mode_cmd->pixel_format) { + format = &formats[i]; + break; + } + } + + if (!format) { + dev_err(dev->dev, "unsupported pixel format: %4.4s\n", + (char *)&mode_cmd->pixel_format); + ret = -EINVAL; + goto fail; + } + + omap_fb = kzalloc(sizeof(*omap_fb), GFP_KERNEL); + if (!omap_fb) { + dev_err(dev->dev, "could not allocate fb\n"); + ret = -ENOMEM; + goto fail; + } + + fb = &omap_fb->base; + omap_fb->format = format; + + for (i = 0; i < n; i++) { + struct plane *plane = &omap_fb->planes[i]; + int size, pitch = mode_cmd->pitches[i]; + + if (pitch < (mode_cmd->width * format->planes[i].stride_bpp)) { + dev_err(dev->dev, "provided buffer pitch is too small! %d < %d\n", + pitch, mode_cmd->width * format->planes[i].stride_bpp); + ret = -EINVAL; + goto fail; + } + + size = pitch * mode_cmd->height / format->planes[i].sub_y; + + if (size > (omap_gem_mmap_size(bos[i]) - mode_cmd->offsets[i])) { + dev_err(dev->dev, "provided buffer object is too small! %d < %d\n", + bos[i]->size - mode_cmd->offsets[i], size); + ret = -EINVAL; + goto fail; + } + + plane->bo = bos[i]; + plane->offset = mode_cmd->offsets[i]; + plane->pitch = pitch; + plane->paddr = 0; + } + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + ret = drm_framebuffer_init(dev, fb, &omap_framebuffer_funcs); + if (ret) { + dev_err(dev->dev, "framebuffer init failed: %d\n", ret); + goto fail; + } + + DBG("create: FB ID: %d (%p)", fb->base.id, fb); + + return fb; + +fail: + if (fb) + omap_framebuffer_destroy(fb); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/omapdrm/omap_fbdev.c b/drivers/gpu/drm/omapdrm/omap_fbdev.c new file mode 100644 index 000000000000..f0033bd3e4ae --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_fbdev.c @@ -0,0 +1,399 @@ +/* + * drivers/gpu/drm/omapdrm/omap_fbdev.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_fb_helper.h" + +MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')"); +static bool ywrap_enabled = true; +module_param_named(ywrap, ywrap_enabled, bool, 0644); + +/* + * fbdev funcs, to implement legacy fbdev interface on top of drm driver + */ + +#define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) + +struct omap_fbdev { + struct drm_fb_helper base; + struct drm_framebuffer *fb; + struct drm_gem_object *bo; + bool ywrap_enabled; + + /* for deferred dmm roll when getting called in atomic ctx */ + struct work_struct work; +}; + +static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h); +static struct drm_fb_helper *get_fb(struct fb_info *fbi); + +static ssize_t omap_fbdev_write(struct fb_info *fbi, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t res; + + res = fb_sys_write(fbi, buf, count, ppos); + omap_fbdev_flush(fbi, 0, 0, fbi->var.xres, fbi->var.yres); + + return res; +} + +static void omap_fbdev_fillrect(struct fb_info *fbi, + const struct fb_fillrect *rect) +{ + sys_fillrect(fbi, rect); + omap_fbdev_flush(fbi, rect->dx, rect->dy, rect->width, rect->height); +} + +static void omap_fbdev_copyarea(struct fb_info *fbi, + const struct fb_copyarea *area) +{ + sys_copyarea(fbi, area); + omap_fbdev_flush(fbi, area->dx, area->dy, area->width, area->height); +} + +static void omap_fbdev_imageblit(struct fb_info *fbi, + const struct fb_image *image) +{ + sys_imageblit(fbi, image); + omap_fbdev_flush(fbi, image->dx, image->dy, + image->width, image->height); +} + +static void pan_worker(struct work_struct *work) +{ + struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work); + struct fb_info *fbi = fbdev->base.fbdev; + int npages; + + /* DMM roll shifts in 4K pages: */ + npages = fbi->fix.line_length >> PAGE_SHIFT; + omap_gem_roll(fbdev->bo, fbi->var.yoffset * npages); +} + +static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct drm_fb_helper *helper = get_fb(fbi); + struct omap_fbdev *fbdev = to_omap_fbdev(helper); + + if (!helper) + goto fallback; + + if (!fbdev->ywrap_enabled) + goto fallback; + + if (drm_can_sleep()) { + pan_worker(&fbdev->work); + } else { + struct omap_drm_private *priv = helper->dev->dev_private; + queue_work(priv->wq, &fbdev->work); + } + + return 0; + +fallback: + return drm_fb_helper_pan_display(var, fbi); +} + +static struct fb_ops omap_fb_ops = { + .owner = THIS_MODULE, + + /* Note: to properly handle manual update displays, we wrap the + * basic fbdev ops which write to the framebuffer + */ + .fb_read = fb_sys_read, + .fb_write = omap_fbdev_write, + .fb_fillrect = omap_fbdev_fillrect, + .fb_copyarea = omap_fbdev_copyarea, + .fb_imageblit = omap_fbdev_imageblit, + + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_pan_display = omap_fbdev_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, +}; + +static int omap_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct omap_fbdev *fbdev = to_omap_fbdev(helper); + struct drm_device *dev = helper->dev; + struct omap_drm_private *priv = dev->dev_private; + struct drm_framebuffer *fb = NULL; + union omap_gem_size gsize; + struct fb_info *fbi = NULL; + struct drm_mode_fb_cmd2 mode_cmd = {0}; + dma_addr_t paddr; + int ret; + + /* only doing ARGB32 since this is what is needed to alpha-blend + * with video overlays: + */ + sizes->surface_bpp = 32; + sizes->surface_depth = 32; + + DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, + sizes->surface_height, sizes->surface_bpp, + sizes->fb_width, sizes->fb_height); + + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + + mode_cmd.pitches[0] = align_pitch( + mode_cmd.width * ((sizes->surface_bpp + 7) / 8), + mode_cmd.width, sizes->surface_bpp); + + fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; + if (fbdev->ywrap_enabled) { + /* need to align pitch to page size if using DMM scrolling */ + mode_cmd.pitches[0] = ALIGN(mode_cmd.pitches[0], PAGE_SIZE); + } + + /* allocate backing bo */ + gsize = (union omap_gem_size){ + .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), + }; + DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); + fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); + if (!fbdev->bo) { + dev_err(dev->dev, "failed to allocate buffer object\n"); + ret = -ENOMEM; + goto fail; + } + + fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); + if (IS_ERR(fb)) { + dev_err(dev->dev, "failed to allocate fb\n"); + /* note: if fb creation failed, we can't rely on fb destroy + * to unref the bo: + */ + drm_gem_object_unreference(fbdev->bo); + ret = PTR_ERR(fb); + goto fail; + } + + /* note: this keeps the bo pinned.. which is perhaps not ideal, + * but is needed as long as we use fb_mmap() to mmap to userspace + * (since this happens using fix.smem_start). Possibly we could + * implement our own mmap using GEM mmap support to avoid this + * (non-tiled buffer doesn't need to be pinned for fbcon to write + * to it). Then we just need to be sure that we are able to re- + * pin it in case of an opps. + */ + ret = omap_gem_get_paddr(fbdev->bo, &paddr, true); + if (ret) { + dev_err(dev->dev, + "could not map (paddr)! Skipping framebuffer alloc\n"); + ret = -ENOMEM; + goto fail; + } + + mutex_lock(&dev->struct_mutex); + + fbi = framebuffer_alloc(0, dev->dev); + if (!fbi) { + dev_err(dev->dev, "failed to allocate fb info\n"); + ret = -ENOMEM; + goto fail_unlock; + } + + DBG("fbi=%p, dev=%p", fbi, dev); + + fbdev->fb = fb; + helper->fb = fb; + helper->fbdev = fbi; + + fbi->par = helper; + fbi->flags = FBINFO_DEFAULT; + fbi->fbops = &omap_fb_ops; + + strcpy(fbi->fix.id, MODULE_NAME); + + ret = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (ret) { + ret = -ENOMEM; + goto fail_unlock; + } + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); + + dev->mode_config.fb_base = paddr; + + fbi->screen_base = omap_gem_vaddr(fbdev->bo); + fbi->screen_size = fbdev->bo->size; + fbi->fix.smem_start = paddr; + fbi->fix.smem_len = fbdev->bo->size; + + /* if we have DMM, then we can use it for scrolling by just + * shuffling pages around in DMM rather than doing sw blit. + */ + if (fbdev->ywrap_enabled) { + DRM_INFO("Enabling DMM ywrap scrolling\n"); + fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; + fbi->fix.ywrapstep = 1; + } + + + DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); + DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); + + mutex_unlock(&dev->struct_mutex); + + return 0; + +fail_unlock: + mutex_unlock(&dev->struct_mutex); +fail: + + if (ret) { + if (fbi) + framebuffer_release(fbi); + if (fb) { + drm_framebuffer_unregister_private(fb); + drm_framebuffer_remove(fb); + } + } + + return ret; +} + +static void omap_crtc_fb_gamma_set(struct drm_crtc *crtc, + u16 red, u16 green, u16 blue, int regno) +{ + DBG("fbdev: set gamma"); +} + +static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, + u16 *red, u16 *green, u16 *blue, int regno) +{ + DBG("fbdev: get gamma"); +} + +static struct drm_fb_helper_funcs omap_fb_helper_funcs = { + .gamma_set = omap_crtc_fb_gamma_set, + .gamma_get = omap_crtc_fb_gamma_get, + .fb_probe = omap_fbdev_create, +}; + +static struct drm_fb_helper *get_fb(struct fb_info *fbi) +{ + if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { + /* these are not the fb's you're looking for */ + return NULL; + } + return fbi->par; +} + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h) +{ + struct drm_fb_helper *helper = get_fb(fbi); + + if (!helper) + return; + + VERB("flush fbdev: %d,%d %dx%d, fbi=%p", x, y, w, h, fbi); + + omap_framebuffer_flush(helper->fb, x, y, w, h); +} + +/* initialize fbdev helper */ +struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_fbdev *fbdev = NULL; + struct drm_fb_helper *helper; + int ret = 0; + + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) { + dev_err(dev->dev, "could not allocate fbdev\n"); + goto fail; + } + + INIT_WORK(&fbdev->work, pan_worker); + + helper = &fbdev->base; + + helper->funcs = &omap_fb_helper_funcs; + + ret = drm_fb_helper_init(dev, helper, + priv->num_crtcs, priv->num_connectors); + if (ret) { + dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); + goto fail; + } + + drm_fb_helper_single_add_all_connectors(helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + + drm_fb_helper_initial_config(helper, 32); + + priv->fbdev = helper; + + return helper; + +fail: + kfree(fbdev); + return NULL; +} + +void omap_fbdev_free(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_fb_helper *helper = priv->fbdev; + struct omap_fbdev *fbdev; + struct fb_info *fbi; + + DBG(); + + fbi = helper->fbdev; + + /* only cleanup framebuffer if it is present */ + if (fbi) { + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + drm_fb_helper_fini(helper); + + fbdev = to_omap_fbdev(priv->fbdev); + + /* this will free the backing object */ + if (fbdev->fb) { + drm_framebuffer_unregister_private(fbdev->fb); + drm_framebuffer_remove(fbdev->fb); + } + + kfree(fbdev); + + priv->fbdev = NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_gem.c b/drivers/gpu/drm/omapdrm/omap_gem.c new file mode 100644 index 000000000000..e8302b02691d --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_gem.c @@ -0,0 +1,1511 @@ +/* + * drivers/gpu/drm/omapdrm/omap_gem.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include <linux/spinlock.h> +#include <linux/shmem_fs.h> + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +/* remove these once drm core helpers are merged */ +struct page **_drm_gem_get_pages(struct drm_gem_object *obj, gfp_t gfpmask); +void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages, + bool dirty, bool accessed); +int _drm_gem_create_mmap_offset_size(struct drm_gem_object *obj, size_t size); + +/* + * GEM buffer object implementation. + */ + +#define to_omap_bo(x) container_of(x, struct omap_gem_object, base) + +/* note: we use upper 8 bits of flags for driver-internal flags: */ +#define OMAP_BO_DMA 0x01000000 /* actually is physically contiguous */ +#define OMAP_BO_EXT_SYNC 0x02000000 /* externally allocated sync object */ +#define OMAP_BO_EXT_MEM 0x04000000 /* externally allocated memory */ + + +struct omap_gem_object { + struct drm_gem_object base; + + struct list_head mm_list; + + uint32_t flags; + + /** width/height for tiled formats (rounded up to slot boundaries) */ + uint16_t width, height; + + /** roll applied when mapping to DMM */ + uint32_t roll; + + /** + * If buffer is allocated physically contiguous, the OMAP_BO_DMA flag + * is set and the paddr is valid. Also if the buffer is remapped in + * TILER and paddr_cnt > 0, then paddr is valid. But if you are using + * the physical address and OMAP_BO_DMA is not set, then you should + * be going thru omap_gem_{get,put}_paddr() to ensure the mapping is + * not removed from under your feet. + * + * Note that OMAP_BO_SCANOUT is a hint from userspace that DMA capable + * buffer is requested, but doesn't mean that it is. Use the + * OMAP_BO_DMA flag to determine if the buffer has a DMA capable + * physical address. + */ + dma_addr_t paddr; + + /** + * # of users of paddr + */ + uint32_t paddr_cnt; + + /** + * tiler block used when buffer is remapped in DMM/TILER. + */ + struct tiler_block *block; + + /** + * Array of backing pages, if allocated. Note that pages are never + * allocated for buffers originally allocated from contiguous memory + */ + struct page **pages; + + /** addresses corresponding to pages in above array */ + dma_addr_t *addrs; + + /** + * Virtual address, if mapped. + */ + void *vaddr; + + /** + * sync-object allocated on demand (if needed) + * + * Per-buffer sync-object for tracking pending and completed hw/dma + * read and write operations. The layout in memory is dictated by + * the SGX firmware, which uses this information to stall the command + * stream if a surface is not ready yet. + * + * Note that when buffer is used by SGX, the sync-object needs to be + * allocated from a special heap of sync-objects. This way many sync + * objects can be packed in a page, and not waste GPU virtual address + * space. Because of this we have to have a omap_gem_set_sync_object() + * API to allow replacement of the syncobj after it has (potentially) + * already been allocated. A bit ugly but I haven't thought of a + * better alternative. + */ + struct { + uint32_t write_pending; + uint32_t write_complete; + uint32_t read_pending; + uint32_t read_complete; + } *sync; +}; + +static int get_pages(struct drm_gem_object *obj, struct page ***pages); +static uint64_t mmap_offset(struct drm_gem_object *obj); + +/* To deal with userspace mmap'ings of 2d tiled buffers, which (a) are + * not necessarily pinned in TILER all the time, and (b) when they are + * they are not necessarily page aligned, we reserve one or more small + * regions in each of the 2d containers to use as a user-GART where we + * can create a second page-aligned mapping of parts of the buffer + * being accessed from userspace. + * + * Note that we could optimize slightly when we know that multiple + * tiler containers are backed by the same PAT.. but I'll leave that + * for later.. + */ +#define NUM_USERGART_ENTRIES 2 +struct usergart_entry { + struct tiler_block *block; /* the reserved tiler block */ + dma_addr_t paddr; + struct drm_gem_object *obj; /* the current pinned obj */ + pgoff_t obj_pgoff; /* page offset of obj currently + mapped in */ +}; +static struct { + struct usergart_entry entry[NUM_USERGART_ENTRIES]; + int height; /* height in rows */ + int height_shift; /* ilog2(height in rows) */ + int slot_shift; /* ilog2(width per slot) */ + int stride_pfn; /* stride in pages */ + int last; /* index of last used entry */ +} *usergart; + +static void evict_entry(struct drm_gem_object *obj, + enum tiler_fmt fmt, struct usergart_entry *entry) +{ + if (obj->dev->dev_mapping) { + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int n = usergart[fmt].height; + size_t size = PAGE_SIZE * n; + loff_t off = mmap_offset(obj) + + (entry->obj_pgoff << PAGE_SHIFT); + const int m = 1 + ((omap_obj->width << fmt) / PAGE_SIZE); + if (m > 1) { + int i; + /* if stride > than PAGE_SIZE then sparse mapping: */ + for (i = n; i > 0; i--) { + unmap_mapping_range(obj->dev->dev_mapping, + off, PAGE_SIZE, 1); + off += PAGE_SIZE * m; + } + } else { + unmap_mapping_range(obj->dev->dev_mapping, off, size, 1); + } + } + + entry->obj = NULL; +} + +/* Evict a buffer from usergart, if it is mapped there */ +static void evict(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + if (omap_obj->flags & OMAP_BO_TILED) { + enum tiler_fmt fmt = gem2fmt(omap_obj->flags); + int i; + + if (!usergart) + return; + + for (i = 0; i < NUM_USERGART_ENTRIES; i++) { + struct usergart_entry *entry = &usergart[fmt].entry[i]; + if (entry->obj == obj) + evict_entry(obj, fmt, entry); + } + } +} + +/* GEM objects can either be allocated from contiguous memory (in which + * case obj->filp==NULL), or w/ shmem backing (obj->filp!=NULL). But non + * contiguous buffers can be remapped in TILER/DMM if they need to be + * contiguous... but we don't do this all the time to reduce pressure + * on TILER/DMM space when we know at allocation time that the buffer + * will need to be scanned out. + */ +static inline bool is_shmem(struct drm_gem_object *obj) +{ + return obj->filp != NULL; +} + +/** + * shmem buffers that are mapped cached can simulate coherency via using + * page faulting to keep track of dirty pages + */ +static inline bool is_cached_coherent(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + return is_shmem(obj) && + ((omap_obj->flags & OMAP_BO_CACHE_MASK) == OMAP_BO_CACHED); +} + +static DEFINE_SPINLOCK(sync_lock); + +/** ensure backing pages are allocated */ +static int omap_gem_attach_pages(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct page **pages; + int npages = obj->size >> PAGE_SHIFT; + int i, ret; + dma_addr_t *addrs; + + WARN_ON(omap_obj->pages); + + /* TODO: __GFP_DMA32 .. but somehow GFP_HIGHMEM is coming from the + * mapping_gfp_mask(mapping) which conflicts w/ GFP_DMA32.. probably + * we actually want CMA memory for it all anyways.. + */ + pages = _drm_gem_get_pages(obj, GFP_KERNEL); + if (IS_ERR(pages)) { + dev_err(obj->dev->dev, "could not get pages: %ld\n", PTR_ERR(pages)); + return PTR_ERR(pages); + } + + /* for non-cached buffers, ensure the new pages are clean because + * DSS, GPU, etc. are not cache coherent: + */ + if (omap_obj->flags & (OMAP_BO_WC|OMAP_BO_UNCACHED)) { + addrs = kmalloc(npages * sizeof(*addrs), GFP_KERNEL); + if (!addrs) { + ret = -ENOMEM; + goto free_pages; + } + + for (i = 0; i < npages; i++) { + addrs[i] = dma_map_page(dev->dev, pages[i], + 0, PAGE_SIZE, DMA_BIDIRECTIONAL); + } + } else { + addrs = kzalloc(npages * sizeof(*addrs), GFP_KERNEL); + if (!addrs) { + ret = -ENOMEM; + goto free_pages; + } + } + + omap_obj->addrs = addrs; + omap_obj->pages = pages; + + return 0; + +free_pages: + _drm_gem_put_pages(obj, pages, true, false); + + return ret; +} + +/** release backing pages */ +static void omap_gem_detach_pages(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + /* for non-cached buffers, ensure the new pages are clean because + * DSS, GPU, etc. are not cache coherent: + */ + if (omap_obj->flags & (OMAP_BO_WC|OMAP_BO_UNCACHED)) { + int i, npages = obj->size >> PAGE_SHIFT; + for (i = 0; i < npages; i++) { + dma_unmap_page(obj->dev->dev, omap_obj->addrs[i], + PAGE_SIZE, DMA_BIDIRECTIONAL); + } + } + + kfree(omap_obj->addrs); + omap_obj->addrs = NULL; + + _drm_gem_put_pages(obj, omap_obj->pages, true, false); + omap_obj->pages = NULL; +} + +/* get buffer flags */ +uint32_t omap_gem_flags(struct drm_gem_object *obj) +{ + return to_omap_bo(obj)->flags; +} + +/** get mmap offset */ +static uint64_t mmap_offset(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (!obj->map_list.map) { + /* Make it mmapable */ + size_t size = omap_gem_mmap_size(obj); + int ret = _drm_gem_create_mmap_offset_size(obj, size); + + if (ret) { + dev_err(dev->dev, "could not allocate mmap offset\n"); + return 0; + } + } + + return (uint64_t)obj->map_list.hash.key << PAGE_SHIFT; +} + +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj) +{ + uint64_t offset; + mutex_lock(&obj->dev->struct_mutex); + offset = mmap_offset(obj); + mutex_unlock(&obj->dev->struct_mutex); + return offset; +} + +/** get mmap size */ +size_t omap_gem_mmap_size(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + size_t size = obj->size; + + if (omap_obj->flags & OMAP_BO_TILED) { + /* for tiled buffers, the virtual size has stride rounded up + * to 4kb.. (to hide the fact that row n+1 might start 16kb or + * 32kb later!). But we don't back the entire buffer with + * pages, only the valid picture part.. so need to adjust for + * this in the size used to mmap and generate mmap offset + */ + size = tiler_vsize(gem2fmt(omap_obj->flags), + omap_obj->width, omap_obj->height); + } + + return size; +} + +/* get tiled size, returns -EINVAL if not tiled buffer */ +int omap_gem_tiled_size(struct drm_gem_object *obj, uint16_t *w, uint16_t *h) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (omap_obj->flags & OMAP_BO_TILED) { + *w = omap_obj->width; + *h = omap_obj->height; + return 0; + } + return -EINVAL; +} + +/* Normal handling for the case of faulting in non-tiled buffers */ +static int fault_1d(struct drm_gem_object *obj, + struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + unsigned long pfn; + pgoff_t pgoff; + + /* We don't use vmf->pgoff since that has the fake offset: */ + pgoff = ((unsigned long)vmf->virtual_address - + vma->vm_start) >> PAGE_SHIFT; + + if (omap_obj->pages) { + omap_gem_cpu_sync(obj, pgoff); + pfn = page_to_pfn(omap_obj->pages[pgoff]); + } else { + BUG_ON(!(omap_obj->flags & OMAP_BO_DMA)); + pfn = (omap_obj->paddr >> PAGE_SHIFT) + pgoff; + } + + VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, + pfn, pfn << PAGE_SHIFT); + + return vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn); +} + +/* Special handling for the case of faulting in 2d tiled buffers */ +static int fault_2d(struct drm_gem_object *obj, + struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct usergart_entry *entry; + enum tiler_fmt fmt = gem2fmt(omap_obj->flags); + struct page *pages[64]; /* XXX is this too much to have on stack? */ + unsigned long pfn; + pgoff_t pgoff, base_pgoff; + void __user *vaddr; + int i, ret, slots; + + /* + * Note the height of the slot is also equal to the number of pages + * that need to be mapped in to fill 4kb wide CPU page. If the slot + * height is 64, then 64 pages fill a 4kb wide by 64 row region. + */ + const int n = usergart[fmt].height; + const int n_shift = usergart[fmt].height_shift; + + /* + * If buffer width in bytes > PAGE_SIZE then the virtual stride is + * rounded up to next multiple of PAGE_SIZE.. this need to be taken + * into account in some of the math, so figure out virtual stride + * in pages + */ + const int m = 1 + ((omap_obj->width << fmt) / PAGE_SIZE); + + /* We don't use vmf->pgoff since that has the fake offset: */ + pgoff = ((unsigned long)vmf->virtual_address - + vma->vm_start) >> PAGE_SHIFT; + + /* + * Actual address we start mapping at is rounded down to previous slot + * boundary in the y direction: + */ + base_pgoff = round_down(pgoff, m << n_shift); + + /* figure out buffer width in slots */ + slots = omap_obj->width >> usergart[fmt].slot_shift; + + vaddr = vmf->virtual_address - ((pgoff - base_pgoff) << PAGE_SHIFT); + + entry = &usergart[fmt].entry[usergart[fmt].last]; + + /* evict previous buffer using this usergart entry, if any: */ + if (entry->obj) + evict_entry(entry->obj, fmt, entry); + + entry->obj = obj; + entry->obj_pgoff = base_pgoff; + + /* now convert base_pgoff to phys offset from virt offset: */ + base_pgoff = (base_pgoff >> n_shift) * slots; + + /* for wider-than 4k.. figure out which part of the slot-row we want: */ + if (m > 1) { + int off = pgoff % m; + entry->obj_pgoff += off; + base_pgoff /= m; + slots = min(slots - (off << n_shift), n); + base_pgoff += off << n_shift; + vaddr += off << PAGE_SHIFT; + } + + /* + * Map in pages. Beyond the valid pixel part of the buffer, we set + * pages[i] to NULL to get a dummy page mapped in.. if someone + * reads/writes it they will get random/undefined content, but at + * least it won't be corrupting whatever other random page used to + * be mapped in, or other undefined behavior. + */ + memcpy(pages, &omap_obj->pages[base_pgoff], + sizeof(struct page *) * slots); + memset(pages + slots, 0, + sizeof(struct page *) * (n - slots)); + + ret = tiler_pin(entry->block, pages, ARRAY_SIZE(pages), 0, true); + if (ret) { + dev_err(obj->dev->dev, "failed to pin: %d\n", ret); + return ret; + } + + pfn = entry->paddr >> PAGE_SHIFT; + + VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, + pfn, pfn << PAGE_SHIFT); + + for (i = n; i > 0; i--) { + vm_insert_mixed(vma, (unsigned long)vaddr, pfn); + pfn += usergart[fmt].stride_pfn; + vaddr += PAGE_SIZE * m; + } + + /* simple round-robin: */ + usergart[fmt].last = (usergart[fmt].last + 1) % NUM_USERGART_ENTRIES; + + return 0; +} + +/** + * omap_gem_fault - pagefault handler for GEM objects + * @vma: the VMA of the GEM object + * @vmf: fault detail + * + * Invoked when a fault occurs on an mmap of a GEM managed area. GEM + * does most of the work for us including the actual map/unmap calls + * but we need to do the actual page work. + * + * The VMA was set up by GEM. In doing so it also ensured that the + * vma->vm_private_data points to the GEM object that is backing this + * mapping. + */ +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct drm_device *dev = obj->dev; + struct page **pages; + int ret; + + /* Make sure we don't parallel update on a fault, nor move or remove + * something from beneath our feet + */ + mutex_lock(&dev->struct_mutex); + + /* if a shmem backed object, make sure we have pages attached now */ + ret = get_pages(obj, &pages); + if (ret) + goto fail; + + /* where should we do corresponding put_pages().. we are mapping + * the original page, rather than thru a GART, so we can't rely + * on eviction to trigger this. But munmap() or all mappings should + * probably trigger put_pages()? + */ + + if (omap_obj->flags & OMAP_BO_TILED) + ret = fault_2d(obj, vma, vmf); + else + ret = fault_1d(obj, vma, vmf); + + +fail: + mutex_unlock(&dev->struct_mutex); + switch (ret) { + case 0: + case -ERESTARTSYS: + case -EINTR: + return VM_FAULT_NOPAGE; + case -ENOMEM: + return VM_FAULT_OOM; + default: + return VM_FAULT_SIGBUS; + } +} + +/** We override mainly to fix up some of the vm mapping flags.. */ +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) { + DBG("mmap failed: %d", ret); + return ret; + } + + return omap_gem_mmap_obj(vma->vm_private_data, vma); +} + +int omap_gem_mmap_obj(struct drm_gem_object *obj, + struct vm_area_struct *vma) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; + + if (omap_obj->flags & OMAP_BO_WC) { + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + } else if (omap_obj->flags & OMAP_BO_UNCACHED) { + vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); + } else { + /* + * We do have some private objects, at least for scanout buffers + * on hardware without DMM/TILER. But these are allocated write- + * combine + */ + if (WARN_ON(!obj->filp)) + return -EINVAL; + + /* + * Shunt off cached objs to shmem file so they have their own + * address_space (so unmap_mapping_range does what we want, + * in particular in the case of mmap'd dmabufs) + */ + fput(vma->vm_file); + vma->vm_pgoff = 0; + vma->vm_file = get_file(obj->filp); + + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + } + + return 0; +} + + +/** + * omap_gem_dumb_create - create a dumb buffer + * @drm_file: our client file + * @dev: our device + * @args: the requested arguments copied from userspace + * + * Allocate a buffer suitable for use for a frame buffer of the + * form described by user space. Give userspace a handle by which + * to reference it. + */ +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + union omap_gem_size gsize; + + /* in case someone tries to feed us a completely bogus stride: */ + args->pitch = align_pitch(args->pitch, args->width, args->bpp); + args->size = PAGE_ALIGN(args->pitch * args->height); + + gsize = (union omap_gem_size){ + .bytes = args->size, + }; + + return omap_gem_new_handle(dev, file, gsize, + OMAP_BO_SCANOUT | OMAP_BO_WC, &args->handle); +} + +/** + * omap_gem_dumb_destroy - destroy a dumb buffer + * @file: client file + * @dev: our DRM device + * @handle: the object handle + * + * Destroy a handle that was created via omap_gem_dumb_create. + */ +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, + uint32_t handle) +{ + /* No special work needed, drop the reference and see what falls out */ + return drm_gem_handle_delete(file, handle); +} + +/** + * omap_gem_dumb_map - buffer mapping for dumb interface + * @file: our drm client file + * @dev: drm device + * @handle: GEM handle to the object (from dumb_create) + * + * Do the necessary setup to allow the mapping of the frame buffer + * into user memory. We don't have to do much here at the moment. + */ +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, + uint32_t handle, uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret = 0; + + /* GEM does all our handle to object mapping */ + obj = drm_gem_object_lookup(dev, file, handle); + if (obj == NULL) { + ret = -ENOENT; + goto fail; + } + + *offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + +fail: + return ret; +} + +/* Set scrolling position. This allows us to implement fast scrolling + * for console. + * + * Call only from non-atomic contexts. + */ +int omap_gem_roll(struct drm_gem_object *obj, uint32_t roll) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + uint32_t npages = obj->size >> PAGE_SHIFT; + int ret = 0; + + if (roll > npages) { + dev_err(obj->dev->dev, "invalid roll: %d\n", roll); + return -EINVAL; + } + + omap_obj->roll = roll; + + mutex_lock(&obj->dev->struct_mutex); + + /* if we aren't mapped yet, we don't need to do anything */ + if (omap_obj->block) { + struct page **pages; + ret = get_pages(obj, &pages); + if (ret) + goto fail; + ret = tiler_pin(omap_obj->block, pages, npages, roll, true); + if (ret) + dev_err(obj->dev->dev, "could not repin: %d\n", ret); + } + +fail: + mutex_unlock(&obj->dev->struct_mutex); + + return ret; +} + +/* Sync the buffer for CPU access.. note pages should already be + * attached, ie. omap_gem_get_pages() + */ +void omap_gem_cpu_sync(struct drm_gem_object *obj, int pgoff) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + if (is_cached_coherent(obj) && omap_obj->addrs[pgoff]) { + dma_unmap_page(dev->dev, omap_obj->addrs[pgoff], + PAGE_SIZE, DMA_BIDIRECTIONAL); + omap_obj->addrs[pgoff] = 0; + } +} + +/* sync the buffer for DMA access */ +void omap_gem_dma_sync(struct drm_gem_object *obj, + enum dma_data_direction dir) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + if (is_cached_coherent(obj)) { + int i, npages = obj->size >> PAGE_SHIFT; + struct page **pages = omap_obj->pages; + bool dirty = false; + + for (i = 0; i < npages; i++) { + if (!omap_obj->addrs[i]) { + omap_obj->addrs[i] = dma_map_page(dev->dev, pages[i], 0, + PAGE_SIZE, DMA_BIDIRECTIONAL); + dirty = true; + } + } + + if (dirty) { + unmap_mapping_range(obj->filp->f_mapping, 0, + omap_gem_mmap_size(obj), 1); + } + } +} + +/* Get physical address for DMA.. if 'remap' is true, and the buffer is not + * already contiguous, remap it to pin in physically contiguous memory.. (ie. + * map in TILER) + */ +int omap_gem_get_paddr(struct drm_gem_object *obj, + dma_addr_t *paddr, bool remap) +{ + struct omap_drm_private *priv = obj->dev->dev_private; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + mutex_lock(&obj->dev->struct_mutex); + + if (remap && is_shmem(obj) && priv->has_dmm) { + if (omap_obj->paddr_cnt == 0) { + struct page **pages; + uint32_t npages = obj->size >> PAGE_SHIFT; + enum tiler_fmt fmt = gem2fmt(omap_obj->flags); + struct tiler_block *block; + + BUG_ON(omap_obj->block); + + ret = get_pages(obj, &pages); + if (ret) + goto fail; + + if (omap_obj->flags & OMAP_BO_TILED) { + block = tiler_reserve_2d(fmt, + omap_obj->width, + omap_obj->height, 0); + } else { + block = tiler_reserve_1d(obj->size); + } + + if (IS_ERR(block)) { + ret = PTR_ERR(block); + dev_err(obj->dev->dev, + "could not remap: %d (%d)\n", ret, fmt); + goto fail; + } + + /* TODO: enable async refill.. */ + ret = tiler_pin(block, pages, npages, + omap_obj->roll, true); + if (ret) { + tiler_release(block); + dev_err(obj->dev->dev, + "could not pin: %d\n", ret); + goto fail; + } + + omap_obj->paddr = tiler_ssptr(block); + omap_obj->block = block; + + DBG("got paddr: %08x", omap_obj->paddr); + } + + omap_obj->paddr_cnt++; + + *paddr = omap_obj->paddr; + } else if (omap_obj->flags & OMAP_BO_DMA) { + *paddr = omap_obj->paddr; + } else { + ret = -EINVAL; + goto fail; + } + +fail: + mutex_unlock(&obj->dev->struct_mutex); + + return ret; +} + +/* Release physical address, when DMA is no longer being performed.. this + * could potentially unpin and unmap buffers from TILER + */ +int omap_gem_put_paddr(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + mutex_lock(&obj->dev->struct_mutex); + if (omap_obj->paddr_cnt > 0) { + omap_obj->paddr_cnt--; + if (omap_obj->paddr_cnt == 0) { + ret = tiler_unpin(omap_obj->block); + if (ret) { + dev_err(obj->dev->dev, + "could not unpin pages: %d\n", ret); + goto fail; + } + ret = tiler_release(omap_obj->block); + if (ret) { + dev_err(obj->dev->dev, + "could not release unmap: %d\n", ret); + } + omap_obj->block = NULL; + } + } +fail: + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* Get rotated scanout address (only valid if already pinned), at the + * specified orientation and x,y offset from top-left corner of buffer + * (only valid for tiled 2d buffers) + */ +int omap_gem_rotated_paddr(struct drm_gem_object *obj, uint32_t orient, + int x, int y, dma_addr_t *paddr) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = -EINVAL; + + mutex_lock(&obj->dev->struct_mutex); + if ((omap_obj->paddr_cnt > 0) && omap_obj->block && + (omap_obj->flags & OMAP_BO_TILED)) { + *paddr = tiler_tsptr(omap_obj->block, orient, x, y); + ret = 0; + } + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* Get tiler stride for the buffer (only valid for 2d tiled buffers) */ +int omap_gem_tiled_stride(struct drm_gem_object *obj, uint32_t orient) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = -EINVAL; + if (omap_obj->flags & OMAP_BO_TILED) + ret = tiler_stride(gem2fmt(omap_obj->flags), orient); + return ret; +} + +/* acquire pages when needed (for example, for DMA where physically + * contiguous buffer is not required + */ +static int get_pages(struct drm_gem_object *obj, struct page ***pages) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + if (is_shmem(obj) && !omap_obj->pages) { + ret = omap_gem_attach_pages(obj); + if (ret) { + dev_err(obj->dev->dev, "could not attach pages\n"); + return ret; + } + } + + /* TODO: even phys-contig.. we should have a list of pages? */ + *pages = omap_obj->pages; + + return 0; +} + +/* if !remap, and we don't have pages backing, then fail, rather than + * increasing the pin count (which we don't really do yet anyways, + * because we don't support swapping pages back out). And 'remap' + * might not be quite the right name, but I wanted to keep it working + * similarly to omap_gem_get_paddr(). Note though that mutex is not + * aquired if !remap (because this can be called in atomic ctxt), + * but probably omap_gem_get_paddr() should be changed to work in the + * same way. If !remap, a matching omap_gem_put_pages() call is not + * required (and should not be made). + */ +int omap_gem_get_pages(struct drm_gem_object *obj, struct page ***pages, + bool remap) +{ + int ret; + if (!remap) { + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (!omap_obj->pages) + return -ENOMEM; + *pages = omap_obj->pages; + return 0; + } + mutex_lock(&obj->dev->struct_mutex); + ret = get_pages(obj, pages); + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* release pages when DMA no longer being performed */ +int omap_gem_put_pages(struct drm_gem_object *obj) +{ + /* do something here if we dynamically attach/detach pages.. at + * least they would no longer need to be pinned if everyone has + * released the pages.. + */ + return 0; +} + +/* Get kernel virtual address for CPU access.. this more or less only + * exists for omap_fbdev. This should be called with struct_mutex + * held. + */ +void *omap_gem_vaddr(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + WARN_ON(!mutex_is_locked(&obj->dev->struct_mutex)); + if (!omap_obj->vaddr) { + struct page **pages; + int ret = get_pages(obj, &pages); + if (ret) + return ERR_PTR(ret); + omap_obj->vaddr = vmap(pages, obj->size >> PAGE_SHIFT, + VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + } + return omap_obj->vaddr; +} + +#ifdef CONFIG_PM +/* re-pin objects in DMM in resume path: */ +int omap_gem_resume(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct omap_drm_private *priv = drm_dev->dev_private; + struct omap_gem_object *omap_obj; + int ret = 0; + + list_for_each_entry(omap_obj, &priv->obj_list, mm_list) { + if (omap_obj->block) { + struct drm_gem_object *obj = &omap_obj->base; + uint32_t npages = obj->size >> PAGE_SHIFT; + WARN_ON(!omap_obj->pages); /* this can't happen */ + ret = tiler_pin(omap_obj->block, + omap_obj->pages, npages, + omap_obj->roll, true); + if (ret) { + dev_err(dev, "could not repin: %d\n", ret); + return ret; + } + } + } + + return 0; +} +#endif + +#ifdef CONFIG_DEBUG_FS +void omap_gem_describe(struct drm_gem_object *obj, struct seq_file *m) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + uint64_t off = 0; + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (obj->map_list.map) + off = (uint64_t)obj->map_list.hash.key; + + seq_printf(m, "%08x: %2d (%2d) %08llx %08Zx (%2d) %p %4d", + omap_obj->flags, obj->name, obj->refcount.refcount.counter, + off, omap_obj->paddr, omap_obj->paddr_cnt, + omap_obj->vaddr, omap_obj->roll); + + if (omap_obj->flags & OMAP_BO_TILED) { + seq_printf(m, " %dx%d", omap_obj->width, omap_obj->height); + if (omap_obj->block) { + struct tcm_area *area = &omap_obj->block->area; + seq_printf(m, " (%dx%d, %dx%d)", + area->p0.x, area->p0.y, + area->p1.x, area->p1.y); + } + } else { + seq_printf(m, " %d", obj->size); + } + + seq_printf(m, "\n"); +} + +void omap_gem_describe_objects(struct list_head *list, struct seq_file *m) +{ + struct omap_gem_object *omap_obj; + int count = 0; + size_t size = 0; + + list_for_each_entry(omap_obj, list, mm_list) { + struct drm_gem_object *obj = &omap_obj->base; + seq_printf(m, " "); + omap_gem_describe(obj, m); + count++; + size += obj->size; + } + + seq_printf(m, "Total %d objects, %zu bytes\n", count, size); +} +#endif + +/* Buffer Synchronization: + */ + +struct omap_gem_sync_waiter { + struct list_head list; + struct omap_gem_object *omap_obj; + enum omap_gem_op op; + uint32_t read_target, write_target; + /* notify called w/ sync_lock held */ + void (*notify)(void *arg); + void *arg; +}; + +/* list of omap_gem_sync_waiter.. the notify fxn gets called back when + * the read and/or write target count is achieved which can call a user + * callback (ex. to kick 3d and/or 2d), wakeup blocked task (prep for + * cpu access), etc. + */ +static LIST_HEAD(waiters); + +static inline bool is_waiting(struct omap_gem_sync_waiter *waiter) +{ + struct omap_gem_object *omap_obj = waiter->omap_obj; + if ((waiter->op & OMAP_GEM_READ) && + (omap_obj->sync->read_complete < waiter->read_target)) + return true; + if ((waiter->op & OMAP_GEM_WRITE) && + (omap_obj->sync->write_complete < waiter->write_target)) + return true; + return false; +} + +/* macro for sync debug.. */ +#define SYNCDBG 0 +#define SYNC(fmt, ...) do { if (SYNCDBG) \ + printk(KERN_ERR "%s:%d: "fmt"\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) + + +static void sync_op_update(void) +{ + struct omap_gem_sync_waiter *waiter, *n; + list_for_each_entry_safe(waiter, n, &waiters, list) { + if (!is_waiting(waiter)) { + list_del(&waiter->list); + SYNC("notify: %p", waiter); + waiter->notify(waiter->arg); + kfree(waiter); + } + } +} + +static inline int sync_op(struct drm_gem_object *obj, + enum omap_gem_op op, bool start) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if (!omap_obj->sync) { + omap_obj->sync = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); + if (!omap_obj->sync) { + ret = -ENOMEM; + goto unlock; + } + } + + if (start) { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_pending++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_pending++; + } else { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_complete++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_complete++; + sync_op_update(); + } + +unlock: + spin_unlock(&sync_lock); + + return ret; +} + +/* it is a bit lame to handle updates in this sort of polling way, but + * in case of PVR, the GPU can directly update read/write complete + * values, and not really tell us which ones it updated.. this also + * means that sync_lock is not quite sufficient. So we'll need to + * do something a bit better when it comes time to add support for + * separate 2d hw.. + */ +void omap_gem_op_update(void) +{ + spin_lock(&sync_lock); + sync_op_update(); + spin_unlock(&sync_lock); +} + +/* mark the start of read and/or write operation */ +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, true); +} + +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, false); +} + +static DECLARE_WAIT_QUEUE_HEAD(sync_event); + +static void sync_notify(void *arg) +{ + struct task_struct **waiter_task = arg; + *waiter_task = NULL; + wake_up_all(&sync_event); +} + +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + if (omap_obj->sync) { + struct task_struct *waiter_task = current; + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_KERNEL); + + if (!waiter) + return -ENOMEM; + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = sync_notify; + waiter->arg = &waiter_task; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + ret = wait_event_interruptible(sync_event, + (waiter_task == NULL)); + spin_lock(&sync_lock); + if (waiter_task) { + SYNC("interrupted: %p", waiter); + /* we were interrupted */ + list_del(&waiter->list); + waiter_task = NULL; + } else { + /* freed in sync_op_update() */ + waiter = NULL; + } + } + spin_unlock(&sync_lock); + + if (waiter) + kfree(waiter); + } + return ret; +} + +/* call fxn(arg), either synchronously or asynchronously if the op + * is currently blocked.. fxn() can be called from any context + * + * (TODO for now fxn is called back from whichever context calls + * omap_gem_op_update().. but this could be better defined later + * if needed) + * + * TODO more code in common w/ _sync().. + */ +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, + void (*fxn)(void *arg), void *arg) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (omap_obj->sync) { + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_ATOMIC); + + if (!waiter) + return -ENOMEM; + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = fxn; + waiter->arg = arg; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + return 0; + } + + spin_unlock(&sync_lock); + } + + /* no waiting.. */ + fxn(arg); + + return 0; +} + +/* special API so PVR can update the buffer to use a sync-object allocated + * from it's sync-obj heap. Only used for a newly allocated (from PVR's + * perspective) sync-object, so we overwrite the new syncobj w/ values + * from the already allocated syncobj (if there is one) + */ +int omap_gem_set_sync_object(struct drm_gem_object *obj, void *syncobj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if ((omap_obj->flags & OMAP_BO_EXT_SYNC) && !syncobj) { + /* clearing a previously set syncobj */ + syncobj = kmemdup(omap_obj->sync, sizeof(*omap_obj->sync), + GFP_ATOMIC); + if (!syncobj) { + ret = -ENOMEM; + goto unlock; + } + omap_obj->flags &= ~OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } else if (syncobj && !(omap_obj->flags & OMAP_BO_EXT_SYNC)) { + /* replacing an existing syncobj */ + if (omap_obj->sync) { + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); + kfree(omap_obj->sync); + } + omap_obj->flags |= OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } + +unlock: + spin_unlock(&sync_lock); + return ret; +} + +int omap_gem_init_object(struct drm_gem_object *obj) +{ + return -EINVAL; /* unused */ +} + +/* don't call directly.. called from GEM core when it is time to actually + * free the object.. + */ +void omap_gem_free_object(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + evict(obj); + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + list_del(&omap_obj->mm_list); + + if (obj->map_list.map) + drm_gem_free_mmap_offset(obj); + + /* this means the object is still pinned.. which really should + * not happen. I think.. + */ + WARN_ON(omap_obj->paddr_cnt > 0); + + /* don't free externally allocated backing memory */ + if (!(omap_obj->flags & OMAP_BO_EXT_MEM)) { + if (omap_obj->pages) + omap_gem_detach_pages(obj); + + if (!is_shmem(obj)) { + dma_free_writecombine(dev->dev, obj->size, + omap_obj->vaddr, omap_obj->paddr); + } else if (omap_obj->vaddr) { + vunmap(omap_obj->vaddr); + } + } + + /* don't free externally allocated syncobj */ + if (!(omap_obj->flags & OMAP_BO_EXT_SYNC)) + kfree(omap_obj->sync); + + drm_gem_object_release(obj); + + kfree(obj); +} + +/* convenience method to construct a GEM buffer object, and userspace handle */ +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, + union omap_gem_size gsize, uint32_t flags, uint32_t *handle) +{ + struct drm_gem_object *obj; + int ret; + + obj = omap_gem_new(dev, gsize, flags); + if (!obj) + return -ENOMEM; + + ret = drm_gem_handle_create(file, obj, handle); + if (ret) { + drm_gem_object_release(obj); + kfree(obj); /* TODO isn't there a dtor to call? just copying i915 */ + return ret; + } + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(obj); + + return 0; +} + +/* GEM buffer object constructor */ +struct drm_gem_object *omap_gem_new(struct drm_device *dev, + union omap_gem_size gsize, uint32_t flags) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_gem_object *omap_obj; + struct drm_gem_object *obj = NULL; + size_t size; + int ret; + + if (flags & OMAP_BO_TILED) { + if (!usergart) { + dev_err(dev->dev, "Tiled buffers require DMM\n"); + goto fail; + } + + /* tiled buffers are always shmem paged backed.. when they are + * scanned out, they are remapped into DMM/TILER + */ + flags &= ~OMAP_BO_SCANOUT; + + /* currently don't allow cached buffers.. there is some caching + * stuff that needs to be handled better + */ + flags &= ~(OMAP_BO_CACHED|OMAP_BO_UNCACHED); + flags |= OMAP_BO_WC; + + /* align dimensions to slot boundaries... */ + tiler_align(gem2fmt(flags), + &gsize.tiled.width, &gsize.tiled.height); + + /* ...and calculate size based on aligned dimensions */ + size = tiler_size(gem2fmt(flags), + gsize.tiled.width, gsize.tiled.height); + } else { + size = PAGE_ALIGN(gsize.bytes); + } + + omap_obj = kzalloc(sizeof(*omap_obj), GFP_KERNEL); + if (!omap_obj) { + dev_err(dev->dev, "could not allocate GEM object\n"); + goto fail; + } + + list_add(&omap_obj->mm_list, &priv->obj_list); + + obj = &omap_obj->base; + + if ((flags & OMAP_BO_SCANOUT) && !priv->has_dmm) { + /* attempt to allocate contiguous memory if we don't + * have DMM for remappign discontiguous buffers + */ + omap_obj->vaddr = dma_alloc_writecombine(dev->dev, size, + &omap_obj->paddr, GFP_KERNEL); + if (omap_obj->vaddr) + flags |= OMAP_BO_DMA; + + } + + omap_obj->flags = flags; + + if (flags & OMAP_BO_TILED) { + omap_obj->width = gsize.tiled.width; + omap_obj->height = gsize.tiled.height; + } + + if (flags & (OMAP_BO_DMA|OMAP_BO_EXT_MEM)) + ret = drm_gem_private_object_init(dev, obj, size); + else + ret = drm_gem_object_init(dev, obj, size); + + if (ret) + goto fail; + + return obj; + +fail: + if (obj) + omap_gem_free_object(obj); + + return NULL; +} + +/* init/cleanup.. if DMM is used, we need to set some stuff up.. */ +void omap_gem_init(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + const enum tiler_fmt fmts[] = { + TILFMT_8BIT, TILFMT_16BIT, TILFMT_32BIT + }; + int i, j; + + if (!dmm_is_available()) { + /* DMM only supported on OMAP4 and later, so this isn't fatal */ + dev_warn(dev->dev, "DMM not available, disable DMM support\n"); + return; + } + + usergart = kzalloc(3 * sizeof(*usergart), GFP_KERNEL); + if (!usergart) { + dev_warn(dev->dev, "could not allocate usergart\n"); + return; + } + + /* reserve 4k aligned/wide regions for userspace mappings: */ + for (i = 0; i < ARRAY_SIZE(fmts); i++) { + uint16_t h = 1, w = PAGE_SIZE >> i; + tiler_align(fmts[i], &w, &h); + /* note: since each region is 1 4kb page wide, and minimum + * number of rows, the height ends up being the same as the + * # of pages in the region + */ + usergart[i].height = h; + usergart[i].height_shift = ilog2(h); + usergart[i].stride_pfn = tiler_stride(fmts[i], 0) >> PAGE_SHIFT; + usergart[i].slot_shift = ilog2((PAGE_SIZE / h) >> i); + for (j = 0; j < NUM_USERGART_ENTRIES; j++) { + struct usergart_entry *entry = &usergart[i].entry[j]; + struct tiler_block *block = + tiler_reserve_2d(fmts[i], w, h, + PAGE_SIZE); + if (IS_ERR(block)) { + dev_err(dev->dev, + "reserve failed: %d, %d, %ld\n", + i, j, PTR_ERR(block)); + return; + } + entry->paddr = tiler_ssptr(block); + entry->block = block; + + DBG("%d:%d: %dx%d: paddr=%08x stride=%d", i, j, w, h, + entry->paddr, + usergart[i].stride_pfn << PAGE_SHIFT); + } + } + + priv->has_dmm = true; +} + +void omap_gem_deinit(struct drm_device *dev) +{ + /* I believe we can rely on there being no more outstanding GEM + * objects which could depend on usergart/dmm at this point. + */ + kfree(usergart); +} diff --git a/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c b/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c new file mode 100644 index 000000000000..ac74d1bc67bf --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c @@ -0,0 +1,225 @@ +/* + * drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include <linux/dma-buf.h> + +static struct sg_table *omap_gem_map_dma_buf( + struct dma_buf_attachment *attachment, + enum dma_data_direction dir) +{ + struct drm_gem_object *obj = attachment->dmabuf->priv; + struct sg_table *sg; + dma_addr_t paddr; + int ret; + + sg = kzalloc(sizeof(*sg), GFP_KERNEL); + if (!sg) + return ERR_PTR(-ENOMEM); + + /* camera, etc, need physically contiguous.. but we need a + * better way to know this.. + */ + ret = omap_gem_get_paddr(obj, &paddr, true); + if (ret) + goto out; + + ret = sg_alloc_table(sg, 1, GFP_KERNEL); + if (ret) + goto out; + + sg_init_table(sg->sgl, 1); + sg_dma_len(sg->sgl) = obj->size; + sg_set_page(sg->sgl, pfn_to_page(PFN_DOWN(paddr)), obj->size, 0); + sg_dma_address(sg->sgl) = paddr; + + /* this should be after _get_paddr() to ensure we have pages attached */ + omap_gem_dma_sync(obj, dir); + + return sg; +out: + kfree(sg); + return ERR_PTR(ret); +} + +static void omap_gem_unmap_dma_buf(struct dma_buf_attachment *attachment, + struct sg_table *sg, enum dma_data_direction dir) +{ + struct drm_gem_object *obj = attachment->dmabuf->priv; + omap_gem_put_paddr(obj); + sg_free_table(sg); + kfree(sg); +} + +static void omap_gem_dmabuf_release(struct dma_buf *buffer) +{ + struct drm_gem_object *obj = buffer->priv; + /* release reference that was taken when dmabuf was exported + * in omap_gem_prime_set().. + */ + drm_gem_object_unreference_unlocked(obj); +} + + +static int omap_gem_dmabuf_begin_cpu_access(struct dma_buf *buffer, + size_t start, size_t len, enum dma_data_direction dir) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + if (omap_gem_flags(obj) & OMAP_BO_TILED) { + /* TODO we would need to pin at least part of the buffer to + * get de-tiled view. For now just reject it. + */ + return -ENOMEM; + } + /* make sure we have the pages: */ + return omap_gem_get_pages(obj, &pages, true); +} + +static void omap_gem_dmabuf_end_cpu_access(struct dma_buf *buffer, + size_t start, size_t len, enum dma_data_direction dir) +{ + struct drm_gem_object *obj = buffer->priv; + omap_gem_put_pages(obj); +} + + +static void *omap_gem_dmabuf_kmap_atomic(struct dma_buf *buffer, + unsigned long page_num) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + omap_gem_get_pages(obj, &pages, false); + omap_gem_cpu_sync(obj, page_num); + return kmap_atomic(pages[page_num]); +} + +static void omap_gem_dmabuf_kunmap_atomic(struct dma_buf *buffer, + unsigned long page_num, void *addr) +{ + kunmap_atomic(addr); +} + +static void *omap_gem_dmabuf_kmap(struct dma_buf *buffer, + unsigned long page_num) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + omap_gem_get_pages(obj, &pages, false); + omap_gem_cpu_sync(obj, page_num); + return kmap(pages[page_num]); +} + +static void omap_gem_dmabuf_kunmap(struct dma_buf *buffer, + unsigned long page_num, void *addr) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + omap_gem_get_pages(obj, &pages, false); + kunmap(pages[page_num]); +} + +/* + * TODO maybe we can split up drm_gem_mmap to avoid duplicating + * some here.. or at least have a drm_dmabuf_mmap helper. + */ +static int omap_gem_dmabuf_mmap(struct dma_buf *buffer, + struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = buffer->priv; + int ret = 0; + + if (WARN_ON(!obj->filp)) + return -EINVAL; + + /* Check for valid size. */ + if (omap_gem_mmap_size(obj) < vma->vm_end - vma->vm_start) { + ret = -EINVAL; + goto out_unlock; + } + + if (!obj->dev->driver->gem_vm_ops) { + ret = -EINVAL; + goto out_unlock; + } + + vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_ops = obj->dev->driver->gem_vm_ops; + vma->vm_private_data = obj; + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + + /* Take a ref for this mapping of the object, so that the fault + * handler can dereference the mmap offset's pointer to the object. + * This reference is cleaned up by the corresponding vm_close + * (which should happen whether the vma was created by this call, or + * by a vm_open due to mremap or partial unmap or whatever). + */ + vma->vm_ops->open(vma); + +out_unlock: + + return omap_gem_mmap_obj(obj, vma); +} + +struct dma_buf_ops omap_dmabuf_ops = { + .map_dma_buf = omap_gem_map_dma_buf, + .unmap_dma_buf = omap_gem_unmap_dma_buf, + .release = omap_gem_dmabuf_release, + .begin_cpu_access = omap_gem_dmabuf_begin_cpu_access, + .end_cpu_access = omap_gem_dmabuf_end_cpu_access, + .kmap_atomic = omap_gem_dmabuf_kmap_atomic, + .kunmap_atomic = omap_gem_dmabuf_kunmap_atomic, + .kmap = omap_gem_dmabuf_kmap, + .kunmap = omap_gem_dmabuf_kunmap, + .mmap = omap_gem_dmabuf_mmap, +}; + +struct dma_buf *omap_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, int flags) +{ + return dma_buf_export(obj, &omap_dmabuf_ops, obj->size, flags); +} + +struct drm_gem_object *omap_gem_prime_import(struct drm_device *dev, + struct dma_buf *buffer) +{ + struct drm_gem_object *obj; + + /* is this one of own objects? */ + if (buffer->ops == &omap_dmabuf_ops) { + obj = buffer->priv; + /* is it from our device? */ + if (obj->dev == dev) { + /* + * Importing dmabuf exported from out own gem increases + * refcount on gem itself instead of f_count of dmabuf. + */ + drm_gem_object_reference(obj); + dma_buf_put(buffer); + return obj; + } + } + + /* + * TODO add support for importing buffers from other devices.. + * for now we don't need this but would be nice to add eventually + */ + return ERR_PTR(-EINVAL); +} diff --git a/drivers/gpu/drm/omapdrm/omap_gem_helpers.c b/drivers/gpu/drm/omapdrm/omap_gem_helpers.c new file mode 100644 index 000000000000..e4a66a35fc6a --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_gem_helpers.c @@ -0,0 +1,169 @@ +/* + * drivers/gpu/drm/omapdrm/omap_gem_helpers.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* temporary copy of drm_gem_{get,put}_pages() until the + * "drm/gem: add functions to get/put pages" patch is merged.. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/shmem_fs.h> + +#include <drm/drmP.h> + +/** + * drm_gem_get_pages - helper to allocate backing pages for a GEM object + * @obj: obj in question + * @gfpmask: gfp mask of requested pages + */ +struct page **_drm_gem_get_pages(struct drm_gem_object *obj, gfp_t gfpmask) +{ + struct inode *inode; + struct address_space *mapping; + struct page *p, **pages; + int i, npages; + + /* This is the shared memory object that backs the GEM resource */ + inode = obj->filp->f_path.dentry->d_inode; + mapping = inode->i_mapping; + + npages = obj->size >> PAGE_SHIFT; + + pages = drm_malloc_ab(npages, sizeof(struct page *)); + if (pages == NULL) + return ERR_PTR(-ENOMEM); + + gfpmask |= mapping_gfp_mask(mapping); + + for (i = 0; i < npages; i++) { + p = shmem_read_mapping_page_gfp(mapping, i, gfpmask); + if (IS_ERR(p)) + goto fail; + pages[i] = p; + + /* There is a hypothetical issue w/ drivers that require + * buffer memory in the low 4GB.. if the pages are un- + * pinned, and swapped out, they can end up swapped back + * in above 4GB. If pages are already in memory, then + * shmem_read_mapping_page_gfp will ignore the gfpmask, + * even if the already in-memory page disobeys the mask. + * + * It is only a theoretical issue today, because none of + * the devices with this limitation can be populated with + * enough memory to trigger the issue. But this BUG_ON() + * is here as a reminder in case the problem with + * shmem_read_mapping_page_gfp() isn't solved by the time + * it does become a real issue. + * + * See this thread: http://lkml.org/lkml/2011/7/11/238 + */ + BUG_ON((gfpmask & __GFP_DMA32) && + (page_to_pfn(p) >= 0x00100000UL)); + } + + return pages; + +fail: + while (i--) + page_cache_release(pages[i]); + + drm_free_large(pages); + return ERR_CAST(p); +} + +/** + * drm_gem_put_pages - helper to free backing pages for a GEM object + * @obj: obj in question + * @pages: pages to free + */ +void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages, + bool dirty, bool accessed) +{ + int i, npages; + + npages = obj->size >> PAGE_SHIFT; + + for (i = 0; i < npages; i++) { + if (dirty) + set_page_dirty(pages[i]); + + if (accessed) + mark_page_accessed(pages[i]); + + /* Undo the reference we took when populating the table */ + page_cache_release(pages[i]); + } + + drm_free_large(pages); +} + +int +_drm_gem_create_mmap_offset_size(struct drm_gem_object *obj, size_t size) +{ + struct drm_device *dev = obj->dev; + struct drm_gem_mm *mm = dev->mm_private; + struct drm_map_list *list; + struct drm_local_map *map; + int ret = 0; + + /* Set the object up for mmap'ing */ + list = &obj->map_list; + list->map = kzalloc(sizeof(struct drm_map_list), GFP_KERNEL); + if (!list->map) + return -ENOMEM; + + map = list->map; + map->type = _DRM_GEM; + map->size = size; + map->handle = obj; + + /* Get a DRM GEM mmap offset allocated... */ + list->file_offset_node = drm_mm_search_free(&mm->offset_manager, + size / PAGE_SIZE, 0, 0); + + if (!list->file_offset_node) { + DRM_ERROR("failed to allocate offset for bo %d\n", obj->name); + ret = -ENOSPC; + goto out_free_list; + } + + list->file_offset_node = drm_mm_get_block(list->file_offset_node, + size / PAGE_SIZE, 0); + if (!list->file_offset_node) { + ret = -ENOMEM; + goto out_free_list; + } + + list->hash.key = list->file_offset_node->start; + ret = drm_ht_insert_item(&mm->offset_hash, &list->hash); + if (ret) { + DRM_ERROR("failed to add to map hash\n"); + goto out_free_mm; + } + + return 0; + +out_free_mm: + drm_mm_put_block(list->file_offset_node); +out_free_list: + kfree(list->map); + list->map = NULL; + + return ret; +} diff --git a/drivers/gpu/drm/omapdrm/omap_irq.c b/drivers/gpu/drm/omapdrm/omap_irq.c new file mode 100644 index 000000000000..e01303ee00c3 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_irq.c @@ -0,0 +1,322 @@ +/* + * drivers/gpu/drm/omapdrm/omap_irq.c + * + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +static DEFINE_SPINLOCK(list_lock); + +static void omap_irq_error_handler(struct omap_drm_irq *irq, + uint32_t irqstatus) +{ + DRM_ERROR("errors: %08x\n", irqstatus); +} + +/* call with list_lock and dispc runtime held */ +static void omap_irq_update(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_drm_irq *irq; + uint32_t irqmask = priv->vblank_mask; + + BUG_ON(!spin_is_locked(&list_lock)); + + list_for_each_entry(irq, &priv->irq_list, node) + irqmask |= irq->irqmask; + + DBG("irqmask=%08x", irqmask); + + dispc_write_irqenable(irqmask); + dispc_read_irqenable(); /* flush posted write */ +} + +void omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq) +{ + struct omap_drm_private *priv = dev->dev_private; + unsigned long flags; + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + + if (!WARN_ON(irq->registered)) { + irq->registered = true; + list_add(&irq->node, &priv->irq_list); + omap_irq_update(dev); + } + + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); +} + +void omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq) +{ + unsigned long flags; + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + + if (!WARN_ON(!irq->registered)) { + irq->registered = false; + list_del(&irq->node); + omap_irq_update(dev); + } + + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); +} + +struct omap_irq_wait { + struct omap_drm_irq irq; + int count; +}; + +static DECLARE_WAIT_QUEUE_HEAD(wait_event); + +static void wait_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_irq_wait *wait = + container_of(irq, struct omap_irq_wait, irq); + wait->count--; + wake_up_all(&wait_event); +} + +struct omap_irq_wait * omap_irq_wait_init(struct drm_device *dev, + uint32_t irqmask, int count) +{ + struct omap_irq_wait *wait = kzalloc(sizeof(*wait), GFP_KERNEL); + wait->irq.irq = wait_irq; + wait->irq.irqmask = irqmask; + wait->count = count; + omap_irq_register(dev, &wait->irq); + return wait; +} + +int omap_irq_wait(struct drm_device *dev, struct omap_irq_wait *wait, + unsigned long timeout) +{ + int ret = wait_event_timeout(wait_event, (wait->count <= 0), timeout); + omap_irq_unregister(dev, &wait->irq); + kfree(wait); + if (ret == 0) + return -1; + return 0; +} + +/** + * enable_vblank - enable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Enable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + * + * RETURNS + * Zero on success, appropriate errno if the given @crtc's vblank + * interrupt cannot be enabled. + */ +int omap_irq_enable_vblank(struct drm_device *dev, int crtc) +{ + struct omap_drm_private *priv = dev->dev_private; + unsigned long flags; + + DBG("dev=%p, crtc=%d", dev, crtc); + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + priv->vblank_mask |= pipe2vbl(crtc); + omap_irq_update(dev); + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); + + return 0; +} + +/** + * disable_vblank - disable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Disable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + */ +void omap_irq_disable_vblank(struct drm_device *dev, int crtc) +{ + struct omap_drm_private *priv = dev->dev_private; + unsigned long flags; + + DBG("dev=%p, crtc=%d", dev, crtc); + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + priv->vblank_mask &= ~pipe2vbl(crtc); + omap_irq_update(dev); + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); +} + +irqreturn_t omap_irq_handler(DRM_IRQ_ARGS) +{ + struct drm_device *dev = (struct drm_device *) arg; + struct omap_drm_private *priv = dev->dev_private; + struct omap_drm_irq *handler, *n; + unsigned long flags; + unsigned int id; + u32 irqstatus; + + irqstatus = dispc_read_irqstatus(); + dispc_clear_irqstatus(irqstatus); + dispc_read_irqstatus(); /* flush posted write */ + + VERB("irqs: %08x", irqstatus); + + for (id = 0; id < priv->num_crtcs; id++) + if (irqstatus & pipe2vbl(id)) + drm_handle_vblank(dev, id); + + spin_lock_irqsave(&list_lock, flags); + list_for_each_entry_safe(handler, n, &priv->irq_list, node) { + if (handler->irqmask & irqstatus) { + spin_unlock_irqrestore(&list_lock, flags); + handler->irq(handler, handler->irqmask & irqstatus); + spin_lock_irqsave(&list_lock, flags); + } + } + spin_unlock_irqrestore(&list_lock, flags); + + return IRQ_HANDLED; +} + +void omap_irq_preinstall(struct drm_device *dev) +{ + DBG("dev=%p", dev); + dispc_runtime_get(); + dispc_clear_irqstatus(0xffffffff); + dispc_runtime_put(); +} + +int omap_irq_postinstall(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_drm_irq *error_handler = &priv->error_handler; + + DBG("dev=%p", dev); + + INIT_LIST_HEAD(&priv->irq_list); + + error_handler->irq = omap_irq_error_handler; + error_handler->irqmask = DISPC_IRQ_OCP_ERR; + + /* for now ignore DISPC_IRQ_SYNC_LOST_DIGIT.. really I think + * we just need to ignore it while enabling tv-out + */ + error_handler->irqmask &= ~DISPC_IRQ_SYNC_LOST_DIGIT; + + omap_irq_register(dev, error_handler); + + return 0; +} + +void omap_irq_uninstall(struct drm_device *dev) +{ + DBG("dev=%p", dev); + // TODO prolly need to call drm_irq_uninstall() somewhere too +} + +/* + * We need a special version, instead of just using drm_irq_install(), + * because we need to register the irq via omapdss. Once omapdss and + * omapdrm are merged together we can assign the dispc hwmod data to + * ourselves and drop these and just use drm_irq_{install,uninstall}() + */ + +int omap_drm_irq_install(struct drm_device *dev) +{ + int ret; + + mutex_lock(&dev->struct_mutex); + + if (dev->irq_enabled) { + mutex_unlock(&dev->struct_mutex); + return -EBUSY; + } + dev->irq_enabled = 1; + mutex_unlock(&dev->struct_mutex); + + /* Before installing handler */ + if (dev->driver->irq_preinstall) + dev->driver->irq_preinstall(dev); + + ret = dispc_request_irq(dev->driver->irq_handler, dev); + + if (ret < 0) { + mutex_lock(&dev->struct_mutex); + dev->irq_enabled = 0; + mutex_unlock(&dev->struct_mutex); + return ret; + } + + /* After installing handler */ + if (dev->driver->irq_postinstall) + ret = dev->driver->irq_postinstall(dev); + + if (ret < 0) { + mutex_lock(&dev->struct_mutex); + dev->irq_enabled = 0; + mutex_unlock(&dev->struct_mutex); + dispc_free_irq(dev); + } + + return ret; +} + +int omap_drm_irq_uninstall(struct drm_device *dev) +{ + unsigned long irqflags; + int irq_enabled, i; + + mutex_lock(&dev->struct_mutex); + irq_enabled = dev->irq_enabled; + dev->irq_enabled = 0; + mutex_unlock(&dev->struct_mutex); + + /* + * Wake up any waiters so they don't hang. + */ + if (dev->num_crtcs) { + spin_lock_irqsave(&dev->vbl_lock, irqflags); + for (i = 0; i < dev->num_crtcs; i++) { + DRM_WAKEUP(&dev->vbl_queue[i]); + dev->vblank_enabled[i] = 0; + dev->last_vblank[i] = + dev->driver->get_vblank_counter(dev, i); + } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + } + + if (!irq_enabled) + return -EINVAL; + + if (dev->driver->irq_uninstall) + dev->driver->irq_uninstall(dev); + + dispc_free_irq(dev); + + return 0; +} diff --git a/drivers/gpu/drm/omapdrm/omap_plane.c b/drivers/gpu/drm/omapdrm/omap_plane.c new file mode 100644 index 000000000000..dd68d14ce615 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_plane.c @@ -0,0 +1,450 @@ +/* + * drivers/gpu/drm/omapdrm/omap_plane.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kfifo.h> + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +/* some hackery because omapdss has an 'enum omap_plane' (which would be + * better named omap_plane_id).. and compiler seems unhappy about having + * both a 'struct omap_plane' and 'enum omap_plane' + */ +#define omap_plane _omap_plane + +/* + * plane funcs + */ + +struct callback { + void (*fxn)(void *); + void *arg; +}; + +#define to_omap_plane(x) container_of(x, struct omap_plane, base) + +struct omap_plane { + struct drm_plane base; + int id; /* TODO rename omap_plane -> omap_plane_id in omapdss so I can use the enum */ + const char *name; + struct omap_overlay_info info; + struct omap_drm_apply apply; + + /* position/orientation of scanout within the fb: */ + struct omap_drm_window win; + bool enabled; + + /* last fb that we pinned: */ + struct drm_framebuffer *pinned_fb; + + uint32_t nformats; + uint32_t formats[32]; + + struct omap_drm_irq error_irq; + + /* set of bo's pending unpin until next post_apply() */ + DECLARE_KFIFO_PTR(unpin_fifo, struct drm_gem_object *); + + // XXX maybe get rid of this and handle vblank in crtc too? + struct callback apply_done_cb; +}; + +static void unpin(void *arg, struct drm_gem_object *bo) +{ + struct drm_plane *plane = arg; + struct omap_plane *omap_plane = to_omap_plane(plane); + + if (kfifo_put(&omap_plane->unpin_fifo, + (const struct drm_gem_object **)&bo)) { + /* also hold a ref so it isn't free'd while pinned */ + drm_gem_object_reference(bo); + } else { + dev_err(plane->dev->dev, "unpin fifo full!\n"); + omap_gem_put_paddr(bo); + } +} + +/* update which fb (if any) is pinned for scanout */ +static int update_pin(struct drm_plane *plane, struct drm_framebuffer *fb) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + struct drm_framebuffer *pinned_fb = omap_plane->pinned_fb; + + if (pinned_fb != fb) { + int ret; + + DBG("%p -> %p", pinned_fb, fb); + + if (fb) + drm_framebuffer_reference(fb); + + ret = omap_framebuffer_replace(pinned_fb, fb, plane, unpin); + + if (pinned_fb) + drm_framebuffer_unreference(pinned_fb); + + if (ret) { + dev_err(plane->dev->dev, "could not swap %p -> %p\n", + omap_plane->pinned_fb, fb); + if (fb) + drm_framebuffer_unreference(fb); + omap_plane->pinned_fb = NULL; + return ret; + } + + omap_plane->pinned_fb = fb; + } + + return 0; +} + +static void omap_plane_pre_apply(struct omap_drm_apply *apply) +{ + struct omap_plane *omap_plane = + container_of(apply, struct omap_plane, apply); + struct omap_drm_window *win = &omap_plane->win; + struct drm_plane *plane = &omap_plane->base; + struct drm_device *dev = plane->dev; + struct omap_overlay_info *info = &omap_plane->info; + struct drm_crtc *crtc = plane->crtc; + enum omap_channel channel; + bool enabled = omap_plane->enabled && crtc; + bool ilace, replication; + int ret; + + DBG("%s, enabled=%d", omap_plane->name, enabled); + + /* if fb has changed, pin new fb: */ + update_pin(plane, enabled ? plane->fb : NULL); + + if (!enabled) { + dispc_ovl_enable(omap_plane->id, false); + return; + } + + channel = omap_crtc_channel(crtc); + + /* update scanout: */ + omap_framebuffer_update_scanout(plane->fb, win, info); + + DBG("%dx%d -> %dx%d (%d)", info->width, info->height, + info->out_width, info->out_height, + info->screen_width); + DBG("%d,%d %08x %08x", info->pos_x, info->pos_y, + info->paddr, info->p_uv_addr); + + /* TODO: */ + ilace = false; + replication = false; + + /* and finally, update omapdss: */ + ret = dispc_ovl_setup(omap_plane->id, info, + replication, omap_crtc_timings(crtc), false); + if (ret) { + dev_err(dev->dev, "dispc_ovl_setup failed: %d\n", ret); + return; + } + + dispc_ovl_enable(omap_plane->id, true); + dispc_ovl_set_channel_out(omap_plane->id, channel); +} + +static void omap_plane_post_apply(struct omap_drm_apply *apply) +{ + struct omap_plane *omap_plane = + container_of(apply, struct omap_plane, apply); + struct drm_plane *plane = &omap_plane->base; + struct omap_overlay_info *info = &omap_plane->info; + struct drm_gem_object *bo = NULL; + struct callback cb; + + cb = omap_plane->apply_done_cb; + omap_plane->apply_done_cb.fxn = NULL; + + while (kfifo_get(&omap_plane->unpin_fifo, &bo)) { + omap_gem_put_paddr(bo); + drm_gem_object_unreference_unlocked(bo); + } + + if (cb.fxn) + cb.fxn(cb.arg); + + if (omap_plane->enabled) { + omap_framebuffer_flush(plane->fb, info->pos_x, info->pos_y, + info->out_width, info->out_height); + } +} + +static int apply(struct drm_plane *plane) +{ + if (plane->crtc) { + struct omap_plane *omap_plane = to_omap_plane(plane); + return omap_crtc_apply(plane->crtc, &omap_plane->apply); + } + return 0; +} + +int omap_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h, + void (*fxn)(void *), void *arg) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + struct omap_drm_window *win = &omap_plane->win; + + win->crtc_x = crtc_x; + win->crtc_y = crtc_y; + win->crtc_w = crtc_w; + win->crtc_h = crtc_h; + + /* src values are in Q16 fixed point, convert to integer: */ + win->src_x = src_x >> 16; + win->src_y = src_y >> 16; + win->src_w = src_w >> 16; + win->src_h = src_h >> 16; + + if (fxn) { + /* omap_crtc should ensure that a new page flip + * isn't permitted while there is one pending: + */ + BUG_ON(omap_plane->apply_done_cb.fxn); + + omap_plane->apply_done_cb.fxn = fxn; + omap_plane->apply_done_cb.arg = arg; + } + + plane->fb = fb; + plane->crtc = crtc; + + return apply(plane); +} + +static int omap_plane_update(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + omap_plane->enabled = true; + return omap_plane_mode_set(plane, crtc, fb, + crtc_x, crtc_y, crtc_w, crtc_h, + src_x, src_y, src_w, src_h, + NULL, NULL); +} + +static int omap_plane_disable(struct drm_plane *plane) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + omap_plane->win.rotation = BIT(DRM_ROTATE_0); + return omap_plane_dpms(plane, DRM_MODE_DPMS_OFF); +} + +static void omap_plane_destroy(struct drm_plane *plane) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + + DBG("%s", omap_plane->name); + + omap_irq_unregister(plane->dev, &omap_plane->error_irq); + + omap_plane_disable(plane); + drm_plane_cleanup(plane); + + WARN_ON(!kfifo_is_empty(&omap_plane->unpin_fifo)); + kfifo_free(&omap_plane->unpin_fifo); + + kfree(omap_plane); +} + +int omap_plane_dpms(struct drm_plane *plane, int mode) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + bool enabled = (mode == DRM_MODE_DPMS_ON); + int ret = 0; + + if (enabled != omap_plane->enabled) { + omap_plane->enabled = enabled; + ret = apply(plane); + } + + return ret; +} + +/* helper to install properties which are common to planes and crtcs */ +void omap_plane_install_properties(struct drm_plane *plane, + struct drm_mode_object *obj) +{ + struct drm_device *dev = plane->dev; + struct omap_drm_private *priv = dev->dev_private; + struct drm_property *prop; + + if (priv->has_dmm) { + prop = priv->rotation_prop; + if (!prop) { + const struct drm_prop_enum_list props[] = { + { DRM_ROTATE_0, "rotate-0" }, + { DRM_ROTATE_90, "rotate-90" }, + { DRM_ROTATE_180, "rotate-180" }, + { DRM_ROTATE_270, "rotate-270" }, + { DRM_REFLECT_X, "reflect-x" }, + { DRM_REFLECT_Y, "reflect-y" }, + }; + prop = drm_property_create_bitmask(dev, 0, "rotation", + props, ARRAY_SIZE(props)); + if (prop == NULL) + return; + priv->rotation_prop = prop; + } + drm_object_attach_property(obj, prop, 0); + } + + prop = priv->zorder_prop; + if (!prop) { + prop = drm_property_create_range(dev, 0, "zorder", 0, 3); + if (prop == NULL) + return; + priv->zorder_prop = prop; + } + drm_object_attach_property(obj, prop, 0); +} + +int omap_plane_set_property(struct drm_plane *plane, + struct drm_property *property, uint64_t val) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + struct omap_drm_private *priv = plane->dev->dev_private; + int ret = -EINVAL; + + if (property == priv->rotation_prop) { + DBG("%s: rotation: %02x", omap_plane->name, (uint32_t)val); + omap_plane->win.rotation = val; + ret = apply(plane); + } else if (property == priv->zorder_prop) { + DBG("%s: zorder: %02x", omap_plane->name, (uint32_t)val); + omap_plane->info.zorder = val; + ret = apply(plane); + } + + return ret; +} + +static const struct drm_plane_funcs omap_plane_funcs = { + .update_plane = omap_plane_update, + .disable_plane = omap_plane_disable, + .destroy = omap_plane_destroy, + .set_property = omap_plane_set_property, +}; + +static void omap_plane_error_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_plane *omap_plane = + container_of(irq, struct omap_plane, error_irq); + DRM_ERROR("%s: errors: %08x\n", omap_plane->name, irqstatus); +} + +static const char *plane_names[] = { + [OMAP_DSS_GFX] = "gfx", + [OMAP_DSS_VIDEO1] = "vid1", + [OMAP_DSS_VIDEO2] = "vid2", + [OMAP_DSS_VIDEO3] = "vid3", +}; + +static const uint32_t error_irqs[] = { + [OMAP_DSS_GFX] = DISPC_IRQ_GFX_FIFO_UNDERFLOW, + [OMAP_DSS_VIDEO1] = DISPC_IRQ_VID1_FIFO_UNDERFLOW, + [OMAP_DSS_VIDEO2] = DISPC_IRQ_VID2_FIFO_UNDERFLOW, + [OMAP_DSS_VIDEO3] = DISPC_IRQ_VID3_FIFO_UNDERFLOW, +}; + +/* initialize plane */ +struct drm_plane *omap_plane_init(struct drm_device *dev, + int id, bool private_plane) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_plane *plane = NULL; + struct omap_plane *omap_plane; + struct omap_overlay_info *info; + int ret; + + DBG("%s: priv=%d", plane_names[id], private_plane); + + omap_plane = kzalloc(sizeof(*omap_plane), GFP_KERNEL); + if (!omap_plane) { + dev_err(dev->dev, "could not allocate plane\n"); + goto fail; + } + + ret = kfifo_alloc(&omap_plane->unpin_fifo, 16, GFP_KERNEL); + if (ret) { + dev_err(dev->dev, "could not allocate unpin FIFO\n"); + goto fail; + } + + omap_plane->nformats = omap_framebuffer_get_formats( + omap_plane->formats, ARRAY_SIZE(omap_plane->formats), + dss_feat_get_supported_color_modes(id)); + omap_plane->id = id; + omap_plane->name = plane_names[id]; + + plane = &omap_plane->base; + + omap_plane->apply.pre_apply = omap_plane_pre_apply; + omap_plane->apply.post_apply = omap_plane_post_apply; + + omap_plane->error_irq.irqmask = error_irqs[id]; + omap_plane->error_irq.irq = omap_plane_error_irq; + omap_irq_register(dev, &omap_plane->error_irq); + + drm_plane_init(dev, plane, (1 << priv->num_crtcs) - 1, &omap_plane_funcs, + omap_plane->formats, omap_plane->nformats, private_plane); + + omap_plane_install_properties(plane, &plane->base); + + /* get our starting configuration, set defaults for parameters + * we don't currently use, etc: + */ + info = &omap_plane->info; + info->rotation_type = OMAP_DSS_ROT_DMA; + info->rotation = OMAP_DSS_ROT_0; + info->global_alpha = 0xff; + info->mirror = 0; + + /* Set defaults depending on whether we are a CRTC or overlay + * layer. + * TODO add ioctl to give userspace an API to change this.. this + * will come in a subsequent patch. + */ + if (private_plane) + omap_plane->info.zorder = 0; + else + omap_plane->info.zorder = id; + + return plane; + +fail: + if (plane) + omap_plane_destroy(plane); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/tcm-sita.c b/drivers/gpu/drm/omapdrm/tcm-sita.c new file mode 100644 index 000000000000..efb609510540 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/tcm-sita.c @@ -0,0 +1,703 @@ +/* + * tcm-sita.c + * + * SImple Tiler Allocator (SiTA): 2D and 1D allocation(reservation) algorithm + * + * Authors: Ravi Ramachandra <r.ramachandra@ti.com>, + * Lajos Molnar <molnar@ti.com> + * + * Copyright (C) 2009-2010 Texas Instruments, Inc. + * + * This package 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. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include "tcm-sita.h" + +#define ALIGN_DOWN(value, align) ((value) & ~((align) - 1)) + +/* Individual selection criteria for different scan areas */ +static s32 CR_L2R_T2B = CR_BIAS_HORIZONTAL; +static s32 CR_R2L_T2B = CR_DIAGONAL_BALANCE; + +/********************************************* + * TCM API - Sita Implementation + *********************************************/ +static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align, + struct tcm_area *area); +static s32 sita_reserve_1d(struct tcm *tcm, u32 slots, struct tcm_area *area); +static s32 sita_free(struct tcm *tcm, struct tcm_area *area); +static void sita_deinit(struct tcm *tcm); + +/********************************************* + * Main Scanner functions + *********************************************/ +static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *area); + +static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area); + +static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area); + +static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots, + struct tcm_area *field, struct tcm_area *area); + +/********************************************* + * Support Infrastructure Methods + *********************************************/ +static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h); + +static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h, + struct tcm_area *field, s32 criteria, + struct score *best); + +static void get_nearness_factor(struct tcm_area *field, + struct tcm_area *candidate, + struct nearness_factor *nf); + +static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area, + struct neighbor_stats *stat); + +static void fill_area(struct tcm *tcm, + struct tcm_area *area, struct tcm_area *parent); + + +/*********************************************/ + +/********************************************* + * Utility Methods + *********************************************/ +struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr) +{ + struct tcm *tcm; + struct sita_pvt *pvt; + struct tcm_area area = {0}; + s32 i; + + if (width == 0 || height == 0) + return NULL; + + tcm = kmalloc(sizeof(*tcm), GFP_KERNEL); + pvt = kmalloc(sizeof(*pvt), GFP_KERNEL); + if (!tcm || !pvt) + goto error; + + memset(tcm, 0, sizeof(*tcm)); + memset(pvt, 0, sizeof(*pvt)); + + /* Updating the pointers to SiTA implementation APIs */ + tcm->height = height; + tcm->width = width; + tcm->reserve_2d = sita_reserve_2d; + tcm->reserve_1d = sita_reserve_1d; + tcm->free = sita_free; + tcm->deinit = sita_deinit; + tcm->pvt = (void *)pvt; + + spin_lock_init(&(pvt->lock)); + + /* Creating tam map */ + pvt->map = kmalloc(sizeof(*pvt->map) * tcm->width, GFP_KERNEL); + if (!pvt->map) + goto error; + + for (i = 0; i < tcm->width; i++) { + pvt->map[i] = + kmalloc(sizeof(**pvt->map) * tcm->height, + GFP_KERNEL); + if (pvt->map[i] == NULL) { + while (i--) + kfree(pvt->map[i]); + kfree(pvt->map); + goto error; + } + } + + if (attr && attr->x <= tcm->width && attr->y <= tcm->height) { + pvt->div_pt.x = attr->x; + pvt->div_pt.y = attr->y; + + } else { + /* Defaulting to 3:1 ratio on width for 2D area split */ + /* Defaulting to 3:1 ratio on height for 2D and 1D split */ + pvt->div_pt.x = (tcm->width * 3) / 4; + pvt->div_pt.y = (tcm->height * 3) / 4; + } + + spin_lock(&(pvt->lock)); + assign(&area, 0, 0, width - 1, height - 1); + fill_area(tcm, &area, NULL); + spin_unlock(&(pvt->lock)); + return tcm; + +error: + kfree(tcm); + kfree(pvt); + return NULL; +} + +static void sita_deinit(struct tcm *tcm) +{ + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + struct tcm_area area = {0}; + s32 i; + + area.p1.x = tcm->width - 1; + area.p1.y = tcm->height - 1; + + spin_lock(&(pvt->lock)); + fill_area(tcm, &area, NULL); + spin_unlock(&(pvt->lock)); + + for (i = 0; i < tcm->height; i++) + kfree(pvt->map[i]); + kfree(pvt->map); + kfree(pvt); +} + +/** + * Reserve a 1D area in the container + * + * @param num_slots size of 1D area + * @param area pointer to the area that will be populated with the + * reserved area + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 sita_reserve_1d(struct tcm *tcm, u32 num_slots, + struct tcm_area *area) +{ + s32 ret; + struct tcm_area field = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + spin_lock(&(pvt->lock)); + + /* Scanning entire container */ + assign(&field, tcm->width - 1, tcm->height - 1, 0, 0); + + ret = scan_r2l_b2t_one_dim(tcm, num_slots, &field, area); + if (!ret) + /* update map */ + fill_area(tcm, area, area); + + spin_unlock(&(pvt->lock)); + return ret; +} + +/** + * Reserve a 2D area in the container + * + * @param w width + * @param h height + * @param area pointer to the area that will be populated with the reserved + * area + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align, + struct tcm_area *area) +{ + s32 ret; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + /* not supporting more than 64 as alignment */ + if (align > 64) + return -EINVAL; + + /* we prefer 1, 32 and 64 as alignment */ + align = align <= 1 ? 1 : align <= 32 ? 32 : 64; + + spin_lock(&(pvt->lock)); + ret = scan_areas_and_find_fit(tcm, w, h, align, area); + if (!ret) + /* update map */ + fill_area(tcm, area, area); + + spin_unlock(&(pvt->lock)); + return ret; +} + +/** + * Unreserve a previously allocated 2D or 1D area + * @param area area to be freed + * @return 0 - success + */ +static s32 sita_free(struct tcm *tcm, struct tcm_area *area) +{ + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + spin_lock(&(pvt->lock)); + + /* check that this is in fact an existing area */ + WARN_ON(pvt->map[area->p0.x][area->p0.y] != area || + pvt->map[area->p1.x][area->p1.y] != area); + + /* Clear the contents of the associated tiles in the map */ + fill_area(tcm, area, NULL); + + spin_unlock(&(pvt->lock)); + + return 0; +} + +/** + * Note: In general the cordinates in the scan field area relevant to the can + * sweep directions. The scan origin (e.g. top-left corner) will always be + * the p0 member of the field. Therfore, for a scan from top-left p0.x <= p1.x + * and p0.y <= p1.y; whereas, for a scan from bottom-right p1.x <= p0.x and p1.y + * <= p0.y + */ + +/** + * Raster scan horizontally right to left from top to bottom to find a place for + * a 2D area of given size inside a scan field. + * + * @param w width of desired area + * @param h height of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best position + * @param field area to scan (inclusive) + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area) +{ + s32 x, y; + s16 start_x, end_x, start_y, end_y, found_x = -1; + struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map; + struct score best = {{0}, {0}, {0}, 0}; + + start_x = field->p0.x; + end_x = field->p1.x; + start_y = field->p0.y; + end_y = field->p1.y; + + /* check scan area co-ordinates */ + if (field->p0.x < field->p1.x || + field->p1.y < field->p0.y) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (w > LEN(start_x, end_x) || h > LEN(end_y, start_y)) + return -ENOSPC; + + /* adjust start_x and end_y, as allocation would not fit beyond */ + start_x = ALIGN_DOWN(start_x - w + 1, align); /* - 1 to be inclusive */ + end_y = end_y - h + 1; + + /* check if allocation would still fit in scan area */ + if (start_x < end_x) + return -ENOSPC; + + /* scan field top-to-bottom, right-to-left */ + for (y = start_y; y <= end_y; y++) { + for (x = start_x; x >= end_x; x -= align) { + if (is_area_free(map, x, y, w, h)) { + found_x = x; + + /* update best candidate */ + if (update_candidate(tcm, x, y, w, h, field, + CR_R2L_T2B, &best)) + goto done; + + /* change upper x bound */ + end_x = x + 1; + break; + } else if (map[x][y] && map[x][y]->is2d) { + /* step over 2D areas */ + x = ALIGN(map[x][y]->p0.x - w + 1, align); + } + } + + /* break if you find a free area shouldering the scan field */ + if (found_x == start_x) + break; + } + + if (!best.a.tcm) + return -ENOSPC; +done: + assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y); + return 0; +} + +/** + * Raster scan horizontally left to right from top to bottom to find a place for + * a 2D area of given size inside a scan field. + * + * @param w width of desired area + * @param h height of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best position + * @param field area to scan (inclusive) + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area) +{ + s32 x, y; + s16 start_x, end_x, start_y, end_y, found_x = -1; + struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map; + struct score best = {{0}, {0}, {0}, 0}; + + start_x = field->p0.x; + end_x = field->p1.x; + start_y = field->p0.y; + end_y = field->p1.y; + + /* check scan area co-ordinates */ + if (field->p1.x < field->p0.x || + field->p1.y < field->p0.y) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (w > LEN(end_x, start_x) || h > LEN(end_y, start_y)) + return -ENOSPC; + + start_x = ALIGN(start_x, align); + + /* check if allocation would still fit in scan area */ + if (w > LEN(end_x, start_x)) + return -ENOSPC; + + /* adjust end_x and end_y, as allocation would not fit beyond */ + end_x = end_x - w + 1; /* + 1 to be inclusive */ + end_y = end_y - h + 1; + + /* scan field top-to-bottom, left-to-right */ + for (y = start_y; y <= end_y; y++) { + for (x = start_x; x <= end_x; x += align) { + if (is_area_free(map, x, y, w, h)) { + found_x = x; + + /* update best candidate */ + if (update_candidate(tcm, x, y, w, h, field, + CR_L2R_T2B, &best)) + goto done; + /* change upper x bound */ + end_x = x - 1; + + break; + } else if (map[x][y] && map[x][y]->is2d) { + /* step over 2D areas */ + x = ALIGN_DOWN(map[x][y]->p1.x, align); + } + } + + /* break if you find a free area shouldering the scan field */ + if (found_x == start_x) + break; + } + + if (!best.a.tcm) + return -ENOSPC; +done: + assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y); + return 0; +} + +/** + * Raster scan horizontally right to left from bottom to top to find a place + * for a 1D area of given size inside a scan field. + * + * @param num_slots size of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best + * position + * @param field area to scan (inclusive) + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots, + struct tcm_area *field, struct tcm_area *area) +{ + s32 found = 0; + s16 x, y; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + struct tcm_area *p; + + /* check scan area co-ordinates */ + if (field->p0.y < field->p1.y) + return -EINVAL; + + /** + * Currently we only support full width 1D scan field, which makes sense + * since 1D slot-ordering spans the full container width. + */ + if (tcm->width != field->p0.x - field->p1.x + 1) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (num_slots > tcm->width * LEN(field->p0.y, field->p1.y)) + return -ENOSPC; + + x = field->p0.x; + y = field->p0.y; + + /* find num_slots consecutive free slots to the left */ + while (found < num_slots) { + if (y < 0) + return -ENOSPC; + + /* remember bottom-right corner */ + if (found == 0) { + area->p1.x = x; + area->p1.y = y; + } + + /* skip busy regions */ + p = pvt->map[x][y]; + if (p) { + /* move to left of 2D areas, top left of 1D */ + x = p->p0.x; + if (!p->is2d) + y = p->p0.y; + + /* start over */ + found = 0; + } else { + /* count consecutive free slots */ + found++; + if (found == num_slots) + break; + } + + /* move to the left */ + if (x == 0) + y--; + x = (x ? : tcm->width) - 1; + + } + + /* set top-left corner */ + area->p0.x = x; + area->p0.y = y; + return 0; +} + +/** + * Find a place for a 2D area of given size inside a scan field based on its + * alignment needs. + * + * @param w width of desired area + * @param h height of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best position + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *area) +{ + s32 ret = 0; + struct tcm_area field = {0}; + u16 boundary_x, boundary_y; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + if (align > 1) { + /* prefer top-left corner */ + boundary_x = pvt->div_pt.x - 1; + boundary_y = pvt->div_pt.y - 1; + + /* expand width and height if needed */ + if (w > pvt->div_pt.x) + boundary_x = tcm->width - 1; + if (h > pvt->div_pt.y) + boundary_y = tcm->height - 1; + + assign(&field, 0, 0, boundary_x, boundary_y); + ret = scan_l2r_t2b(tcm, w, h, align, &field, area); + + /* scan whole container if failed, but do not scan 2x */ + if (ret != 0 && (boundary_x != tcm->width - 1 || + boundary_y != tcm->height - 1)) { + /* scan the entire container if nothing found */ + assign(&field, 0, 0, tcm->width - 1, tcm->height - 1); + ret = scan_l2r_t2b(tcm, w, h, align, &field, area); + } + } else if (align == 1) { + /* prefer top-right corner */ + boundary_x = pvt->div_pt.x; + boundary_y = pvt->div_pt.y - 1; + + /* expand width and height if needed */ + if (w > (tcm->width - pvt->div_pt.x)) + boundary_x = 0; + if (h > pvt->div_pt.y) + boundary_y = tcm->height - 1; + + assign(&field, tcm->width - 1, 0, boundary_x, boundary_y); + ret = scan_r2l_t2b(tcm, w, h, align, &field, area); + + /* scan whole container if failed, but do not scan 2x */ + if (ret != 0 && (boundary_x != 0 || + boundary_y != tcm->height - 1)) { + /* scan the entire container if nothing found */ + assign(&field, tcm->width - 1, 0, 0, tcm->height - 1); + ret = scan_r2l_t2b(tcm, w, h, align, &field, + area); + } + } + + return ret; +} + +/* check if an entire area is free */ +static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h) +{ + u16 x = 0, y = 0; + for (y = y0; y < y0 + h; y++) { + for (x = x0; x < x0 + w; x++) { + if (map[x][y]) + return false; + } + } + return true; +} + +/* fills an area with a parent tcm_area */ +static void fill_area(struct tcm *tcm, struct tcm_area *area, + struct tcm_area *parent) +{ + s32 x, y; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + struct tcm_area a, a_; + + /* set area's tcm; otherwise, enumerator considers it invalid */ + area->tcm = tcm; + + tcm_for_each_slice(a, *area, a_) { + for (x = a.p0.x; x <= a.p1.x; ++x) + for (y = a.p0.y; y <= a.p1.y; ++y) + pvt->map[x][y] = parent; + + } +} + +/** + * Compares a candidate area to the current best area, and if it is a better + * fit, it updates the best to this one. + * + * @param x0, y0, w, h top, left, width, height of candidate area + * @param field scan field + * @param criteria scan criteria + * @param best best candidate and its scores + * + * @return 1 (true) if the candidate area is known to be the final best, so no + * more searching should be performed + */ +static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h, + struct tcm_area *field, s32 criteria, + struct score *best) +{ + struct score me; /* score for area */ + + /* + * NOTE: For horizontal bias we always give the first found, because our + * scan is horizontal-raster-based and the first candidate will always + * have the horizontal bias. + */ + bool first = criteria & CR_BIAS_HORIZONTAL; + + assign(&me.a, x0, y0, x0 + w - 1, y0 + h - 1); + + /* calculate score for current candidate */ + if (!first) { + get_neighbor_stats(tcm, &me.a, &me.n); + me.neighs = me.n.edge + me.n.busy; + get_nearness_factor(field, &me.a, &me.f); + } + + /* the 1st candidate is always the best */ + if (!best->a.tcm) + goto better; + + BUG_ON(first); + + /* diagonal balance check */ + if ((criteria & CR_DIAGONAL_BALANCE) && + best->neighs <= me.neighs && + (best->neighs < me.neighs || + /* this implies that neighs and occupied match */ + best->n.busy < me.n.busy || + (best->n.busy == me.n.busy && + /* check the nearness factor */ + best->f.x + best->f.y > me.f.x + me.f.y))) + goto better; + + /* not better, keep going */ + return 0; + +better: + /* save current area as best */ + memcpy(best, &me, sizeof(me)); + best->a.tcm = tcm; + return first; +} + +/** + * Calculate the nearness factor of an area in a search field. The nearness + * factor is smaller if the area is closer to the search origin. + */ +static void get_nearness_factor(struct tcm_area *field, struct tcm_area *area, + struct nearness_factor *nf) +{ + /** + * Using signed math as field coordinates may be reversed if + * search direction is right-to-left or bottom-to-top. + */ + nf->x = (s32)(area->p0.x - field->p0.x) * 1000 / + (field->p1.x - field->p0.x); + nf->y = (s32)(area->p0.y - field->p0.y) * 1000 / + (field->p1.y - field->p0.y); +} + +/* get neighbor statistics */ +static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area, + struct neighbor_stats *stat) +{ + s16 x = 0, y = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + /* Clearing any exisiting values */ + memset(stat, 0, sizeof(*stat)); + + /* process top & bottom edges */ + for (x = area->p0.x; x <= area->p1.x; x++) { + if (area->p0.y == 0) + stat->edge++; + else if (pvt->map[x][area->p0.y - 1]) + stat->busy++; + + if (area->p1.y == tcm->height - 1) + stat->edge++; + else if (pvt->map[x][area->p1.y + 1]) + stat->busy++; + } + + /* process left & right edges */ + for (y = area->p0.y; y <= area->p1.y; ++y) { + if (area->p0.x == 0) + stat->edge++; + else if (pvt->map[area->p0.x - 1][y]) + stat->busy++; + + if (area->p1.x == tcm->width - 1) + stat->edge++; + else if (pvt->map[area->p1.x + 1][y]) + stat->busy++; + } +} diff --git a/drivers/gpu/drm/omapdrm/tcm-sita.h b/drivers/gpu/drm/omapdrm/tcm-sita.h new file mode 100644 index 000000000000..0444f868671c --- /dev/null +++ b/drivers/gpu/drm/omapdrm/tcm-sita.h @@ -0,0 +1,95 @@ +/* + * tcm_sita.h + * + * SImple Tiler Allocator (SiTA) private structures. + * + * Author: Ravi Ramachandra <r.ramachandra@ti.com> + * + * Copyright (C) 2009-2011 Texas Instruments, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TCM_SITA_H +#define _TCM_SITA_H + +#include "tcm.h" + +/* length between two coordinates */ +#define LEN(a, b) ((a) > (b) ? (a) - (b) + 1 : (b) - (a) + 1) + +enum criteria { + CR_MAX_NEIGHS = 0x01, + CR_FIRST_FOUND = 0x10, + CR_BIAS_HORIZONTAL = 0x20, + CR_BIAS_VERTICAL = 0x40, + CR_DIAGONAL_BALANCE = 0x80 +}; + +/* nearness to the beginning of the search field from 0 to 1000 */ +struct nearness_factor { + s32 x; + s32 y; +}; + +/* + * Statistics on immediately neighboring slots. Edge is the number of + * border segments that are also border segments of the scan field. Busy + * refers to the number of neighbors that are occupied. + */ +struct neighbor_stats { + u16 edge; + u16 busy; +}; + +/* structure to keep the score of a potential allocation */ +struct score { + struct nearness_factor f; + struct neighbor_stats n; + struct tcm_area a; + u16 neighs; /* number of busy neighbors */ +}; + +struct sita_pvt { + spinlock_t lock; /* spinlock to protect access */ + struct tcm_pt div_pt; /* divider point splitting container */ + struct tcm_area ***map; /* pointers to the parent area for each slot */ +}; + +/* assign coordinates to area */ +static inline +void assign(struct tcm_area *a, u16 x0, u16 y0, u16 x1, u16 y1) +{ + a->p0.x = x0; + a->p0.y = y0; + a->p1.x = x1; + a->p1.y = y1; +} + +#endif diff --git a/drivers/gpu/drm/omapdrm/tcm.h b/drivers/gpu/drm/omapdrm/tcm.h new file mode 100644 index 000000000000..a8d5ce47686f --- /dev/null +++ b/drivers/gpu/drm/omapdrm/tcm.h @@ -0,0 +1,328 @@ +/* + * tcm.h + * + * TILER container manager specification and support functions for TI + * TILER driver. + * + * Author: Lajos Molnar <molnar@ti.com> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TCM_H +#define TCM_H + +struct tcm; + +/* point */ +struct tcm_pt { + u16 x; + u16 y; +}; + +/* 1d or 2d area */ +struct tcm_area { + bool is2d; /* whether area is 1d or 2d */ + struct tcm *tcm; /* parent */ + struct tcm_pt p0; + struct tcm_pt p1; +}; + +struct tcm { + u16 width, height; /* container dimensions */ + int lut_id; /* Lookup table identifier */ + + unsigned int y_offset; /* offset to use for y coordinates */ + + /* 'pvt' structure shall contain any tcm details (attr) along with + linked list of allocated areas and mutex for mutually exclusive access + to the list. It may also contain copies of width and height to notice + any changes to the publicly available width and height fields. */ + void *pvt; + + /* function table */ + s32 (*reserve_2d)(struct tcm *tcm, u16 height, u16 width, u8 align, + struct tcm_area *area); + s32 (*reserve_1d)(struct tcm *tcm, u32 slots, struct tcm_area *area); + s32 (*free) (struct tcm *tcm, struct tcm_area *area); + void (*deinit) (struct tcm *tcm); +}; + +/*============================================================================= + BASIC TILER CONTAINER MANAGER INTERFACE +=============================================================================*/ + +/* + * NOTE: + * + * Since some basic parameter checking is done outside the TCM algorithms, + * TCM implementation do NOT have to check the following: + * + * area pointer is NULL + * width and height fits within container + * number of pages is more than the size of the container + * + */ + +struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr); + + +/** + * Deinitialize tiler container manager. + * + * @param tcm Pointer to container manager. + * + * @return 0 on success, non-0 error value on error. The call + * should free as much memory as possible and meaningful + * even on failure. Some error codes: -ENODEV: invalid + * manager. + */ +static inline void tcm_deinit(struct tcm *tcm) +{ + if (tcm) + tcm->deinit(tcm); +} + +/** + * Reserves a 2D area in the container. + * + * @param tcm Pointer to container manager. + * @param height Height(in pages) of area to be reserved. + * @param width Width(in pages) of area to be reserved. + * @param align Alignment requirement for top-left corner of area. Not + * all values may be supported by the container manager, + * but it must support 0 (1), 32 and 64. + * 0 value is equivalent to 1. + * @param area Pointer to where the reserved area should be stored. + * + * @return 0 on success. Non-0 error code on failure. Also, + * the tcm field of the area will be set to NULL on + * failure. Some error codes: -ENODEV: invalid manager, + * -EINVAL: invalid area, -ENOMEM: not enough space for + * allocation. + */ +static inline s32 tcm_reserve_2d(struct tcm *tcm, u16 width, u16 height, + u16 align, struct tcm_area *area) +{ + /* perform rudimentary error checking */ + s32 res = tcm == NULL ? -ENODEV : + (area == NULL || width == 0 || height == 0 || + /* align must be a 2 power */ + (align & (align - 1))) ? -EINVAL : + (height > tcm->height || width > tcm->width) ? -ENOMEM : 0; + + if (!res) { + area->is2d = true; + res = tcm->reserve_2d(tcm, height, width, align, area); + area->tcm = res ? NULL : tcm; + } + + return res; +} + +/** + * Reserves a 1D area in the container. + * + * @param tcm Pointer to container manager. + * @param slots Number of (contiguous) slots to reserve. + * @param area Pointer to where the reserved area should be stored. + * + * @return 0 on success. Non-0 error code on failure. Also, + * the tcm field of the area will be set to NULL on + * failure. Some error codes: -ENODEV: invalid manager, + * -EINVAL: invalid area, -ENOMEM: not enough space for + * allocation. + */ +static inline s32 tcm_reserve_1d(struct tcm *tcm, u32 slots, + struct tcm_area *area) +{ + /* perform rudimentary error checking */ + s32 res = tcm == NULL ? -ENODEV : + (area == NULL || slots == 0) ? -EINVAL : + slots > (tcm->width * (u32) tcm->height) ? -ENOMEM : 0; + + if (!res) { + area->is2d = false; + res = tcm->reserve_1d(tcm, slots, area); + area->tcm = res ? NULL : tcm; + } + + return res; +} + +/** + * Free a previously reserved area from the container. + * + * @param area Pointer to area reserved by a prior call to + * tcm_reserve_1d or tcm_reserve_2d call, whether + * it was successful or not. (Note: all fields of + * the structure must match.) + * + * @return 0 on success. Non-0 error code on failure. Also, the tcm + * field of the area is set to NULL on success to avoid subsequent + * freeing. This call will succeed even if supplying + * the area from a failed reserved call. + */ +static inline s32 tcm_free(struct tcm_area *area) +{ + s32 res = 0; /* free succeeds by default */ + + if (area && area->tcm) { + res = area->tcm->free(area->tcm, area); + if (res == 0) + area->tcm = NULL; + } + + return res; +} + +/*============================================================================= + HELPER FUNCTION FOR ANY TILER CONTAINER MANAGER +=============================================================================*/ + +/** + * This method slices off the topmost 2D slice from the parent area, and stores + * it in the 'slice' parameter. The 'parent' parameter will get modified to + * contain the remaining portion of the area. If the whole parent area can + * fit in a 2D slice, its tcm pointer is set to NULL to mark that it is no + * longer a valid area. + * + * @param parent Pointer to a VALID parent area that will get modified + * @param slice Pointer to the slice area that will get modified + */ +static inline void tcm_slice(struct tcm_area *parent, struct tcm_area *slice) +{ + *slice = *parent; + + /* check if we need to slice */ + if (slice->tcm && !slice->is2d && + slice->p0.y != slice->p1.y && + (slice->p0.x || (slice->p1.x != slice->tcm->width - 1))) { + /* set end point of slice (start always remains) */ + slice->p1.x = slice->tcm->width - 1; + slice->p1.y = (slice->p0.x) ? slice->p0.y : slice->p1.y - 1; + /* adjust remaining area */ + parent->p0.x = 0; + parent->p0.y = slice->p1.y + 1; + } else { + /* mark this as the last slice */ + parent->tcm = NULL; + } +} + +/* Verify if a tcm area is logically valid */ +static inline bool tcm_area_is_valid(struct tcm_area *area) +{ + return area && area->tcm && + /* coordinate bounds */ + area->p1.x < area->tcm->width && + area->p1.y < area->tcm->height && + area->p0.y <= area->p1.y && + /* 1D coordinate relationship + p0.x check */ + ((!area->is2d && + area->p0.x < area->tcm->width && + area->p0.x + area->p0.y * area->tcm->width <= + area->p1.x + area->p1.y * area->tcm->width) || + /* 2D coordinate relationship */ + (area->is2d && + area->p0.x <= area->p1.x)); +} + +/* see if a coordinate is within an area */ +static inline bool __tcm_is_in(struct tcm_pt *p, struct tcm_area *a) +{ + u16 i; + + if (a->is2d) { + return p->x >= a->p0.x && p->x <= a->p1.x && + p->y >= a->p0.y && p->y <= a->p1.y; + } else { + i = p->x + p->y * a->tcm->width; + return i >= a->p0.x + a->p0.y * a->tcm->width && + i <= a->p1.x + a->p1.y * a->tcm->width; + } +} + +/* calculate area width */ +static inline u16 __tcm_area_width(struct tcm_area *area) +{ + return area->p1.x - area->p0.x + 1; +} + +/* calculate area height */ +static inline u16 __tcm_area_height(struct tcm_area *area) +{ + return area->p1.y - area->p0.y + 1; +} + +/* calculate number of slots in an area */ +static inline u16 __tcm_sizeof(struct tcm_area *area) +{ + return area->is2d ? + __tcm_area_width(area) * __tcm_area_height(area) : + (area->p1.x - area->p0.x + 1) + (area->p1.y - area->p0.y) * + area->tcm->width; +} +#define tcm_sizeof(area) __tcm_sizeof(&(area)) +#define tcm_awidth(area) __tcm_area_width(&(area)) +#define tcm_aheight(area) __tcm_area_height(&(area)) +#define tcm_is_in(pt, area) __tcm_is_in(&(pt), &(area)) + +/* limit a 1D area to the first N pages */ +static inline s32 tcm_1d_limit(struct tcm_area *a, u32 num_pg) +{ + if (__tcm_sizeof(a) < num_pg) + return -ENOMEM; + if (!num_pg) + return -EINVAL; + + a->p1.x = (a->p0.x + num_pg - 1) % a->tcm->width; + a->p1.y = a->p0.y + ((a->p0.x + num_pg - 1) / a->tcm->width); + return 0; +} + +/** + * Iterate through 2D slices of a valid area. Behaves + * syntactically as a for(;;) statement. + * + * @param var Name of a local variable of type 'struct + * tcm_area *' that will get modified to + * contain each slice. + * @param area Pointer to the VALID parent area. This + * structure will not get modified + * throughout the loop. + * + */ +#define tcm_for_each_slice(var, area, safe) \ + for (safe = area, \ + tcm_slice(&safe, &var); \ + var.tcm; tcm_slice(&safe, &var)) + +#endif diff --git a/drivers/gpu/drm/radeon/atom.c b/drivers/gpu/drm/radeon/atom.c index 5ce9bf51a8de..46a9c3772850 100644 --- a/drivers/gpu/drm/radeon/atom.c +++ b/drivers/gpu/drm/radeon/atom.c @@ -1238,6 +1238,8 @@ static int atom_iio_len[] = { 1, 2, 3, 3, 3, 3, 4, 4, 4, 3 }; static void atom_index_iio(struct atom_context *ctx, int base) { ctx->iio = kzalloc(2 * 256, GFP_KERNEL); + if (!ctx->iio) + return; while (CU8(base) == ATOM_IIO_START) { ctx->iio[CU8(base + 1)] = base + 2; base += 2; @@ -1287,6 +1289,10 @@ struct atom_context *atom_parse(struct card_info *card, void *bios) ctx->cmd_table = CU16(base + ATOM_ROM_CMD_PTR); ctx->data_table = CU16(base + ATOM_ROM_DATA_PTR); atom_index_iio(ctx, CU16(ctx->data_table + ATOM_DATA_IIO_PTR) + 4); + if (!ctx->iio) { + atom_destroy(ctx); + return NULL; + } str = CSTR(CU16(base + ATOM_ROM_MSG_PTR)); while (*str && ((*str == '\n') || (*str == '\r'))) @@ -1335,8 +1341,7 @@ int atom_asic_init(struct atom_context *ctx) void atom_destroy(struct atom_context *ctx) { - if (ctx->iio) - kfree(ctx->iio); + kfree(ctx->iio); kfree(ctx); } diff --git a/drivers/gpu/drm/radeon/evergreen.c b/drivers/gpu/drm/radeon/evergreen.c index 2916de896a60..3c38ea46531c 100644 --- a/drivers/gpu/drm/radeon/evergreen.c +++ b/drivers/gpu/drm/radeon/evergreen.c @@ -403,6 +403,19 @@ void evergreen_pm_misc(struct radeon_device *rdev) rdev->pm.current_vddc = voltage->voltage; DRM_DEBUG("Setting: vddc: %d\n", voltage->voltage); } + + /* starting with BTC, there is one state that is used for both + * MH and SH. Difference is that we always use the high clock index for + * mclk and vddci. + */ + if ((rdev->pm.pm_method == PM_METHOD_PROFILE) && + (rdev->family >= CHIP_BARTS) && + rdev->pm.active_crtc_count && + ((rdev->pm.profile_index == PM_PROFILE_MID_MH_IDX) || + (rdev->pm.profile_index == PM_PROFILE_LOW_MH_IDX))) + voltage = &rdev->pm.power_state[req_ps_idx]. + clock_info[rdev->pm.profiles[PM_PROFILE_HIGH_MH_IDX].dpms_on_cm_idx].voltage; + /* 0xff01 is a flag rather then an actual voltage */ if (voltage->vddci == 0xff01) return; diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c index dbcb0752f083..6d4b5611daf4 100644 --- a/drivers/gpu/drm/radeon/r600.c +++ b/drivers/gpu/drm/radeon/r600.c @@ -109,6 +109,19 @@ void r600_fini(struct radeon_device *rdev); void r600_irq_disable(struct radeon_device *rdev); static void r600_pcie_gen2_enable(struct radeon_device *rdev); +/** + * r600_get_xclk - get the xclk + * + * @rdev: radeon_device pointer + * + * Returns the reference clock used by the gfx engine + * (r6xx, IGPs, APUs). + */ +u32 r600_get_xclk(struct radeon_device *rdev) +{ + return rdev->clock.spll.reference_freq; +} + /* get temperature in millidegrees */ int rv6xx_get_temp(struct radeon_device *rdev) { @@ -4448,14 +4461,14 @@ static void r600_pcie_gen2_enable(struct radeon_device *rdev) } /** - * r600_get_gpu_clock - return GPU clock counter snapshot + * r600_get_gpu_clock_counter - return GPU clock counter snapshot * * @rdev: radeon_device pointer * * Fetches a GPU clock counter snapshot (R6xx-cayman). * Returns the 64 bit clock counter snapshot. */ -uint64_t r600_get_gpu_clock(struct radeon_device *rdev) +uint64_t r600_get_gpu_clock_counter(struct radeon_device *rdev) { uint64_t clock; diff --git a/drivers/gpu/drm/radeon/r600_hdmi.c b/drivers/gpu/drm/radeon/r600_hdmi.c index ff80efe9cb7d..95970ec47c45 100644 --- a/drivers/gpu/drm/radeon/r600_hdmi.c +++ b/drivers/gpu/drm/radeon/r600_hdmi.c @@ -544,7 +544,6 @@ void r600_hdmi_disable(struct drm_encoder *encoder) /* Called for ATOM_ENCODER_MODE_HDMI only */ if (!dig || !dig->afmt) { - WARN_ON(1); return; } if (!dig->afmt->enabled) diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h index bb43a849759b..8263af3fd832 100644 --- a/drivers/gpu/drm/radeon/radeon.h +++ b/drivers/gpu/drm/radeon/radeon.h @@ -1178,6 +1178,10 @@ struct radeon_asic { bool (*gui_idle)(struct radeon_device *rdev); /* wait for mc_idle */ int (*mc_wait_for_idle)(struct radeon_device *rdev); + /* get the reference clock */ + u32 (*get_xclk)(struct radeon_device *rdev); + /* get the gpu clock counter */ + uint64_t (*get_gpu_clock_counter)(struct radeon_device *rdev); /* gart */ struct { void (*tlb_flush)(struct radeon_device *rdev); @@ -1859,6 +1863,8 @@ void radeon_ring_write(struct radeon_ring *ring, uint32_t v); #define radeon_post_page_flip(rdev, crtc) (rdev)->asic->pflip.post_page_flip((rdev), (crtc)) #define radeon_wait_for_vblank(rdev, crtc) (rdev)->asic->display.wait_for_vblank((rdev), (crtc)) #define radeon_mc_wait_for_idle(rdev) (rdev)->asic->mc_wait_for_idle((rdev)) +#define radeon_get_xclk(rdev) (rdev)->asic->get_xclk((rdev)) +#define radeon_get_gpu_clock_counter(rdev) (rdev)->asic->get_gpu_clock_counter((rdev)) /* Common functions */ /* AGP */ diff --git a/drivers/gpu/drm/radeon/radeon_asic.c b/drivers/gpu/drm/radeon/radeon_asic.c index 67f008febec7..aba0a893ea98 100644 --- a/drivers/gpu/drm/radeon/radeon_asic.c +++ b/drivers/gpu/drm/radeon/radeon_asic.c @@ -934,6 +934,8 @@ static struct radeon_asic r600_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &r600_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &r600_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1018,6 +1020,8 @@ static struct radeon_asic rs780_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &r600_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &r600_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1102,6 +1106,8 @@ static struct radeon_asic rv770_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &r600_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &r600_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1186,6 +1192,8 @@ static struct radeon_asic evergreen_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &evergreen_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1270,6 +1278,8 @@ static struct radeon_asic sumo_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &evergreen_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1354,6 +1364,8 @@ static struct radeon_asic btc_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &evergreen_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1438,6 +1450,8 @@ static struct radeon_asic cayman_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &cayman_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1565,6 +1579,8 @@ static struct radeon_asic trinity_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &cayman_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1692,6 +1708,8 @@ static struct radeon_asic si_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &si_get_xclk, + .get_gpu_clock_counter = &si_get_gpu_clock_counter, .gart = { .tlb_flush = &si_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, diff --git a/drivers/gpu/drm/radeon/radeon_asic.h b/drivers/gpu/drm/radeon/radeon_asic.h index f4134a823958..3535f73ad3e2 100644 --- a/drivers/gpu/drm/radeon/radeon_asic.h +++ b/drivers/gpu/drm/radeon/radeon_asic.h @@ -389,7 +389,8 @@ void r600_kms_blit_copy(struct radeon_device *rdev, unsigned num_gpu_pages, struct radeon_sa_bo *vb); int r600_mc_wait_for_idle(struct radeon_device *rdev); -uint64_t r600_get_gpu_clock(struct radeon_device *rdev); +u32 r600_get_xclk(struct radeon_device *rdev); +uint64_t r600_get_gpu_clock_counter(struct radeon_device *rdev); /* * rv770,rv730,rv710,rv740 @@ -407,6 +408,7 @@ int rv770_copy_dma(struct radeon_device *rdev, uint64_t src_offset, uint64_t dst_offset, unsigned num_gpu_pages, struct radeon_fence **fence); +u32 rv770_get_xclk(struct radeon_device *rdev); /* * evergreen @@ -515,11 +517,12 @@ void si_vm_set_page(struct radeon_device *rdev, uint32_t incr, uint32_t flags); void si_vm_flush(struct radeon_device *rdev, int ridx, struct radeon_vm *vm); int si_ib_parse(struct radeon_device *rdev, struct radeon_ib *ib); -uint64_t si_get_gpu_clock(struct radeon_device *rdev); int si_copy_dma(struct radeon_device *rdev, uint64_t src_offset, uint64_t dst_offset, unsigned num_gpu_pages, struct radeon_fence **fence); void si_dma_vm_flush(struct radeon_device *rdev, int ridx, struct radeon_vm *vm); +u32 si_get_xclk(struct radeon_device *rdev); +uint64_t si_get_gpu_clock_counter(struct radeon_device *rdev); #endif diff --git a/drivers/gpu/drm/radeon/radeon_atpx_handler.c b/drivers/gpu/drm/radeon/radeon_atpx_handler.c index 15f5ded65e0c..d96070bf8388 100644 --- a/drivers/gpu/drm/radeon/radeon_atpx_handler.c +++ b/drivers/gpu/drm/radeon/radeon_atpx_handler.c @@ -43,6 +43,12 @@ struct atpx_verify_interface { u32 function_bits; /* supported functions bit vector */ } __packed; +struct atpx_px_params { + u16 size; /* structure size in bytes (includes size field) */ + u32 valid_flags; /* which flags are valid */ + u32 flags; /* flags */ +} __packed; + struct atpx_power_control { u16 size; u8 dgpu_state; @@ -123,9 +129,61 @@ static void radeon_atpx_parse_functions(struct radeon_atpx_functions *f, u32 mas } /** + * radeon_atpx_validate_functions - validate ATPX functions + * + * @atpx: radeon atpx struct + * + * Validate that required functions are enabled (all asics). + * returns 0 on success, error on failure. + */ +static int radeon_atpx_validate(struct radeon_atpx *atpx) +{ + /* make sure required functions are enabled */ + /* dGPU power control is required */ + atpx->functions.power_cntl = true; + + if (atpx->functions.px_params) { + union acpi_object *info; + struct atpx_px_params output; + size_t size; + u32 valid_bits; + + info = radeon_atpx_call(atpx->handle, ATPX_FUNCTION_GET_PX_PARAMETERS, NULL); + if (!info) + return -EIO; + + memset(&output, 0, sizeof(output)); + + size = *(u16 *) info->buffer.pointer; + if (size < 10) { + printk("ATPX buffer is too small: %zu\n", size); + kfree(info); + return -EINVAL; + } + size = min(sizeof(output), size); + + memcpy(&output, info->buffer.pointer, size); + + valid_bits = output.flags & output.valid_flags; + /* if separate mux flag is set, mux controls are required */ + if (valid_bits & ATPX_SEPARATE_MUX_FOR_I2C) { + atpx->functions.i2c_mux_cntl = true; + atpx->functions.disp_mux_cntl = true; + } + /* if any outputs are muxed, mux controls are required */ + if (valid_bits & (ATPX_CRT1_RGB_SIGNAL_MUXED | + ATPX_TV_SIGNAL_MUXED | + ATPX_DFP_SIGNAL_MUXED)) + atpx->functions.disp_mux_cntl = true; + + kfree(info); + } + return 0; +} + +/** * radeon_atpx_verify_interface - verify ATPX * - * @handle: acpi handle * @atpx: radeon atpx struct * * Execute the ATPX_FUNCTION_VERIFY_INTERFACE ATPX function @@ -406,8 +464,19 @@ static bool radeon_atpx_pci_probe_handle(struct pci_dev *pdev) */ static int radeon_atpx_init(void) { + int r; + /* set up the ATPX handle */ - return radeon_atpx_verify_interface(&radeon_atpx_priv.atpx); + r = radeon_atpx_verify_interface(&radeon_atpx_priv.atpx); + if (r) + return r; + + /* validate the atpx setup */ + r = radeon_atpx_validate(&radeon_atpx_priv.atpx); + if (r) + return r; + + return 0; } /** diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 8794de10a6c7..44b8034a400d 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c @@ -759,6 +759,11 @@ int radeon_atombios_init(struct radeon_device *rdev) atom_card_info->pll_write = cail_pll_write; rdev->mode_info.atom_context = atom_parse(atom_card_info, rdev->bios); + if (!rdev->mode_info.atom_context) { + radeon_atombios_fini(rdev); + return -ENOMEM; + } + mutex_init(&rdev->mode_info.atom_context->mutex); radeon_atom_initialize_bios_scratch_regs(rdev->ddev); atom_allocate_fb_scratch(rdev->mode_info.atom_context); @@ -778,9 +783,11 @@ void radeon_atombios_fini(struct radeon_device *rdev) { if (rdev->mode_info.atom_context) { kfree(rdev->mode_info.atom_context->scratch); - kfree(rdev->mode_info.atom_context); } + kfree(rdev->mode_info.atom_context); + rdev->mode_info.atom_context = NULL; kfree(rdev->mode_info.atom_card_info); + rdev->mode_info.atom_card_info = NULL; } /* COMBIOS */ diff --git a/drivers/gpu/drm/radeon/radeon_kms.c b/drivers/gpu/drm/radeon/radeon_kms.c index 9c312f9afb68..c75cb2c6ba71 100644 --- a/drivers/gpu/drm/radeon/radeon_kms.c +++ b/drivers/gpu/drm/radeon/radeon_kms.c @@ -185,11 +185,7 @@ int radeon_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) if (info->request == RADEON_INFO_TIMESTAMP) { if (rdev->family >= CHIP_R600) { value_ptr64 = (uint64_t*)((unsigned long)info->value); - if (rdev->family >= CHIP_TAHITI) { - value64 = si_get_gpu_clock(rdev); - } else { - value64 = r600_get_gpu_clock(rdev); - } + value64 = radeon_get_gpu_clock_counter(rdev); if (DRM_COPY_TO_USER(value_ptr64, &value64, sizeof(value64))) { DRM_ERROR("copy_to_user %s:%u\n", __func__, __LINE__); @@ -282,7 +278,10 @@ int radeon_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) break; case RADEON_INFO_CLOCK_CRYSTAL_FREQ: /* return clock value in KHz */ - value = rdev->clock.spll.reference_freq * 10; + if (rdev->asic->get_xclk) + value = radeon_get_xclk(rdev) * 10; + else + value = rdev->clock.spll.reference_freq * 10; break; case RADEON_INFO_NUM_BACKENDS: if (rdev->family >= CHIP_TAHITI) diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c index 0bfa656aa87d..338fd6a74e87 100644 --- a/drivers/gpu/drm/radeon/radeon_pm.c +++ b/drivers/gpu/drm/radeon/radeon_pm.c @@ -169,7 +169,7 @@ static void radeon_set_power_state(struct radeon_device *rdev) /* starting with BTC, there is one state that is used for both * MH and SH. Difference is that we always use the high clock index for - * mclk. + * mclk and vddci. */ if ((rdev->pm.pm_method == PM_METHOD_PROFILE) && (rdev->family >= CHIP_BARTS) && diff --git a/drivers/gpu/drm/radeon/rv770.c b/drivers/gpu/drm/radeon/rv770.c index 1b2444f4d8f4..d63fe1d0f53f 100644 --- a/drivers/gpu/drm/radeon/rv770.c +++ b/drivers/gpu/drm/radeon/rv770.c @@ -43,6 +43,31 @@ static void rv770_gpu_init(struct radeon_device *rdev); void rv770_fini(struct radeon_device *rdev); static void rv770_pcie_gen2_enable(struct radeon_device *rdev); +#define PCIE_BUS_CLK 10000 +#define TCLK (PCIE_BUS_CLK / 10) + +/** + * rv770_get_xclk - get the xclk + * + * @rdev: radeon_device pointer + * + * Returns the reference clock used by the gfx engine + * (r7xx-cayman). + */ +u32 rv770_get_xclk(struct radeon_device *rdev) +{ + u32 reference_clock = rdev->clock.spll.reference_freq; + u32 tmp = RREG32(CG_CLKPIN_CNTL); + + if (tmp & MUX_TCLK_TO_XCLK) + return TCLK; + + if (tmp & XTALIN_DIVIDE) + return reference_clock / 4; + + return reference_clock; +} + u32 rv770_page_flip(struct radeon_device *rdev, int crtc_id, u64 crtc_base) { struct radeon_crtc *radeon_crtc = rdev->mode_info.crtcs[crtc_id]; diff --git a/drivers/gpu/drm/radeon/rv770d.h b/drivers/gpu/drm/radeon/rv770d.h index 20e29d23d348..c55f950a4af7 100644 --- a/drivers/gpu/drm/radeon/rv770d.h +++ b/drivers/gpu/drm/radeon/rv770d.h @@ -128,6 +128,10 @@ #define GUI_ACTIVE (1<<31) #define GRBM_STATUS2 0x8014 +#define CG_CLKPIN_CNTL 0x660 +# define MUX_TCLK_TO_XCLK (1 << 8) +# define XTALIN_DIVIDE (1 << 9) + #define CG_MULT_THERMAL_STATUS 0x740 #define ASIC_T(x) ((x) << 16) #define ASIC_T_MASK 0x3FF0000 diff --git a/drivers/gpu/drm/radeon/si.c b/drivers/gpu/drm/radeon/si.c index 719f03e061db..80979ed951eb 100644 --- a/drivers/gpu/drm/radeon/si.c +++ b/drivers/gpu/drm/radeon/si.c @@ -70,6 +70,33 @@ extern u32 evergreen_get_number_of_dram_channels(struct radeon_device *rdev); extern void evergreen_print_gpu_status_regs(struct radeon_device *rdev); extern bool evergreen_is_display_hung(struct radeon_device *rdev); +#define PCIE_BUS_CLK 10000 +#define TCLK (PCIE_BUS_CLK / 10) + +/** + * si_get_xclk - get the xclk + * + * @rdev: radeon_device pointer + * + * Returns the reference clock used by the gfx engine + * (SI). + */ +u32 si_get_xclk(struct radeon_device *rdev) +{ + u32 reference_clock = rdev->clock.spll.reference_freq; + u32 tmp; + + tmp = RREG32(CG_CLKPIN_CNTL_2); + if (tmp & MUX_TCLK_TO_XCLK) + return TCLK; + + tmp = RREG32(CG_CLKPIN_CNTL); + if (tmp & XTALIN_DIVIDE) + return reference_clock / 4; + + return reference_clock; +} + /* get temperature in millidegrees */ int si_get_temp(struct radeon_device *rdev) { @@ -4582,14 +4609,14 @@ void si_fini(struct radeon_device *rdev) } /** - * si_get_gpu_clock - return GPU clock counter snapshot + * si_get_gpu_clock_counter - return GPU clock counter snapshot * * @rdev: radeon_device pointer * * Fetches a GPU clock counter snapshot (SI). * Returns the 64 bit clock counter snapshot. */ -uint64_t si_get_gpu_clock(struct radeon_device *rdev) +uint64_t si_get_gpu_clock_counter(struct radeon_device *rdev) { uint64_t clock; diff --git a/drivers/gpu/drm/radeon/sid.h b/drivers/gpu/drm/radeon/sid.h index 07fc455e35ae..23fc08fc8e7f 100644 --- a/drivers/gpu/drm/radeon/sid.h +++ b/drivers/gpu/drm/radeon/sid.h @@ -58,6 +58,11 @@ #define VGA_HDP_CONTROL 0x328 #define VGA_MEMORY_DISABLE (1 << 4) +#define CG_CLKPIN_CNTL 0x660 +# define XTALIN_DIVIDE (1 << 1) +#define CG_CLKPIN_CNTL_2 0x664 +# define MUX_TCLK_TO_XCLK (1 << 8) + #define DMIF_ADDR_CONFIG 0xBD4 #define SRBM_STATUS 0xE50 diff --git a/drivers/gpu/stub/Kconfig b/drivers/gpu/stub/Kconfig deleted file mode 100644 index 419917955bf6..000000000000 --- a/drivers/gpu/stub/Kconfig +++ /dev/null @@ -1,18 +0,0 @@ -config STUB_POULSBO - tristate "Intel GMA500 Stub Driver" - depends on PCI - depends on NET # for THERMAL - # Poulsbo stub depends on ACPI_VIDEO when ACPI is enabled - # but for select to work, need to select ACPI_VIDEO's dependencies, ick - select BACKLIGHT_CLASS_DEVICE if ACPI - select VIDEO_OUTPUT_CONTROL if ACPI - select INPUT if ACPI - select ACPI_VIDEO if ACPI - select THERMAL if ACPI - help - Choose this option if you have a system that has Intel GMA500 - (Poulsbo) integrated graphics. If M is selected, the module will - be called Poulsbo. This driver is a stub driver for Poulsbo that - will call poulsbo.ko to enable the acpi backlight control sysfs - entry file because there have no poulsbo native driver can support - intel opregion. diff --git a/drivers/gpu/stub/Makefile b/drivers/gpu/stub/Makefile deleted file mode 100644 index cd940cc9d36d..000000000000 --- a/drivers/gpu/stub/Makefile +++ /dev/null @@ -1 +0,0 @@ -obj-$(CONFIG_STUB_POULSBO) += poulsbo.o diff --git a/drivers/gpu/stub/poulsbo.c b/drivers/gpu/stub/poulsbo.c deleted file mode 100644 index 7edfd27b8dee..000000000000 --- a/drivers/gpu/stub/poulsbo.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Intel Poulsbo Stub driver - * - * Copyright (C) 2010 Novell <jlee@novell.com> - * - * 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 <linux/module.h> -#include <linux/pci.h> -#include <linux/acpi.h> -#include <acpi/video.h> - -#define DRIVER_NAME "poulsbo" - -enum { - CHIP_PSB_8108 = 0, - CHIP_PSB_8109 = 1, -}; - -static DEFINE_PCI_DEVICE_TABLE(pciidlist) = { - {0x8086, 0x8108, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PSB_8108}, \ - {0x8086, 0x8109, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PSB_8109}, \ - {0, 0, 0} -}; - -static int poulsbo_probe(struct pci_dev *pdev, const struct pci_device_id *id) -{ - return acpi_video_register(); -} - -static void poulsbo_remove(struct pci_dev *pdev) -{ - acpi_video_unregister(); -} - -static struct pci_driver poulsbo_driver = { - .name = DRIVER_NAME, - .id_table = pciidlist, - .probe = poulsbo_probe, - .remove = poulsbo_remove, -}; - -static int __init poulsbo_init(void) -{ - return pci_register_driver(&poulsbo_driver); -} - -static void __exit poulsbo_exit(void) -{ - pci_unregister_driver(&poulsbo_driver); -} - -module_init(poulsbo_init); -module_exit(poulsbo_exit); - -MODULE_AUTHOR("Lee, Chun-Yi <jlee@novell.com>"); -MODULE_DESCRIPTION("Poulsbo Stub Driver"); -MODULE_LICENSE("GPL"); - -MODULE_DEVICE_TABLE(pci, pciidlist); |