From c182be37ed7cb04c344501b88b8fdb747016e6cf Mon Sep 17 00:00:00 2001 From: Kristian Høgsberg Date: Sat, 12 Sep 2009 04:33:34 +1000 Subject: drm: Add async event synchronization for drmWaitVblank MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a new flag to the drmWaitVblank ioctl, which asks the drm to return immediately and notify userspace when the specified vblank sequence happens by sending an event back on the drm fd. The event mechanism works with the other flags supported by the ioctls, specifically, the vblank sequence can be specified relatively or absolutely, and works for primary and seconday crtc. The signal field of the vblank request is used to provide user data, which will be sent back to user space in the vblank event. Signed-off-by: Kristian Høgsberg Reviewed-by: Jesse Barnes Signed-off-by: Dave Airlie --- include/drm/drm.h | 33 +++++++++++++++++++++++++++++++-- include/drm/drmP.h | 26 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bdde46..fa6d9155873 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -454,6 +454,7 @@ struct drm_irq_busid { enum drm_vblank_seq_type { _DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */ _DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */ + _DRM_VBLANK_EVENT = 0x4000000, /**< Send event instead of blocking */ _DRM_VBLANK_FLIP = 0x8000000, /**< Scheduled buffer swap should flip */ _DRM_VBLANK_NEXTONMISS = 0x10000000, /**< If missed, wait for next vblank */ _DRM_VBLANK_SECONDARY = 0x20000000, /**< Secondary display controller */ @@ -461,8 +462,8 @@ enum drm_vblank_seq_type { }; #define _DRM_VBLANK_TYPES_MASK (_DRM_VBLANK_ABSOLUTE | _DRM_VBLANK_RELATIVE) -#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_SIGNAL | _DRM_VBLANK_SECONDARY | \ - _DRM_VBLANK_NEXTONMISS) +#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_EVENT | _DRM_VBLANK_SIGNAL | \ + _DRM_VBLANK_SECONDARY | _DRM_VBLANK_NEXTONMISS) struct drm_wait_vblank_request { enum drm_vblank_seq_type type; @@ -698,6 +699,34 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + * + * Event types 0 - 0x7fffffff are generic drm events, 0x80000000 and + * up are chipset specific. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_VBLANK 0x01 + +struct drm_event_vblank { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 sequence; + __u32 reserved; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index eeefb6369e1..fe52254df60 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -426,6 +426,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy)(struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -449,6 +457,10 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; + int event_space; }; /** Wait queue */ @@ -897,6 +909,12 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_vblank_event { + struct drm_pending_event base; + int pipe; + struct drm_event_vblank event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -996,6 +1014,12 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + /** + * List of events + */ + struct list_head vblank_event_list; + spinlock_t event_lock; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1132,6 +1156,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ -- cgit v1.2.3-70-g09d2 From 7a9c906094de8b3dc227de448019dbc386cd25d4 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 15 Sep 2009 22:57:31 +0200 Subject: drm: make drm_mode_object_find typesafe I've wasted half a day hunting a bug that could easily be spotted by gcc. Prevent this from reoccurring. Signed-off-by: Daniel Vetter Signed-off-by: Eric Anholt --- drivers/gpu/drm/drm_crtc.c | 3 ++- include/drm/drm_crtc.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 5cae0b3eee9..ee0cde1ecca 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -247,7 +247,8 @@ static void drm_mode_object_put(struct drm_device *dev, mutex_unlock(&dev->mode_config.idr_mutex); } -void *drm_mode_object_find(struct drm_device *dev, uint32_t id, uint32_t type) +struct drm_mode_object *drm_mode_object_find(struct drm_device *dev, + uint32_t id, uint32_t type) { struct drm_mode_object *obj = NULL; diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index b69347b8904..bfcc60d101d 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -711,7 +711,8 @@ extern void drm_mode_connector_detach_encoder(struct drm_connector *connector, struct drm_encoder *encoder); extern bool drm_mode_crtc_set_gamma_size(struct drm_crtc *crtc, int gamma_size); -extern void *drm_mode_object_find(struct drm_device *dev, uint32_t id, uint32_t type); +extern struct drm_mode_object *drm_mode_object_find(struct drm_device *dev, + uint32_t id, uint32_t type); /* IOCTLs */ extern int drm_mode_getresources(struct drm_device *dev, void *data, struct drm_file *file_priv); -- cgit v1.2.3-70-g09d2 From 48764bf43f746113fc77877d7e80f2df23ca4cbb Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 15 Sep 2009 22:57:32 +0200 Subject: drm/i915: add i915_lp_ring_sync helper This just waits until the hw passed the current ring position with cmd execution. This slightly changes the existing i915_wait_request function to make uninterruptible waiting possible - no point in returning to userspace while mucking around with the overlay, that piece of hw is just too fragile. Also replace a magic 0 with the symbolic constant (and kill the then superflous comment) while I was looking at the code. Signed-off-by: Daniel Vetter Signed-off-by: Eric Anholt --- drivers/gpu/drm/i915/i915_drv.h | 1 + drivers/gpu/drm/i915/i915_gem.c | 49 ++++++++++++++++++++++++++++++++--------- include/drm/drm_os_linux.h | 2 +- 3 files changed, 41 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 95391191316..e440f70e477 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -803,6 +803,7 @@ void i915_gem_cleanup_ringbuffer(struct drm_device *dev); int i915_gem_do_init(struct drm_device *dev, unsigned long start, unsigned long end); int i915_gem_idle(struct drm_device *dev); +int i915_lp_ring_sync(struct drm_device *dev); int i915_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); int i915_gem_object_set_to_gtt_domain(struct drm_gem_object *obj, int write); diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index abfc27b0c2e..7d1e9adf0f4 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -1820,12 +1820,8 @@ i915_gem_retire_work_handler(struct work_struct *work) mutex_unlock(&dev->struct_mutex); } -/** - * Waits for a sequence number to be signaled, and cleans up the - * request and object lists appropriately for that event. - */ static int -i915_wait_request(struct drm_device *dev, uint32_t seqno) +i915_do_wait_request(struct drm_device *dev, uint32_t seqno, int interruptible) { drm_i915_private_t *dev_priv = dev->dev_private; u32 ier; @@ -1852,10 +1848,15 @@ i915_wait_request(struct drm_device *dev, uint32_t seqno) dev_priv->mm.waiting_gem_seqno = seqno; i915_user_irq_get(dev); - ret = wait_event_interruptible(dev_priv->irq_queue, - i915_seqno_passed(i915_get_gem_seqno(dev), - seqno) || - atomic_read(&dev_priv->mm.wedged)); + if (interruptible) + ret = wait_event_interruptible(dev_priv->irq_queue, + i915_seqno_passed(i915_get_gem_seqno(dev), seqno) || + atomic_read(&dev_priv->mm.wedged)); + else + wait_event(dev_priv->irq_queue, + i915_seqno_passed(i915_get_gem_seqno(dev), seqno) || + atomic_read(&dev_priv->mm.wedged)); + i915_user_irq_put(dev); dev_priv->mm.waiting_gem_seqno = 0; @@ -1879,6 +1880,34 @@ i915_wait_request(struct drm_device *dev, uint32_t seqno) return ret; } +/** + * Waits for a sequence number to be signaled, and cleans up the + * request and object lists appropriately for that event. + */ +static int +i915_wait_request(struct drm_device *dev, uint32_t seqno) +{ + return i915_do_wait_request(dev, seqno, 1); +} + +/** + * Waits for the ring to finish up to the latest request. Usefull for waiting + * for flip events, e.g for the overlay support. */ +int i915_lp_ring_sync(struct drm_device *dev) +{ + uint32_t seqno; + int ret; + + seqno = i915_add_request(dev, NULL, 0); + + if (seqno == 0) + return -ENOMEM; + + ret = i915_do_wait_request(dev, seqno, 0); + BUG_ON(ret == -ERESTARTSYS); + return ret; +} + static void i915_gem_flush(struct drm_device *dev, uint32_t invalidate_domains, @@ -1947,7 +1976,7 @@ i915_gem_flush(struct drm_device *dev, #endif BEGIN_LP_RING(2); OUT_RING(cmd); - OUT_RING(0); /* noop */ + OUT_RING(MI_NOOP); ADVANCE_LP_RING(); } } diff --git a/include/drm/drm_os_linux.h b/include/drm/drm_os_linux.h index 26641e95e0a..393369147a2 100644 --- a/include/drm/drm_os_linux.h +++ b/include/drm/drm_os_linux.h @@ -123,5 +123,5 @@ do { \ remove_wait_queue(&(queue), &entry); \ } while (0) -#define DRM_WAKEUP( queue ) wake_up_interruptible( queue ) +#define DRM_WAKEUP( queue ) wake_up( queue ) #define DRM_INIT_WAITQUEUE( queue ) init_waitqueue_head( queue ) -- cgit v1.2.3-70-g09d2 From 02e792fbaadb75dec8e476a05b610e49908fc6a4 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 15 Sep 2009 22:57:34 +0200 Subject: drm/i915: implement drmmode overlay support v4 This implements intel overlay support for kms via a device-specific ioctl. Thomas Hellstrom brought up the idea of a general ioctl (on dri-devel). We've reached the conclusion that such an infrastructure only makes sense when multiple kms overlay implementations exists, which atm don't (and it doesn't look like this is gonna change). Open issues: - Runs in sync with the gpu, i.e. unnecessary waiting. I've decided to wait on this because the hw tends to hang when changing something in this area. I left some dummy functions as infrastructure. - polyphase filtering uses a static table. - uses uninterruptible sleeps. Unfortunately the alternatives may unnecessarily wedged the hw if/when we timeout too early (and userspace only overloaded the batch buffers with stuff worth a few secs of gpu time). Changes since v1: - fix off-by-one misconception on my side. This fixes fullscreen playback. Changes since v2: - add underrun detection as spec'ed for i965. - flush caches properly, fixing visual corruptions. Changes since v4: - fix up cache flushing of overlay memory regs. - killed require_pipe_a logic - it hangs the chip. Tested-By: diego.abelenda@gmail.com (on a 865G) Signed-off-by: Daniel Vetter [anholt: Resolved against the MADVISE ioctl going in before this one] Signed-off-by: Eric Anholt --- drivers/gpu/drm/i915/Makefile | 1 + drivers/gpu/drm/i915/i915_dma.c | 7 + drivers/gpu/drm/i915/i915_drv.h | 5 + drivers/gpu/drm/i915/i915_reg.h | 5 + drivers/gpu/drm/i915/intel_display.c | 25 +- drivers/gpu/drm/i915/intel_drv.h | 28 + drivers/gpu/drm/i915/intel_overlay.c | 1293 ++++++++++++++++++++++++++++++++++ include/drm/i915_drm.h | 71 ++ 8 files changed, 1432 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/i915/intel_overlay.c (limited to 'include') diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index fa7b9be096b..87b21996cd6 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -23,6 +23,7 @@ i915-y := i915_drv.o i915_dma.o i915_irq.o i915_mem.o \ intel_fb.o \ intel_tv.o \ intel_dvo.o \ + intel_overlay.o \ dvo_ch7xxx.o \ dvo_ch7017.o \ dvo_ivch.o \ diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index e5b138be45f..138be49259c 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -807,6 +807,9 @@ static int i915_getparam(struct drm_device *dev, void *data, case I915_PARAM_NUM_FENCES_AVAIL: value = dev_priv->num_fence_regs - dev_priv->fence_reg_start; break; + case I915_PARAM_HAS_OVERLAY: + value = dev_priv->overlay ? 1 : 0; + break; default: DRM_DEBUG_DRIVER("Unknown parameter %d\n", param->param); @@ -1548,6 +1551,8 @@ int i915_driver_unload(struct drm_device *dev) mutex_unlock(&dev->struct_mutex); drm_mm_takedown(&dev_priv->vram); i915_gem_lastclose(dev); + + intel_cleanup_overlay(dev); } pci_dev_put(dev_priv->bridge_dev); @@ -1656,6 +1661,8 @@ struct drm_ioctl_desc i915_ioctls[] = { DRM_IOCTL_DEF(DRM_I915_GEM_GET_APERTURE, i915_gem_get_aperture_ioctl, 0), DRM_IOCTL_DEF(DRM_I915_GET_PIPE_FROM_CRTC_ID, intel_get_pipe_from_crtc_id, 0), DRM_IOCTL_DEF(DRM_I915_GEM_MADVISE, i915_gem_madvise_ioctl, 0), + DRM_IOCTL_DEF(DRM_I915_OVERLAY_PUT_IMAGE, intel_overlay_put_image, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_I915_OVERLAY_ATTRS, intel_overlay_attrs, DRM_MASTER|DRM_CONTROL_ALLOW), }; int i915_max_ioctl = DRM_ARRAY_SIZE(i915_ioctls); diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 4e8b26161a7..ce03fd5b3f5 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -170,6 +170,8 @@ struct drm_i915_display_funcs { /* clock gating init */ }; +struct intel_overlay; + typedef struct drm_i915_private { struct drm_device *dev; @@ -241,6 +243,9 @@ typedef struct drm_i915_private { struct intel_opregion opregion; + /* overlay */ + struct intel_overlay *overlay; + /* LVDS info */ int backlight_duty_cycle; /* restore backlight to this value */ bool panel_wants_dither; diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 51fd152f47f..d1be1849580 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -140,6 +140,7 @@ #define MI_NOOP MI_INSTR(0, 0) #define MI_USER_INTERRUPT MI_INSTR(0x02, 0) #define MI_WAIT_FOR_EVENT MI_INSTR(0x03, 0) +#define MI_WAIT_FOR_OVERLAY_FLIP (1<<16) #define MI_WAIT_FOR_PLANE_B_FLIP (1<<6) #define MI_WAIT_FOR_PLANE_A_FLIP (1<<2) #define MI_WAIT_FOR_PLANE_A_SCANLINES (1<<1) @@ -151,6 +152,10 @@ #define MI_END_SCENE (1 << 4) /* flush binner and incr scene count */ #define MI_BATCH_BUFFER_END MI_INSTR(0x0a, 0) #define MI_REPORT_HEAD MI_INSTR(0x07, 0) +#define MI_OVERLAY_FLIP MI_INSTR(0x11,0) +#define MI_OVERLAY_CONTINUE (0x0<<21) +#define MI_OVERLAY_ON (0x1<<21) +#define MI_OVERLAY_OFF (0x2<<21) #define MI_LOAD_SCAN_LINES_INCL MI_INSTR(0x12, 0) #define MI_STORE_DWORD_IMM MI_INSTR(0x20, 1) #define MI_MEM_VIRTUAL (1 << 22) /* 965+ only */ diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 0be624a52e5..6f818fadcbe 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -1781,6 +1781,22 @@ static void igdng_crtc_dpms(struct drm_crtc *crtc, int mode) } } +static void intel_crtc_dpms_overlay(struct intel_crtc *intel_crtc, bool enable) +{ + struct intel_overlay *overlay; + + if (!enable && intel_crtc->overlay) { + overlay = intel_crtc->overlay; + mutex_lock(&overlay->dev->struct_mutex); + intel_overlay_switch_off(overlay); + mutex_unlock(&overlay->dev->struct_mutex); + } + /* Let userspace switch the overlay on again. In most cases userspace + * has to recompute where to put it anyway. */ + + return; +} + static void i9xx_crtc_dpms(struct drm_crtc *crtc, int mode) { struct drm_device *dev = crtc->dev; @@ -1839,12 +1855,13 @@ static void i9xx_crtc_dpms(struct drm_crtc *crtc, int mode) intel_update_fbc(crtc, &crtc->mode); /* Give the overlay scaler a chance to enable if it's on this pipe */ - //intel_crtc_dpms_video(crtc, true); TODO + intel_crtc_dpms_overlay(intel_crtc, true); break; case DRM_MODE_DPMS_OFF: intel_update_watermarks(dev); + /* Give the overlay scaler a chance to disable if it's on this pipe */ - //intel_crtc_dpms_video(crtc, FALSE); TODO + intel_crtc_dpms_overlay(intel_crtc, false); if (dev_priv->cfb_plane == plane && dev_priv->display.disable_fbc) @@ -2039,7 +2056,7 @@ static int i830_get_display_clock_speed(struct drm_device *dev) * Return the pipe currently connected to the panel fitter, * or -1 if the panel fitter is not present or not in use */ -static int intel_panel_fitter_pipe (struct drm_device *dev) +int intel_panel_fitter_pipe (struct drm_device *dev) { struct drm_i915_private *dev_priv = dev->dev_private; u32 pfit_control; @@ -4458,6 +4475,8 @@ void intel_modeset_init(struct drm_device *dev) INIT_WORK(&dev_priv->idle_work, intel_idle_update); setup_timer(&dev_priv->idle_timer, intel_gpu_idle_timer, (unsigned long)dev); + + intel_setup_overlay(dev); } void intel_modeset_cleanup(struct drm_device *dev) diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index ef61fe9507e..c9b1b97ab79 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -110,6 +110,25 @@ struct intel_output { int clone_mask; }; +struct intel_crtc; +struct intel_overlay { + struct drm_device *dev; + struct intel_crtc *crtc; + struct drm_i915_gem_object *vid_bo; + struct drm_i915_gem_object *old_vid_bo; + int active; + int pfit_active; + u32 pfit_vscale_ratio; /* shifted-point number, (1<<12) == 1.0 */ + u32 color_key; + u32 brightness, contrast, saturation; + u32 old_xscale, old_yscale; + /* register access */ + u32 flip_addr; + struct drm_i915_gem_object *reg_bo; + void *virt_addr; + int hw_wedged; +}; + struct intel_crtc { struct drm_crtc base; enum pipe pipe; @@ -121,6 +140,7 @@ struct intel_crtc { bool busy; /* is scanout buffer being updated frequently? */ struct timer_list idle_timer; bool lowfreq_avail; + struct intel_overlay *overlay; }; #define to_intel_crtc(x) container_of(x, struct intel_crtc, base) @@ -148,6 +168,7 @@ intel_dp_set_m_n(struct drm_crtc *crtc, struct drm_display_mode *mode, extern void intel_edp_link_config (struct intel_output *, int *, int *); +extern int intel_panel_fitter_pipe (struct drm_device *dev); extern void intel_crtc_load_lut(struct drm_crtc *crtc); extern void intel_encoder_prepare (struct drm_encoder *encoder); extern void intel_encoder_commit (struct drm_encoder *encoder); @@ -183,4 +204,11 @@ extern int intel_framebuffer_create(struct drm_device *dev, struct drm_framebuffer **fb, struct drm_gem_object *obj); +extern void intel_setup_overlay(struct drm_device *dev); +extern void intel_cleanup_overlay(struct drm_device *dev); +extern int intel_overlay_switch_off(struct intel_overlay *overlay); +extern int intel_overlay_put_image(struct drm_device *dev, void *data, + struct drm_file *file_priv); +extern int intel_overlay_attrs(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __INTEL_DRV_H__ */ diff --git a/drivers/gpu/drm/i915/intel_overlay.c b/drivers/gpu/drm/i915/intel_overlay.c new file mode 100644 index 00000000000..3f6f3a36929 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_overlay.c @@ -0,0 +1,1293 @@ +/* + * Copyright © 2009 + * + * 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 AUTHORS OR COPYRIGHT HOLDERS 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: + * Daniel Vetter + * + * Derived from Xorg ddx, xf86-video-intel, src/i830_video.c + */ +#include "drmP.h" +#include "drm.h" +#include "i915_drm.h" +#include "i915_drv.h" +#include "i915_reg.h" +#include "intel_drv.h" + +/* Limits for overlay size. According to intel doc, the real limits are: + * Y width: 4095, UV width (planar): 2047, Y height: 2047, + * UV width (planar): * 1023. But the xorg thinks 2048 for height and width. Use + * the mininum of both. */ +#define IMAGE_MAX_WIDTH 2048 +#define IMAGE_MAX_HEIGHT 2046 /* 2 * 1023 */ +/* on 830 and 845 these large limits result in the card hanging */ +#define IMAGE_MAX_WIDTH_LEGACY 1024 +#define IMAGE_MAX_HEIGHT_LEGACY 1088 + +/* overlay register definitions */ +/* OCMD register */ +#define OCMD_TILED_SURFACE (0x1<<19) +#define OCMD_MIRROR_MASK (0x3<<17) +#define OCMD_MIRROR_MODE (0x3<<17) +#define OCMD_MIRROR_HORIZONTAL (0x1<<17) +#define OCMD_MIRROR_VERTICAL (0x2<<17) +#define OCMD_MIRROR_BOTH (0x3<<17) +#define OCMD_BYTEORDER_MASK (0x3<<14) /* zero for YUYV or FOURCC YUY2 */ +#define OCMD_UV_SWAP (0x1<<14) /* YVYU */ +#define OCMD_Y_SWAP (0x2<<14) /* UYVY or FOURCC UYVY */ +#define OCMD_Y_AND_UV_SWAP (0x3<<14) /* VYUY */ +#define OCMD_SOURCE_FORMAT_MASK (0xf<<10) +#define OCMD_RGB_888 (0x1<<10) /* not in i965 Intel docs */ +#define OCMD_RGB_555 (0x2<<10) /* not in i965 Intel docs */ +#define OCMD_RGB_565 (0x3<<10) /* not in i965 Intel docs */ +#define OCMD_YUV_422_PACKED (0x8<<10) +#define OCMD_YUV_411_PACKED (0x9<<10) /* not in i965 Intel docs */ +#define OCMD_YUV_420_PLANAR (0xc<<10) +#define OCMD_YUV_422_PLANAR (0xd<<10) +#define OCMD_YUV_410_PLANAR (0xe<<10) /* also 411 */ +#define OCMD_TVSYNCFLIP_PARITY (0x1<<9) +#define OCMD_TVSYNCFLIP_ENABLE (0x1<<7) +#define OCMD_BUF_TYPE_MASK (Ox1<<5) +#define OCMD_BUF_TYPE_FRAME (0x0<<5) +#define OCMD_BUF_TYPE_FIELD (0x1<<5) +#define OCMD_TEST_MODE (0x1<<4) +#define OCMD_BUFFER_SELECT (0x3<<2) +#define OCMD_BUFFER0 (0x0<<2) +#define OCMD_BUFFER1 (0x1<<2) +#define OCMD_FIELD_SELECT (0x1<<2) +#define OCMD_FIELD0 (0x0<<1) +#define OCMD_FIELD1 (0x1<<1) +#define OCMD_ENABLE (0x1<<0) + +/* OCONFIG register */ +#define OCONF_PIPE_MASK (0x1<<18) +#define OCONF_PIPE_A (0x0<<18) +#define OCONF_PIPE_B (0x1<<18) +#define OCONF_GAMMA2_ENABLE (0x1<<16) +#define OCONF_CSC_MODE_BT601 (0x0<<5) +#define OCONF_CSC_MODE_BT709 (0x1<<5) +#define OCONF_CSC_BYPASS (0x1<<4) +#define OCONF_CC_OUT_8BIT (0x1<<3) +#define OCONF_TEST_MODE (0x1<<2) +#define OCONF_THREE_LINE_BUFFER (0x1<<0) +#define OCONF_TWO_LINE_BUFFER (0x0<<0) + +/* DCLRKM (dst-key) register */ +#define DST_KEY_ENABLE (0x1<<31) +#define CLK_RGB24_MASK 0x0 +#define CLK_RGB16_MASK 0x070307 +#define CLK_RGB15_MASK 0x070707 +#define CLK_RGB8I_MASK 0xffffff + +#define RGB16_TO_COLORKEY(c) \ + (((c & 0xF800) << 8) | ((c & 0x07E0) << 5) | ((c & 0x001F) << 3)) +#define RGB15_TO_COLORKEY(c) \ + (((c & 0x7c00) << 9) | ((c & 0x03E0) << 6) | ((c & 0x001F) << 3)) + +/* overlay flip addr flag */ +#define OFC_UPDATE 0x1 + +/* polyphase filter coefficients */ +#define N_HORIZ_Y_TAPS 5 +#define N_VERT_Y_TAPS 3 +#define N_HORIZ_UV_TAPS 3 +#define N_VERT_UV_TAPS 3 +#define N_PHASES 17 +#define MAX_TAPS 5 + +/* memory bufferd overlay registers */ +struct overlay_registers { + u32 OBUF_0Y; + u32 OBUF_1Y; + u32 OBUF_0U; + u32 OBUF_0V; + u32 OBUF_1U; + u32 OBUF_1V; + u32 OSTRIDE; + u32 YRGB_VPH; + u32 UV_VPH; + u32 HORZ_PH; + u32 INIT_PHS; + u32 DWINPOS; + u32 DWINSZ; + u32 SWIDTH; + u32 SWIDTHSW; + u32 SHEIGHT; + u32 YRGBSCALE; + u32 UVSCALE; + u32 OCLRC0; + u32 OCLRC1; + u32 DCLRKV; + u32 DCLRKM; + u32 SCLRKVH; + u32 SCLRKVL; + u32 SCLRKEN; + u32 OCONFIG; + u32 OCMD; + u32 RESERVED1; /* 0x6C */ + u32 OSTART_0Y; + u32 OSTART_1Y; + u32 OSTART_0U; + u32 OSTART_0V; + u32 OSTART_1U; + u32 OSTART_1V; + u32 OTILEOFF_0Y; + u32 OTILEOFF_1Y; + u32 OTILEOFF_0U; + u32 OTILEOFF_0V; + u32 OTILEOFF_1U; + u32 OTILEOFF_1V; + u32 FASTHSCALE; /* 0xA0 */ + u32 UVSCALEV; /* 0xA4 */ + u32 RESERVEDC[(0x200 - 0xA8) / 4]; /* 0xA8 - 0x1FC */ + u16 Y_VCOEFS[N_VERT_Y_TAPS * N_PHASES]; /* 0x200 */ + u16 RESERVEDD[0x100 / 2 - N_VERT_Y_TAPS * N_PHASES]; + u16 Y_HCOEFS[N_HORIZ_Y_TAPS * N_PHASES]; /* 0x300 */ + u16 RESERVEDE[0x200 / 2 - N_HORIZ_Y_TAPS * N_PHASES]; + u16 UV_VCOEFS[N_VERT_UV_TAPS * N_PHASES]; /* 0x500 */ + u16 RESERVEDF[0x100 / 2 - N_VERT_UV_TAPS * N_PHASES]; + u16 UV_HCOEFS[N_HORIZ_UV_TAPS * N_PHASES]; /* 0x600 */ + u16 RESERVEDG[0x100 / 2 - N_HORIZ_UV_TAPS * N_PHASES]; +}; + +/* overlay flip addr flag */ +#define OFC_UPDATE 0x1 + +#define OVERLAY_NONPHYSICAL(dev) (IS_G33(dev) || IS_I965G(dev)) +#define OVERLAY_EXISTS(dev) (!IS_G4X(dev) && !IS_IGDNG(dev)) + + +static struct overlay_registers *intel_overlay_map_regs_atomic(struct intel_overlay *overlay) +{ + drm_i915_private_t *dev_priv = overlay->dev->dev_private; + struct overlay_registers *regs; + + /* no recursive mappings */ + BUG_ON(overlay->virt_addr); + + if (OVERLAY_NONPHYSICAL(overlay->dev)) { + regs = io_mapping_map_atomic_wc(dev_priv->mm.gtt_mapping, + overlay->reg_bo->gtt_offset); + + if (!regs) { + DRM_ERROR("failed to map overlay regs in GTT\n"); + return NULL; + } + } else + regs = overlay->reg_bo->phys_obj->handle->vaddr; + + return overlay->virt_addr = regs; +} + +static void intel_overlay_unmap_regs_atomic(struct intel_overlay *overlay) +{ + struct drm_device *dev = overlay->dev; + drm_i915_private_t *dev_priv = dev->dev_private; + + if (OVERLAY_NONPHYSICAL(overlay->dev)) + io_mapping_unmap_atomic(overlay->virt_addr); + + overlay->virt_addr = NULL; + + I915_READ(OVADD); /* flush wc cashes */ + + return; +} + +/* overlay needs to be disable in OCMD reg */ +static int intel_overlay_on(struct intel_overlay *overlay) +{ + struct drm_device *dev = overlay->dev; + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; + RING_LOCALS; + + BUG_ON(overlay->active); + + BEGIN_LP_RING(6); + OUT_RING(MI_FLUSH); + OUT_RING(MI_NOOP); + OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_ON); + OUT_RING(overlay->flip_addr | OFC_UPDATE); + OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP); + OUT_RING(MI_NOOP); + ADVANCE_LP_RING(); + + ret = i915_lp_ring_sync(dev); + if (ret != 0) { + DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n"); + overlay->hw_wedged = 1; + return 0; + } + + overlay->active = 1; + + return 0; +} + +/* overlay needs to be enabled in OCMD reg */ +static void intel_overlay_continue(struct intel_overlay *overlay, + bool load_polyphase_filter) +{ + struct drm_device *dev = overlay->dev; + drm_i915_private_t *dev_priv = dev->dev_private; + u32 flip_addr = overlay->flip_addr; + u32 tmp; + int ret; + RING_LOCALS; + + BUG_ON(!overlay->active); + + if (load_polyphase_filter) + flip_addr |= OFC_UPDATE; + + /* check for underruns */ + tmp = I915_READ(DOVSTA); + if (tmp & (1 << 17)) + DRM_DEBUG("overlay underrun, DOVSTA: %x\n", tmp); + + BEGIN_LP_RING(6); + OUT_RING(MI_FLUSH); + OUT_RING(MI_NOOP); + OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_CONTINUE); + OUT_RING(flip_addr); + OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP); + OUT_RING(MI_NOOP); + ADVANCE_LP_RING(); + + /* run in lockstep with the hw for easier testing */ + ret = i915_lp_ring_sync(dev); + if (ret != 0) { + DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n"); + overlay->hw_wedged = 1; + } +} + +static int intel_overlay_wait_flip(struct intel_overlay *overlay) +{ + /* don't overcomplicate things for now with asynchronous operations + * see comment above */ + return 0; +} + +/* overlay needs to be disabled in OCMD reg */ +static int intel_overlay_off(struct intel_overlay *overlay) +{ + u32 flip_addr = overlay->flip_addr; + struct drm_device *dev = overlay->dev; + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; + RING_LOCALS; + + BUG_ON(!overlay->active); + + /* According to intel docs the overlay hw may hang (when switching + * off) without loading the filter coeffs. It is however unclear whether + * this applies to the disabling of the overlay or to the switching off + * of the hw. Do it in both cases */ + flip_addr |= OFC_UPDATE; + + /* wait for overlay to go idle */ + BEGIN_LP_RING(6); + OUT_RING(MI_FLUSH); + OUT_RING(MI_NOOP); + OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_CONTINUE); + OUT_RING(flip_addr); + OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP); + OUT_RING(MI_NOOP); + ADVANCE_LP_RING(); + + ret = i915_lp_ring_sync(dev); + if (ret != 0) { + DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n"); + overlay->hw_wedged = 1; + return ret; + } + + /* turn overlay off */ + /* this is not done in userspace! + BEGIN_LP_RING(6); + OUT_RING(MI_FLUSH); + OUT_RING(MI_NOOP); + OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_OFF); + OUT_RING(flip_addr); + OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP); + OUT_RING(MI_NOOP); + ADVANCE_LP_RING(); + + ret = i915_lp_ring_sync(dev); + if (ret != 0) { + DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n"); + overlay->hw_wedged = 1; + return ret; + }*/ + + overlay->active = 0; + + return ret; +} + +/* wait for pending overlay flip and release old frame */ +static int intel_overlay_release_old_vid(struct intel_overlay *overlay) +{ + int ret; + struct drm_gem_object *obj; + + ret = intel_overlay_wait_flip(overlay); + if (ret != 0) + return ret; + + if (!overlay->old_vid_bo) + return 0; + + obj = overlay->old_vid_bo->obj; + i915_gem_object_unpin(obj); + drm_gem_object_unreference(obj); + overlay->old_vid_bo = NULL; + + return 0; +} + +struct put_image_params { + int format; + short dst_x; + short dst_y; + short dst_w; + short dst_h; + short src_w; + short src_scan_h; + short src_scan_w; + short src_h; + short stride_Y; + short stride_UV; + int offset_Y; + int offset_U; + int offset_V; +}; + +static int packed_depth_bytes(u32 format) +{ + switch (format & I915_OVERLAY_DEPTH_MASK) { + case I915_OVERLAY_YUV422: + return 4; + case I915_OVERLAY_YUV411: + /* return 6; not implemented */ + default: + return -EINVAL; + } +} + +static int packed_width_bytes(u32 format, short width) +{ + switch (format & I915_OVERLAY_DEPTH_MASK) { + case I915_OVERLAY_YUV422: + return width << 1; + default: + return -EINVAL; + } +} + +static int uv_hsubsampling(u32 format) +{ + switch (format & I915_OVERLAY_DEPTH_MASK) { + case I915_OVERLAY_YUV422: + case I915_OVERLAY_YUV420: + return 2; + case I915_OVERLAY_YUV411: + case I915_OVERLAY_YUV410: + return 4; + default: + return -EINVAL; + } +} + +static int uv_vsubsampling(u32 format) +{ + switch (format & I915_OVERLAY_DEPTH_MASK) { + case I915_OVERLAY_YUV420: + case I915_OVERLAY_YUV410: + return 2; + case I915_OVERLAY_YUV422: + case I915_OVERLAY_YUV411: + return 1; + default: + return -EINVAL; + } +} + +static u32 calc_swidthsw(struct drm_device *dev, u32 offset, u32 width) +{ + u32 mask, shift, ret; + if (IS_I9XX(dev)) { + mask = 0x3f; + shift = 6; + } else { + mask = 0x1f; + shift = 5; + } + ret = ((offset + width + mask) >> shift) - (offset >> shift); + if (IS_I9XX(dev)) + ret <<= 1; + ret -=1; + return ret << 2; +} + +static const u16 y_static_hcoeffs[N_HORIZ_Y_TAPS * N_PHASES] = { + 0x3000, 0xb4a0, 0x1930, 0x1920, 0xb4a0, + 0x3000, 0xb500, 0x19d0, 0x1880, 0xb440, + 0x3000, 0xb540, 0x1a88, 0x2f80, 0xb3e0, + 0x3000, 0xb580, 0x1b30, 0x2e20, 0xb380, + 0x3000, 0xb5c0, 0x1bd8, 0x2cc0, 0xb320, + 0x3020, 0xb5e0, 0x1c60, 0x2b80, 0xb2c0, + 0x3020, 0xb5e0, 0x1cf8, 0x2a20, 0xb260, + 0x3020, 0xb5e0, 0x1d80, 0x28e0, 0xb200, + 0x3020, 0xb5c0, 0x1e08, 0x3f40, 0xb1c0, + 0x3020, 0xb580, 0x1e78, 0x3ce0, 0xb160, + 0x3040, 0xb520, 0x1ed8, 0x3aa0, 0xb120, + 0x3040, 0xb4a0, 0x1f30, 0x3880, 0xb0e0, + 0x3040, 0xb400, 0x1f78, 0x3680, 0xb0a0, + 0x3020, 0xb340, 0x1fb8, 0x34a0, 0xb060, + 0x3020, 0xb240, 0x1fe0, 0x32e0, 0xb040, + 0x3020, 0xb140, 0x1ff8, 0x3160, 0xb020, + 0xb000, 0x3000, 0x0800, 0x3000, 0xb000}; +static const u16 uv_static_hcoeffs[N_HORIZ_UV_TAPS * N_PHASES] = { + 0x3000, 0x1800, 0x1800, 0xb000, 0x18d0, 0x2e60, + 0xb000, 0x1990, 0x2ce0, 0xb020, 0x1a68, 0x2b40, + 0xb040, 0x1b20, 0x29e0, 0xb060, 0x1bd8, 0x2880, + 0xb080, 0x1c88, 0x3e60, 0xb0a0, 0x1d28, 0x3c00, + 0xb0c0, 0x1db8, 0x39e0, 0xb0e0, 0x1e40, 0x37e0, + 0xb100, 0x1eb8, 0x3620, 0xb100, 0x1f18, 0x34a0, + 0xb100, 0x1f68, 0x3360, 0xb0e0, 0x1fa8, 0x3240, + 0xb0c0, 0x1fe0, 0x3140, 0xb060, 0x1ff0, 0x30a0, + 0x3000, 0x0800, 0x3000}; + +static void update_polyphase_filter(struct overlay_registers *regs) +{ + memcpy(regs->Y_HCOEFS, y_static_hcoeffs, sizeof(y_static_hcoeffs)); + memcpy(regs->UV_HCOEFS, uv_static_hcoeffs, sizeof(uv_static_hcoeffs)); +} + +static bool update_scaling_factors(struct intel_overlay *overlay, + struct overlay_registers *regs, + struct put_image_params *params) +{ + /* fixed point with a 12 bit shift */ + u32 xscale, yscale, xscale_UV, yscale_UV; +#define FP_SHIFT 12 +#define FRACT_MASK 0xfff + bool scale_changed = false; + int uv_hscale = uv_hsubsampling(params->format); + int uv_vscale = uv_vsubsampling(params->format); + + if (params->dst_w > 1) + xscale = ((params->src_scan_w - 1) << FP_SHIFT) + /(params->dst_w); + else + xscale = 1 << FP_SHIFT; + + if (params->dst_h > 1) + yscale = ((params->src_scan_h - 1) << FP_SHIFT) + /(params->dst_h); + else + yscale = 1 << FP_SHIFT; + + /*if (params->format & I915_OVERLAY_YUV_PLANAR) {*/ + xscale_UV = xscale/uv_hscale; + yscale_UV = yscale/uv_vscale; + /* make the Y scale to UV scale ratio an exact multiply */ + xscale = xscale_UV * uv_hscale; + yscale = yscale_UV * uv_vscale; + /*} else { + xscale_UV = 0; + yscale_UV = 0; + }*/ + + if (xscale != overlay->old_xscale || yscale != overlay->old_yscale) + scale_changed = true; + overlay->old_xscale = xscale; + overlay->old_yscale = yscale; + + regs->YRGBSCALE = ((yscale & FRACT_MASK) << 20) + | ((xscale >> FP_SHIFT) << 16) + | ((xscale & FRACT_MASK) << 3); + regs->UVSCALE = ((yscale_UV & FRACT_MASK) << 20) + | ((xscale_UV >> FP_SHIFT) << 16) + | ((xscale_UV & FRACT_MASK) << 3); + regs->UVSCALEV = ((yscale >> FP_SHIFT) << 16) + | ((yscale_UV >> FP_SHIFT) << 0); + + if (scale_changed) + update_polyphase_filter(regs); + + return scale_changed; +} + +static void update_colorkey(struct intel_overlay *overlay, + struct overlay_registers *regs) +{ + u32 key = overlay->color_key; + switch (overlay->crtc->base.fb->bits_per_pixel) { + case 8: + regs->DCLRKV = 0; + regs->DCLRKM = CLK_RGB8I_MASK | DST_KEY_ENABLE; + case 16: + if (overlay->crtc->base.fb->depth == 15) { + regs->DCLRKV = RGB15_TO_COLORKEY(key); + regs->DCLRKM = CLK_RGB15_MASK | DST_KEY_ENABLE; + } else { + regs->DCLRKV = RGB16_TO_COLORKEY(key); + regs->DCLRKM = CLK_RGB16_MASK | DST_KEY_ENABLE; + } + case 24: + case 32: + regs->DCLRKV = key; + regs->DCLRKM = CLK_RGB24_MASK | DST_KEY_ENABLE; + } +} + +static u32 overlay_cmd_reg(struct put_image_params *params) +{ + u32 cmd = OCMD_ENABLE | OCMD_BUF_TYPE_FRAME | OCMD_BUFFER0; + + if (params->format & I915_OVERLAY_YUV_PLANAR) { + switch (params->format & I915_OVERLAY_DEPTH_MASK) { + case I915_OVERLAY_YUV422: + cmd |= OCMD_YUV_422_PLANAR; + break; + case I915_OVERLAY_YUV420: + cmd |= OCMD_YUV_420_PLANAR; + break; + case I915_OVERLAY_YUV411: + case I915_OVERLAY_YUV410: + cmd |= OCMD_YUV_410_PLANAR; + break; + } + } else { /* YUV packed */ + switch (params->format & I915_OVERLAY_DEPTH_MASK) { + case I915_OVERLAY_YUV422: + cmd |= OCMD_YUV_422_PACKED; + break; + case I915_OVERLAY_YUV411: + cmd |= OCMD_YUV_411_PACKED; + break; + } + + switch (params->format & I915_OVERLAY_SWAP_MASK) { + case I915_OVERLAY_NO_SWAP: + break; + case I915_OVERLAY_UV_SWAP: + cmd |= OCMD_UV_SWAP; + break; + case I915_OVERLAY_Y_SWAP: + cmd |= OCMD_Y_SWAP; + break; + case I915_OVERLAY_Y_AND_UV_SWAP: + cmd |= OCMD_Y_AND_UV_SWAP; + break; + } + } + + return cmd; +} + +int intel_overlay_do_put_image(struct intel_overlay *overlay, + struct drm_gem_object *new_bo, + struct put_image_params *params) +{ + int ret, tmp_width; + struct overlay_registers *regs; + bool scale_changed = false; + struct drm_i915_gem_object *bo_priv = new_bo->driver_private; + struct drm_device *dev = overlay->dev; + + BUG_ON(!mutex_is_locked(&dev->struct_mutex)); + BUG_ON(!mutex_is_locked(&dev->mode_config.mutex)); + BUG_ON(!overlay); + + if (overlay->hw_wedged) + return -EBUSY; + + ret = intel_overlay_release_old_vid(overlay); + if (ret != 0) + return ret; + + ret = i915_gem_object_pin(new_bo, PAGE_SIZE); + if (ret != 0) + return ret; + + ret = i915_gem_object_set_to_gtt_domain(new_bo, 0); + if (ret != 0) + goto out_unpin; + + if (!overlay->active) { + regs = intel_overlay_map_regs_atomic(overlay); + if (!regs) { + ret = -ENOMEM; + goto out_unpin; + } + regs->OCONFIG = OCONF_CC_OUT_8BIT; + if (IS_I965GM(overlay->dev)) + regs->OCONFIG |= OCONF_CSC_MODE_BT709; + regs->OCONFIG |= overlay->crtc->pipe == 0 ? + OCONF_PIPE_A : OCONF_PIPE_B; + intel_overlay_unmap_regs_atomic(overlay); + + ret = intel_overlay_on(overlay); + if (ret != 0) + goto out_unpin; + } + + regs = intel_overlay_map_regs_atomic(overlay); + if (!regs) { + ret = -ENOMEM; + goto out_unpin; + } + + regs->DWINPOS = (params->dst_y << 16) | params->dst_x; + regs->DWINSZ = (params->dst_h << 16) | params->dst_w; + + if (params->format & I915_OVERLAY_YUV_PACKED) + tmp_width = packed_width_bytes(params->format, params->src_w); + else + tmp_width = params->src_w; + + regs->SWIDTH = params->src_w; + regs->SWIDTHSW = calc_swidthsw(overlay->dev, + params->offset_Y, tmp_width); + regs->SHEIGHT = params->src_h; + regs->OBUF_0Y = bo_priv->gtt_offset + params-> offset_Y; + regs->OSTRIDE = params->stride_Y; + + if (params->format & I915_OVERLAY_YUV_PLANAR) { + int uv_hscale = uv_hsubsampling(params->format); + int uv_vscale = uv_vsubsampling(params->format); + u32 tmp_U, tmp_V; + regs->SWIDTH |= (params->src_w/uv_hscale) << 16; + tmp_U = calc_swidthsw(overlay->dev, params->offset_U, + params->src_w/uv_hscale); + tmp_V = calc_swidthsw(overlay->dev, params->offset_V, + params->src_w/uv_hscale); + regs->SWIDTHSW |= max_t(u32, tmp_U, tmp_V) << 16; + regs->SHEIGHT |= (params->src_h/uv_vscale) << 16; + regs->OBUF_0U = bo_priv->gtt_offset + params->offset_U; + regs->OBUF_0V = bo_priv->gtt_offset + params->offset_V; + regs->OSTRIDE |= params->stride_UV << 16; + } + + scale_changed = update_scaling_factors(overlay, regs, params); + + update_colorkey(overlay, regs); + + regs->OCMD = overlay_cmd_reg(params); + + intel_overlay_unmap_regs_atomic(overlay); + + intel_overlay_continue(overlay, scale_changed); + + overlay->old_vid_bo = overlay->vid_bo; + overlay->vid_bo = new_bo->driver_private; + + return 0; + +out_unpin: + i915_gem_object_unpin(new_bo); + return ret; +} + +int intel_overlay_switch_off(struct intel_overlay *overlay) +{ + int ret; + struct overlay_registers *regs; + struct drm_gem_object *obj; + struct drm_device *dev = overlay->dev; + + BUG_ON(!mutex_is_locked(&dev->struct_mutex)); + BUG_ON(!mutex_is_locked(&dev->mode_config.mutex)); + + if (!overlay->active) + return 0; + + if (overlay->hw_wedged) + return -EBUSY; + + ret = intel_overlay_release_old_vid(overlay); + if (ret != 0) + return ret; + + regs = intel_overlay_map_regs_atomic(overlay); + regs->OCMD = 0; + intel_overlay_unmap_regs_atomic(overlay); + + ret = intel_overlay_off(overlay); + /* never have the overlay hw on without showing a frame */ + BUG_ON(!overlay->vid_bo); + obj = overlay->vid_bo->obj; + + i915_gem_object_unpin(obj); + drm_gem_object_unreference(obj); + overlay->vid_bo = NULL; + + overlay->crtc->overlay = NULL; + overlay->crtc = NULL; + + return 0; +} + +static int check_overlay_possible_on_crtc(struct intel_overlay *overlay, + struct intel_crtc *crtc) +{ + drm_i915_private_t *dev_priv = overlay->dev->dev_private; + u32 pipeconf; + int pipeconf_reg = (crtc->pipe == 0) ? PIPEACONF : PIPEBCONF; + + if (!crtc->base.enabled || crtc->dpms_mode != DRM_MODE_DPMS_ON) + return -EINVAL; + + pipeconf = I915_READ(pipeconf_reg); + + /* can't use the overlay with double wide pipe */ + if (!IS_I965G(overlay->dev) && pipeconf & PIPEACONF_DOUBLE_WIDE) + return -EINVAL; + + return 0; +} + +static void update_pfit_vscale_ratio(struct intel_overlay *overlay) +{ + struct drm_device *dev = overlay->dev; + drm_i915_private_t *dev_priv = dev->dev_private; + u32 ratio; + u32 pfit_control = I915_READ(PFIT_CONTROL); + + /* XXX: This is not the same logic as in the xorg driver, but more in + * line with the intel documentation for the i965 */ + if (!IS_I965G(dev) && (pfit_control & VERT_AUTO_SCALE)) { + ratio = I915_READ(PFIT_AUTO_RATIOS) >> PFIT_VERT_SCALE_SHIFT; + } else { /* on i965 use the PGM reg to read out the autoscaler values */ + ratio = I915_READ(PFIT_PGM_RATIOS); + if (IS_I965G(dev)) + ratio >>= PFIT_VERT_SCALE_SHIFT_965; + else + ratio >>= PFIT_VERT_SCALE_SHIFT; + } + + overlay->pfit_vscale_ratio = ratio; +} + +static int check_overlay_dst(struct intel_overlay *overlay, + struct drm_intel_overlay_put_image *rec) +{ + struct drm_display_mode *mode = &overlay->crtc->base.mode; + + if ((rec->dst_x < mode->crtc_hdisplay) + && (rec->dst_x + rec->dst_width + <= mode->crtc_hdisplay) + && (rec->dst_y < mode->crtc_vdisplay) + && (rec->dst_y + rec->dst_height + <= mode->crtc_vdisplay)) + return 0; + else + return -EINVAL; +} + +static int check_overlay_scaling(struct put_image_params *rec) +{ + u32 tmp; + + /* downscaling limit is 8.0 */ + tmp = ((rec->src_scan_h << 16) / rec->dst_h) >> 16; + if (tmp > 7) + return -EINVAL; + tmp = ((rec->src_scan_w << 16) / rec->dst_w) >> 16; + if (tmp > 7) + return -EINVAL; + + return 0; +} + +static int check_overlay_src(struct drm_device *dev, + struct drm_intel_overlay_put_image *rec, + struct drm_gem_object *new_bo) +{ + u32 stride_mask; + int depth; + int uv_hscale = uv_hsubsampling(rec->flags); + int uv_vscale = uv_vsubsampling(rec->flags); + size_t tmp; + + /* check src dimensions */ + if (IS_845G(dev) || IS_I830(dev)) { + if (rec->src_height > IMAGE_MAX_HEIGHT_LEGACY + || rec->src_width > IMAGE_MAX_WIDTH_LEGACY) + return -EINVAL; + } else { + if (rec->src_height > IMAGE_MAX_HEIGHT + || rec->src_width > IMAGE_MAX_WIDTH) + return -EINVAL; + } + /* better safe than sorry, use 4 as the maximal subsampling ratio */ + if (rec->src_height < N_VERT_Y_TAPS*4 + || rec->src_width < N_HORIZ_Y_TAPS*4) + return -EINVAL; + + /* check alingment constrains */ + switch (rec->flags & I915_OVERLAY_TYPE_MASK) { + case I915_OVERLAY_RGB: + /* not implemented */ + return -EINVAL; + case I915_OVERLAY_YUV_PACKED: + depth = packed_depth_bytes(rec->flags); + if (uv_vscale != 1) + return -EINVAL; + if (depth < 0) + return depth; + /* ignore UV planes */ + rec->stride_UV = 0; + rec->offset_U = 0; + rec->offset_V = 0; + /* check pixel alignment */ + if (rec->offset_Y % depth) + return -EINVAL; + break; + case I915_OVERLAY_YUV_PLANAR: + if (uv_vscale < 0 || uv_hscale < 0) + return -EINVAL; + /* no offset restrictions for planar formats */ + break; + default: + return -EINVAL; + } + + if (rec->src_width % uv_hscale) + return -EINVAL; + + /* stride checking */ + stride_mask = 63; + + if (rec->stride_Y & stride_mask || rec->stride_UV & stride_mask) + return -EINVAL; + if (IS_I965G(dev) && rec->stride_Y < 512) + return -EINVAL; + + tmp = (rec->flags & I915_OVERLAY_TYPE_MASK) == I915_OVERLAY_YUV_PLANAR ? + 4 : 8; + if (rec->stride_Y > tmp*1024 || rec->stride_UV > 2*1024) + return -EINVAL; + + /* check buffer dimensions */ + switch (rec->flags & I915_OVERLAY_TYPE_MASK) { + case I915_OVERLAY_RGB: + case I915_OVERLAY_YUV_PACKED: + /* always 4 Y values per depth pixels */ + if (packed_width_bytes(rec->flags, rec->src_width) + > rec->stride_Y) + return -EINVAL; + + tmp = rec->stride_Y*rec->src_height; + if (rec->offset_Y + tmp > new_bo->size) + return -EINVAL; + break; + case I915_OVERLAY_YUV_PLANAR: + if (rec->src_width > rec->stride_Y) + return -EINVAL; + if (rec->src_width/uv_hscale > rec->stride_UV) + return -EINVAL; + + tmp = rec->stride_Y*rec->src_height; + if (rec->offset_Y + tmp > new_bo->size) + return -EINVAL; + tmp = rec->stride_UV*rec->src_height; + tmp /= uv_vscale; + if (rec->offset_U + tmp > new_bo->size + || rec->offset_V + tmp > new_bo->size) + return -EINVAL; + break; + } + + return 0; +} + +int intel_overlay_put_image(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_intel_overlay_put_image *put_image_rec = data; + drm_i915_private_t *dev_priv = dev->dev_private; + struct intel_overlay *overlay; + struct drm_mode_object *drmmode_obj; + struct intel_crtc *crtc; + struct drm_gem_object *new_bo; + struct put_image_params *params; + int ret; + + if (!dev_priv) { + DRM_ERROR("called with no initialization\n"); + return -EINVAL; + } + + overlay = dev_priv->overlay; + if (!overlay) { + DRM_DEBUG("userspace bug: no overlay\n"); + return -ENODEV; + } + + if (!(put_image_rec->flags & I915_OVERLAY_ENABLE)) { + mutex_lock(&dev->mode_config.mutex); + mutex_lock(&dev->struct_mutex); + + ret = intel_overlay_switch_off(overlay); + + mutex_unlock(&dev->struct_mutex); + mutex_unlock(&dev->mode_config.mutex); + + return ret; + } + + params = kmalloc(sizeof(struct put_image_params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + drmmode_obj = drm_mode_object_find(dev, put_image_rec->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drmmode_obj) + return -ENOENT; + crtc = to_intel_crtc(obj_to_crtc(drmmode_obj)); + + new_bo = drm_gem_object_lookup(dev, file_priv, + put_image_rec->bo_handle); + if (!new_bo) + return -ENOENT; + + mutex_lock(&dev->mode_config.mutex); + mutex_lock(&dev->struct_mutex); + + if (overlay->crtc != crtc) { + struct drm_display_mode *mode = &crtc->base.mode; + ret = intel_overlay_switch_off(overlay); + if (ret != 0) + goto out_unlock; + + ret = check_overlay_possible_on_crtc(overlay, crtc); + if (ret != 0) + goto out_unlock; + + overlay->crtc = crtc; + crtc->overlay = overlay; + + if (intel_panel_fitter_pipe(dev) == crtc->pipe + /* and line to wide, i.e. one-line-mode */ + && mode->hdisplay > 1024) { + overlay->pfit_active = 1; + update_pfit_vscale_ratio(overlay); + } else + overlay->pfit_active = 0; + } + + ret = check_overlay_dst(overlay, put_image_rec); + if (ret != 0) + goto out_unlock; + + if (overlay->pfit_active) { + params->dst_y = ((((u32)put_image_rec->dst_y) << 12) / + overlay->pfit_vscale_ratio); + /* shifting right rounds downwards, so add 1 */ + params->dst_h = ((((u32)put_image_rec->dst_height) << 12) / + overlay->pfit_vscale_ratio) + 1; + } else { + params->dst_y = put_image_rec->dst_y; + params->dst_h = put_image_rec->dst_height; + } + params->dst_x = put_image_rec->dst_x; + params->dst_w = put_image_rec->dst_width; + + params->src_w = put_image_rec->src_width; + params->src_h = put_image_rec->src_height; + params->src_scan_w = put_image_rec->src_scan_width; + params->src_scan_h = put_image_rec->src_scan_height; + if (params->src_scan_h > params->src_h + || params->src_scan_w > params->src_w) { + ret = -EINVAL; + goto out_unlock; + } + + ret = check_overlay_src(dev, put_image_rec, new_bo); + if (ret != 0) + goto out_unlock; + params->format = put_image_rec->flags & ~I915_OVERLAY_FLAGS_MASK; + params->stride_Y = put_image_rec->stride_Y; + params->stride_UV = put_image_rec->stride_UV; + params->offset_Y = put_image_rec->offset_Y; + params->offset_U = put_image_rec->offset_U; + params->offset_V = put_image_rec->offset_V; + + /* Check scaling after src size to prevent a divide-by-zero. */ + ret = check_overlay_scaling(params); + if (ret != 0) + goto out_unlock; + + ret = intel_overlay_do_put_image(overlay, new_bo, params); + if (ret != 0) + goto out_unlock; + + mutex_unlock(&dev->struct_mutex); + mutex_unlock(&dev->mode_config.mutex); + + kfree(params); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + mutex_unlock(&dev->mode_config.mutex); + drm_gem_object_unreference(new_bo); + kfree(params); + + return ret; +} + +static void update_reg_attrs(struct intel_overlay *overlay, + struct overlay_registers *regs) +{ + regs->OCLRC0 = (overlay->contrast << 18) | (overlay->brightness & 0xff); + regs->OCLRC1 = overlay->saturation; +} + +static bool check_gamma_bounds(u32 gamma1, u32 gamma2) +{ + int i; + + if (gamma1 & 0xff000000 || gamma2 & 0xff000000) + return false; + + for (i = 0; i < 3; i++) { + if (((gamma1 >> i * 8) & 0xff) >= ((gamma2 >> i*8) & 0xff)) + return false; + } + + return true; +} + +static bool check_gamma5_errata(u32 gamma5) +{ + int i; + + for (i = 0; i < 3; i++) { + if (((gamma5 >> i*8) & 0xff) == 0x80) + return false; + } + + return true; +} + +static int check_gamma(struct drm_intel_overlay_attrs *attrs) +{ + if (!check_gamma_bounds(0, attrs->gamma0) + || !check_gamma_bounds(attrs->gamma0, attrs->gamma1) + || !check_gamma_bounds(attrs->gamma1, attrs->gamma2) + || !check_gamma_bounds(attrs->gamma2, attrs->gamma3) + || !check_gamma_bounds(attrs->gamma3, attrs->gamma4) + || !check_gamma_bounds(attrs->gamma4, attrs->gamma5) + || !check_gamma_bounds(attrs->gamma5, 0x00ffffff)) + return -EINVAL; + if (!check_gamma5_errata(attrs->gamma5)) + return -EINVAL; + return 0; +} + +int intel_overlay_attrs(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_intel_overlay_attrs *attrs = data; + drm_i915_private_t *dev_priv = dev->dev_private; + struct intel_overlay *overlay; + struct overlay_registers *regs; + int ret; + + if (!dev_priv) { + DRM_ERROR("called with no initialization\n"); + return -EINVAL; + } + + overlay = dev_priv->overlay; + if (!overlay) { + DRM_DEBUG("userspace bug: no overlay\n"); + return -ENODEV; + } + + mutex_lock(&dev->mode_config.mutex); + mutex_lock(&dev->struct_mutex); + + if (!(attrs->flags & I915_OVERLAY_UPDATE_ATTRS)) { + attrs->color_key = overlay->color_key; + attrs->brightness = overlay->brightness; + attrs->contrast = overlay->contrast; + attrs->saturation = overlay->saturation; + + if (IS_I9XX(dev)) { + attrs->gamma0 = I915_READ(OGAMC0); + attrs->gamma1 = I915_READ(OGAMC1); + attrs->gamma2 = I915_READ(OGAMC2); + attrs->gamma3 = I915_READ(OGAMC3); + attrs->gamma4 = I915_READ(OGAMC4); + attrs->gamma5 = I915_READ(OGAMC5); + } + ret = 0; + } else { + overlay->color_key = attrs->color_key; + if (attrs->brightness >= -128 && attrs->brightness <= 127) { + overlay->brightness = attrs->brightness; + } else { + ret = -EINVAL; + goto out_unlock; + } + if (attrs->contrast <= 255) { + overlay->contrast = attrs->contrast; + } else { + ret = -EINVAL; + goto out_unlock; + } + if (attrs->saturation <= 1023) { + overlay->saturation = attrs->saturation; + } else { + ret = -EINVAL; + goto out_unlock; + } + + regs = intel_overlay_map_regs_atomic(overlay); + if (!regs) { + ret = -ENOMEM; + goto out_unlock; + } + + update_reg_attrs(overlay, regs); + + intel_overlay_unmap_regs_atomic(overlay); + + if (attrs->flags & I915_OVERLAY_UPDATE_GAMMA) { + if (!IS_I9XX(dev)) { + ret = -EINVAL; + goto out_unlock; + } + + if (overlay->active) { + ret = -EBUSY; + goto out_unlock; + } + + ret = check_gamma(attrs); + if (ret != 0) + goto out_unlock; + + I915_WRITE(OGAMC0, attrs->gamma0); + I915_WRITE(OGAMC1, attrs->gamma1); + I915_WRITE(OGAMC2, attrs->gamma2); + I915_WRITE(OGAMC3, attrs->gamma3); + I915_WRITE(OGAMC4, attrs->gamma4); + I915_WRITE(OGAMC5, attrs->gamma5); + } + ret = 0; + } + +out_unlock: + mutex_unlock(&dev->struct_mutex); + mutex_unlock(&dev->mode_config.mutex); + + return ret; +} + +void intel_setup_overlay(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + struct intel_overlay *overlay; + struct drm_gem_object *reg_bo; + struct overlay_registers *regs; + int ret; + + if (!OVERLAY_EXISTS(dev)) + return; + + overlay = kzalloc(sizeof(struct intel_overlay), GFP_KERNEL); + if (!overlay) + return; + overlay->dev = dev; + + reg_bo = drm_gem_object_alloc(dev, PAGE_SIZE); + if (!reg_bo) + goto out_free; + overlay->reg_bo = reg_bo->driver_private; + + if (OVERLAY_NONPHYSICAL(dev)) { + ret = i915_gem_object_pin(reg_bo, PAGE_SIZE); + if (ret) { + DRM_ERROR("failed to pin overlay register bo\n"); + goto out_free_bo; + } + overlay->flip_addr = overlay->reg_bo->gtt_offset; + } else { + ret = i915_gem_attach_phys_object(dev, reg_bo, + I915_GEM_PHYS_OVERLAY_REGS); + if (ret) { + DRM_ERROR("failed to attach phys overlay regs\n"); + goto out_free_bo; + } + overlay->flip_addr = overlay->reg_bo->phys_obj->handle->busaddr; + } + + /* init all values */ + overlay->color_key = 0x0101fe; + overlay->brightness = -19; + overlay->contrast = 75; + overlay->saturation = 146; + + regs = intel_overlay_map_regs_atomic(overlay); + if (!regs) + goto out_free_bo; + + memset(regs, 0, sizeof(struct overlay_registers)); + update_polyphase_filter(regs); + + update_reg_attrs(overlay, regs); + + intel_overlay_unmap_regs_atomic(overlay); + + dev_priv->overlay = overlay; + DRM_INFO("initialized overlay support\n"); + return; + +out_free_bo: + drm_gem_object_unreference(reg_bo); +out_free: + kfree(overlay); + return; +} + +void intel_cleanup_overlay(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + + if (dev_priv->overlay) { + /* The bo's should be free'd by the generic code already. + * Furthermore modesetting teardown happens beforehand so the + * hardware should be off already */ + BUG_ON(dev_priv->overlay->active); + + kfree(dev_priv->overlay); + } +} diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h index 7e0cb1da92e..c900499f2f6 100644 --- a/include/drm/i915_drm.h +++ b/include/drm/i915_drm.h @@ -186,6 +186,8 @@ typedef struct _drm_i915_sarea { #define DRM_I915_GEM_MMAP_GTT 0x24 #define DRM_I915_GET_PIPE_FROM_CRTC_ID 0x25 #define DRM_I915_GEM_MADVISE 0x26 +#define DRM_I915_OVERLAY_PUT_IMAGE 0x27 +#define DRM_I915_OVERLAY_ATTRS 0x28 #define DRM_IOCTL_I915_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT, drm_i915_init_t) #define DRM_IOCTL_I915_FLUSH DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLUSH) @@ -223,6 +225,8 @@ typedef struct _drm_i915_sarea { #define DRM_IOCTL_I915_GEM_GET_APERTURE DRM_IOR (DRM_COMMAND_BASE + DRM_I915_GEM_GET_APERTURE, struct drm_i915_gem_get_aperture) #define DRM_IOCTL_I915_GET_PIPE_FROM_CRTC_ID DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GET_PIPE_FROM_CRTC_ID, struct drm_intel_get_pipe_from_crtc_id) #define DRM_IOCTL_I915_GEM_MADVISE DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GEM_MADVISE, struct drm_i915_gem_madvise) +#define DRM_IOCTL_I915_OVERLAY_PUT_IMAGE DRM_IOW(DRM_COMMAND_BASE + DRM_IOCTL_I915_OVERLAY_ATTRS, struct drm_intel_overlay_put_image) +#define DRM_IOCTL_I915_OVERLAY_ATTRS DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_OVERLAY_ATTRS, struct drm_intel_overlay_attrs) /* Allow drivers to submit batchbuffers directly to hardware, relying * on the security mechanisms provided by hardware. @@ -266,6 +270,7 @@ typedef struct drm_i915_irq_wait { #define I915_PARAM_CHIPSET_ID 4 #define I915_PARAM_HAS_GEM 5 #define I915_PARAM_NUM_FENCES_AVAIL 6 +#define I915_PARAM_HAS_OVERLAY 7 typedef struct drm_i915_getparam { int param; @@ -686,4 +691,70 @@ struct drm_i915_gem_madvise { __u32 retained; }; +/* flags */ +#define I915_OVERLAY_TYPE_MASK 0xff +#define I915_OVERLAY_YUV_PLANAR 0x01 +#define I915_OVERLAY_YUV_PACKED 0x02 +#define I915_OVERLAY_RGB 0x03 + +#define I915_OVERLAY_DEPTH_MASK 0xff00 +#define I915_OVERLAY_RGB24 0x1000 +#define I915_OVERLAY_RGB16 0x2000 +#define I915_OVERLAY_RGB15 0x3000 +#define I915_OVERLAY_YUV422 0x0100 +#define I915_OVERLAY_YUV411 0x0200 +#define I915_OVERLAY_YUV420 0x0300 +#define I915_OVERLAY_YUV410 0x0400 + +#define I915_OVERLAY_SWAP_MASK 0xff0000 +#define I915_OVERLAY_NO_SWAP 0x000000 +#define I915_OVERLAY_UV_SWAP 0x010000 +#define I915_OVERLAY_Y_SWAP 0x020000 +#define I915_OVERLAY_Y_AND_UV_SWAP 0x030000 + +#define I915_OVERLAY_FLAGS_MASK 0xff000000 +#define I915_OVERLAY_ENABLE 0x01000000 + +struct drm_intel_overlay_put_image { + /* various flags and src format description */ + __u32 flags; + /* source picture description */ + __u32 bo_handle; + /* stride values and offsets are in bytes, buffer relative */ + __u16 stride_Y; /* stride for packed formats */ + __u16 stride_UV; + __u32 offset_Y; /* offset for packet formats */ + __u32 offset_U; + __u32 offset_V; + /* in pixels */ + __u16 src_width; + __u16 src_height; + /* to compensate the scaling factors for partially covered surfaces */ + __u16 src_scan_width; + __u16 src_scan_height; + /* output crtc description */ + __u32 crtc_id; + __u16 dst_x; + __u16 dst_y; + __u16 dst_width; + __u16 dst_height; +}; + +/* flags */ +#define I915_OVERLAY_UPDATE_ATTRS (1<<0) +#define I915_OVERLAY_UPDATE_GAMMA (1<<1) +struct drm_intel_overlay_attrs { + __u32 flags; + __u32 color_key; + __s32 brightness; + __u32 contrast; + __u32 saturation; + __u32 gamma0; + __u32 gamma1; + __u32 gamma2; + __u32 gamma3; + __u32 gamma4; + __u32 gamma5; +}; + #endif /* _I915_DRM_H_ */ -- cgit v1.2.3-70-g09d2 From c9a9c5e02aedc1a2815877b0268f886d2640b771 Mon Sep 17 00:00:00 2001 From: Kristian Høgsberg Date: Sat, 12 Sep 2009 04:33:34 +1000 Subject: drm: Add async event synchronization for drmWaitVblank MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a new flag to the drmWaitVblank ioctl, which asks the drm to return immediately and notify userspace when the specified vblank sequence happens by sending an event back on the drm fd. The event mechanism works with the other flags supported by the ioctls, specifically, the vblank sequence can be specified relatively or absolutely, and works for primary and seconday crtc. The signal field of the vblank request is used to provide user data, which will be sent back to user space in the vblank event. Signed-off-by: Kristian Høgsberg Reviewed-by: Jesse Barnes Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_fops.c | 98 ++++++++++++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_irq.c | 95 +++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_stub.c | 2 + drivers/gpu/drm/i915/i915_drv.c | 1 + include/drm/drm.h | 33 +++++++++++++- include/drm/drmP.h | 26 +++++++++++ 6 files changed, 251 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 251bc0e3b5e..8ac7fbf6b2b 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,9 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); + priv->event_space = 4096; /* set aside 4k for event buffer */ if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -413,6 +416,30 @@ static void drm_master_release(struct drm_device *dev, struct file *filp) } } +static void drm_events_release(struct drm_file *file_priv) +{ + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *e, *et; + struct drm_pending_vblank_event *v, *vt; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + + /* Remove pending flips */ + list_for_each_entry_safe(v, vt, &dev->vblank_event_list, base.link) + if (v->base.file_priv == file_priv) { + list_del(&v->base.link); + drm_vblank_put(dev, v->pipe); + v->base.destroy(&v->base); + } + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + e->destroy(e); + + spin_unlock_irqrestore(&dev->event_lock, flags); +} + /** * Release file. * @@ -451,6 +478,8 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + drm_events_release(file_priv); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +573,74 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +static bool +drm_dequeue_event(struct drm_file *file_priv, + size_t total, size_t max, struct drm_pending_event **out) +{ + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *e; + unsigned long flags; + bool ret = false; + + spin_lock_irqsave(&dev->event_lock, flags); + + *out = NULL; + if (list_empty(&file_priv->event_list)) + goto out; + e = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (e->event->length + total > max) + goto out; + + file_priv->event_space += e->event->length; + list_del(&e->link); + *out = e; + ret = true; + +out: + spin_unlock_irqrestore(&dev->event_lock, flags); + return ret; +} + +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_pending_event *e; + size_t total; + ssize_t ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (drm_dequeue_event(file_priv, total, count, &e)) { + if (copy_to_user(buffer + total, + e->event, e->event->length)) { + total = -EFAULT; + break; + } + + total += e->event->length; + e->destroy(e); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 0a6f0b3bdc7..72754aca7ab 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -550,6 +550,62 @@ out: return ret; } +static int drm_queue_vblank_event(struct drm_device *dev, int pipe, + union drm_wait_vblank *vblwait, + struct drm_file *file_priv) +{ + struct drm_pending_vblank_event *e; + struct timeval now; + unsigned long flags; + unsigned int seq; + + e = kzalloc(sizeof *e, GFP_KERNEL); + if (e == NULL) + return -ENOMEM; + + e->pipe = pipe; + e->event.base.type = DRM_EVENT_VBLANK; + e->event.base.length = sizeof e->event; + e->event.user_data = vblwait->request.signal; + e->base.event = &e->event.base; + e->base.file_priv = file_priv; + e->base.destroy = (void (*) (struct drm_pending_event *)) kfree; + + do_gettimeofday(&now); + spin_lock_irqsave(&dev->event_lock, flags); + + if (file_priv->event_space < sizeof e->event) { + spin_unlock_irqrestore(&dev->event_lock, flags); + kfree(e); + return -ENOMEM; + } + + file_priv->event_space -= sizeof e->event; + seq = drm_vblank_count(dev, pipe); + if ((vblwait->request.type & _DRM_VBLANK_NEXTONMISS) && + (seq - vblwait->request.sequence) <= (1 << 23)) { + vblwait->request.sequence = seq + 1; + } + + DRM_DEBUG("event on vblank count %d, current %d, crtc %d\n", + vblwait->request.sequence, seq, pipe); + + e->event.sequence = vblwait->request.sequence; + if ((seq - vblwait->request.sequence) <= (1 << 23)) { + e->event.tv_sec = now.tv_sec; + e->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, e->pipe); + list_add_tail(&e->base.link, &e->base.file_priv->event_list); + wake_up_interruptible(&e->base.file_priv->event_wait); + } else { + list_add_tail(&e->base.link, &dev->vblank_event_list); + } + + spin_unlock_irqrestore(&dev->event_lock, flags); + + return 0; +} + /** * Wait for VBLANK. * @@ -609,6 +665,9 @@ int drm_wait_vblank(struct drm_device *dev, void *data, goto done; } + if (flags & _DRM_VBLANK_EVENT) + return drm_queue_vblank_event(dev, crtc, vblwait, file_priv); + if ((flags & _DRM_VBLANK_NEXTONMISS) && (seq - vblwait->request.sequence) <= (1<<23)) { vblwait->request.sequence = seq + 1; @@ -641,6 +700,38 @@ done: return ret; } +void drm_handle_vblank_events(struct drm_device *dev, int crtc) +{ + struct drm_pending_vblank_event *e, *t; + struct timeval now; + unsigned long flags; + unsigned int seq; + + do_gettimeofday(&now); + seq = drm_vblank_count(dev, crtc); + + spin_lock_irqsave(&dev->event_lock, flags); + + list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { + if (e->pipe != crtc) + continue; + if ((seq - e->event.sequence) > (1<<23)) + continue; + + DRM_DEBUG("vblank event on %d, current %d\n", + e->event.sequence, seq); + + e->event.sequence = seq; + e->event.tv_sec = now.tv_sec; + e->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, e->pipe); + list_move_tail(&e->base.link, &e->base.file_priv->event_list); + wake_up_interruptible(&e->base.file_priv->event_wait); + } + + spin_unlock_irqrestore(&dev->event_lock, flags); +} + /** * drm_handle_vblank - handle a vblank event * @dev: DRM device @@ -651,7 +742,11 @@ done: */ void drm_handle_vblank(struct drm_device *dev, int crtc) { + if (!dev->num_crtcs) + return; + atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + drm_handle_vblank_events(dev, crtc); } EXPORT_SYMBOL(drm_handle_vblank); diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c index 55bb8a82d61..adb864dfef3 100644 --- a/drivers/gpu/drm/drm_stub.c +++ b/drivers/gpu/drm/drm_stub.c @@ -220,9 +220,11 @@ static int drm_fill_in_dev(struct drm_device * dev, struct pci_dev *pdev, INIT_LIST_HEAD(&dev->ctxlist); INIT_LIST_HEAD(&dev->vmalist); INIT_LIST_HEAD(&dev->maplist); + INIT_LIST_HEAD(&dev->vblank_event_list); spin_lock_init(&dev->count_lock); spin_lock_init(&dev->drw_lock); + spin_lock_init(&dev->event_lock); init_timer(&dev->timer); mutex_init(&dev->struct_mutex); mutex_init(&dev->ctxlist_mutex); diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 7f436ec075f..2fa21786205 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -333,6 +333,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bdde46..fa6d9155873 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -454,6 +454,7 @@ struct drm_irq_busid { enum drm_vblank_seq_type { _DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */ _DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */ + _DRM_VBLANK_EVENT = 0x4000000, /**< Send event instead of blocking */ _DRM_VBLANK_FLIP = 0x8000000, /**< Scheduled buffer swap should flip */ _DRM_VBLANK_NEXTONMISS = 0x10000000, /**< If missed, wait for next vblank */ _DRM_VBLANK_SECONDARY = 0x20000000, /**< Secondary display controller */ @@ -461,8 +462,8 @@ enum drm_vblank_seq_type { }; #define _DRM_VBLANK_TYPES_MASK (_DRM_VBLANK_ABSOLUTE | _DRM_VBLANK_RELATIVE) -#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_SIGNAL | _DRM_VBLANK_SECONDARY | \ - _DRM_VBLANK_NEXTONMISS) +#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_EVENT | _DRM_VBLANK_SIGNAL | \ + _DRM_VBLANK_SECONDARY | _DRM_VBLANK_NEXTONMISS) struct drm_wait_vblank_request { enum drm_vblank_seq_type type; @@ -698,6 +699,34 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + * + * Event types 0 - 0x7fffffff are generic drm events, 0x80000000 and + * up are chipset specific. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_VBLANK 0x01 + +struct drm_event_vblank { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 sequence; + __u32 reserved; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index c8e64bbadbc..b0b36838ab1 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -426,6 +426,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy)(struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -449,6 +457,10 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; + int event_space; }; /** Wait queue */ @@ -900,6 +912,12 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_vblank_event { + struct drm_pending_event base; + int pipe; + struct drm_event_vblank event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -999,6 +1017,12 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + /** + * List of events + */ + struct list_head vblank_event_list; + spinlock_t event_lock; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1135,6 +1159,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ -- cgit v1.2.3-70-g09d2 From 420a457088669e055e767dfb8468909cd1799cf9 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Tue, 17 Nov 2009 14:41:23 -0800 Subject: drm: kill some unused DRM_PROC macros from drmP.h i915_gem_proc.c appears to have been the last user of the DRM_PROC_* macros, and it has gone away. The macros should die as well. Signed-off-by: Andres Salomon Signed-off-by: Andrew Morton Signed-off-by: Dave Airlie --- include/drm/drmP.h | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'include') diff --git a/include/drm/drmP.h b/include/drm/drmP.h index b0b36838ab1..4977fa9038d 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -245,16 +245,6 @@ extern void drm_ut_debug_printk(unsigned int request_level, #endif -#define DRM_PROC_LIMIT (PAGE_SIZE-80) - -#define DRM_PROC_PRINT(fmt, arg...) \ - len += sprintf(&buf[len], fmt , ##arg); \ - if (len > DRM_PROC_LIMIT) { *eof = 1; return len - offset; } - -#define DRM_PROC_PRINT_RET(ret, fmt, arg...) \ - len += sprintf(&buf[len], fmt , ##arg); \ - if (len > DRM_PROC_LIMIT) { ret; *eof = 1; return len - offset; } - /*@}*/ /***********************************************************************/ -- cgit v1.2.3-70-g09d2 From 156822f7175d9ceb9d7e808502d3c5de8841e047 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Tue, 17 Nov 2009 14:41:23 -0800 Subject: drm: kill more unused DRM macros There are a few more macros in drmP.h that are unused; DRM_GET_PRIV_SAREA, DRM_ARRAY_SIZE, and DRM_WAITCOUNT can go away completely. Unfortunately, DRM_COPY is still used in one place, but we can at least move it to where it's used. It's an awful looking macro.. [akpm: fix overeagerness] Signed-off-by: Andres Salomon Cc: Dave Airlie Signed-off-by: Andrew Morton Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_drv.c | 12 ++++++++++++ include/drm/drmP.h | 23 ----------------------- 2 files changed, 12 insertions(+), 23 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index a75ca63deea..43297ca45f3 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -365,6 +365,18 @@ static void __exit drm_core_exit(void) module_init(drm_core_init); module_exit(drm_core_exit); +/** + * Copy and IOCTL return string to user space + */ +#define DRM_COPY(name, value) \ + len = strlen(value); \ + if (len > name##_len) len = name##_len; \ + name##_len = strlen(value); \ + if (len && name) { \ + if (copy_to_user(name, value, len)) \ + return -EFAULT; \ + } + /** * Get version information * diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 4977fa9038d..1b72a526ba6 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -255,19 +255,8 @@ extern void drm_ut_debug_printk(unsigned int request_level, #define DRM_LEFTCOUNT(x) (((x)->rp + (x)->count - (x)->wp) % ((x)->count + 1)) #define DRM_BUFCOUNT(x) ((x)->count - DRM_LEFTCOUNT(x)) -#define DRM_WAITCOUNT(dev,idx) DRM_BUFCOUNT(&dev->queuelist[idx]->waitlist) #define DRM_IF_VERSION(maj, min) (maj << 16 | min) -/** - * Get the private SAREA mapping. - * - * \param _dev DRM device. - * \param _ctx context number. - * \param _map output mapping. - */ -#define DRM_GET_PRIV_SAREA(_dev, _ctx, _map) do { \ - (_map) = (_dev)->context_sareas[_ctx]; \ -} while(0) /** * Test that the hardware lock is held by the caller, returning otherwise. @@ -286,18 +275,6 @@ do { \ } \ } while (0) -/** - * Copy and IOCTL return string to user space - */ -#define DRM_COPY( name, value ) \ - len = strlen( value ); \ - if ( len > name##_len ) len = name##_len; \ - name##_len = strlen( value ); \ - if ( len && name ) { \ - if ( copy_to_user( name, value, len ) ) \ - return -EFAULT; \ - } - /** * Ioctl function type. * -- cgit v1.2.3-70-g09d2 From d91d8a3f88059d93e34ac70d059153ec69a9ffc7 Mon Sep 17 00:00:00 2001 From: Kristian Høgsberg Date: Tue, 17 Nov 2009 12:43:55 -0500 Subject: drm/kms: add page flipping ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a page flipping ioctl to the KMS API. The ioctl takes an fb ID and a ctrc ID and flips the crtc to the given fb at the next vblank. The ioctl returns immediately but the flip doesn't happen until after any rendering that's currently queued up against the new framebuffer is done. After submitting a page flip, any execbuffer involving the old front buffer will block until the flip is completed. Optionally, a vblank event can be generated when the swap eventually happens. Signed-off-by: Kristian Høgsberg Reviewed-by: Chris Wilson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_crtc.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_drv.c | 1 + include/drm/drm.h | 1 + include/drm/drm_crtc.h | 16 +++++++++++ include/drm/drm_mode.h | 33 ++++++++++++++++++++++ 5 files changed, 120 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 5cae0b3eee9..32756e67dd5 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -2478,3 +2478,72 @@ out: mutex_unlock(&dev->mode_config.mutex); return ret; } + +int drm_mode_page_flip_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv) +{ + struct drm_mode_crtc_page_flip *page_flip = data; + struct drm_mode_object *obj; + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + struct drm_pending_vblank_event *e = NULL; + unsigned long flags; + int ret = -EINVAL; + + if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS || + page_flip->reserved != 0) + return -EINVAL; + + mutex_lock(&dev->mode_config.mutex); + obj = drm_mode_object_find(dev, page_flip->crtc_id, DRM_MODE_OBJECT_CRTC); + if (!obj) + goto out; + crtc = obj_to_crtc(obj); + + if (crtc->funcs->page_flip == NULL) + goto out; + + obj = drm_mode_object_find(dev, page_flip->fb_id, DRM_MODE_OBJECT_FB); + if (!obj) + goto out; + fb = obj_to_fb(obj); + + if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) { + ret = -ENOMEM; + spin_lock_irqsave(&dev->event_lock, flags); + if (file_priv->event_space < sizeof e->event) { + spin_unlock_irqrestore(&dev->event_lock, flags); + goto out; + } + file_priv->event_space -= sizeof e->event; + spin_unlock_irqrestore(&dev->event_lock, flags); + + e = kzalloc(sizeof *e, GFP_KERNEL); + if (e == NULL) { + spin_lock_irqsave(&dev->event_lock, flags); + file_priv->event_space += sizeof e->event; + spin_unlock_irqrestore(&dev->event_lock, flags); + goto out; + } + + e->event.base.type = DRM_EVENT_VBLANK; + e->event.base.length = sizeof e->event; + e->event.user_data = page_flip->user_data; + e->base.event = &e->event.base; + e->base.file_priv = file_priv; + e->base.destroy = + (void (*) (struct drm_pending_event *)) kfree; + } + + ret = crtc->funcs->page_flip(crtc, fb, e); + if (ret) { + spin_lock_irqsave(&dev->event_lock, flags); + file_priv->event_space += sizeof e->event; + spin_unlock_irqrestore(&dev->event_lock, flags); + kfree(e); + } + +out: + mutex_unlock(&dev->mode_config.mutex); + return ret; +} diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 5bd3f9461e2..bfaf59b02bd 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/include/drm/drm.h b/include/drm/drm.h index fa6d9155873..3919a4f792a 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -687,6 +687,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOWR(0xB0, struct drm_mode_crtc_page_flip) /** * Device specific ioctls should only be in their respective headers diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index b69347b8904..4cc8a32dc4c 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -290,6 +290,7 @@ struct drm_property { struct drm_crtc; struct drm_connector; struct drm_encoder; +struct drm_pending_vblank_event; /** * drm_crtc_funcs - control CRTCs for a given device @@ -333,6 +334,19 @@ struct drm_crtc_funcs { void (*destroy)(struct drm_crtc *crtc); int (*set_config)(struct drm_mode_set *set); + + /* + * Flip to the given framebuffer. This implements the page + * flip ioctl descibed in drm_mode.h, specifically, the + * implementation must return immediately and block all + * rendering to the current fb until the flip has completed. + * If userspace set the event flag in the ioctl, the event + * argument will point to an event to send back when the flip + * completes, otherwise it will be NULL. + */ + int (*page_flip)(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event); }; /** @@ -756,6 +770,8 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv); extern struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay, int vdisplay, int vrefresh, bool reduced, bool interlaced, bool margins); diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index 1f908416aed..68ddc6175ae 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -268,4 +268,37 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_EVENT 0x01 +#define DRM_MODE_PAGE_FLIP_FLAGS DRM_MODE_PAGE_FLIP_EVENT + +/* + * Request a page flip on the specified crtc. + * + * This ioctl will ask KMS to schedule a page flip for the specified + * crtc. Once any pending rendering targeting the specified fb (as of + * ioctl time) has completed, the crtc will be reprogrammed to display + * that fb after the next vertical refresh. The ioctl returns + * immediately, but subsequent rendering to the current fb will block + * in the execbuffer ioctl until the page flip happens. If a page + * flip is already pending as the ioctl is called, EBUSY will be + * returned. + * + * The ioctl supports one flag, DRM_MODE_PAGE_FLIP_EVENT, which will + * request that drm sends back a vblank event (see drm.h: struct + * drm_event_vblank) when the page flip is done. The user_data field + * passed in with this ioctl will be returned as the user_data field + * in the vblank event struct. + * + * The reserved field must be zero until we figure out something + * clever to use it for. + */ + +struct drm_mode_crtc_page_flip { + __u32 crtc_id; + __u32 fb_id; + __u32 flags; + __u32 reserved; + __u64 user_data; +}; + #endif -- cgit v1.2.3-70-g09d2 From 7a654158bdf9dc318fd451fbf606ed100d6ba25f Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 4 Nov 2009 09:42:56 +0100 Subject: drm: set the type of the drm_framebuffer::fbdev field The fbdev field of the drm_framebuffer structure is always used to store a pointer to a fb_info, so there is no reason for it to be void*. Signed-off-by: Clemens Ladisch Signed-off-by: Dave Airlie --- include/drm/drm_crtc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 4cc8a32dc4c..d84fba15c9d 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -256,7 +256,7 @@ struct drm_framebuffer { unsigned int depth; int bits_per_pixel; int flags; - void *fbdev; + struct fb_info *fbdev; u32 pseudo_palette[17]; struct list_head filp_head; /* if you are using the helper */ -- cgit v1.2.3-70-g09d2 From c8e0f93a381d1d76135e567f13a4418fce66fd95 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Sun, 22 Nov 2009 03:49:37 +0100 Subject: drm/i915: Replace a calloc followed by copying data over it with malloc. Execbufs involve quite a bit of payload, to the extent that cache misses show up in the profiles here, and a suspicion that some of those cachelines may get evicted and then reloaded in the subsequent copy. This is still abstracted like drm_calloc_large since we want to check for size overflow, and because we want to choose between kmalloc and vmalloc on the fly. cairo's interface for malloc-with-calloc's-args was used as the model. Signed-off-by: Eric Anholt --- drivers/gpu/drm/i915/i915_gem.c | 4 ++-- include/drm/drmP.h | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index 2065b8f7e87..481c0ab888c 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -3563,8 +3563,8 @@ i915_gem_execbuffer(struct drm_device *dev, void *data, return -EINVAL; } /* Copy in the exec list from userland */ - exec_list = drm_calloc_large(sizeof(*exec_list), args->buffer_count); - object_list = drm_calloc_large(sizeof(*object_list), args->buffer_count); + exec_list = drm_malloc_ab(sizeof(*exec_list), args->buffer_count); + object_list = drm_malloc_ab(sizeof(*object_list), args->buffer_count); if (exec_list == NULL || object_list == NULL) { DRM_ERROR("Failed to allocate exec or object list " "for %d buffers\n", diff --git a/include/drm/drmP.h b/include/drm/drmP.h index b0b36838ab1..1b807d0f6cd 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -1545,14 +1545,27 @@ static __inline__ void drm_core_dropmap(struct drm_local_map *map) static __inline__ void *drm_calloc_large(size_t nmemb, size_t size) { + if (size != 0 && nmemb > ULONG_MAX / size) + return NULL; + if (size * nmemb <= PAGE_SIZE) return kcalloc(nmemb, size, GFP_KERNEL); + return __vmalloc(size * nmemb, + GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO, PAGE_KERNEL); +} + +/* Modeled after cairo's malloc_ab, it's like calloc but without the zeroing. */ +static __inline__ void *drm_malloc_ab(size_t nmemb, size_t size) +{ if (size != 0 && nmemb > ULONG_MAX / size) return NULL; + if (size * nmemb <= PAGE_SIZE) + return kmalloc(nmemb * size, GFP_KERNEL); + return __vmalloc(size * nmemb, - GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO, PAGE_KERNEL); + GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL); } static __inline void drm_free_large(void *ptr) -- cgit v1.2.3-70-g09d2 From 7bd4d7be5c8c35a401b1589201e5d43a64d3f05b Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Thu, 19 Nov 2009 10:50:22 -0800 Subject: drm: use page flip event to signal flip completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't actually know which frame number the flip will complete on, so userspace needs a specific flip notification to tell it when the last flip completed. Signed-off-by: Jesse Barnes Signed-off-by: Eric Anholt Acked-by: Kristian Høgsberg --- drivers/gpu/drm/drm_crtc.c | 2 +- include/drm/drm.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index ac2fa193072..3bc870d38a9 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -2527,7 +2527,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, goto out; } - e->event.base.type = DRM_EVENT_VBLANK; + e->event.base.type = DRM_EVENT_FLIP_COMPLETE; e->event.base.length = sizeof e->event; e->event.user_data = page_flip->user_data; e->base.event = &e->event.base; diff --git a/include/drm/drm.h b/include/drm/drm.h index 3919a4f792a..309d0a5ed68 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -718,6 +718,7 @@ struct drm_event { }; #define DRM_EVENT_VBLANK 0x01 +#define DRM_EVENT_FLIP_COMPLETE 0x02 struct drm_event_vblank { struct drm_event base; -- cgit v1.2.3-70-g09d2 From e9560f7cb20722e0e7db46bbb6f43c2194a238d5 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Thu, 19 Nov 2009 10:49:07 -0800 Subject: drm/i915: add GETPARAM request for page flipping Add a GETPARAM request for checking if page flipping is supported. Useful for the 2D driver to enable the flipping path. Signed-off-by: Jesse Barnes Signed-off-by: Eric Anholt --- drivers/gpu/drm/i915/i915_dma.c | 3 +++ include/drm/i915_drm.h | 1 + 2 files changed, 4 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index a8efe78e5c5..fe89d0c723e 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -810,6 +810,9 @@ static int i915_getparam(struct drm_device *dev, void *data, case I915_PARAM_HAS_OVERLAY: value = dev_priv->overlay ? 1 : 0; break; + case I915_PARAM_HAS_PAGEFLIPPING: + value = 1; + break; default: DRM_DEBUG_DRIVER("Unknown parameter %d\n", param->param); diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h index c900499f2f6..1b4f3a565d7 100644 --- a/include/drm/i915_drm.h +++ b/include/drm/i915_drm.h @@ -271,6 +271,7 @@ typedef struct drm_i915_irq_wait { #define I915_PARAM_HAS_GEM 5 #define I915_PARAM_NUM_FENCES_AVAIL 6 #define I915_PARAM_HAS_OVERLAY 7 +#define I915_PARAM_HAS_PAGEFLIPPING 8 typedef struct drm_i915_getparam { int param; -- cgit v1.2.3-70-g09d2 From 778c902640530371a169ad1c03566e7c51b09874 Mon Sep 17 00:00:00 2001 From: Li Peng Date: Mon, 9 Nov 2009 12:51:22 +0800 Subject: drm/i915: Fix sync to vblank when VGA output is turned off In current vblank-wait implementation, if we turn off VGA output, drm_wait_vblank will still wait on the disabled pipe until timeout, because vblank on the pipe is assumed be enabled. This would cause slow system response on some system such as moblin. This patch resolve the issue by adding a drm helper function drm_vblank_off which explicitly clear vblank_enabled[crtc], wake up any waiting queue and save last vblank counter before turning off crtc. It also slightly change drm_vblank_get to ensure that we will will return immediately if trying to wait on a disabled pipe. Signed-off-by: Li Peng Reviewed-by: Jesse Barnes [anholt: hand-applied for conflicts with overlay changes] Signed-off-by: Eric Anholt --- drivers/gpu/drm/drm_irq.c | 34 ++++++++++++++++++++++++++-------- drivers/gpu/drm/i915/intel_display.c | 1 + include/drm/drmP.h | 1 + 3 files changed, 28 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 6b3ce6d3884..7998ee66b31 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -429,15 +429,21 @@ int drm_vblank_get(struct drm_device *dev, int crtc) spin_lock_irqsave(&dev->vbl_lock, irqflags); /* Going from 0->1 means we have to enable interrupts again */ - if (atomic_add_return(1, &dev->vblank_refcount[crtc]) == 1 && - !dev->vblank_enabled[crtc]) { - ret = dev->driver->enable_vblank(dev, crtc); - DRM_DEBUG("enabling vblank on crtc %d, ret: %d\n", crtc, ret); - if (ret) + if (atomic_add_return(1, &dev->vblank_refcount[crtc]) == 1) { + if (!dev->vblank_enabled[crtc]) { + ret = dev->driver->enable_vblank(dev, crtc); + DRM_DEBUG("enabling vblank on crtc %d, ret: %d\n", crtc, ret); + if (ret) + atomic_dec(&dev->vblank_refcount[crtc]); + else { + dev->vblank_enabled[crtc] = 1; + drm_update_vblank_count(dev, crtc); + } + } + } else { + if (!dev->vblank_enabled[crtc]) { atomic_dec(&dev->vblank_refcount[crtc]); - else { - dev->vblank_enabled[crtc] = 1; - drm_update_vblank_count(dev, crtc); + ret = -EINVAL; } } spin_unlock_irqrestore(&dev->vbl_lock, irqflags); @@ -464,6 +470,18 @@ void drm_vblank_put(struct drm_device *dev, int crtc) } EXPORT_SYMBOL(drm_vblank_put); +void drm_vblank_off(struct drm_device *dev, int crtc) +{ + unsigned long irqflags; + + spin_lock_irqsave(&dev->vbl_lock, irqflags); + DRM_WAKEUP(&dev->vbl_queue[crtc]); + dev->vblank_enabled[crtc] = 0; + dev->last_vblank[crtc] = dev->driver->get_vblank_counter(dev, crtc); + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); +} +EXPORT_SYMBOL(drm_vblank_off); + /** * drm_vblank_pre_modeset - account for vblanks across mode sets * @dev: DRM device diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 267adc6fbfc..65b76fffd9e 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -1924,6 +1924,7 @@ static void i9xx_crtc_dpms(struct drm_crtc *crtc, int mode) /* Give the overlay scaler a chance to disable if it's on this pipe */ intel_crtc_dpms_overlay(intel_crtc, false); + drm_vblank_off(dev, pipe); if (dev_priv->cfb_plane == plane && dev_priv->display.disable_fbc) diff --git a/include/drm/drmP.h b/include/drm/drmP.h index febf6c530c6..fd919959d14 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -1288,6 +1288,7 @@ extern u32 drm_vblank_count(struct drm_device *dev, int crtc); extern void drm_handle_vblank(struct drm_device *dev, int crtc); extern int drm_vblank_get(struct drm_device *dev, int crtc); extern void drm_vblank_put(struct drm_device *dev, int crtc); +extern void drm_vblank_off(struct drm_device *dev, int crtc); extern void drm_vblank_cleanup(struct drm_device *dev); /* Modesetting support */ extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); -- cgit v1.2.3-70-g09d2 From 04b2d218001bdddb41b84c9f78d6bb3cd3b5b31c Mon Sep 17 00:00:00 2001 From: Kristian Høgsberg Date: Fri, 6 Nov 2009 08:39:18 -0500 Subject: drm/i915: Fix typo in ioctl struct name. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kristian Høgsberg Signed-off-by: Eric Anholt --- include/drm/i915_drm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h index 1b4f3a565d7..02045ed08ed 100644 --- a/include/drm/i915_drm.h +++ b/include/drm/i915_drm.h @@ -223,7 +223,7 @@ typedef struct _drm_i915_sarea { #define DRM_IOCTL_I915_GEM_SET_TILING DRM_IOWR (DRM_COMMAND_BASE + DRM_I915_GEM_SET_TILING, struct drm_i915_gem_set_tiling) #define DRM_IOCTL_I915_GEM_GET_TILING DRM_IOWR (DRM_COMMAND_BASE + DRM_I915_GEM_GET_TILING, struct drm_i915_gem_get_tiling) #define DRM_IOCTL_I915_GEM_GET_APERTURE DRM_IOR (DRM_COMMAND_BASE + DRM_I915_GEM_GET_APERTURE, struct drm_i915_gem_get_aperture) -#define DRM_IOCTL_I915_GET_PIPE_FROM_CRTC_ID DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GET_PIPE_FROM_CRTC_ID, struct drm_intel_get_pipe_from_crtc_id) +#define DRM_IOCTL_I915_GET_PIPE_FROM_CRTC_ID DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GET_PIPE_FROM_CRTC_ID, struct drm_i915_get_pipe_from_crtc_id) #define DRM_IOCTL_I915_GEM_MADVISE DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GEM_MADVISE, struct drm_i915_gem_madvise) #define DRM_IOCTL_I915_OVERLAY_PUT_IMAGE DRM_IOW(DRM_COMMAND_BASE + DRM_IOCTL_I915_OVERLAY_ATTRS, struct drm_intel_overlay_put_image) #define DRM_IOCTL_I915_OVERLAY_ATTRS DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_OVERLAY_ATTRS, struct drm_intel_overlay_attrs) -- cgit v1.2.3-70-g09d2 From 7ac96a9cb4982140e206bf3b58236efb2498ab3f Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Thu, 3 Dec 2009 17:44:37 -0500 Subject: drm/modes: Add drm_mode_hsync() Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_modes.c | 28 +++++++++++++++++++++++++++- include/drm/drm_crtc.h | 7 ++++--- 2 files changed, 31 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index 51f677215f1..6d81a02463a 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -553,6 +553,32 @@ int drm_mode_height(struct drm_display_mode *mode) } EXPORT_SYMBOL(drm_mode_height); +/** drm_mode_hsync - get the hsync of a mode + * @mode: mode + * + * LOCKING: + * None. + * + * Return @modes's hsync rate in kHz, rounded to the nearest int. + */ +int drm_mode_hsync(struct drm_display_mode *mode) +{ + unsigned int calc_val; + + if (mode->hsync) + return mode->hsync; + + if (mode->htotal < 0) + return 0; + + calc_val = (mode->clock * 1000) / mode->htotal; /* hsync in Hz */ + calc_val += 500; /* round to 1000Hz */ + calc_val /= 1000; /* truncate to kHz */ + + return calc_val; +} +EXPORT_SYMBOL(drm_mode_hsync); + /** * drm_mode_vrefresh - get the vrefresh of a mode * @mode: mode @@ -560,7 +586,7 @@ EXPORT_SYMBOL(drm_mode_height); * LOCKING: * None. * - * Return @mode's vrefresh rate or calculate it if necessary. + * Return @mode's vrefresh rate in Hz or calculate it if necessary. * * FIXME: why is this needed? shouldn't vrefresh be set already? * diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index d84fba15c9d..938f327a2a3 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -123,7 +123,7 @@ struct drm_display_mode { int type; /* Proposed mode values */ - int clock; + int clock; /* in kHz */ int hdisplay; int hsync_start; int hsync_end; @@ -164,8 +164,8 @@ struct drm_display_mode { int *private; int private_flags; - int vrefresh; - float hsync; + int vrefresh; /* in Hz */ + int hsync; /* in kHz */ }; enum drm_connector_status { @@ -681,6 +681,7 @@ extern void drm_mode_validate_size(struct drm_device *dev, extern void drm_mode_prune_invalid(struct drm_device *dev, struct list_head *mode_list, bool verbose); extern void drm_mode_sort(struct list_head *mode_list); +extern int drm_mode_hsync(struct drm_display_mode *mode); extern int drm_mode_vrefresh(struct drm_display_mode *mode); extern void drm_mode_set_crtcinfo(struct drm_display_mode *p, int adjust_flags); -- cgit v1.2.3-70-g09d2 From 2dbdc52c8162291aa7541b8ba6e1c1587f50c1dd Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Thu, 3 Dec 2009 17:44:39 -0500 Subject: drm/edid: Add new detailed block types from EDID 1.4 Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- include/drm/drm_edid.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include') diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 7d6c9a2dfcb..9087557fd83 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -120,6 +120,9 @@ struct detailed_non_pixel { } data; } __attribute__((packed)); +#define EDID_DETAIL_EST_TIMINGS 0xf7 +#define EDID_DETAIL_CVT_3BYTE 0xf8 +#define EDID_DETAIL_COLOR_MGMT_DATA 0xf9 #define EDID_DETAIL_STD_MODES 0xfa #define EDID_DETAIL_MONITOR_CPDATA 0xfb #define EDID_DETAIL_MONITOR_NAME 0xfc -- cgit v1.2.3-70-g09d2 From 9340d8cfeacd16cef1cbe94527f7baaed7640669 Mon Sep 17 00:00:00 2001 From: Adam Jackson Date: Thu, 3 Dec 2009 17:44:40 -0500 Subject: drm/edid: Decode 3-byte CVT codes from EDID 1.4 Signed-off-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_edid.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++ include/drm/drm_edid.h | 5 +++++ 2 files changed, 53 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index cc8e6968271..30af8f3e642 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -897,6 +897,51 @@ static int drm_gtf_modes_for_range(struct drm_connector *connector, return modes; } +static int drm_cvt_modes(struct drm_connector *connector, + struct detailed_timing *timing) +{ + int i, j, modes = 0; + struct drm_display_mode *newmode; + struct drm_device *dev = connector->dev; + struct cvt_timing *cvt; + const int rates[] = { 60, 85, 75, 60, 50 }; + + for (i = 0; i < 4; i++) { + int width, height; + cvt = &(timing->data.other_data.data.cvt[i]); + + height = (cvt->code[0] + ((cvt->code[1] & 0xf0) << 8) + 1) * 2; + switch (cvt->code[1] & 0xc0) { + case 0x00: + width = height * 4 / 3; + break; + case 0x40: + width = height * 16 / 9; + break; + case 0x80: + width = height * 16 / 10; + break; + case 0xc0: + width = height * 15 / 9; + break; + } + + for (j = 1; j < 5; j++) { + if (cvt->code[2] & (1 << j)) { + newmode = drm_cvt_mode(dev, width, height, + rates[j], j == 0, + false, false); + if (newmode) { + drm_mode_probed_add(connector, newmode); + modes++; + } + } + } + } + + return modes; +} + static int add_detailed_modes(struct drm_connector *connector, struct detailed_timing *timing, struct edid *edid, u32 quirks, int preferred) @@ -941,6 +986,9 @@ static int add_detailed_modes(struct drm_connector *connector, } } break; + case EDID_DETAIL_CVT_3BYTE: + modes += drm_cvt_modes(connector, timing); + break; default: break; } diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 9087557fd83..d33c3e03860 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -106,6 +106,10 @@ struct detailed_data_color_point { u8 wpindex2[3]; } __attribute__((packed)); +struct cvt_timing { + u8 code[3]; +} __attribute__((packed)); + struct detailed_non_pixel { u8 pad1; u8 type; /* ff=serial, fe=string, fd=monitor range, fc=monitor name @@ -117,6 +121,7 @@ struct detailed_non_pixel { struct detailed_data_monitor_range range; struct detailed_data_wpindex color; struct std_timing timings[5]; + struct cvt_timing cvt[4]; } data; } __attribute__((packed)); -- cgit v1.2.3-70-g09d2 From 862302ffe422378a5213f558fc5cdf62c37050a9 Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Wed, 2 Dec 2009 18:15:25 +0000 Subject: drm: Add support for drm master_[set|drop] callbacks. The vmwgfx driver has a per master rw lock around TTM, to guarantee mutual exclusion when needed. This is typically when all evictable buffers are evicted due to 1) vt switch 2) master switch 3) suspend / resume. In the multi-master case, on master switch the new master takes the previously active master lock in write mode, and then evicts all buffers. Any clients to previous masters will then block on that lock when trying to validate a buffer. fbdev also acts as a virtual master wrt this. Signed-off-by: Thomas Hellstrom Signed-off-by: Jakob Bornecrantz Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_fops.c | 14 ++++++++++++++ drivers/gpu/drm/drm_stub.c | 11 +++++++++++ include/drm/drmP.h | 9 +++++++++ 3 files changed, 34 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 8ac7fbf6b2b..08d14df3bb4 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -300,6 +300,18 @@ static int drm_open_helper(struct inode *inode, struct file *filp, goto out_free; } } + mutex_lock(&dev->struct_mutex); + if (dev->driver->master_set) { + ret = dev->driver->master_set(dev, priv, true); + if (ret) { + /* drop both references if this fails */ + drm_master_put(&priv->minor->master); + drm_master_put(&priv->master); + mutex_unlock(&dev->struct_mutex); + goto out_free; + } + } + mutex_unlock(&dev->struct_mutex); } else { /* get a reference to the master */ priv->master = drm_master_get(priv->minor->master); @@ -533,6 +545,8 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master == file_priv->master) { /* drop the reference held my the minor */ + if (dev->driver->master_drop) + dev->driver->master_drop(dev, file_priv, true); drm_master_put(&file_priv->minor->master); } } diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c index adb864dfef3..2c1b52847e9 100644 --- a/drivers/gpu/drm/drm_stub.c +++ b/drivers/gpu/drm/drm_stub.c @@ -174,6 +174,8 @@ void drm_master_put(struct drm_master **master) int drm_setmaster_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { + int ret = 0; + if (file_priv->is_master) return 0; @@ -188,6 +190,13 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data, mutex_lock(&dev->struct_mutex); file_priv->minor->master = drm_master_get(file_priv->master); file_priv->is_master = 1; + if (dev->driver->master_set) { + ret = dev->driver->master_set(dev, file_priv, false); + if (unlikely(ret != 0)) { + file_priv->is_master = 0; + drm_master_put(&file_priv->minor->master); + } + } mutex_unlock(&dev->struct_mutex); } @@ -204,6 +213,8 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data, return -EINVAL; mutex_lock(&dev->struct_mutex); + if (dev->driver->master_drop) + dev->driver->master_drop(dev, file_priv, false); drm_master_put(&file_priv->minor->master); file_priv->is_master = 0; mutex_unlock(&dev->struct_mutex); diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 1b72a526ba6..770772c014a 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -774,6 +774,15 @@ struct drm_driver { /* Master routines */ int (*master_create)(struct drm_device *dev, struct drm_master *master); void (*master_destroy)(struct drm_device *dev, struct drm_master *master); + /** + * master_set is called whenever the minor master is set. + * master_drop is called whenever the minor master is dropped. + */ + + int (*master_set)(struct drm_device *dev, struct drm_file *file_priv, + bool from_open); + void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv, + bool from_release); int (*proc_init)(struct drm_minor *minor); void (*proc_cleanup)(struct drm_minor *minor); -- cgit v1.2.3-70-g09d2 From 1a95916f5465ad6c91398f17924949db7e0b5c36 Mon Sep 17 00:00:00 2001 From: Kristian Høgsberg Date: Wed, 2 Dec 2009 12:13:48 -0500 Subject: drm: Add compatibility #ifdefs for *BSD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This let's use use the linux drm headers as the canonical source for libdrm on all platforms. Signed-off-by: Kristian Høgsberg Signed-off-by: Dave Airlie --- include/drm/drm.h | 29 +++++++++++++++++++---------- include/drm/drmP.h | 3 +++ include/drm/drm_mode.h | 3 --- include/drm/i915_drm.h | 4 ++-- include/drm/mga_drm.h | 2 +- include/drm/radeon_drm.h | 2 +- include/drm/via_drm.h | 2 +- 7 files changed, 27 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/include/drm/drm.h b/include/drm/drm.h index 3919a4f792a..0114ac94f96 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -36,17 +36,27 @@ #ifndef _DRM_H_ #define _DRM_H_ +#if defined(__linux__) + #include -#include /* For _IO* macros */ -#define DRM_IOCTL_NR(n) _IOC_NR(n) -#define DRM_IOC_VOID _IOC_NONE -#define DRM_IOC_READ _IOC_READ -#define DRM_IOC_WRITE _IOC_WRITE -#define DRM_IOC_READWRITE _IOC_READ|_IOC_WRITE -#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size) +#include +typedef unsigned int drm_handle_t; + +#else /* One of the BSDs */ -#define DRM_MAJOR 226 -#define DRM_MAX_MINOR 15 +#include +#include +typedef int8_t __s8; +typedef uint8_t __u8; +typedef int16_t __s16; +typedef uint16_t __u16; +typedef int32_t __s32; +typedef uint32_t __u32; +typedef int64_t __s64; +typedef uint64_t __u64; +typedef unsigned long drm_handle_t; + +#endif #define DRM_NAME "drm" /**< Name in kernel, /dev, and /proc */ #define DRM_MIN_ORDER 5 /**< At least 2^5 bytes = 32 bytes */ @@ -59,7 +69,6 @@ #define _DRM_LOCK_IS_CONT(lock) ((lock) & _DRM_LOCK_CONT) #define _DRM_LOCKING_CONTEXT(lock) ((lock) & ~(_DRM_LOCK_HELD|_DRM_LOCK_CONT)) -typedef unsigned int drm_handle_t; typedef unsigned int drm_context_t; typedef unsigned int drm_drawable_t; typedef unsigned int drm_magic_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 770772c014a..db56a6add5d 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -289,6 +289,9 @@ typedef int drm_ioctl_t(struct drm_device *dev, void *data, typedef int drm_ioctl_compat_t(struct file *filp, unsigned int cmd, unsigned long arg); +#define DRM_IOCTL_NR(n) _IOC_NR(n) +#define DRM_MAJOR 226 + #define DRM_AUTH 0x1 #define DRM_MASTER 0x2 #define DRM_ROOT_ONLY 0x4 diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index 68ddc6175ae..09ca6adf4dd 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -27,9 +27,6 @@ #ifndef _DRM_MODE_H #define _DRM_MODE_H -#include -#include - #define DRM_DISPLAY_INFO_LEN 32 #define DRM_CONNECTOR_NAME_LEN 32 #define DRM_DISPLAY_MODE_LEN 32 diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h index 7e0cb1da92e..a04c3ab1d72 100644 --- a/include/drm/i915_drm.h +++ b/include/drm/i915_drm.h @@ -27,11 +27,11 @@ #ifndef _I915_DRM_H_ #define _I915_DRM_H_ +#include "drm.h" + /* Please note that modifications to all structs defined here are * subject to backwards-compatibility constraints. */ -#include -#include "drm.h" /* Each region is a minimum of 16k, and there are at most 255 of them. */ diff --git a/include/drm/mga_drm.h b/include/drm/mga_drm.h index 325fd6fb4a4..3ffbc4798af 100644 --- a/include/drm/mga_drm.h +++ b/include/drm/mga_drm.h @@ -35,7 +35,7 @@ #ifndef __MGA_DRM_H__ #define __MGA_DRM_H__ -#include +#include "drm.h" /* WARNING: If you change any of these defines, make sure to change the * defines in the Xserver file (mga_sarea.h) diff --git a/include/drm/radeon_drm.h b/include/drm/radeon_drm.h index 3b9932ab175..39537f3cf98 100644 --- a/include/drm/radeon_drm.h +++ b/include/drm/radeon_drm.h @@ -33,7 +33,7 @@ #ifndef __RADEON_DRM_H__ #define __RADEON_DRM_H__ -#include +#include "drm.h" /* WARNING: If you change any of these defines, make sure to change the * defines in the X server file (radeon_sarea.h) diff --git a/include/drm/via_drm.h b/include/drm/via_drm.h index 170786e5c2f..fd11a5bd892 100644 --- a/include/drm/via_drm.h +++ b/include/drm/via_drm.h @@ -24,7 +24,7 @@ #ifndef _VIA_DRM_H_ #define _VIA_DRM_H_ -#include +#include "drm.h" /* WARNING: These defines must be the same as what the Xserver uses. * if you change them, you must change the defines in the Xserver. -- cgit v1.2.3-70-g09d2 From c3a73ba13bac7fd96030f39202b2d37fb19c46a6 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Thu, 19 Nov 2009 16:29:45 +0000 Subject: drm/ttm: Fix build failure due to missing struct page drm/ttm fails to build on MIPS because "struct page" is not known: | In file included from drivers/gpu/drm/ttm/ttm_memory.c:28: | include/drm/ttm/ttm_memory.h:154: warning: 'struct page' declared inside parameter list | include/drm/ttm/ttm_memory.h:154: warning: its scope is only this definition or declaration, which is probably not what you want | include/drm/ttm/ttm_memory.h:156: warning: 'struct page' declared inside parameter list | drivers/gpu/drm/ttm/ttm_memory.c:540: error: conflicting types for 'ttm_mem_global_alloc_page' | include/drm/ttm/ttm_memory.h:154: error: previous declaration of 'ttm_mem_global_alloc_page' was here | drivers/gpu/drm/ttm/ttm_memory.c:561: error: conflicting types for 'ttm_mem_global_free_page' | include/drm/ttm/ttm_memory.h:156: error: previous declaration of 'ttm_mem_global_free_page' was here Signed-off-by: Martin Michlmayr Cc: stable@kernel.org Acked-by: Thomas Hellstrom Signed-off-by: Dave Airlie --- include/drm/ttm/ttm_memory.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/drm/ttm/ttm_memory.h b/include/drm/ttm/ttm_memory.h index 6983a7cf4da..b199170b3c2 100644 --- a/include/drm/ttm/ttm_memory.h +++ b/include/drm/ttm/ttm_memory.h @@ -33,6 +33,7 @@ #include #include #include +#include /** * struct ttm_mem_shrink - callback to shrink TTM memory usage. -- cgit v1.2.3-70-g09d2 From 884840aa3ce3214259e69557be5b4ce0d781ffa4 Mon Sep 17 00:00:00 2001 From: Jakob Bornecrantz Date: Thu, 3 Dec 2009 23:25:47 +0000 Subject: drm: Add dirty ioctl and property This commit adds a ioctl and property to allow userspace to notify the kernel that a framebuffer has changed. Instead of snooping the command stream this allows finer grained tracking of which areas have changed. The primary user for this functionality is virtual hardware like the vmware svga device, but also Xen hardware likes to be notify. There is also real hardware like DisplayLink and DisplayPort that might take advantage of this ioctl. Signed-off-by: Jakob Bornecrantz Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_crtc.c | 104 +++++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_drv.c | 1 + include/drm/drm.h | 1 + include/drm/drm_crtc.h | 19 +++++++++ include/drm/drm_mode.h | 44 +++++++++++++++++++ 5 files changed, 169 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 32756e67dd5..4fe321dc900 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -125,6 +125,15 @@ static struct drm_prop_enum_list drm_tv_subconnector_enum_list[] = DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name, drm_tv_subconnector_enum_list) +static struct drm_prop_enum_list drm_dirty_info_enum_list[] = { + { DRM_MODE_DIRTY_OFF, "Off" }, + { DRM_MODE_DIRTY_ON, "On" }, + { DRM_MODE_DIRTY_ANNOTATE, "Annotate" }, +}; + +DRM_ENUM_NAME_FN(drm_get_dirty_info_name, + drm_dirty_info_enum_list) + struct drm_conn_prop_enum_list { int type; char *name; @@ -801,6 +810,36 @@ int drm_mode_create_dithering_property(struct drm_device *dev) } EXPORT_SYMBOL(drm_mode_create_dithering_property); +/** + * drm_mode_create_dirty_property - create dirty property + * @dev: DRM device + * + * Called by a driver the first time it's needed, must be attached to desired + * connectors. + */ +int drm_mode_create_dirty_info_property(struct drm_device *dev) +{ + struct drm_property *dirty_info; + int i; + + if (dev->mode_config.dirty_info_property) + return 0; + + dirty_info = + drm_property_create(dev, DRM_MODE_PROP_ENUM | + DRM_MODE_PROP_IMMUTABLE, + "dirty", + ARRAY_SIZE(drm_dirty_info_enum_list)); + for (i = 0; i < ARRAY_SIZE(drm_dirty_info_enum_list); i++) + drm_property_add_enum(dirty_info, i, + drm_dirty_info_enum_list[i].type, + drm_dirty_info_enum_list[i].name); + dev->mode_config.dirty_info_property = dirty_info; + + return 0; +} +EXPORT_SYMBOL(drm_mode_create_dirty_info_property); + /** * drm_mode_config_init - initialize DRM mode_configuration structure * @dev: DRM device @@ -1753,6 +1792,71 @@ out: return ret; } +int drm_mode_dirtyfb_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv) +{ + struct drm_clip_rect __user *clips_ptr; + struct drm_clip_rect *clips = NULL; + struct drm_mode_fb_dirty_cmd *r = data; + struct drm_mode_object *obj; + struct drm_framebuffer *fb; + unsigned flags; + int num_clips; + int ret = 0; + + mutex_lock(&dev->mode_config.mutex); + obj = drm_mode_object_find(dev, r->fb_id, DRM_MODE_OBJECT_FB); + if (!obj) { + DRM_ERROR("invalid framebuffer id\n"); + ret = -EINVAL; + goto out_err1; + } + fb = obj_to_fb(obj); + + num_clips = r->num_clips; + clips_ptr = (struct drm_clip_rect *)(unsigned long)r->clips_ptr; + + if (!num_clips != !clips_ptr) { + ret = -EINVAL; + goto out_err1; + } + + flags = DRM_MODE_FB_DIRTY_FLAGS & r->flags; + + /* If userspace annotates copy, clips must come in pairs */ + if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY && (num_clips % 2)) { + ret = -EINVAL; + goto out_err1; + } + + if (num_clips && clips_ptr) { + clips = kzalloc(num_clips * sizeof(*clips), GFP_KERNEL); + if (!clips) { + ret = -ENOMEM; + goto out_err1; + } + + ret = copy_from_user(clips, clips_ptr, + num_clips * sizeof(*clips)); + if (ret) + goto out_err2; + } + + if (fb->funcs->dirty) { + ret = fb->funcs->dirty(fb, flags, r->color, clips, num_clips); + } else { + ret = -ENOSYS; + goto out_err2; + } + +out_err2: + kfree(clips); +out_err1: + mutex_unlock(&dev->mode_config.mutex); + return ret; +} + + /** * drm_fb_release - remove and free the FBs on this file * @filp: file * from the ioctl diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index bfaf59b02bd..ff2f1042cb4 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -146,6 +146,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW) }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/include/drm/drm.h b/include/drm/drm.h index 0114ac94f96..43a35b092f0 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -697,6 +697,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) #define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOWR(0xB0, struct drm_mode_crtc_page_flip) +#define DRM_IOCTL_MODE_DIRTYFB DRM_IOWR(0xB1, struct drm_mode_fb_dirty_cmd) /** * Device specific ioctls should only be in their respective headers diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 938f327a2a3..219f075d273 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -242,6 +242,21 @@ struct drm_framebuffer_funcs { int (*create_handle)(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned int *handle); + /** + * Optinal callback for the dirty fb ioctl. + * + * Userspace can notify the driver via this callback + * that a area of the framebuffer has changed and should + * be flushed to the display hardware. + * + * See documentation in drm_mode.h for the struct + * drm_mode_fb_dirty_cmd for more information as all + * the semantics and arguments have a one to one mapping + * on this function. + */ + int (*dirty)(struct drm_framebuffer *framebuffer, unsigned flags, + unsigned color, struct drm_clip_rect *clips, + unsigned num_clips); }; struct drm_framebuffer { @@ -610,6 +625,7 @@ struct drm_mode_config { /* Optional properties */ struct drm_property *scaling_mode_property; struct drm_property *dithering_mode_property; + struct drm_property *dirty_info_property; }; #define obj_to_crtc(x) container_of(x, struct drm_crtc, base) @@ -718,6 +734,7 @@ extern int drm_mode_create_tv_properties(struct drm_device *dev, int num_formats char *formats[]); extern int drm_mode_create_scaling_mode_property(struct drm_device *dev); extern int drm_mode_create_dithering_property(struct drm_device *dev); +extern int drm_mode_create_dirty_info_property(struct drm_device *dev); extern char *drm_get_encoder_name(struct drm_encoder *encoder); extern int drm_mode_connector_attach_encoder(struct drm_connector *connector, @@ -745,6 +762,8 @@ extern int drm_mode_rmfb(struct drm_device *dev, void *data, struct drm_file *file_priv); extern int drm_mode_getfb(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern int drm_mode_dirtyfb_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv); extern int drm_mode_addmode_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern int drm_mode_rmmode_ioctl(struct drm_device *dev, diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index 09ca6adf4dd..43009bc2e75 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -75,6 +75,11 @@ #define DRM_MODE_DITHERING_OFF 0 #define DRM_MODE_DITHERING_ON 1 +/* Dirty info options */ +#define DRM_MODE_DIRTY_OFF 0 +#define DRM_MODE_DIRTY_ON 1 +#define DRM_MODE_DIRTY_ANNOTATE 2 + struct drm_mode_modeinfo { __u32 clock; __u16 hdisplay, hsync_start, hsync_end, htotal, hskew; @@ -222,6 +227,45 @@ struct drm_mode_fb_cmd { __u32 handle; }; +#define DRM_MODE_FB_DIRTY_ANNOTATE_COPY 0x01 +#define DRM_MODE_FB_DIRTY_ANNOTATE_FILL 0x02 +#define DRM_MODE_FB_DIRTY_FLAGS 0x03 + +/* + * Mark a region of a framebuffer as dirty. + * + * Some hardware does not automatically update display contents + * as a hardware or software draw to a framebuffer. This ioctl + * allows userspace to tell the kernel and the hardware what + * regions of the framebuffer have changed. + * + * The kernel or hardware is free to update more then just the + * region specified by the clip rects. The kernel or hardware + * may also delay and/or coalesce several calls to dirty into a + * single update. + * + * Userspace may annotate the updates, the annotates are a + * promise made by the caller that the change is either a copy + * of pixels or a fill of a single color in the region specified. + * + * If the DRM_MODE_FB_DIRTY_ANNOTATE_COPY flag is given then + * the number of updated regions are half of num_clips given, + * where the clip rects are paired in src and dst. The width and + * height of each one of the pairs must match. + * + * If the DRM_MODE_FB_DIRTY_ANNOTATE_FILL flag is given the caller + * promises that the region specified of the clip rects is filled + * completely with a single color as given in the color argument. + */ + +struct drm_mode_fb_dirty_cmd { + __u32 fb_id; + __u32 flags; + __u32 color; + __u32 num_clips; + __u64 clips_ptr; +}; + struct drm_mode_mode_cmd { __u32 connector_id; struct drm_mode_modeinfo mode; -- cgit v1.2.3-70-g09d2 From 88071539a3f5195f9e9dae38a3e35b3ce4b9f9fc Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Sun, 6 Dec 2009 21:46:24 +0100 Subject: drm/ttm: Add user-space objects. Add objects needed for user-space to maintain reference counts on ttm objects. This is used by the vmwgfx driver which allows user-space to maintain map-counts on dma buffers, lock-counts on the ttm lock and ref-counts on gpu surfaces, gpu contexts and dma buffer. Signed-off-by: Thomas Hellstrom Signed-off-by: Dave Airlie --- drivers/gpu/drm/ttm/Makefile | 3 +- drivers/gpu/drm/ttm/ttm_object.c | 452 +++++++++++++++++++++++++++++++++++++++ include/drm/ttm/ttm_object.h | 267 +++++++++++++++++++++++ 3 files changed, 721 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/ttm/ttm_object.c create mode 100644 include/drm/ttm/ttm_object.h (limited to 'include') diff --git a/drivers/gpu/drm/ttm/Makefile b/drivers/gpu/drm/ttm/Makefile index b0a9de7a57c..a9cc9f89536 100644 --- a/drivers/gpu/drm/ttm/Makefile +++ b/drivers/gpu/drm/ttm/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm ttm-y := ttm_agp_backend.o ttm_memory.o ttm_tt.o ttm_bo.o \ - ttm_bo_util.o ttm_bo_vm.o ttm_module.o ttm_global.o + ttm_bo_util.o ttm_bo_vm.o ttm_module.o ttm_global.o \ + ttm_object.o obj-$(CONFIG_DRM_TTM) += ttm.o diff --git a/drivers/gpu/drm/ttm/ttm_object.c b/drivers/gpu/drm/ttm/ttm_object.c new file mode 100644 index 00000000000..1099abac824 --- /dev/null +++ b/drivers/gpu/drm/ttm/ttm_object.c @@ -0,0 +1,452 @@ +/************************************************************************** + * + * Copyright (c) 2009 VMware, Inc., Palo Alto, CA., USA + * All Rights Reserved. + * + * 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS 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: Thomas Hellstrom + */ +/** @file ttm_ref_object.c + * + * Base- and reference object implementation for the various + * ttm objects. Implements reference counting, minimal security checks + * and release on file close. + */ + +/** + * struct ttm_object_file + * + * @tdev: Pointer to the ttm_object_device. + * + * @lock: Lock that protects the ref_list list and the + * ref_hash hash tables. + * + * @ref_list: List of ttm_ref_objects to be destroyed at + * file release. + * + * @ref_hash: Hash tables of ref objects, one per ttm_ref_type, + * for fast lookup of ref objects given a base object. + */ + +#include "ttm/ttm_object.h" +#include "ttm/ttm_module.h" +#include +#include +#include +#include +#include + +struct ttm_object_file { + struct ttm_object_device *tdev; + rwlock_t lock; + struct list_head ref_list; + struct drm_open_hash ref_hash[TTM_REF_NUM]; + struct kref refcount; +}; + +/** + * struct ttm_object_device + * + * @object_lock: lock that protects the object_hash hash table. + * + * @object_hash: hash table for fast lookup of object global names. + * + * @object_count: Per device object count. + * + * This is the per-device data structure needed for ttm object management. + */ + +struct ttm_object_device { + rwlock_t object_lock; + struct drm_open_hash object_hash; + atomic_t object_count; + struct ttm_mem_global *mem_glob; +}; + +/** + * struct ttm_ref_object + * + * @hash: Hash entry for the per-file object reference hash. + * + * @head: List entry for the per-file list of ref-objects. + * + * @kref: Ref count. + * + * @obj: Base object this ref object is referencing. + * + * @ref_type: Type of ref object. + * + * This is similar to an idr object, but it also has a hash table entry + * that allows lookup with a pointer to the referenced object as a key. In + * that way, one can easily detect whether a base object is referenced by + * a particular ttm_object_file. It also carries a ref count to avoid creating + * multiple ref objects if a ttm_object_file references the same base + * object more than once. + */ + +struct ttm_ref_object { + struct drm_hash_item hash; + struct list_head head; + struct kref kref; + struct ttm_base_object *obj; + enum ttm_ref_type ref_type; + struct ttm_object_file *tfile; +}; + +static inline struct ttm_object_file * +ttm_object_file_ref(struct ttm_object_file *tfile) +{ + kref_get(&tfile->refcount); + return tfile; +} + +static void ttm_object_file_destroy(struct kref *kref) +{ + struct ttm_object_file *tfile = + container_of(kref, struct ttm_object_file, refcount); + + kfree(tfile); +} + + +static inline void ttm_object_file_unref(struct ttm_object_file **p_tfile) +{ + struct ttm_object_file *tfile = *p_tfile; + + *p_tfile = NULL; + kref_put(&tfile->refcount, ttm_object_file_destroy); +} + + +int ttm_base_object_init(struct ttm_object_file *tfile, + struct ttm_base_object *base, + bool shareable, + enum ttm_object_type object_type, + void (*refcount_release) (struct ttm_base_object **), + void (*ref_obj_release) (struct ttm_base_object *, + enum ttm_ref_type ref_type)) +{ + struct ttm_object_device *tdev = tfile->tdev; + int ret; + + base->shareable = shareable; + base->tfile = ttm_object_file_ref(tfile); + base->refcount_release = refcount_release; + base->ref_obj_release = ref_obj_release; + base->object_type = object_type; + write_lock(&tdev->object_lock); + kref_init(&base->refcount); + ret = drm_ht_just_insert_please(&tdev->object_hash, + &base->hash, + (unsigned long)base, 31, 0, 0); + write_unlock(&tdev->object_lock); + if (unlikely(ret != 0)) + goto out_err0; + + ret = ttm_ref_object_add(tfile, base, TTM_REF_USAGE, NULL); + if (unlikely(ret != 0)) + goto out_err1; + + ttm_base_object_unref(&base); + + return 0; +out_err1: + (void)drm_ht_remove_item(&tdev->object_hash, &base->hash); +out_err0: + return ret; +} +EXPORT_SYMBOL(ttm_base_object_init); + +static void ttm_release_base(struct kref *kref) +{ + struct ttm_base_object *base = + container_of(kref, struct ttm_base_object, refcount); + struct ttm_object_device *tdev = base->tfile->tdev; + + (void)drm_ht_remove_item(&tdev->object_hash, &base->hash); + write_unlock(&tdev->object_lock); + if (base->refcount_release) { + ttm_object_file_unref(&base->tfile); + base->refcount_release(&base); + } + write_lock(&tdev->object_lock); +} + +void ttm_base_object_unref(struct ttm_base_object **p_base) +{ + struct ttm_base_object *base = *p_base; + struct ttm_object_device *tdev = base->tfile->tdev; + + *p_base = NULL; + + /* + * Need to take the lock here to avoid racing with + * users trying to look up the object. + */ + + write_lock(&tdev->object_lock); + (void)kref_put(&base->refcount, &ttm_release_base); + write_unlock(&tdev->object_lock); +} +EXPORT_SYMBOL(ttm_base_object_unref); + +struct ttm_base_object *ttm_base_object_lookup(struct ttm_object_file *tfile, + uint32_t key) +{ + struct ttm_object_device *tdev = tfile->tdev; + struct ttm_base_object *base; + struct drm_hash_item *hash; + int ret; + + read_lock(&tdev->object_lock); + ret = drm_ht_find_item(&tdev->object_hash, key, &hash); + + if (likely(ret == 0)) { + base = drm_hash_entry(hash, struct ttm_base_object, hash); + kref_get(&base->refcount); + } + read_unlock(&tdev->object_lock); + + if (unlikely(ret != 0)) + return NULL; + + if (tfile != base->tfile && !base->shareable) { + printk(KERN_ERR TTM_PFX + "Attempted access of non-shareable object.\n"); + ttm_base_object_unref(&base); + return NULL; + } + + return base; +} +EXPORT_SYMBOL(ttm_base_object_lookup); + +int ttm_ref_object_add(struct ttm_object_file *tfile, + struct ttm_base_object *base, + enum ttm_ref_type ref_type, bool *existed) +{ + struct drm_open_hash *ht = &tfile->ref_hash[ref_type]; + struct ttm_ref_object *ref; + struct drm_hash_item *hash; + struct ttm_mem_global *mem_glob = tfile->tdev->mem_glob; + int ret = -EINVAL; + + if (existed != NULL) + *existed = true; + + while (ret == -EINVAL) { + read_lock(&tfile->lock); + ret = drm_ht_find_item(ht, base->hash.key, &hash); + + if (ret == 0) { + ref = drm_hash_entry(hash, struct ttm_ref_object, hash); + kref_get(&ref->kref); + read_unlock(&tfile->lock); + break; + } + + read_unlock(&tfile->lock); + ret = ttm_mem_global_alloc(mem_glob, sizeof(*ref), + false, false); + if (unlikely(ret != 0)) + return ret; + ref = kmalloc(sizeof(*ref), GFP_KERNEL); + if (unlikely(ref == NULL)) { + ttm_mem_global_free(mem_glob, sizeof(*ref)); + return -ENOMEM; + } + + ref->hash.key = base->hash.key; + ref->obj = base; + ref->tfile = tfile; + ref->ref_type = ref_type; + kref_init(&ref->kref); + + write_lock(&tfile->lock); + ret = drm_ht_insert_item(ht, &ref->hash); + + if (likely(ret == 0)) { + list_add_tail(&ref->head, &tfile->ref_list); + kref_get(&base->refcount); + write_unlock(&tfile->lock); + if (existed != NULL) + *existed = false; + break; + } + + write_unlock(&tfile->lock); + BUG_ON(ret != -EINVAL); + + ttm_mem_global_free(mem_glob, sizeof(*ref)); + kfree(ref); + } + + return ret; +} +EXPORT_SYMBOL(ttm_ref_object_add); + +static void ttm_ref_object_release(struct kref *kref) +{ + struct ttm_ref_object *ref = + container_of(kref, struct ttm_ref_object, kref); + struct ttm_base_object *base = ref->obj; + struct ttm_object_file *tfile = ref->tfile; + struct drm_open_hash *ht; + struct ttm_mem_global *mem_glob = tfile->tdev->mem_glob; + + ht = &tfile->ref_hash[ref->ref_type]; + (void)drm_ht_remove_item(ht, &ref->hash); + list_del(&ref->head); + write_unlock(&tfile->lock); + + if (ref->ref_type != TTM_REF_USAGE && base->ref_obj_release) + base->ref_obj_release(base, ref->ref_type); + + ttm_base_object_unref(&ref->obj); + ttm_mem_global_free(mem_glob, sizeof(*ref)); + kfree(ref); + write_lock(&tfile->lock); +} + +int ttm_ref_object_base_unref(struct ttm_object_file *tfile, + unsigned long key, enum ttm_ref_type ref_type) +{ + struct drm_open_hash *ht = &tfile->ref_hash[ref_type]; + struct ttm_ref_object *ref; + struct drm_hash_item *hash; + int ret; + + write_lock(&tfile->lock); + ret = drm_ht_find_item(ht, key, &hash); + if (unlikely(ret != 0)) { + write_unlock(&tfile->lock); + return -EINVAL; + } + ref = drm_hash_entry(hash, struct ttm_ref_object, hash); + kref_put(&ref->kref, ttm_ref_object_release); + write_unlock(&tfile->lock); + return 0; +} +EXPORT_SYMBOL(ttm_ref_object_base_unref); + +void ttm_object_file_release(struct ttm_object_file **p_tfile) +{ + struct ttm_ref_object *ref; + struct list_head *list; + unsigned int i; + struct ttm_object_file *tfile = *p_tfile; + + *p_tfile = NULL; + write_lock(&tfile->lock); + + /* + * Since we release the lock within the loop, we have to + * restart it from the beginning each time. + */ + + while (!list_empty(&tfile->ref_list)) { + list = tfile->ref_list.next; + ref = list_entry(list, struct ttm_ref_object, head); + ttm_ref_object_release(&ref->kref); + } + + for (i = 0; i < TTM_REF_NUM; ++i) + drm_ht_remove(&tfile->ref_hash[i]); + + write_unlock(&tfile->lock); + ttm_object_file_unref(&tfile); +} +EXPORT_SYMBOL(ttm_object_file_release); + +struct ttm_object_file *ttm_object_file_init(struct ttm_object_device *tdev, + unsigned int hash_order) +{ + struct ttm_object_file *tfile = kmalloc(sizeof(*tfile), GFP_KERNEL); + unsigned int i; + unsigned int j = 0; + int ret; + + if (unlikely(tfile == NULL)) + return NULL; + + rwlock_init(&tfile->lock); + tfile->tdev = tdev; + kref_init(&tfile->refcount); + INIT_LIST_HEAD(&tfile->ref_list); + + for (i = 0; i < TTM_REF_NUM; ++i) { + ret = drm_ht_create(&tfile->ref_hash[i], hash_order); + if (ret) { + j = i; + goto out_err; + } + } + + return tfile; +out_err: + for (i = 0; i < j; ++i) + drm_ht_remove(&tfile->ref_hash[i]); + + kfree(tfile); + + return NULL; +} +EXPORT_SYMBOL(ttm_object_file_init); + +struct ttm_object_device *ttm_object_device_init(struct ttm_mem_global + *mem_glob, + unsigned int hash_order) +{ + struct ttm_object_device *tdev = kmalloc(sizeof(*tdev), GFP_KERNEL); + int ret; + + if (unlikely(tdev == NULL)) + return NULL; + + tdev->mem_glob = mem_glob; + rwlock_init(&tdev->object_lock); + atomic_set(&tdev->object_count, 0); + ret = drm_ht_create(&tdev->object_hash, hash_order); + + if (likely(ret == 0)) + return tdev; + + kfree(tdev); + return NULL; +} +EXPORT_SYMBOL(ttm_object_device_init); + +void ttm_object_device_release(struct ttm_object_device **p_tdev) +{ + struct ttm_object_device *tdev = *p_tdev; + + *p_tdev = NULL; + + write_lock(&tdev->object_lock); + drm_ht_remove(&tdev->object_hash); + write_unlock(&tdev->object_lock); + + kfree(tdev); +} +EXPORT_SYMBOL(ttm_object_device_release); diff --git a/include/drm/ttm/ttm_object.h b/include/drm/ttm/ttm_object.h new file mode 100644 index 00000000000..703ca4db0a2 --- /dev/null +++ b/include/drm/ttm/ttm_object.h @@ -0,0 +1,267 @@ +/************************************************************************** + * + * Copyright (c) 2006-2009 VMware, Inc., Palo Alto, CA., USA + * All Rights Reserved. + * + * 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS 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: Thomas Hellstrom + */ +/** @file ttm_object.h + * + * Base- and reference object implementation for the various + * ttm objects. Implements reference counting, minimal security checks + * and release on file close. + */ + +#ifndef _TTM_OBJECT_H_ +#define _TTM_OBJECT_H_ + +#include +#include "drm_hashtab.h" +#include +#include + +/** + * enum ttm_ref_type + * + * Describes what type of reference a ref object holds. + * + * TTM_REF_USAGE is a simple refcount on a base object. + * + * TTM_REF_SYNCCPU_READ is a SYNCCPU_READ reference on a + * buffer object. + * + * TTM_REF_SYNCCPU_WRITE is a SYNCCPU_WRITE reference on a + * buffer object. + * + */ + +enum ttm_ref_type { + TTM_REF_USAGE, + TTM_REF_SYNCCPU_READ, + TTM_REF_SYNCCPU_WRITE, + TTM_REF_NUM +}; + +/** + * enum ttm_object_type + * + * One entry per ttm object type. + * Device-specific types should use the + * ttm_driver_typex types. + */ + +enum ttm_object_type { + ttm_fence_type, + ttm_buffer_type, + ttm_lock_type, + ttm_driver_type0 = 256, + ttm_driver_type1 +}; + +struct ttm_object_file; +struct ttm_object_device; + +/** + * struct ttm_base_object + * + * @hash: hash entry for the per-device object hash. + * @type: derived type this object is base class for. + * @shareable: Other ttm_object_files can access this object. + * + * @tfile: Pointer to ttm_object_file of the creator. + * NULL if the object was not created by a user request. + * (kernel object). + * + * @refcount: Number of references to this object, not + * including the hash entry. A reference to a base object can + * only be held by a ref object. + * + * @refcount_release: A function to be called when there are + * no more references to this object. This function should + * destroy the object (or make sure destruction eventually happens), + * and when it is called, the object has + * already been taken out of the per-device hash. The parameter + * "base" should be set to NULL by the function. + * + * @ref_obj_release: A function to be called when a reference object + * with another ttm_ref_type than TTM_REF_USAGE is deleted. + * this function may, for example, release a lock held by a user-space + * process. + * + * This struct is intended to be used as a base struct for objects that + * are visible to user-space. It provides a global name, race-safe + * access and refcounting, minimal access contol and hooks for unref actions. + */ + +struct ttm_base_object { + struct drm_hash_item hash; + enum ttm_object_type object_type; + bool shareable; + struct ttm_object_file *tfile; + struct kref refcount; + void (*refcount_release) (struct ttm_base_object **base); + void (*ref_obj_release) (struct ttm_base_object *base, + enum ttm_ref_type ref_type); +}; + +/** + * ttm_base_object_init + * + * @tfile: Pointer to a struct ttm_object_file. + * @base: The struct ttm_base_object to initialize. + * @shareable: This object is shareable with other applcations. + * (different @tfile pointers.) + * @type: The object type. + * @refcount_release: See the struct ttm_base_object description. + * @ref_obj_release: See the struct ttm_base_object description. + * + * Initializes a struct ttm_base_object. + */ + +extern int ttm_base_object_init(struct ttm_object_file *tfile, + struct ttm_base_object *base, + bool shareable, + enum ttm_object_type type, + void (*refcount_release) (struct ttm_base_object + **), + void (*ref_obj_release) (struct ttm_base_object + *, + enum ttm_ref_type + ref_type)); + +/** + * ttm_base_object_lookup + * + * @tfile: Pointer to a struct ttm_object_file. + * @key: Hash key + * + * Looks up a struct ttm_base_object with the key @key. + * Also verifies that the object is visible to the application, by + * comparing the @tfile argument and checking the object shareable flag. + */ + +extern struct ttm_base_object *ttm_base_object_lookup(struct ttm_object_file + *tfile, uint32_t key); + +/** + * ttm_base_object_unref + * + * @p_base: Pointer to a pointer referncing a struct ttm_base_object. + * + * Decrements the base object refcount and clears the pointer pointed to by + * p_base. + */ + +extern void ttm_base_object_unref(struct ttm_base_object **p_base); + +/** + * ttm_ref_object_add. + * + * @tfile: A struct ttm_object_file representing the application owning the + * ref_object. + * @base: The base object to reference. + * @ref_type: The type of reference. + * @existed: Upon completion, indicates that an identical reference object + * already existed, and the refcount was upped on that object instead. + * + * Adding a ref object to a base object is basically like referencing the + * base object, but a user-space application holds the reference. When the + * file corresponding to @tfile is closed, all its reference objects are + * deleted. A reference object can have different types depending on what + * it's intended for. It can be refcounting to prevent object destruction, + * When user-space takes a lock, it can add a ref object to that lock to + * make sure the lock is released if the application dies. A ref object + * will hold a single reference on a base object. + */ +extern int ttm_ref_object_add(struct ttm_object_file *tfile, + struct ttm_base_object *base, + enum ttm_ref_type ref_type, bool *existed); +/** + * ttm_ref_object_base_unref + * + * @key: Key representing the base object. + * @ref_type: Ref type of the ref object to be dereferenced. + * + * Unreference a ref object with type @ref_type + * on the base object identified by @key. If there are no duplicate + * references, the ref object will be destroyed and the base object + * will be unreferenced. + */ +extern int ttm_ref_object_base_unref(struct ttm_object_file *tfile, + unsigned long key, + enum ttm_ref_type ref_type); + +/** + * ttm_object_file_init - initialize a struct ttm_object file + * + * @tdev: A struct ttm_object device this file is initialized on. + * @hash_order: Order of the hash table used to hold the reference objects. + * + * This is typically called by the file_ops::open function. + */ + +extern struct ttm_object_file *ttm_object_file_init(struct ttm_object_device + *tdev, + unsigned int hash_order); + +/** + * ttm_object_file_release - release data held by a ttm_object_file + * + * @p_tfile: Pointer to pointer to the ttm_object_file object to release. + * *p_tfile will be set to NULL by this function. + * + * Releases all data associated by a ttm_object_file. + * Typically called from file_ops::release. The caller must + * ensure that there are no concurrent users of tfile. + */ + +extern void ttm_object_file_release(struct ttm_object_file **p_tfile); + +/** + * ttm_object device init - initialize a struct ttm_object_device + * + * @hash_order: Order of hash table used to hash the base objects. + * + * This function is typically called on device initialization to prepare + * data structures needed for ttm base and ref objects. + */ + +extern struct ttm_object_device *ttm_object_device_init + (struct ttm_mem_global *mem_glob, unsigned int hash_order); + +/** + * ttm_object_device_release - release data held by a ttm_object_device + * + * @p_tdev: Pointer to pointer to the ttm_object_device object to release. + * *p_tdev will be set to NULL by this function. + * + * Releases all data associated by a ttm_object_device. + * Typically called from driver::unload before the destruction of the + * device private data structure. + */ + +extern void ttm_object_device_release(struct ttm_object_device **p_tdev); + +#endif -- cgit v1.2.3-70-g09d2 From 4aff1013f5e4ae08a24155c029a2c5e1a7929de6 Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Sun, 6 Dec 2009 21:46:25 +0100 Subject: drm/ttm: Add ttm lock functionality. This is intended to be used by ttm-aware drivers to 1) Block clients to inactive masters when they try to validate buffers for GPU use. 2) Optionally block clients to the current master when there is thrashing due to GPU memory shortage. Used by the vmwgfx driver. Signed-off-by: Thomas Hellstrom Signed-off-by: Dave Airlie --- drivers/gpu/drm/ttm/Makefile | 2 +- drivers/gpu/drm/ttm/ttm_lock.c | 311 +++++++++++++++++++++++++++++++++++++++++ include/drm/ttm/ttm_lock.h | 247 ++++++++++++++++++++++++++++++++ 3 files changed, 559 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/ttm/ttm_lock.c create mode 100644 include/drm/ttm/ttm_lock.h (limited to 'include') diff --git a/drivers/gpu/drm/ttm/Makefile b/drivers/gpu/drm/ttm/Makefile index a9cc9f89536..4473549bc5d 100644 --- a/drivers/gpu/drm/ttm/Makefile +++ b/drivers/gpu/drm/ttm/Makefile @@ -4,6 +4,6 @@ ccflags-y := -Iinclude/drm ttm-y := ttm_agp_backend.o ttm_memory.o ttm_tt.o ttm_bo.o \ ttm_bo_util.o ttm_bo_vm.o ttm_module.o ttm_global.o \ - ttm_object.o + ttm_object.o ttm_lock.o obj-$(CONFIG_DRM_TTM) += ttm.o diff --git a/drivers/gpu/drm/ttm/ttm_lock.c b/drivers/gpu/drm/ttm/ttm_lock.c new file mode 100644 index 00000000000..f619ebcaa4e --- /dev/null +++ b/drivers/gpu/drm/ttm/ttm_lock.c @@ -0,0 +1,311 @@ +/************************************************************************** + * + * Copyright (c) 2007-2009 VMware, Inc., Palo Alto, CA., USA + * All Rights Reserved. + * + * 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS 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: Thomas Hellstrom + */ + +#include "ttm/ttm_lock.h" +#include "ttm/ttm_module.h" +#include +#include +#include +#include +#include + +#define TTM_WRITE_LOCK_PENDING (1 << 0) +#define TTM_VT_LOCK_PENDING (1 << 1) +#define TTM_SUSPEND_LOCK_PENDING (1 << 2) +#define TTM_VT_LOCK (1 << 3) +#define TTM_SUSPEND_LOCK (1 << 4) + +void ttm_lock_init(struct ttm_lock *lock) +{ + spin_lock_init(&lock->lock); + init_waitqueue_head(&lock->queue); + lock->rw = 0; + lock->flags = 0; + lock->kill_takers = false; + lock->signal = SIGKILL; +} +EXPORT_SYMBOL(ttm_lock_init); + +void ttm_read_unlock(struct ttm_lock *lock) +{ + spin_lock(&lock->lock); + if (--lock->rw == 0) + wake_up_all(&lock->queue); + spin_unlock(&lock->lock); +} +EXPORT_SYMBOL(ttm_read_unlock); + +static bool __ttm_read_lock(struct ttm_lock *lock) +{ + bool locked = false; + + spin_lock(&lock->lock); + if (unlikely(lock->kill_takers)) { + send_sig(lock->signal, current, 0); + spin_unlock(&lock->lock); + return false; + } + if (lock->rw >= 0 && lock->flags == 0) { + ++lock->rw; + locked = true; + } + spin_unlock(&lock->lock); + return locked; +} + +int ttm_read_lock(struct ttm_lock *lock, bool interruptible) +{ + int ret = 0; + + if (interruptible) + ret = wait_event_interruptible(lock->queue, + __ttm_read_lock(lock)); + else + wait_event(lock->queue, __ttm_read_lock(lock)); + return ret; +} +EXPORT_SYMBOL(ttm_read_lock); + +static bool __ttm_read_trylock(struct ttm_lock *lock, bool *locked) +{ + bool block = true; + + *locked = false; + + spin_lock(&lock->lock); + if (unlikely(lock->kill_takers)) { + send_sig(lock->signal, current, 0); + spin_unlock(&lock->lock); + return false; + } + if (lock->rw >= 0 && lock->flags == 0) { + ++lock->rw; + block = false; + *locked = true; + } else if (lock->flags == 0) { + block = false; + } + spin_unlock(&lock->lock); + + return !block; +} + +int ttm_read_trylock(struct ttm_lock *lock, bool interruptible) +{ + int ret = 0; + bool locked; + + if (interruptible) + ret = wait_event_interruptible + (lock->queue, __ttm_read_trylock(lock, &locked)); + else + wait_event(lock->queue, __ttm_read_trylock(lock, &locked)); + + if (unlikely(ret != 0)) { + BUG_ON(locked); + return ret; + } + + return (locked) ? 0 : -EBUSY; +} + +void ttm_write_unlock(struct ttm_lock *lock) +{ + spin_lock(&lock->lock); + lock->rw = 0; + wake_up_all(&lock->queue); + spin_unlock(&lock->lock); +} +EXPORT_SYMBOL(ttm_write_unlock); + +static bool __ttm_write_lock(struct ttm_lock *lock) +{ + bool locked = false; + + spin_lock(&lock->lock); + if (unlikely(lock->kill_takers)) { + send_sig(lock->signal, current, 0); + spin_unlock(&lock->lock); + return false; + } + if (lock->rw == 0 && ((lock->flags & ~TTM_WRITE_LOCK_PENDING) == 0)) { + lock->rw = -1; + lock->flags &= ~TTM_WRITE_LOCK_PENDING; + locked = true; + } else { + lock->flags |= TTM_WRITE_LOCK_PENDING; + } + spin_unlock(&lock->lock); + return locked; +} + +int ttm_write_lock(struct ttm_lock *lock, bool interruptible) +{ + int ret = 0; + + if (interruptible) { + ret = wait_event_interruptible(lock->queue, + __ttm_write_lock(lock)); + if (unlikely(ret != 0)) { + spin_lock(&lock->lock); + lock->flags &= ~TTM_WRITE_LOCK_PENDING; + wake_up_all(&lock->queue); + spin_unlock(&lock->lock); + } + } else + wait_event(lock->queue, __ttm_read_lock(lock)); + + return ret; +} +EXPORT_SYMBOL(ttm_write_lock); + +void ttm_write_lock_downgrade(struct ttm_lock *lock) +{ + spin_lock(&lock->lock); + lock->rw = 1; + wake_up_all(&lock->queue); + spin_unlock(&lock->lock); +} + +static int __ttm_vt_unlock(struct ttm_lock *lock) +{ + int ret = 0; + + spin_lock(&lock->lock); + if (unlikely(!(lock->flags & TTM_VT_LOCK))) + ret = -EINVAL; + lock->flags &= ~TTM_VT_LOCK; + wake_up_all(&lock->queue); + spin_unlock(&lock->lock); + printk(KERN_INFO TTM_PFX "vt unlock.\n"); + + return ret; +} + +static void ttm_vt_lock_remove(struct ttm_base_object **p_base) +{ + struct ttm_base_object *base = *p_base; + struct ttm_lock *lock = container_of(base, struct ttm_lock, base); + int ret; + + *p_base = NULL; + ret = __ttm_vt_unlock(lock); + BUG_ON(ret != 0); +} + +static bool __ttm_vt_lock(struct ttm_lock *lock) +{ + bool locked = false; + + spin_lock(&lock->lock); + if (lock->rw == 0) { + lock->flags &= ~TTM_VT_LOCK_PENDING; + lock->flags |= TTM_VT_LOCK; + locked = true; + } else { + lock->flags |= TTM_VT_LOCK_PENDING; + } + spin_unlock(&lock->lock); + return locked; +} + +int ttm_vt_lock(struct ttm_lock *lock, + bool interruptible, + struct ttm_object_file *tfile) +{ + int ret = 0; + + if (interruptible) { + ret = wait_event_interruptible(lock->queue, + __ttm_vt_lock(lock)); + if (unlikely(ret != 0)) { + spin_lock(&lock->lock); + lock->flags &= ~TTM_VT_LOCK_PENDING; + wake_up_all(&lock->queue); + spin_unlock(&lock->lock); + return ret; + } + } else + wait_event(lock->queue, __ttm_vt_lock(lock)); + + /* + * Add a base-object, the destructor of which will + * make sure the lock is released if the client dies + * while holding it. + */ + + ret = ttm_base_object_init(tfile, &lock->base, false, + ttm_lock_type, &ttm_vt_lock_remove, NULL); + if (ret) + (void)__ttm_vt_unlock(lock); + else { + lock->vt_holder = tfile; + printk(KERN_INFO TTM_PFX "vt lock.\n"); + } + + return ret; +} +EXPORT_SYMBOL(ttm_vt_lock); + +int ttm_vt_unlock(struct ttm_lock *lock) +{ + return ttm_ref_object_base_unref(lock->vt_holder, + lock->base.hash.key, TTM_REF_USAGE); +} +EXPORT_SYMBOL(ttm_vt_unlock); + +void ttm_suspend_unlock(struct ttm_lock *lock) +{ + spin_lock(&lock->lock); + lock->flags &= ~TTM_SUSPEND_LOCK; + wake_up_all(&lock->queue); + spin_unlock(&lock->lock); +} + +static bool __ttm_suspend_lock(struct ttm_lock *lock) +{ + bool locked = false; + + spin_lock(&lock->lock); + if (lock->rw == 0) { + lock->flags &= ~TTM_SUSPEND_LOCK_PENDING; + lock->flags |= TTM_SUSPEND_LOCK; + locked = true; + } else { + lock->flags |= TTM_SUSPEND_LOCK_PENDING; + } + spin_unlock(&lock->lock); + return locked; +} + +void ttm_suspend_lock(struct ttm_lock *lock) +{ + wait_event(lock->queue, __ttm_suspend_lock(lock)); +} diff --git a/include/drm/ttm/ttm_lock.h b/include/drm/ttm/ttm_lock.h new file mode 100644 index 00000000000..81ba0b0b891 --- /dev/null +++ b/include/drm/ttm/ttm_lock.h @@ -0,0 +1,247 @@ +/************************************************************************** + * + * Copyright (c) 2007-2009 VMware, Inc., Palo Alto, CA., USA + * All Rights Reserved. + * + * 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS 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: Thomas Hellstrom + */ + +/** @file ttm_lock.h + * This file implements a simple replacement for the buffer manager use + * of the DRM heavyweight hardware lock. + * The lock is a read-write lock. Taking it in read mode and write mode + * is relatively fast, and intended for in-kernel use only. + * + * The vt mode is used only when there is a need to block all + * user-space processes from validating buffers. + * It's allowed to leave kernel space with the vt lock held. + * If a user-space process dies while having the vt-lock, + * it will be released during the file descriptor release. The vt lock + * excludes write lock and read lock. + * + * The suspend mode is used to lock out all TTM users when preparing for + * and executing suspend operations. + * + */ + +#ifndef _TTM_LOCK_H_ +#define _TTM_LOCK_H_ + +#include "ttm/ttm_object.h" +#include +#include + +/** + * struct ttm_lock + * + * @base: ttm base object used solely to release the lock if the client + * holding the lock dies. + * @queue: Queue for processes waiting for lock change-of-status. + * @lock: Spinlock protecting some lock members. + * @rw: Read-write lock counter. Protected by @lock. + * @flags: Lock state. Protected by @lock. + * @kill_takers: Boolean whether to kill takers of the lock. + * @signal: Signal to send when kill_takers is true. + */ + +struct ttm_lock { + struct ttm_base_object base; + wait_queue_head_t queue; + spinlock_t lock; + int32_t rw; + uint32_t flags; + bool kill_takers; + int signal; + struct ttm_object_file *vt_holder; +}; + + +/** + * ttm_lock_init + * + * @lock: Pointer to a struct ttm_lock + * Initializes the lock. + */ +extern void ttm_lock_init(struct ttm_lock *lock); + +/** + * ttm_read_unlock + * + * @lock: Pointer to a struct ttm_lock + * + * Releases a read lock. + */ +extern void ttm_read_unlock(struct ttm_lock *lock); + +/** + * ttm_read_lock + * + * @lock: Pointer to a struct ttm_lock + * @interruptible: Interruptible sleeping while waiting for a lock. + * + * Takes the lock in read mode. + * Returns: + * -ERESTARTSYS If interrupted by a signal and interruptible is true. + */ +extern int ttm_read_lock(struct ttm_lock *lock, bool interruptible); + +/** + * ttm_read_trylock + * + * @lock: Pointer to a struct ttm_lock + * @interruptible: Interruptible sleeping while waiting for a lock. + * + * Tries to take the lock in read mode. If the lock is already held + * in write mode, the function will return -EBUSY. If the lock is held + * in vt or suspend mode, the function will sleep until these modes + * are unlocked. + * + * Returns: + * -EBUSY The lock was already held in write mode. + * -ERESTARTSYS If interrupted by a signal and interruptible is true. + */ +extern int ttm_read_trylock(struct ttm_lock *lock, bool interruptible); + +/** + * ttm_write_unlock + * + * @lock: Pointer to a struct ttm_lock + * + * Releases a write lock. + */ +extern void ttm_write_unlock(struct ttm_lock *lock); + +/** + * ttm_write_lock + * + * @lock: Pointer to a struct ttm_lock + * @interruptible: Interruptible sleeping while waiting for a lock. + * + * Takes the lock in write mode. + * Returns: + * -ERESTARTSYS If interrupted by a signal and interruptible is true. + */ +extern int ttm_write_lock(struct ttm_lock *lock, bool interruptible); + +/** + * ttm_lock_downgrade + * + * @lock: Pointer to a struct ttm_lock + * + * Downgrades a write lock to a read lock. + */ +extern void ttm_lock_downgrade(struct ttm_lock *lock); + +/** + * ttm_suspend_lock + * + * @lock: Pointer to a struct ttm_lock + * + * Takes the lock in suspend mode. Excludes read and write mode. + */ +extern void ttm_suspend_lock(struct ttm_lock *lock); + +/** + * ttm_suspend_unlock + * + * @lock: Pointer to a struct ttm_lock + * + * Releases a suspend lock + */ +extern void ttm_suspend_unlock(struct ttm_lock *lock); + +/** + * ttm_vt_lock + * + * @lock: Pointer to a struct ttm_lock + * @interruptible: Interruptible sleeping while waiting for a lock. + * @tfile: Pointer to a struct ttm_object_file to register the lock with. + * + * Takes the lock in vt mode. + * Returns: + * -ERESTARTSYS If interrupted by a signal and interruptible is true. + * -ENOMEM: Out of memory when locking. + */ +extern int ttm_vt_lock(struct ttm_lock *lock, bool interruptible, + struct ttm_object_file *tfile); + +/** + * ttm_vt_unlock + * + * @lock: Pointer to a struct ttm_lock + * + * Releases a vt lock. + * Returns: + * -EINVAL If the lock was not held. + */ +extern int ttm_vt_unlock(struct ttm_lock *lock); + +/** + * ttm_write_unlock + * + * @lock: Pointer to a struct ttm_lock + * + * Releases a write lock. + */ +extern void ttm_write_unlock(struct ttm_lock *lock); + +/** + * ttm_write_lock + * + * @lock: Pointer to a struct ttm_lock + * @interruptible: Interruptible sleeping while waiting for a lock. + * + * Takes the lock in write mode. + * Returns: + * -ERESTARTSYS If interrupted by a signal and interruptible is true. + */ +extern int ttm_write_lock(struct ttm_lock *lock, bool interruptible); + +/** + * ttm_lock_set_kill + * + * @lock: Pointer to a struct ttm_lock + * @val: Boolean whether to kill processes taking the lock. + * @signal: Signal to send to the process taking the lock. + * + * The kill-when-taking-lock functionality is used to kill processes that keep + * on using the TTM functionality when its resources has been taken down, for + * example when the X server exits. A typical sequence would look like this: + * - X server takes lock in write mode. + * - ttm_lock_set_kill() is called with @val set to true. + * - As part of X server exit, TTM resources are taken down. + * - X server releases the lock on file release. + * - Another dri client wants to render, takes the lock and is killed. + * + */ +static inline void ttm_lock_set_kill(struct ttm_lock *lock, bool val, + int signal) +{ + lock->kill_takers = val; + if (val) + lock->signal = signal; +} + +#endif -- cgit v1.2.3-70-g09d2 From c078aa2fc4d8e022c3b611e07b25ff77afdf9b73 Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Sun, 6 Dec 2009 21:46:26 +0100 Subject: drm/ttm: Add TTM execbuf utilities. Utilities to reserve, unreserve and fence a list of TTM buffer objects in a deadlock-safe manner. Used by the vmwgfx driver. Signed-off-by: Thomas Hellstrom Signed-off-by: Dave Airlie --- drivers/gpu/drm/ttm/Makefile | 2 +- drivers/gpu/drm/ttm/ttm_execbuf_util.c | 117 +++++++++++++++++++++++++++++++++ include/drm/ttm/ttm_execbuf_util.h | 107 ++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/ttm/ttm_execbuf_util.c create mode 100644 include/drm/ttm/ttm_execbuf_util.h (limited to 'include') diff --git a/drivers/gpu/drm/ttm/Makefile b/drivers/gpu/drm/ttm/Makefile index 4473549bc5d..1e138f5bae0 100644 --- a/drivers/gpu/drm/ttm/Makefile +++ b/drivers/gpu/drm/ttm/Makefile @@ -4,6 +4,6 @@ ccflags-y := -Iinclude/drm ttm-y := ttm_agp_backend.o ttm_memory.o ttm_tt.o ttm_bo.o \ ttm_bo_util.o ttm_bo_vm.o ttm_module.o ttm_global.o \ - ttm_object.o ttm_lock.o + ttm_object.o ttm_lock.o ttm_execbuf_util.o obj-$(CONFIG_DRM_TTM) += ttm.o diff --git a/drivers/gpu/drm/ttm/ttm_execbuf_util.c b/drivers/gpu/drm/ttm/ttm_execbuf_util.c new file mode 100644 index 00000000000..c285c2902d1 --- /dev/null +++ b/drivers/gpu/drm/ttm/ttm_execbuf_util.c @@ -0,0 +1,117 @@ +/************************************************************************** + * + * Copyright (c) 2006-2009 VMware, Inc., Palo Alto, CA., USA + * All Rights Reserved. + * + * 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS 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. + * + **************************************************************************/ + +#include "ttm/ttm_execbuf_util.h" +#include "ttm/ttm_bo_driver.h" +#include "ttm/ttm_placement.h" +#include +#include +#include + +void ttm_eu_backoff_reservation(struct list_head *list) +{ + struct ttm_validate_buffer *entry; + + list_for_each_entry(entry, list, head) { + struct ttm_buffer_object *bo = entry->bo; + if (!entry->reserved) + continue; + + entry->reserved = false; + ttm_bo_unreserve(bo); + } +} +EXPORT_SYMBOL(ttm_eu_backoff_reservation); + +/* + * Reserve buffers for validation. + * + * If a buffer in the list is marked for CPU access, we back off and + * wait for that buffer to become free for GPU access. + * + * If a buffer is reserved for another validation, the validator with + * the highest validation sequence backs off and waits for that buffer + * to become unreserved. This prevents deadlocks when validating multiple + * buffers in different orders. + */ + +int ttm_eu_reserve_buffers(struct list_head *list, uint32_t val_seq) +{ + struct ttm_validate_buffer *entry; + int ret; + +retry: + list_for_each_entry(entry, list, head) { + struct ttm_buffer_object *bo = entry->bo; + + entry->reserved = false; + ret = ttm_bo_reserve(bo, true, false, true, val_seq); + if (ret != 0) { + ttm_eu_backoff_reservation(list); + if (ret == -EAGAIN) { + ret = ttm_bo_wait_unreserved(bo, true); + if (unlikely(ret != 0)) + return ret; + goto retry; + } else + return ret; + } + + entry->reserved = true; + if (unlikely(atomic_read(&bo->cpu_writers) > 0)) { + ttm_eu_backoff_reservation(list); + ret = ttm_bo_wait_cpu(bo, false); + if (ret) + return ret; + goto retry; + } + } + return 0; +} +EXPORT_SYMBOL(ttm_eu_reserve_buffers); + +void ttm_eu_fence_buffer_objects(struct list_head *list, void *sync_obj) +{ + struct ttm_validate_buffer *entry; + + list_for_each_entry(entry, list, head) { + struct ttm_buffer_object *bo = entry->bo; + struct ttm_bo_driver *driver = bo->bdev->driver; + void *old_sync_obj; + + spin_lock(&bo->lock); + old_sync_obj = bo->sync_obj; + bo->sync_obj = driver->sync_obj_ref(sync_obj); + bo->sync_obj_arg = entry->new_sync_obj_arg; + spin_unlock(&bo->lock); + ttm_bo_unreserve(bo); + entry->reserved = false; + if (old_sync_obj) + driver->sync_obj_unref(&old_sync_obj); + } +} +EXPORT_SYMBOL(ttm_eu_fence_buffer_objects); diff --git a/include/drm/ttm/ttm_execbuf_util.h b/include/drm/ttm/ttm_execbuf_util.h new file mode 100644 index 00000000000..cd2c475da9e --- /dev/null +++ b/include/drm/ttm/ttm_execbuf_util.h @@ -0,0 +1,107 @@ +/************************************************************************** + * + * Copyright (c) 2006-2009 VMware, Inc., Palo Alto, CA., USA + * All Rights Reserved. + * + * 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS 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: Thomas Hellstrom + */ + +#ifndef _TTM_EXECBUF_UTIL_H_ +#define _TTM_EXECBUF_UTIL_H_ + +#include "ttm/ttm_bo_api.h" +#include + +/** + * struct ttm_validate_buffer + * + * @head: list head for thread-private list. + * @bo: refcounted buffer object pointer. + * @new_sync_obj_arg: New sync_obj_arg for @bo, to be used once + * adding a new sync object. + * @reservied: Indicates whether @bo has been reserved for validation. + */ + +struct ttm_validate_buffer { + struct list_head head; + struct ttm_buffer_object *bo; + void *new_sync_obj_arg; + bool reserved; +}; + +/** + * function ttm_eu_backoff_reservation + * + * @list: thread private list of ttm_validate_buffer structs. + * + * Undoes all buffer validation reservations for bos pointed to by + * the list entries. + */ + +extern void ttm_eu_backoff_reservation(struct list_head *list); + +/** + * function ttm_eu_reserve_buffers + * + * @list: thread private list of ttm_validate_buffer structs. + * @val_seq: A unique sequence number. + * + * Tries to reserve bos pointed to by the list entries for validation. + * If the function returns 0, all buffers are marked as "unfenced", + * taken off the lru lists and are not synced for write CPU usage. + * + * If the function detects a deadlock due to multiple threads trying to + * reserve the same buffers in reverse order, all threads except one will + * back off and retry. This function may sleep while waiting for + * CPU write reservations to be cleared, and for other threads to + * unreserve their buffers. + * + * This function may return -ERESTART or -EAGAIN if the calling process + * receives a signal while waiting. In that case, no buffers on the list + * will be reserved upon return. + * + * Buffers reserved by this function should be unreserved by + * a call to either ttm_eu_backoff_reservation() or + * ttm_eu_fence_buffer_objects() when command submission is complete or + * has failed. + */ + +extern int ttm_eu_reserve_buffers(struct list_head *list, uint32_t val_seq); + +/** + * function ttm_eu_fence_buffer_objects. + * + * @list: thread private list of ttm_validate_buffer structs. + * @sync_obj: The new sync object for the buffers. + * + * This function should be called when command submission is complete, and + * it will add a new sync object to bos pointed to by entries on @list. + * It also unreserves all buffers, putting them on lru lists. + * + */ + +extern void ttm_eu_fence_buffer_objects(struct list_head *list, void *sync_obj); + +#endif -- cgit v1.2.3-70-g09d2 From 4bfd75cb08a362cb1df35dc6a5032d12843c6d87 Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Sun, 6 Dec 2009 21:46:27 +0100 Subject: drm/ttm: Export symbols needed for the vmwgfx driver. Signed-off-by: Thomas Hellstrom Signed-off-by: Dave Airlie --- drivers/gpu/drm/ttm/ttm_bo_util.c | 1 + drivers/gpu/drm/ttm/ttm_memory.c | 3 +++ drivers/gpu/drm/ttm/ttm_tt.c | 1 + include/drm/ttm/ttm_bo_driver.h | 9 +++++++++ 4 files changed, 14 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/ttm/ttm_bo_util.c b/drivers/gpu/drm/ttm/ttm_bo_util.c index c70927ecda2..ceae52f45c3 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_util.c +++ b/drivers/gpu/drm/ttm/ttm_bo_util.c @@ -369,6 +369,7 @@ pgprot_t ttm_io_prot(uint32_t caching_flags, pgprot_t tmp) #endif return tmp; } +EXPORT_SYMBOL(ttm_io_prot); static int ttm_bo_ioremap(struct ttm_buffer_object *bo, unsigned long bus_base, diff --git a/drivers/gpu/drm/ttm/ttm_memory.c b/drivers/gpu/drm/ttm/ttm_memory.c index 336976e8652..8bfde5f4084 100644 --- a/drivers/gpu/drm/ttm/ttm_memory.c +++ b/drivers/gpu/drm/ttm/ttm_memory.c @@ -461,6 +461,7 @@ void ttm_mem_global_free(struct ttm_mem_global *glob, { return ttm_mem_global_free_zone(glob, NULL, amount); } +EXPORT_SYMBOL(ttm_mem_global_free); static int ttm_mem_global_reserve(struct ttm_mem_global *glob, struct ttm_mem_zone *single_zone, @@ -534,6 +535,7 @@ int ttm_mem_global_alloc(struct ttm_mem_global *glob, uint64_t memory, return ttm_mem_global_alloc_zone(glob, NULL, memory, no_wait, interruptible); } +EXPORT_SYMBOL(ttm_mem_global_alloc); int ttm_mem_global_alloc_page(struct ttm_mem_global *glob, struct page *page, @@ -589,3 +591,4 @@ size_t ttm_round_pot(size_t size) } return 0; } +EXPORT_SYMBOL(ttm_round_pot); diff --git a/drivers/gpu/drm/ttm/ttm_tt.c b/drivers/gpu/drm/ttm/ttm_tt.c index a55ee1a56c1..90ea46aed05 100644 --- a/drivers/gpu/drm/ttm/ttm_tt.c +++ b/drivers/gpu/drm/ttm/ttm_tt.c @@ -192,6 +192,7 @@ int ttm_tt_populate(struct ttm_tt *ttm) ttm->state = tt_unbound; return 0; } +EXPORT_SYMBOL(ttm_tt_populate); #ifdef CONFIG_X86 static inline int ttm_tt_set_page_caching(struct page *p, diff --git a/include/drm/ttm/ttm_bo_driver.h b/include/drm/ttm/ttm_bo_driver.h index e8cd6d20aed..7a39ab9aa1d 100644 --- a/include/drm/ttm/ttm_bo_driver.h +++ b/include/drm/ttm/ttm_bo_driver.h @@ -544,6 +544,15 @@ extern int ttm_tt_set_user(struct ttm_tt *ttm, */ extern int ttm_tt_bind(struct ttm_tt *ttm, struct ttm_mem_reg *bo_mem); +/** + * ttm_tt_populate: + * + * @ttm: The struct ttm_tt to contain the backing pages. + * + * Add backing pages to all of @ttm + */ +extern int ttm_tt_populate(struct ttm_tt *ttm); + /** * ttm_ttm_destroy: * -- cgit v1.2.3-70-g09d2 From ab2c0672984f7f7ebec6d5f615fd5a6ebad26f3d Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Fri, 4 Dec 2009 10:55:24 +1000 Subject: drm/intel: refactor DP i2c support and DP common header to drm helper Both radeon and nouveau can re-use this code so move it up a level so they can. However the hw interfaces for aux ch are different enough that the code to translate from mode, address, bytes to actual hw interfaces isn't generic, so move that code into the Intel driver. Signed-off-by: Dave Airlie --- drivers/gpu/drm/Makefile | 2 +- drivers/gpu/drm/drm_dp_i2c_helper.c | 209 +++++++++++++++++++++++++++ drivers/gpu/drm/i915/Makefile | 1 - drivers/gpu/drm/i915/intel_display.c | 2 +- drivers/gpu/drm/i915/intel_dp.c | 72 ++++++++- drivers/gpu/drm/i915/intel_dp.h | 144 ------------------ drivers/gpu/drm/i915/intel_dp_i2c.c | 273 ----------------------------------- include/drm/drm_dp_helper.h | 149 +++++++++++++++++++ 8 files changed, 426 insertions(+), 426 deletions(-) create mode 100644 drivers/gpu/drm/drm_dp_i2c_helper.c delete mode 100644 drivers/gpu/drm/i915/intel_dp.h delete mode 100644 drivers/gpu/drm/i915/intel_dp_i2c.c create mode 100644 include/drm/drm_dp_helper.h (limited to 'include') diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 3c8827a7aab..91567ac806f 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -15,7 +15,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ drm-$(CONFIG_COMPAT) += drm_ioc32.o -drm_kms_helper-y := drm_fb_helper.o drm_crtc_helper.o +drm_kms_helper-y := drm_fb_helper.o drm_crtc_helper.o drm_dp_i2c_helper.o obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o diff --git a/drivers/gpu/drm/drm_dp_i2c_helper.c b/drivers/gpu/drm/drm_dp_i2c_helper.c new file mode 100644 index 00000000000..f1c7c856e9d --- /dev/null +++ b/drivers/gpu/drm/drm_dp_i2c_helper.c @@ -0,0 +1,209 @@ +/* + * Copyright © 2009 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "drm_dp_helper.h" +#include "drmP.h" + +/* Run a single AUX_CH I2C transaction, writing/reading data as necessary */ +static int +i2c_algo_dp_aux_transaction(struct i2c_adapter *adapter, int mode, + uint8_t write_byte, uint8_t *read_byte) +{ + struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; + int ret; + + ret = (*algo_data->aux_ch)(adapter, mode, + write_byte, read_byte); + return ret; +} + +/* + * I2C over AUX CH + */ + +/* + * Send the address. If the I2C link is running, this 'restarts' + * the connection with the new address, this is used for doing + * a write followed by a read (as needed for DDC) + */ +static int +i2c_algo_dp_aux_address(struct i2c_adapter *adapter, u16 address, bool reading) +{ + struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; + int mode = MODE_I2C_START; + int ret; + + if (reading) + mode |= MODE_I2C_READ; + else + mode |= MODE_I2C_WRITE; + algo_data->address = address; + algo_data->running = true; + ret = i2c_algo_dp_aux_transaction(adapter, mode, 0, NULL); + return ret; +} + +/* + * Stop the I2C transaction. This closes out the link, sending + * a bare address packet with the MOT bit turned off + */ +static void +i2c_algo_dp_aux_stop(struct i2c_adapter *adapter, bool reading) +{ + struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; + int mode = MODE_I2C_STOP; + + if (reading) + mode |= MODE_I2C_READ; + else + mode |= MODE_I2C_WRITE; + if (algo_data->running) { + (void) i2c_algo_dp_aux_transaction(adapter, mode, 0, NULL); + algo_data->running = false; + } +} + +/* + * Write a single byte to the current I2C address, the + * the I2C link must be running or this returns -EIO + */ +static int +i2c_algo_dp_aux_put_byte(struct i2c_adapter *adapter, u8 byte) +{ + struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; + int ret; + + if (!algo_data->running) + return -EIO; + + ret = i2c_algo_dp_aux_transaction(adapter, MODE_I2C_WRITE, byte, NULL); + return ret; +} + +/* + * Read a single byte from the current I2C address, the + * I2C link must be running or this returns -EIO + */ +static int +i2c_algo_dp_aux_get_byte(struct i2c_adapter *adapter, u8 *byte_ret) +{ + struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; + int ret; + + if (!algo_data->running) + return -EIO; + + ret = i2c_algo_dp_aux_transaction(adapter, MODE_I2C_READ, 0, byte_ret); + return ret; +} + +static int +i2c_algo_dp_aux_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + int ret = 0; + bool reading = false; + int m; + int b; + + for (m = 0; m < num; m++) { + u16 len = msgs[m].len; + u8 *buf = msgs[m].buf; + reading = (msgs[m].flags & I2C_M_RD) != 0; + ret = i2c_algo_dp_aux_address(adapter, msgs[m].addr, reading); + if (ret < 0) + break; + if (reading) { + for (b = 0; b < len; b++) { + ret = i2c_algo_dp_aux_get_byte(adapter, &buf[b]); + if (ret < 0) + break; + } + } else { + for (b = 0; b < len; b++) { + ret = i2c_algo_dp_aux_put_byte(adapter, buf[b]); + if (ret < 0) + break; + } + } + if (ret < 0) + break; + } + if (ret >= 0) + ret = num; + i2c_algo_dp_aux_stop(adapter, reading); + DRM_DEBUG("dp_aux_xfer return %d\n", ret); + return ret; +} + +static u32 +i2c_algo_dp_aux_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_10BIT_ADDR; +} + +static const struct i2c_algorithm i2c_dp_aux_algo = { + .master_xfer = i2c_algo_dp_aux_xfer, + .functionality = i2c_algo_dp_aux_functionality, +}; + +static void +i2c_dp_aux_reset_bus(struct i2c_adapter *adapter) +{ + (void) i2c_algo_dp_aux_address(adapter, 0, false); + (void) i2c_algo_dp_aux_stop(adapter, false); + +} + +static int +i2c_dp_aux_prepare_bus(struct i2c_adapter *adapter) +{ + adapter->algo = &i2c_dp_aux_algo; + adapter->retries = 3; + i2c_dp_aux_reset_bus(adapter); + return 0; +} + +int +i2c_dp_aux_add_bus(struct i2c_adapter *adapter) +{ + int error; + + error = i2c_dp_aux_prepare_bus(adapter); + if (error) + return error; + error = i2c_add_adapter(adapter); + return error; +} +EXPORT_SYMBOL(i2c_dp_aux_add_bus); diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index fa7b9be096b..e3d049229cd 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -15,7 +15,6 @@ i915-y := i915_drv.o i915_dma.o i915_irq.o i915_mem.o \ intel_lvds.o \ intel_bios.o \ intel_dp.o \ - intel_dp_i2c.o \ intel_hdmi.o \ intel_sdvo.o \ intel_modes.o \ diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 3ba6546b7c7..ccd180dce4c 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -32,7 +32,7 @@ #include "intel_drv.h" #include "i915_drm.h" #include "i915_drv.h" -#include "intel_dp.h" +#include "drm_dp_helper.h" #include "drm_crtc_helper.h" diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index d83447557f9..63424d5db9c 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -33,7 +33,7 @@ #include "intel_drv.h" #include "i915_drm.h" #include "i915_drv.h" -#include "intel_dp.h" +#include "drm_dp_helper.h" #define DP_LINK_STATUS_SIZE 6 #define DP_LINK_CHECK_TIMEOUT (10 * 1000) @@ -382,17 +382,77 @@ intel_dp_aux_native_read(struct intel_output *intel_output, } static int -intel_dp_i2c_aux_ch(struct i2c_adapter *adapter, - uint8_t *send, int send_bytes, - uint8_t *recv, int recv_bytes) +intel_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, + uint8_t write_byte, uint8_t *read_byte) { + struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; struct intel_dp_priv *dp_priv = container_of(adapter, struct intel_dp_priv, adapter); struct intel_output *intel_output = dp_priv->intel_output; + uint16_t address = algo_data->address; + uint8_t msg[5]; + uint8_t reply[2]; + int msg_bytes; + int reply_bytes; + int ret; + + /* Set up the command byte */ + if (mode & MODE_I2C_READ) + msg[0] = AUX_I2C_READ << 4; + else + msg[0] = AUX_I2C_WRITE << 4; + + if (!(mode & MODE_I2C_STOP)) + msg[0] |= AUX_I2C_MOT << 4; + + msg[1] = address >> 8; + msg[2] = address; + + switch (mode) { + case MODE_I2C_WRITE: + msg[3] = 0; + msg[4] = write_byte; + msg_bytes = 5; + reply_bytes = 1; + break; + case MODE_I2C_READ: + msg[3] = 0; + msg_bytes = 4; + reply_bytes = 2; + break; + default: + msg_bytes = 3; + reply_bytes = 1; + break; + } - return intel_dp_aux_ch(intel_output, - send, send_bytes, recv, recv_bytes); + for (;;) { + ret = intel_dp_aux_ch(intel_output, + msg, msg_bytes, + reply, reply_bytes); + if (ret < 0) { + DRM_DEBUG("aux_ch failed %d\n", ret); + return ret; + } + switch (reply[0] & AUX_I2C_REPLY_MASK) { + case AUX_I2C_REPLY_ACK: + if (mode == MODE_I2C_READ) { + *read_byte = reply[1]; + } + return reply_bytes - 1; + case AUX_I2C_REPLY_NACK: + DRM_DEBUG("aux_ch nack\n"); + return -EREMOTEIO; + case AUX_I2C_REPLY_DEFER: + DRM_DEBUG("aux_ch defer\n"); + udelay(100); + break; + default: + DRM_ERROR("aux_ch invalid reply 0x%02x\n", reply[0]); + return -EREMOTEIO; + } + } } static int diff --git a/drivers/gpu/drm/i915/intel_dp.h b/drivers/gpu/drm/i915/intel_dp.h deleted file mode 100644 index 2b38054d3b6..00000000000 --- a/drivers/gpu/drm/i915/intel_dp.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright © 2008 Keith Packard - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation, and - * that the name of the copyright holders not be used in advertising or - * publicity pertaining to distribution of the software without specific, - * written prior permission. The copyright holders make no representations - * about the suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. - * - * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE - * OF THIS SOFTWARE. - */ - -#ifndef _INTEL_DP_H_ -#define _INTEL_DP_H_ - -/* From the VESA DisplayPort spec */ - -#define AUX_NATIVE_WRITE 0x8 -#define AUX_NATIVE_READ 0x9 -#define AUX_I2C_WRITE 0x0 -#define AUX_I2C_READ 0x1 -#define AUX_I2C_STATUS 0x2 -#define AUX_I2C_MOT 0x4 - -#define AUX_NATIVE_REPLY_ACK (0x0 << 4) -#define AUX_NATIVE_REPLY_NACK (0x1 << 4) -#define AUX_NATIVE_REPLY_DEFER (0x2 << 4) -#define AUX_NATIVE_REPLY_MASK (0x3 << 4) - -#define AUX_I2C_REPLY_ACK (0x0 << 6) -#define AUX_I2C_REPLY_NACK (0x1 << 6) -#define AUX_I2C_REPLY_DEFER (0x2 << 6) -#define AUX_I2C_REPLY_MASK (0x3 << 6) - -/* AUX CH addresses */ -#define DP_LINK_BW_SET 0x100 -# define DP_LINK_BW_1_62 0x06 -# define DP_LINK_BW_2_7 0x0a - -#define DP_LANE_COUNT_SET 0x101 -# define DP_LANE_COUNT_MASK 0x0f -# define DP_LANE_COUNT_ENHANCED_FRAME_EN (1 << 7) - -#define DP_TRAINING_PATTERN_SET 0x102 - -# define DP_TRAINING_PATTERN_DISABLE 0 -# define DP_TRAINING_PATTERN_1 1 -# define DP_TRAINING_PATTERN_2 2 -# define DP_TRAINING_PATTERN_MASK 0x3 - -# define DP_LINK_QUAL_PATTERN_DISABLE (0 << 2) -# define DP_LINK_QUAL_PATTERN_D10_2 (1 << 2) -# define DP_LINK_QUAL_PATTERN_ERROR_RATE (2 << 2) -# define DP_LINK_QUAL_PATTERN_PRBS7 (3 << 2) -# define DP_LINK_QUAL_PATTERN_MASK (3 << 2) - -# define DP_RECOVERED_CLOCK_OUT_EN (1 << 4) -# define DP_LINK_SCRAMBLING_DISABLE (1 << 5) - -# define DP_SYMBOL_ERROR_COUNT_BOTH (0 << 6) -# define DP_SYMBOL_ERROR_COUNT_DISPARITY (1 << 6) -# define DP_SYMBOL_ERROR_COUNT_SYMBOL (2 << 6) -# define DP_SYMBOL_ERROR_COUNT_MASK (3 << 6) - -#define DP_TRAINING_LANE0_SET 0x103 -#define DP_TRAINING_LANE1_SET 0x104 -#define DP_TRAINING_LANE2_SET 0x105 -#define DP_TRAINING_LANE3_SET 0x106 - -# define DP_TRAIN_VOLTAGE_SWING_MASK 0x3 -# define DP_TRAIN_VOLTAGE_SWING_SHIFT 0 -# define DP_TRAIN_MAX_SWING_REACHED (1 << 2) -# define DP_TRAIN_VOLTAGE_SWING_400 (0 << 0) -# define DP_TRAIN_VOLTAGE_SWING_600 (1 << 0) -# define DP_TRAIN_VOLTAGE_SWING_800 (2 << 0) -# define DP_TRAIN_VOLTAGE_SWING_1200 (3 << 0) - -# define DP_TRAIN_PRE_EMPHASIS_MASK (3 << 3) -# define DP_TRAIN_PRE_EMPHASIS_0 (0 << 3) -# define DP_TRAIN_PRE_EMPHASIS_3_5 (1 << 3) -# define DP_TRAIN_PRE_EMPHASIS_6 (2 << 3) -# define DP_TRAIN_PRE_EMPHASIS_9_5 (3 << 3) - -# define DP_TRAIN_PRE_EMPHASIS_SHIFT 3 -# define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED (1 << 5) - -#define DP_DOWNSPREAD_CTRL 0x107 -# define DP_SPREAD_AMP_0_5 (1 << 4) - -#define DP_MAIN_LINK_CHANNEL_CODING_SET 0x108 -# define DP_SET_ANSI_8B10B (1 << 0) - -#define DP_LANE0_1_STATUS 0x202 -#define DP_LANE2_3_STATUS 0x203 - -# define DP_LANE_CR_DONE (1 << 0) -# define DP_LANE_CHANNEL_EQ_DONE (1 << 1) -# define DP_LANE_SYMBOL_LOCKED (1 << 2) - -#define DP_LANE_ALIGN_STATUS_UPDATED 0x204 - -#define DP_INTERLANE_ALIGN_DONE (1 << 0) -#define DP_DOWNSTREAM_PORT_STATUS_CHANGED (1 << 6) -#define DP_LINK_STATUS_UPDATED (1 << 7) - -#define DP_SINK_STATUS 0x205 - -#define DP_RECEIVE_PORT_0_STATUS (1 << 0) -#define DP_RECEIVE_PORT_1_STATUS (1 << 1) - -#define DP_ADJUST_REQUEST_LANE0_1 0x206 -#define DP_ADJUST_REQUEST_LANE2_3 0x207 - -#define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03 -#define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0 -#define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c -#define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2 -#define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30 -#define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4 -#define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0 -#define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6 - -struct i2c_algo_dp_aux_data { - bool running; - u16 address; - int (*aux_ch) (struct i2c_adapter *adapter, - uint8_t *send, int send_bytes, - uint8_t *recv, int recv_bytes); -}; - -int -i2c_dp_aux_add_bus(struct i2c_adapter *adapter); - -#endif /* _INTEL_DP_H_ */ diff --git a/drivers/gpu/drm/i915/intel_dp_i2c.c b/drivers/gpu/drm/i915/intel_dp_i2c.c deleted file mode 100644 index a63b6f57d2d..00000000000 --- a/drivers/gpu/drm/i915/intel_dp_i2c.c +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright © 2009 Keith Packard - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation, and - * that the name of the copyright holders not be used in advertising or - * publicity pertaining to distribution of the software without specific, - * written prior permission. The copyright holders make no representations - * about the suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. - * - * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE - * OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "intel_dp.h" -#include "drmP.h" - -/* Run a single AUX_CH I2C transaction, writing/reading data as necessary */ - -#define MODE_I2C_START 1 -#define MODE_I2C_WRITE 2 -#define MODE_I2C_READ 4 -#define MODE_I2C_STOP 8 - -static int -i2c_algo_dp_aux_transaction(struct i2c_adapter *adapter, int mode, - uint8_t write_byte, uint8_t *read_byte) -{ - struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; - uint16_t address = algo_data->address; - uint8_t msg[5]; - uint8_t reply[2]; - int msg_bytes; - int reply_bytes; - int ret; - - /* Set up the command byte */ - if (mode & MODE_I2C_READ) - msg[0] = AUX_I2C_READ << 4; - else - msg[0] = AUX_I2C_WRITE << 4; - - if (!(mode & MODE_I2C_STOP)) - msg[0] |= AUX_I2C_MOT << 4; - - msg[1] = address >> 8; - msg[2] = address; - - switch (mode) { - case MODE_I2C_WRITE: - msg[3] = 0; - msg[4] = write_byte; - msg_bytes = 5; - reply_bytes = 1; - break; - case MODE_I2C_READ: - msg[3] = 0; - msg_bytes = 4; - reply_bytes = 2; - break; - default: - msg_bytes = 3; - reply_bytes = 1; - break; - } - - for (;;) { - ret = (*algo_data->aux_ch)(adapter, - msg, msg_bytes, - reply, reply_bytes); - if (ret < 0) { - DRM_DEBUG("aux_ch failed %d\n", ret); - return ret; - } - switch (reply[0] & AUX_I2C_REPLY_MASK) { - case AUX_I2C_REPLY_ACK: - if (mode == MODE_I2C_READ) { - *read_byte = reply[1]; - } - return reply_bytes - 1; - case AUX_I2C_REPLY_NACK: - DRM_DEBUG("aux_ch nack\n"); - return -EREMOTEIO; - case AUX_I2C_REPLY_DEFER: - DRM_DEBUG("aux_ch defer\n"); - udelay(100); - break; - default: - DRM_ERROR("aux_ch invalid reply 0x%02x\n", reply[0]); - return -EREMOTEIO; - } - } -} - -/* - * I2C over AUX CH - */ - -/* - * Send the address. If the I2C link is running, this 'restarts' - * the connection with the new address, this is used for doing - * a write followed by a read (as needed for DDC) - */ -static int -i2c_algo_dp_aux_address(struct i2c_adapter *adapter, u16 address, bool reading) -{ - struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; - int mode = MODE_I2C_START; - int ret; - - if (reading) - mode |= MODE_I2C_READ; - else - mode |= MODE_I2C_WRITE; - algo_data->address = address; - algo_data->running = true; - ret = i2c_algo_dp_aux_transaction(adapter, mode, 0, NULL); - return ret; -} - -/* - * Stop the I2C transaction. This closes out the link, sending - * a bare address packet with the MOT bit turned off - */ -static void -i2c_algo_dp_aux_stop(struct i2c_adapter *adapter, bool reading) -{ - struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; - int mode = MODE_I2C_STOP; - - if (reading) - mode |= MODE_I2C_READ; - else - mode |= MODE_I2C_WRITE; - if (algo_data->running) { - (void) i2c_algo_dp_aux_transaction(adapter, mode, 0, NULL); - algo_data->running = false; - } -} - -/* - * Write a single byte to the current I2C address, the - * the I2C link must be running or this returns -EIO - */ -static int -i2c_algo_dp_aux_put_byte(struct i2c_adapter *adapter, u8 byte) -{ - struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; - int ret; - - if (!algo_data->running) - return -EIO; - - ret = i2c_algo_dp_aux_transaction(adapter, MODE_I2C_WRITE, byte, NULL); - return ret; -} - -/* - * Read a single byte from the current I2C address, the - * I2C link must be running or this returns -EIO - */ -static int -i2c_algo_dp_aux_get_byte(struct i2c_adapter *adapter, u8 *byte_ret) -{ - struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; - int ret; - - if (!algo_data->running) - return -EIO; - - ret = i2c_algo_dp_aux_transaction(adapter, MODE_I2C_READ, 0, byte_ret); - return ret; -} - -static int -i2c_algo_dp_aux_xfer(struct i2c_adapter *adapter, - struct i2c_msg *msgs, - int num) -{ - int ret = 0; - bool reading = false; - int m; - int b; - - for (m = 0; m < num; m++) { - u16 len = msgs[m].len; - u8 *buf = msgs[m].buf; - reading = (msgs[m].flags & I2C_M_RD) != 0; - ret = i2c_algo_dp_aux_address(adapter, msgs[m].addr, reading); - if (ret < 0) - break; - if (reading) { - for (b = 0; b < len; b++) { - ret = i2c_algo_dp_aux_get_byte(adapter, &buf[b]); - if (ret < 0) - break; - } - } else { - for (b = 0; b < len; b++) { - ret = i2c_algo_dp_aux_put_byte(adapter, buf[b]); - if (ret < 0) - break; - } - } - if (ret < 0) - break; - } - if (ret >= 0) - ret = num; - i2c_algo_dp_aux_stop(adapter, reading); - DRM_DEBUG("dp_aux_xfer return %d\n", ret); - return ret; -} - -static u32 -i2c_algo_dp_aux_functionality(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | - I2C_FUNC_SMBUS_READ_BLOCK_DATA | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | - I2C_FUNC_10BIT_ADDR; -} - -static const struct i2c_algorithm i2c_dp_aux_algo = { - .master_xfer = i2c_algo_dp_aux_xfer, - .functionality = i2c_algo_dp_aux_functionality, -}; - -static void -i2c_dp_aux_reset_bus(struct i2c_adapter *adapter) -{ - (void) i2c_algo_dp_aux_address(adapter, 0, false); - (void) i2c_algo_dp_aux_stop(adapter, false); - -} - -static int -i2c_dp_aux_prepare_bus(struct i2c_adapter *adapter) -{ - adapter->algo = &i2c_dp_aux_algo; - adapter->retries = 3; - i2c_dp_aux_reset_bus(adapter); - return 0; -} - -int -i2c_dp_aux_add_bus(struct i2c_adapter *adapter) -{ - int error; - - error = i2c_dp_aux_prepare_bus(adapter); - if (error) - return error; - error = i2c_add_adapter(adapter); - return error; -} -EXPORT_SYMBOL(i2c_dp_aux_add_bus); diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h new file mode 100644 index 00000000000..e49879ce95f --- /dev/null +++ b/include/drm/drm_dp_helper.h @@ -0,0 +1,149 @@ +/* + * Copyright © 2008 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef _DRM_DP_HELPER_H_ +#define _DRM_DP_HELPER_H_ + +/* From the VESA DisplayPort spec */ + +#define AUX_NATIVE_WRITE 0x8 +#define AUX_NATIVE_READ 0x9 +#define AUX_I2C_WRITE 0x0 +#define AUX_I2C_READ 0x1 +#define AUX_I2C_STATUS 0x2 +#define AUX_I2C_MOT 0x4 + +#define AUX_NATIVE_REPLY_ACK (0x0 << 4) +#define AUX_NATIVE_REPLY_NACK (0x1 << 4) +#define AUX_NATIVE_REPLY_DEFER (0x2 << 4) +#define AUX_NATIVE_REPLY_MASK (0x3 << 4) + +#define AUX_I2C_REPLY_ACK (0x0 << 6) +#define AUX_I2C_REPLY_NACK (0x1 << 6) +#define AUX_I2C_REPLY_DEFER (0x2 << 6) +#define AUX_I2C_REPLY_MASK (0x3 << 6) + +/* AUX CH addresses */ +#define DP_LINK_BW_SET 0x100 +# define DP_LINK_BW_1_62 0x06 +# define DP_LINK_BW_2_7 0x0a + +#define DP_LANE_COUNT_SET 0x101 +# define DP_LANE_COUNT_MASK 0x0f +# define DP_LANE_COUNT_ENHANCED_FRAME_EN (1 << 7) + +#define DP_TRAINING_PATTERN_SET 0x102 + +# define DP_TRAINING_PATTERN_DISABLE 0 +# define DP_TRAINING_PATTERN_1 1 +# define DP_TRAINING_PATTERN_2 2 +# define DP_TRAINING_PATTERN_MASK 0x3 + +# define DP_LINK_QUAL_PATTERN_DISABLE (0 << 2) +# define DP_LINK_QUAL_PATTERN_D10_2 (1 << 2) +# define DP_LINK_QUAL_PATTERN_ERROR_RATE (2 << 2) +# define DP_LINK_QUAL_PATTERN_PRBS7 (3 << 2) +# define DP_LINK_QUAL_PATTERN_MASK (3 << 2) + +# define DP_RECOVERED_CLOCK_OUT_EN (1 << 4) +# define DP_LINK_SCRAMBLING_DISABLE (1 << 5) + +# define DP_SYMBOL_ERROR_COUNT_BOTH (0 << 6) +# define DP_SYMBOL_ERROR_COUNT_DISPARITY (1 << 6) +# define DP_SYMBOL_ERROR_COUNT_SYMBOL (2 << 6) +# define DP_SYMBOL_ERROR_COUNT_MASK (3 << 6) + +#define DP_TRAINING_LANE0_SET 0x103 +#define DP_TRAINING_LANE1_SET 0x104 +#define DP_TRAINING_LANE2_SET 0x105 +#define DP_TRAINING_LANE3_SET 0x106 + +# define DP_TRAIN_VOLTAGE_SWING_MASK 0x3 +# define DP_TRAIN_VOLTAGE_SWING_SHIFT 0 +# define DP_TRAIN_MAX_SWING_REACHED (1 << 2) +# define DP_TRAIN_VOLTAGE_SWING_400 (0 << 0) +# define DP_TRAIN_VOLTAGE_SWING_600 (1 << 0) +# define DP_TRAIN_VOLTAGE_SWING_800 (2 << 0) +# define DP_TRAIN_VOLTAGE_SWING_1200 (3 << 0) + +# define DP_TRAIN_PRE_EMPHASIS_MASK (3 << 3) +# define DP_TRAIN_PRE_EMPHASIS_0 (0 << 3) +# define DP_TRAIN_PRE_EMPHASIS_3_5 (1 << 3) +# define DP_TRAIN_PRE_EMPHASIS_6 (2 << 3) +# define DP_TRAIN_PRE_EMPHASIS_9_5 (3 << 3) + +# define DP_TRAIN_PRE_EMPHASIS_SHIFT 3 +# define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED (1 << 5) + +#define DP_DOWNSPREAD_CTRL 0x107 +# define DP_SPREAD_AMP_0_5 (1 << 4) + +#define DP_MAIN_LINK_CHANNEL_CODING_SET 0x108 +# define DP_SET_ANSI_8B10B (1 << 0) + +#define DP_LANE0_1_STATUS 0x202 +#define DP_LANE2_3_STATUS 0x203 + +# define DP_LANE_CR_DONE (1 << 0) +# define DP_LANE_CHANNEL_EQ_DONE (1 << 1) +# define DP_LANE_SYMBOL_LOCKED (1 << 2) + +#define DP_LANE_ALIGN_STATUS_UPDATED 0x204 + +#define DP_INTERLANE_ALIGN_DONE (1 << 0) +#define DP_DOWNSTREAM_PORT_STATUS_CHANGED (1 << 6) +#define DP_LINK_STATUS_UPDATED (1 << 7) + +#define DP_SINK_STATUS 0x205 + +#define DP_RECEIVE_PORT_0_STATUS (1 << 0) +#define DP_RECEIVE_PORT_1_STATUS (1 << 1) + +#define DP_ADJUST_REQUEST_LANE0_1 0x206 +#define DP_ADJUST_REQUEST_LANE2_3 0x207 + +#define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03 +#define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0 +#define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c +#define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2 +#define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30 +#define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4 +#define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0 +#define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6 + +#define MODE_I2C_START 1 +#define MODE_I2C_WRITE 2 +#define MODE_I2C_READ 4 +#define MODE_I2C_STOP 8 + +struct i2c_algo_dp_aux_data { + bool running; + u16 address; + int (*aux_ch) (struct i2c_adapter *adapter, + int mode, uint8_t write_byte, + uint8_t *read_byte); +}; + +int +i2c_dp_aux_add_bus(struct i2c_adapter *adapter); + +#endif /* _DRM_DP_HELPER_H_ */ -- cgit v1.2.3-70-g09d2 From 746c1aa4d100f7441423050f34be79f401fbf7d4 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 8 Dec 2009 07:07:28 +1000 Subject: drm/radeon/kms: initial radeon displayport porting This is enough to retrieve EDID and DPCP. Signed-off-by: Dave Airlie --- drivers/gpu/drm/radeon/Makefile | 2 +- drivers/gpu/drm/radeon/atombios_dp.c | 275 +++++++++++++++++++++++++++++ drivers/gpu/drm/radeon/radeon_atombios.c | 17 +- drivers/gpu/drm/radeon/radeon_connectors.c | 57 +++++- drivers/gpu/drm/radeon/radeon_display.c | 7 + drivers/gpu/drm/radeon/radeon_encoders.c | 93 ++++++++++ drivers/gpu/drm/radeon/radeon_i2c.c | 50 ++++-- drivers/gpu/drm/radeon/radeon_mode.h | 21 ++- include/drm/drm_dp_helper.h | 2 + 9 files changed, 500 insertions(+), 24 deletions(-) create mode 100644 drivers/gpu/drm/radeon/atombios_dp.c (limited to 'include') diff --git a/drivers/gpu/drm/radeon/Makefile b/drivers/gpu/drm/radeon/Makefile index b5713eedd6e..feb52eee431 100644 --- a/drivers/gpu/drm/radeon/Makefile +++ b/drivers/gpu/drm/radeon/Makefile @@ -49,7 +49,7 @@ radeon-y += radeon_device.o radeon_kms.o \ radeon_cs.o radeon_bios.o radeon_benchmark.o r100.o r300.o r420.o \ rs400.o rs600.o rs690.o rv515.o r520.o r600.o rv770.o radeon_test.o \ r200.o radeon_legacy_tv.o r600_cs.o r600_blit.o r600_blit_shaders.o \ - r600_blit_kms.o radeon_pm.o + r600_blit_kms.o radeon_pm.o atombios_dp.o radeon-$(CONFIG_COMPAT) += radeon_ioc32.o diff --git a/drivers/gpu/drm/radeon/atombios_dp.c b/drivers/gpu/drm/radeon/atombios_dp.c new file mode 100644 index 00000000000..a4bc8011338 --- /dev/null +++ b/drivers/gpu/drm/radeon/atombios_dp.c @@ -0,0 +1,275 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include "drmP.h" +#include "radeon_drm.h" +#include "radeon.h" + +#include "atom.h" +#include "atom-bits.h" +#include "drm_dp_helper.h" + +#define DP_LINK_STATUS_SIZE 6 + +bool radeon_process_aux_ch(struct radeon_i2c_chan *chan, u8 *req_bytes, + int num_bytes, u8 *read_byte, + u8 read_buf_len, u8 delay) +{ + struct drm_device *dev = chan->dev; + struct radeon_device *rdev = dev->dev_private; + PROCESS_AUX_CHANNEL_TRANSACTION_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, ProcessAuxChannelTransaction); + unsigned char *base; + + memset(&args, 0, sizeof(args)); + + base = (unsigned char *)rdev->mode_info.atom_context->scratch; + + memcpy(base, req_bytes, num_bytes); + + args.lpAuxRequest = 0; + args.lpDataOut = 16; + args.ucDataOutLen = 0; + args.ucChannelID = chan->i2c_id; + args.ucDelay = delay; + + atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args); + + if (args.ucReplyStatus) { + DRM_ERROR("failed to get auxch %02x%02x %02x %02x 0x%02x %02x\n", + req_bytes[1], req_bytes[0], req_bytes[2], req_bytes[3], + chan->i2c_id, args.ucReplyStatus); + return false; + } + + if (args.ucDataOutLen && read_byte && read_buf_len) { + if (read_buf_len < args.ucDataOutLen) { + DRM_ERROR("Buffer to small for return answer %d %d\n", + read_buf_len, args.ucDataOutLen); + return false; + } + { + int len = min(read_buf_len, args.ucDataOutLen); + memcpy(read_byte, base + 16, len); + } + } + return true; +} + +int radeon_dp_encoder_service(struct radeon_device *rdev, int action, int dp_clock, + uint8_t ucconfig, uint8_t lane_num) +{ + DP_ENCODER_SERVICE_PARAMETERS args; + int index = GetIndexIntoMasterTable(COMMAND, DPEncoderService); + + memset(&args, 0, sizeof(args)); + args.ucLinkClock = dp_clock / 10; + args.ucConfig = ucconfig; + args.ucAction = action; + args.ucLaneNum = lane_num; + args.ucStatus = 0; + + atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args); + return args.ucStatus; +} + +int radeon_dp_getsinktype(struct radeon_connector *radeon_connector) +{ + struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + struct drm_device *dev = radeon_connector->base.dev; + struct radeon_device *rdev = dev->dev_private; + + return radeon_dp_encoder_service(rdev, ATOM_DP_ACTION_GET_SINK_TYPE, 0, + radeon_dig_connector->uc_i2c_id, 0); +} + +union dig_transmitter_control { + DIG_TRANSMITTER_CONTROL_PS_ALLOCATION v1; + DIG_TRANSMITTER_CONTROL_PARAMETERS_V2 v2; +}; + +bool radeon_dp_aux_native_write(struct radeon_connector *radeon_connector, uint16_t address, + uint8_t send_bytes, uint8_t *send) +{ + struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + struct drm_device *dev = radeon_connector->base.dev; + struct radeon_device *rdev = dev->dev_private; + u8 msg[20]; + u8 msg_len, dp_msg_len; + bool ret; + + dp_msg_len = 4; + msg[0] = address; + msg[1] = address >> 8; + msg[2] = AUX_NATIVE_WRITE << 4; + dp_msg_len += send_bytes; + msg[3] = (dp_msg_len << 4) | (send_bytes - 1); + + if (send_bytes > 16) + return false; + + memcpy(&msg[4], send, send_bytes); + msg_len = 4 + send_bytes; + ret = radeon_process_aux_ch(radeon_dig_connector->dp_i2c_bus, msg, msg_len, NULL, 0, 0); + return ret; +} + +bool radeon_dp_aux_native_read(struct radeon_connector *radeon_connector, uint16_t address, + uint8_t delay, uint8_t expected_bytes, + uint8_t *read_p) +{ + struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + struct drm_device *dev = radeon_connector->base.dev; + struct radeon_device *rdev = dev->dev_private; + u8 msg[20]; + u8 msg_len, dp_msg_len; + bool ret = false; + msg_len = 4; + dp_msg_len = 4; + msg[0] = address; + msg[1] = address >> 8; + msg[2] = AUX_NATIVE_READ << 4; + msg[3] = (dp_msg_len) << 4; + msg[3] |= expected_bytes - 1; + + ret = radeon_process_aux_ch(radeon_dig_connector->dp_i2c_bus, msg, msg_len, read_p, expected_bytes, delay); + return ret; +} + +void radeon_dp_getdpcp(struct radeon_connector *radeon_connector) +{ + struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + u8 msg[25]; + int ret; + + ret = radeon_dp_aux_native_read(radeon_connector, DP_DPCP_REV, 0, 8, msg); + if (ret) { + memcpy(radeon_dig_connector->dpcp, msg, 8); + { + int i; + printk("DPCP: "); + for (i = 0; i < 8; i++) + printk("%02x ", msg[i]); + printk("\n"); + } + } + radeon_dig_connector->dpcp[0] = 0; + return; +} + +static bool atom_dp_get_link_status(struct radeon_connector *radeon_connector, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + int ret; + ret = radeon_dp_aux_native_read(radeon_connector, DP_LANE0_1_STATUS, 100, + DP_LINK_STATUS_SIZE, link_status); + if (!ret) { + DRM_ERROR("displayport link status failed\n"); + return false; + } + + DRM_INFO("link status %02x %02x %02x %02x %02x %02x\n", + link_status[0], link_status[1], link_status[2], + link_status[3], link_status[4], link_status[5]); + return true; +} + +static void dp_set_power(struct radeon_connector *radeon_connector, u8 power_state) +{ + struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + if (radeon_dig_connector->dpcp[0] >= 0x11) { + radeon_dp_aux_native_write(radeon_connector, 0x600, 1, + &power_state); + } +} + +static void dp_update_dpvs_emph(struct radeon_connector *radeon_connector, + u8 train_set[4]) +{ + struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + +// radeon_dp_digtransmitter_setup_vsemph(); + radeon_dp_aux_native_write(radeon_connector, DP_TRAINING_LANE0_SET, + 0/* lc */, train_set); +} + +static void dp_set_training(struct radeon_connector *radeon_connector, + u8 training) +{ + radeon_dp_aux_native_write(radeon_connector, DP_TRAINING_PATTERN_SET, + 1, &training); +} + +int radeon_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, + uint8_t write_byte, uint8_t *read_byte) +{ + struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; + struct radeon_i2c_chan *auxch = (struct radeon_i2c_chan *)adapter; + int ret = 0; + uint16_t address = algo_data->address; + uint8_t msg[5]; + uint8_t reply[2]; + int msg_len, dp_msg_len; + int reply_bytes; + + /* Set up the command byte */ + if (mode & MODE_I2C_READ) + msg[2] = AUX_I2C_READ << 4; + else + msg[2] = AUX_I2C_WRITE << 4; + + if (!(mode & MODE_I2C_STOP)) + msg[2] |= AUX_I2C_MOT << 4; + + msg[0] = address; + msg[1] = address >> 8; + + reply_bytes = 1; + + msg_len = 4; + dp_msg_len = 3; + switch (mode) { + case MODE_I2C_WRITE: + msg[4] = write_byte; + msg_len++; + dp_msg_len += 2; + break; + case MODE_I2C_READ: + dp_msg_len += 1; + break; + default: + break; + } + + msg[3] = (dp_msg_len) << 4; + ret = radeon_process_aux_ch(auxch, msg, msg_len, reply, reply_bytes, 0); + + if (ret) { + if (read_byte) + *read_byte = reply[0]; + return reply_bytes; + } + return -EREMOTEIO; +} diff --git a/drivers/gpu/drm/radeon/radeon_atombios.c b/drivers/gpu/drm/radeon/radeon_atombios.c index 5e414102c87..de05ac97647 100644 --- a/drivers/gpu/drm/radeon/radeon_atombios.c +++ b/drivers/gpu/drm/radeon/radeon_atombios.c @@ -47,7 +47,7 @@ radeon_add_atom_connector(struct drm_device *dev, int connector_type, struct radeon_i2c_bus_rec *i2c_bus, bool linkb, uint32_t igp_lane_info, - uint16_t connector_object_id); + uint16_t connector_object_id, uint8_t uc_i2c_id); /* from radeon_legacy_encoder.c */ extern void @@ -60,8 +60,8 @@ union atom_supported_devices { struct _ATOM_SUPPORTED_DEVICES_INFO_2d1 info_2d1; }; -static inline struct radeon_i2c_bus_rec radeon_lookup_gpio(struct drm_device - *dev, uint8_t id) +static inline struct radeon_i2c_bus_rec radeon_lookup_gpio(struct drm_device *dev, + uint8_t id) { struct radeon_device *rdev = dev->dev_private; struct atom_context *ctx = rdev->mode_info.atom_context; @@ -276,7 +276,7 @@ bool radeon_get_atom_connector_info_from_object_table(struct drm_device *dev) uint16_t igp_lane_info, conn_id, connector_object_id; bool linkb; struct radeon_i2c_bus_rec ddc_bus; - + ATOM_I2C_ID_CONFIG_ACCESS i2c_id; atom_parse_data_header(ctx, index, &size, &frev, &crev, &data_offset); if (data_offset == 0) @@ -302,7 +302,7 @@ bool radeon_get_atom_connector_info_from_object_table(struct drm_device *dev) path = (ATOM_DISPLAY_OBJECT_PATH *) addr; path_size += le16_to_cpu(path->usSize); linkb = false; - + i2c_id.ucAccess = 0; if (device_support & le16_to_cpu(path->usDeviceTag)) { uint8_t con_obj_id, con_obj_num, con_obj_type; @@ -420,7 +420,7 @@ bool radeon_get_atom_connector_info_from_object_table(struct drm_device *dev) asObjects[j]. usRecordOffset)); ATOM_I2C_RECORD *i2c_record; - + while (record->ucRecordType > 0 && record-> ucRecordType <= @@ -431,6 +431,7 @@ bool radeon_get_atom_connector_info_from_object_table(struct drm_device *dev) i2c_record = (ATOM_I2C_RECORD *) record; + i2c_id.sbfAccess = i2c_record->sucI2cId; line_mux = i2c_record-> sucI2cId. @@ -473,7 +474,7 @@ bool radeon_get_atom_connector_info_from_object_table(struct drm_device *dev) usDeviceTag), connector_type, &ddc_bus, linkb, igp_lane_info, - connector_object_id); + connector_object_id, i2c_id.ucAccess); } } @@ -692,7 +693,7 @@ bool radeon_get_atom_connector_info_from_supported_devices_table(struct connector_type, &bios_connectors[i].ddc_bus, false, 0, - connector_object_id); + connector_object_id, 0); } } diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index 7ab3c501b4d..733427555ee 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -896,6 +896,54 @@ struct drm_connector_funcs radeon_dvi_connector_funcs = { .force = radeon_dvi_force, }; +static int radeon_dp_get_modes(struct drm_connector *connector) +{ + struct radeon_connector *radeon_connector = to_radeon_connector(connector); + int ret; + + ret = radeon_ddc_get_modes(radeon_connector); + return ret; +} + +static enum drm_connector_status radeon_dp_detect(struct drm_connector *connector) +{ + struct radeon_connector *radeon_connector = to_radeon_connector(connector); + struct drm_encoder *encoder = NULL; + struct drm_encoder_helper_funcs *encoder_funcs; + struct drm_mode_object *obj; + int i; + enum drm_connector_status ret = connector_status_disconnected; + int sink_type; + bool dret; + + if (radeon_connector->edid) { + kfree(radeon_connector->edid); + radeon_connector->edid = NULL; + } + + sink_type = radeon_dp_getsinktype(radeon_connector); + if (sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) { + radeon_dp_getdpcp(radeon_connector); + ret = connector_status_connected; + } + return ret; +} + +struct drm_connector_helper_funcs radeon_dp_connector_helper_funcs = { + .get_modes = radeon_dp_get_modes, + .mode_valid = radeon_dvi_mode_valid, + .best_encoder = radeon_dvi_encoder, +}; + +struct drm_connector_funcs radeon_dp_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = radeon_dp_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = radeon_connector_set_property, + .destroy = radeon_connector_destroy, + .force = radeon_dvi_force, +}; + void radeon_add_atom_connector(struct drm_device *dev, uint32_t connector_id, @@ -904,7 +952,7 @@ radeon_add_atom_connector(struct drm_device *dev, struct radeon_i2c_bus_rec *i2c_bus, bool linkb, uint32_t igp_lane_info, - uint16_t connector_object_id) + uint16_t connector_object_id, uint8_t uc_i2c_id) { struct radeon_device *rdev = dev->dev_private; struct drm_connector *connector; @@ -1030,10 +1078,13 @@ radeon_add_atom_connector(struct drm_device *dev, radeon_dig_connector->linkb = linkb; radeon_dig_connector->igp_lane_info = igp_lane_info; radeon_connector->con_priv = radeon_dig_connector; - drm_connector_init(dev, &radeon_connector->base, &radeon_dvi_connector_funcs, connector_type); - ret = drm_connector_helper_add(&radeon_connector->base, &radeon_dvi_connector_helper_funcs); + drm_connector_init(dev, &radeon_connector->base, &radeon_dp_connector_funcs, connector_type); + ret = drm_connector_helper_add(&radeon_connector->base, &radeon_dp_connector_helper_funcs); if (ret) goto failed; + /* add DP i2c bus */ + radeon_dig_connector->uc_i2c_id = uc_i2c_id; + radeon_dig_connector->dp_i2c_bus = radeon_i2c_create_dp(dev, "DP-auxch", true, uc_i2c_id); if (i2c_bus->valid) { radeon_connector->ddc_bus = radeon_i2c_create(dev, i2c_bus, "DP"); if (!radeon_connector->ddc_bus) diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c index 62b02372cb0..a1c2804b694 100644 --- a/drivers/gpu/drm/radeon/radeon_display.c +++ b/drivers/gpu/drm/radeon/radeon_display.c @@ -337,6 +337,13 @@ int radeon_ddc_get_modes(struct radeon_connector *radeon_connector) { int ret = 0; + if (radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + struct radeon_connector_atom_dig *dig = radeon_connector->con_priv; + if (dig->dp_i2c_bus) { + radeon_connector->edid = drm_get_edid(&radeon_connector->base, &dig->dp_i2c_bus->adapter); + DRM_INFO("got edid %p from DP\n", radeon_connector->edid); + } + } if (!radeon_connector->ddc_bus) return -1; if (!radeon_connector->edid) { diff --git a/drivers/gpu/drm/radeon/radeon_encoders.c b/drivers/gpu/drm/radeon/radeon_encoders.c index 291f6dd3683..37f5ea1af96 100644 --- a/drivers/gpu/drm/radeon/radeon_encoders.c +++ b/drivers/gpu/drm/radeon/radeon_encoders.c @@ -850,6 +850,99 @@ atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action) } +static void +atombios_dig_transmitter_setup_vsemph(struct drm_encoder *encoder, u8 lane_num, + u8 lane_set) +{ + struct drm_device *dev = encoder->dev; + struct radeon_device *rdev = dev->dev_private; + struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder); + union dig_transmitter_control args; + int index = 0, num = 0; + uint8_t frev, crev; + struct radeon_encoder_atom_dig *dig; + struct drm_connector *connector; + struct radeon_connector *radeon_connector; + struct radeon_connector_atom_dig *dig_connector; + + connector = radeon_get_connector_for_encoder(encoder); + if (!connector) + return; + + radeon_connector = to_radeon_connector(connector); + + if (!radeon_encoder->enc_priv) + return; + + dig = radeon_encoder->enc_priv; + + if (!radeon_connector->con_priv) + return; + + dig_connector = radeon_connector->con_priv; + + memset(&args, 0, sizeof(args)); + + if (ASIC_IS_DCE32(rdev)) + index = GetIndexIntoMasterTable(COMMAND, UNIPHYTransmitterControl); + else { + switch (radeon_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + index = GetIndexIntoMasterTable(COMMAND, DIG1TransmitterControl); + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + index = GetIndexIntoMasterTable(COMMAND, DIG2TransmitterControl); + break; + } + } + + atom_parse_cmd_header(rdev->mode_info.atom_context, index, &frev, &crev); + + args.v1.ucAction = ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH; + args.v1.asMode.ucLaneSel = lane_num; + args.v1.asMode.ucLaneSet = lane_set; + + if (ASIC_IS_DCE32(rdev)) { + args.v2.acConfig.fDPConnector = 1; + + if (dig->dig_block) + args.v2.acConfig.ucEncoderSel = 1; + + switch (radeon_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + args.v2.acConfig.ucTransmitterSel = 0; + num = 0; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + args.v2.acConfig.ucTransmitterSel = 1; + num = 1; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + args.v2.acConfig.ucTransmitterSel = 2; + num = 2; + break; + } + } else { + args.v1.ucConfig = ATOM_TRANSMITTER_CONFIG_CLKSRC_PPLL; + + switch (radeon_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER; + if (dig_connector->linkb) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB | ATOM_TRANSMITTER_CONFIG_LANE_0_3; + else + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA | ATOM_TRANSMITTER_CONFIG_LANE_0_3; + } + } + + atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args); + + if (ASIC_IS_DCE32(rdev)) + DRM_INFO("Output UNIPHY%d transmitter VSEMPH setup success\n", num); + else + DRM_INFO("Output DIG%d transmitter VSEMPH setup success\n", num); +} + static void atombios_yuv_setup(struct drm_encoder *encoder, bool enable) { diff --git a/drivers/gpu/drm/radeon/radeon_i2c.c b/drivers/gpu/drm/radeon/radeon_i2c.c index 6c645fb4dad..f200312dd5d 100644 --- a/drivers/gpu/drm/radeon/radeon_i2c.c +++ b/drivers/gpu/drm/radeon/radeon_i2c.c @@ -172,20 +172,19 @@ struct radeon_i2c_chan *radeon_i2c_create(struct drm_device *dev, return NULL; i2c->adapter.owner = THIS_MODULE; - i2c->adapter.algo_data = &i2c->algo; i2c->dev = dev; - i2c->algo.setsda = set_data; - i2c->algo.setscl = set_clock; - i2c->algo.getsda = get_data; - i2c->algo.getscl = get_clock; - i2c->algo.udelay = 20; + i2c_set_adapdata(&i2c->adapter, i2c); + i2c->adapter.algo_data = &i2c->algo.bit; + i2c->algo.bit.setsda = set_data; + i2c->algo.bit.setscl = set_clock; + i2c->algo.bit.getsda = get_data; + i2c->algo.bit.getscl = get_clock; + i2c->algo.bit.udelay = 20; /* vesa says 2.2 ms is enough, 1 jiffy doesn't seem to always * make this, 2 jiffies is a lot more reliable */ - i2c->algo.timeout = 2; - i2c->algo.data = i2c; + i2c->algo.bit.timeout = 2; + i2c->algo.bit.data = i2c; i2c->rec = *rec; - i2c_set_adapdata(&i2c->adapter, i2c); - ret = i2c_bit_add_bus(&i2c->adapter); if (ret) { DRM_INFO("Failed to register i2c %s\n", name); @@ -199,6 +198,37 @@ out_free: } +struct radeon_i2c_chan *radeon_i2c_create_dp(struct drm_device *dev, + const char *name, bool dp, u8 i2c_id) +{ + struct radeon_i2c_chan *i2c; + int ret; + + i2c = kzalloc(sizeof(struct radeon_i2c_chan), GFP_KERNEL); + if (i2c == NULL) + return NULL; + + i2c->i2c_id = i2c_id; + i2c->adapter.owner = THIS_MODULE; + i2c->dev = dev; + i2c_set_adapdata(&i2c->adapter, i2c); + i2c->adapter.algo_data = &i2c->algo.dp; + i2c->algo.dp.aux_ch = radeon_dp_i2c_aux_ch; + i2c->algo.dp.address = 0; + ret = i2c_dp_aux_add_bus(&i2c->adapter); + if (ret) { + DRM_INFO("Failed to register i2c %s\n", name); + goto out_free; + } + + return i2c; +out_free: + kfree(i2c); + return NULL; + +} + + void radeon_i2c_destroy(struct radeon_i2c_chan *i2c) { if (!i2c) diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index 135693d5437..ce1cdc748f1 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -164,10 +165,14 @@ struct radeon_pll { }; struct radeon_i2c_chan { - struct drm_device *dev; struct i2c_adapter adapter; - struct i2c_algo_bit_data algo; + struct drm_device *dev; + union { + struct i2c_algo_dp_aux_data dp; + struct i2c_algo_bit_data bit; + } algo; struct radeon_i2c_bus_rec rec; + uint8_t i2c_id; }; /* mostly for macs, but really any system without connector tables */ @@ -328,6 +333,9 @@ struct radeon_encoder { struct radeon_connector_atom_dig { uint32_t igp_lane_info; bool linkb; + uint16_t uc_i2c_id; + struct radeon_i2c_chan *dp_i2c_bus; + u8 dpcp[8]; }; struct radeon_connector { @@ -344,6 +352,8 @@ struct radeon_connector { void *con_priv; bool dac_load_detect; uint16_t connector_object_id; + /* need to keep this for display port */ +// }; struct radeon_framebuffer { @@ -351,6 +361,13 @@ struct radeon_framebuffer { struct drm_gem_object *obj; }; +extern int radeon_dp_getsinktype(struct radeon_connector *radeon_connector); +extern void radeon_dp_getdpcp(struct radeon_connector *connector); +extern int radeon_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, + uint8_t write_byte, uint8_t *read_byte); + +extern struct radeon_i2c_chan *radeon_i2c_create_dp(struct drm_device *dev, + const char *name, bool dp, u8 i2c_id); extern struct radeon_i2c_chan *radeon_i2c_create(struct drm_device *dev, struct radeon_i2c_bus_rec *rec, const char *name); diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h index e49879ce95f..376155f8f81 100644 --- a/include/drm/drm_dp_helper.h +++ b/include/drm/drm_dp_helper.h @@ -43,6 +43,8 @@ #define AUX_I2C_REPLY_MASK (0x3 << 6) /* AUX CH addresses */ +#define DP_DPCP_REV 0x0 + #define DP_LINK_BW_SET 0x100 # define DP_LINK_BW_1_62 0x06 # define DP_LINK_BW_2_7 0x0a -- cgit v1.2.3-70-g09d2 From 1a66c95a64c9ae0bc8382254f544b24b23f498ec Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 20 Nov 2009 19:40:13 -0500 Subject: drm/radeon/kms: DP fixes and cleanup from the ddx - dpcp -> dpcd - fix up dig encoder routing - aux transaction table takes delay in 10 usec units Signed-off-by: Alex Deucher Signed-off-by: Dave Airlie --- drivers/gpu/drm/radeon/atombios_dp.c | 24 ++-- drivers/gpu/drm/radeon/radeon_connectors.c | 2 +- drivers/gpu/drm/radeon/radeon_encoders.c | 221 ++++++++++++----------------- drivers/gpu/drm/radeon/radeon_mode.h | 4 +- include/drm/drm_dp_helper.h | 4 +- 5 files changed, 105 insertions(+), 150 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/radeon/atombios_dp.c b/drivers/gpu/drm/radeon/atombios_dp.c index a4bc8011338..d1c144be973 100644 --- a/drivers/gpu/drm/radeon/atombios_dp.c +++ b/drivers/gpu/drm/radeon/atombios_dp.c @@ -34,7 +34,7 @@ #define DP_LINK_STATUS_SIZE 6 bool radeon_process_aux_ch(struct radeon_i2c_chan *chan, u8 *req_bytes, - int num_bytes, u8 *read_byte, + int num_bytes, u8 *read_byte, u8 read_buf_len, u8 delay) { struct drm_device *dev = chan->dev; @@ -42,9 +42,9 @@ bool radeon_process_aux_ch(struct radeon_i2c_chan *chan, u8 *req_bytes, PROCESS_AUX_CHANNEL_TRANSACTION_PS_ALLOCATION args; int index = GetIndexIntoMasterTable(COMMAND, ProcessAuxChannelTransaction); unsigned char *base; - + memset(&args, 0, sizeof(args)); - + base = (unsigned char *)rdev->mode_info.atom_context->scratch; memcpy(base, req_bytes, num_bytes); @@ -53,7 +53,7 @@ bool radeon_process_aux_ch(struct radeon_i2c_chan *chan, u8 *req_bytes, args.lpDataOut = 16; args.ucDataOutLen = 0; args.ucChannelID = chan->i2c_id; - args.ucDelay = delay; + args.ucDelay = delay / 10; atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args); @@ -158,24 +158,24 @@ bool radeon_dp_aux_native_read(struct radeon_connector *radeon_connector, uint16 return ret; } -void radeon_dp_getdpcp(struct radeon_connector *radeon_connector) +void radeon_dp_getdpcd(struct radeon_connector *radeon_connector) { struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; u8 msg[25]; int ret; - ret = radeon_dp_aux_native_read(radeon_connector, DP_DPCP_REV, 0, 8, msg); + ret = radeon_dp_aux_native_read(radeon_connector, DP_DPCD_REV, 0, 8, msg); if (ret) { - memcpy(radeon_dig_connector->dpcp, msg, 8); - { + memcpy(radeon_dig_connector->dpcd, msg, 8); + { int i; - printk("DPCP: "); + printk("DPCD: "); for (i = 0; i < 8; i++) printk("%02x ", msg[i]); printk("\n"); } } - radeon_dig_connector->dpcp[0] = 0; + radeon_dig_connector->dpcd[0] = 0; return; } @@ -199,8 +199,8 @@ static bool atom_dp_get_link_status(struct radeon_connector *radeon_connector, static void dp_set_power(struct radeon_connector *radeon_connector, u8 power_state) { struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; - if (radeon_dig_connector->dpcp[0] >= 0x11) { - radeon_dp_aux_native_write(radeon_connector, 0x600, 1, + if (radeon_dig_connector->dpcd[0] >= 0x11) { + radeon_dp_aux_native_write(radeon_connector, DP_SET_POWER, 1, &power_state); } } diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index 733427555ee..4d457bc9014 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -923,7 +923,7 @@ static enum drm_connector_status radeon_dp_detect(struct drm_connector *connecto sink_type = radeon_dp_getsinktype(radeon_connector); if (sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) { - radeon_dp_getdpcp(radeon_connector); + radeon_dp_getdpcd(radeon_connector); ret = connector_status_connected; } return ret; diff --git a/drivers/gpu/drm/radeon/radeon_encoders.c b/drivers/gpu/drm/radeon/radeon_encoders.c index 37f5ea1af96..b4e7abadbfb 100644 --- a/drivers/gpu/drm/radeon/radeon_encoders.c +++ b/drivers/gpu/drm/radeon/radeon_encoders.c @@ -605,6 +605,30 @@ atombios_get_encoder_mode(struct drm_encoder *encoder) } } +/* + * DIG Encoder/Transmitter Setup + * + * DCE 3.0/3.1 + * - 2 DIG transmitter blocks. UNIPHY (links A and B) and LVTMA. + * Supports up to 3 digital outputs + * - 2 DIG encoder blocks. + * DIG1 can drive UNIPHY link A or link B + * DIG2 can drive UNIPHY link B or LVTMA + * + * DCE 3.2 + * - 3 DIG transmitter blocks. UNIPHY0/1/2 (links A and B). + * Supports up to 5 digital outputs + * - 2 DIG encoder blocks. + * DIG1/2 can drive UNIPHY0/1/2 link A or link B + * + * Routing + * crtc -> dig encoder -> UNIPHY/LVTMA (1 or 2 links) + * Examples: + * crtc0 -> dig2 -> LVTMA links A+B -> TMDS/HDMI + * crtc1 -> dig1 -> UNIPHY0 link B -> DP + * crtc0 -> dig1 -> UNIPHY2 link A -> LVDS + * crtc1 -> dig2 -> UNIPHY1 link B+A -> TMDS/HDMI + */ static void atombios_dig_encoder_setup(struct drm_encoder *encoder, int action) { @@ -646,10 +670,17 @@ atombios_dig_encoder_setup(struct drm_encoder *encoder, int action) } else { switch (radeon_encoder->encoder_id) { case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: - index = GetIndexIntoMasterTable(COMMAND, DIG1EncoderControl); + /* XXX doesn't really matter which dig encoder we pick as long as it's + * not already in use + */ + if (dig_connector->linkb) + index = GetIndexIntoMasterTable(COMMAND, DIG2EncoderControl); + else + index = GetIndexIntoMasterTable(COMMAND, DIG1EncoderControl); num = 1; break; case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + /* Only dig2 encoder can drive LVTMA */ index = GetIndexIntoMasterTable(COMMAND, DIG2EncoderControl); num = 2; break; @@ -684,16 +715,15 @@ atombios_dig_encoder_setup(struct drm_encoder *encoder, int action) } } - if (radeon_encoder->pixel_clock > 165000) { - args.ucConfig |= ATOM_ENCODER_CONFIG_LINKA_B; + if (radeon_encoder->pixel_clock > 165000) args.ucLaneNum = 8; - } else { - if (dig_connector->linkb) - args.ucConfig |= ATOM_ENCODER_CONFIG_LINKB; - else - args.ucConfig |= ATOM_ENCODER_CONFIG_LINKA; + else args.ucLaneNum = 4; - } + + if (dig_connector->linkb) + args.ucConfig |= ATOM_ENCODER_CONFIG_LINKB; + else + args.ucConfig |= ATOM_ENCODER_CONFIG_LINKA; args.ucEncoderMode = atombios_get_encoder_mode(encoder); @@ -707,7 +737,7 @@ union dig_transmitter_control { }; static void -atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action) +atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action, uint8_t lane_num, uint8_t lane_set) { struct drm_device *dev = encoder->dev; struct radeon_device *rdev = dev->dev_private; @@ -756,6 +786,9 @@ atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action) args.v1.ucAction = action; if (action == ATOM_TRANSMITTER_ACTION_INIT) { args.v1.usInitInfo = radeon_connector->connector_object_id; + } else if (action == ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH) { + args.v1.asMode.ucLaneSel = lane_num; + args.v1.asMode.ucLaneSet = lane_set; } else { if (radeon_encoder->pixel_clock > 165000) args.v1.usPixelClock = cpu_to_le16((radeon_encoder->pixel_clock / 2) / 10); @@ -767,6 +800,8 @@ atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action) args.v2.usPixelClock = cpu_to_le16((radeon_encoder->pixel_clock / 2) / 10); if (dig->dig_block) args.v2.acConfig.ucEncoderSel = 1; + if (dig_connector->linkb) + args.v2.acConfig.ucLinkSel = 1; switch (radeon_encoder->encoder_id) { case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: @@ -792,17 +827,20 @@ atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action) switch (radeon_encoder->encoder_id) { case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER; + /* XXX doesn't really matter which dig encoder we pick as long as it's + * not already in use + */ + if (dig_connector->linkb) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG2_ENCODER; + else + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER; if (rdev->flags & RADEON_IS_IGP) { if (radeon_encoder->pixel_clock > 165000) { - args.v1.ucConfig |= (ATOM_TRANSMITTER_CONFIG_8LANE_LINK | - ATOM_TRANSMITTER_CONFIG_LINKA_B); if (dig_connector->igp_lane_info & 0x3) args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_0_7; else if (dig_connector->igp_lane_info & 0xc) args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_8_15; } else { - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA; if (dig_connector->igp_lane_info & 0x1) args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_0_3; else if (dig_connector->igp_lane_info & 0x2) @@ -812,34 +850,22 @@ atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action) else if (dig_connector->igp_lane_info & 0x8) args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_12_15; } - } else { - if (radeon_encoder->pixel_clock > 165000) - args.v1.ucConfig |= (ATOM_TRANSMITTER_CONFIG_8LANE_LINK | - ATOM_TRANSMITTER_CONFIG_LINKA_B | - ATOM_TRANSMITTER_CONFIG_LANE_0_7); - else { - if (dig_connector->linkb) - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB | ATOM_TRANSMITTER_CONFIG_LANE_0_3; - else - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA | ATOM_TRANSMITTER_CONFIG_LANE_0_3; - } } break; case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + /* Only dig2 encoder can drive LVTMA */ args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG2_ENCODER; - if (radeon_encoder->pixel_clock > 165000) - args.v1.ucConfig |= (ATOM_TRANSMITTER_CONFIG_8LANE_LINK | - ATOM_TRANSMITTER_CONFIG_LINKA_B | - ATOM_TRANSMITTER_CONFIG_LANE_0_7); - else { - if (dig_connector->linkb) - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB | ATOM_TRANSMITTER_CONFIG_LANE_0_3; - else - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA | ATOM_TRANSMITTER_CONFIG_LANE_0_3; - } break; } + if (radeon_encoder->pixel_clock > 165000) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_8LANE_LINK; + + if (dig_connector->linkb) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB; + else + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA; + if (radeon_encoder->devices & (ATOM_DEVICE_DFP_SUPPORT)) { if (dig->coherent_mode) args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_COHERENT; @@ -850,99 +876,6 @@ atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action) } -static void -atombios_dig_transmitter_setup_vsemph(struct drm_encoder *encoder, u8 lane_num, - u8 lane_set) -{ - struct drm_device *dev = encoder->dev; - struct radeon_device *rdev = dev->dev_private; - struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder); - union dig_transmitter_control args; - int index = 0, num = 0; - uint8_t frev, crev; - struct radeon_encoder_atom_dig *dig; - struct drm_connector *connector; - struct radeon_connector *radeon_connector; - struct radeon_connector_atom_dig *dig_connector; - - connector = radeon_get_connector_for_encoder(encoder); - if (!connector) - return; - - radeon_connector = to_radeon_connector(connector); - - if (!radeon_encoder->enc_priv) - return; - - dig = radeon_encoder->enc_priv; - - if (!radeon_connector->con_priv) - return; - - dig_connector = radeon_connector->con_priv; - - memset(&args, 0, sizeof(args)); - - if (ASIC_IS_DCE32(rdev)) - index = GetIndexIntoMasterTable(COMMAND, UNIPHYTransmitterControl); - else { - switch (radeon_encoder->encoder_id) { - case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: - index = GetIndexIntoMasterTable(COMMAND, DIG1TransmitterControl); - break; - case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: - index = GetIndexIntoMasterTable(COMMAND, DIG2TransmitterControl); - break; - } - } - - atom_parse_cmd_header(rdev->mode_info.atom_context, index, &frev, &crev); - - args.v1.ucAction = ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH; - args.v1.asMode.ucLaneSel = lane_num; - args.v1.asMode.ucLaneSet = lane_set; - - if (ASIC_IS_DCE32(rdev)) { - args.v2.acConfig.fDPConnector = 1; - - if (dig->dig_block) - args.v2.acConfig.ucEncoderSel = 1; - - switch (radeon_encoder->encoder_id) { - case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: - args.v2.acConfig.ucTransmitterSel = 0; - num = 0; - break; - case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: - args.v2.acConfig.ucTransmitterSel = 1; - num = 1; - break; - case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: - args.v2.acConfig.ucTransmitterSel = 2; - num = 2; - break; - } - } else { - args.v1.ucConfig = ATOM_TRANSMITTER_CONFIG_CLKSRC_PPLL; - - switch (radeon_encoder->encoder_id) { - case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER; - if (dig_connector->linkb) - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB | ATOM_TRANSMITTER_CONFIG_LANE_0_3; - else - args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA | ATOM_TRANSMITTER_CONFIG_LANE_0_3; - } - } - - atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args); - - if (ASIC_IS_DCE32(rdev)) - DRM_INFO("Output UNIPHY%d transmitter VSEMPH setup success\n", num); - else - DRM_INFO("Output DIG%d transmitter VSEMPH setup success\n", num); -} - static void atombios_yuv_setup(struct drm_encoder *encoder, bool enable) { @@ -1150,13 +1083,33 @@ atombios_set_encoder_crtc_source(struct drm_encoder *encoder) args.v2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID; else args.v2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID; - } else - args.v2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID; + } else { + struct drm_connector *connector; + struct radeon_connector *radeon_connector; + struct radeon_connector_atom_dig *dig_connector; + + connector = radeon_get_connector_for_encoder(encoder); + if (!connector) + return; + radeon_connector = to_radeon_connector(connector); + if (!radeon_connector->con_priv) + return; + dig_connector = radeon_connector->con_priv; + + /* XXX doesn't really matter which dig encoder we pick as long as it's + * not already in use + */ + if (dig_connector->linkb) + args.v2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID; + else + args.v2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID; + } break; case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: args.v2.ucEncoderID = ASIC_INT_DVO_ENCODER_ID; break; case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + /* Only dig2 encoder can drive LVTMA */ args.v2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID; break; case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1: @@ -1259,14 +1212,14 @@ radeon_atom_encoder_mode_set(struct drm_encoder *encoder, case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: /* disable the encoder and transmitter */ - atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_DISABLE); + atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_DISABLE, 0, 0); atombios_dig_encoder_setup(encoder, ATOM_DISABLE); /* setup and enable the encoder and transmitter */ atombios_dig_encoder_setup(encoder, ATOM_ENABLE); - atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_INIT); - atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_SETUP); - atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_ENABLE); + atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_INIT, 0, 0); + atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_SETUP, 0, 0); + atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_ENABLE, 0, 0); break; case ENCODER_OBJECT_ID_INTERNAL_DDI: atombios_ddia_setup(encoder, ATOM_ENABLE); diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index ce1cdc748f1..166f75395f5 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -335,7 +335,7 @@ struct radeon_connector_atom_dig { bool linkb; uint16_t uc_i2c_id; struct radeon_i2c_chan *dp_i2c_bus; - u8 dpcp[8]; + u8 dpcd[8]; }; struct radeon_connector { @@ -362,7 +362,7 @@ struct radeon_framebuffer { }; extern int radeon_dp_getsinktype(struct radeon_connector *radeon_connector); -extern void radeon_dp_getdpcp(struct radeon_connector *connector); +extern void radeon_dp_getdpcd(struct radeon_connector *connector); extern int radeon_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, uint8_t write_byte, uint8_t *read_byte); diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h index 376155f8f81..f09b0b2a99b 100644 --- a/include/drm/drm_dp_helper.h +++ b/include/drm/drm_dp_helper.h @@ -43,7 +43,7 @@ #define AUX_I2C_REPLY_MASK (0x3 << 6) /* AUX CH addresses */ -#define DP_DPCP_REV 0x0 +#define DP_DPCD_REV 0x0 #define DP_LINK_BW_SET 0x100 # define DP_LINK_BW_1_62 0x06 @@ -132,6 +132,8 @@ #define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0 #define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6 +#define DP_SET_POWER 0x600 + #define MODE_I2C_START 1 #define MODE_I2C_WRITE 2 #define MODE_I2C_READ 4 -- cgit v1.2.3-70-g09d2 From 5801ead6bd6bddf5505d6eab55f84d8ee8106cd8 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 24 Nov 2009 13:32:59 -0500 Subject: drm/radeon/kms: add support for DP modesetting Signed-off-by: Alex Deucher Signed-off-by: Dave Airlie --- drivers/gpu/drm/radeon/atom.c | 25 ++ drivers/gpu/drm/radeon/atombios_dp.c | 541 ++++++++++++++++++++++++++--- drivers/gpu/drm/radeon/radeon_connectors.c | 16 +- drivers/gpu/drm/radeon/radeon_encoders.c | 30 +- drivers/gpu/drm/radeon/radeon_mode.h | 13 +- include/drm/drm_dp_helper.h | 57 ++- 6 files changed, 600 insertions(+), 82 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/radeon/atom.c b/drivers/gpu/drm/radeon/atom.c index 85abc0850e7..6578d19dff9 100644 --- a/drivers/gpu/drm/radeon/atom.c +++ b/drivers/gpu/drm/radeon/atom.c @@ -1214,3 +1214,28 @@ void atom_parse_cmd_header(struct atom_context *ctx, int index, uint8_t * frev, *crev = CU8(idx + 3); return; } + +int atom_allocate_fb_scratch(struct atom_context *ctx) +{ + int index = GetIndexIntoMasterTable(DATA, VRAM_UsageByFirmware); + uint16_t data_offset; + int usage_bytes; + struct _ATOM_VRAM_USAGE_BY_FIRMWARE *firmware_usage; + + atom_parse_data_header(ctx, index, NULL, NULL, NULL, &data_offset); + + firmware_usage = (struct _ATOM_VRAM_USAGE_BY_FIRMWARE *)(ctx->bios + data_offset); + + DRM_DEBUG("atom firmware requested %08x %dkb\n", + firmware_usage->asFirmwareVramReserveInfo[0].ulStartAddrUsedByFirmware, + firmware_usage->asFirmwareVramReserveInfo[0].usFirmwareUseInKb); + + usage_bytes = firmware_usage->asFirmwareVramReserveInfo[0].usFirmwareUseInKb * 1024; + if (usage_bytes == 0) + usage_bytes = 20 * 1024; + /* allocate some scratch memory */ + ctx->scratch = kzalloc(usage_bytes, GFP_KERNEL); + if (!ctx->scratch) + return -ENOMEM; + return 0; +} diff --git a/drivers/gpu/drm/radeon/atombios_dp.c b/drivers/gpu/drm/radeon/atombios_dp.c index 76eb5c8a701..ebaf3f8cd60 100644 --- a/drivers/gpu/drm/radeon/atombios_dp.c +++ b/drivers/gpu/drm/radeon/atombios_dp.c @@ -31,9 +31,20 @@ #include "atom-bits.h" #include "drm_dp_helper.h" -#define DP_LINK_STATUS_SIZE 6 - /* move these to drm_dp_helper.c/h */ +#define DP_LINK_CONFIGURATION_SIZE 9 +#define DP_LINK_STATUS_SIZE 6 +#define DP_DPCD_SIZE 8 + +static char *voltage_names[] = { + "0.4V", "0.6V", "0.8V", "1.2V" +}; +static char *pre_emph_names[] = { + "0dB", "3.5dB", "6dB", "9.5dB" +}; +static char *link_train_names[] = { + "pattern 1", "pattern 2", "idle", "off" +}; static const int dp_clocks[] = { 54000, // 1 lane, 1.62 Ghz @@ -46,9 +57,18 @@ static const int dp_clocks[] = { static const int num_dp_clocks = sizeof(dp_clocks) / sizeof(int); -int dp_lanes_for_mode_clock(int max_link_bw, int mode_clock) +/* common helper functions */ +static int dp_lanes_for_mode_clock(u8 dpcd[DP_DPCD_SIZE], int mode_clock) { int i; + u8 max_link_bw; + u8 max_lane_count; + + if (!dpcd) + return 0; + + max_link_bw = dpcd[DP_MAX_LINK_RATE]; + max_lane_count = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK; switch (max_link_bw) { case DP_LINK_BW_1_62: @@ -56,6 +76,19 @@ int dp_lanes_for_mode_clock(int max_link_bw, int mode_clock) for (i = 0; i < num_dp_clocks; i++) { if (i % 2) continue; + switch (max_lane_count) { + case 1: + if (i > 1) + return 0; + break; + case 2: + if (i > 3) + return 0; + break; + case 4: + default: + break; + } if (dp_clocks[i] > mode_clock) { if (i < 2) return 1; @@ -68,6 +101,19 @@ int dp_lanes_for_mode_clock(int max_link_bw, int mode_clock) break; case DP_LINK_BW_2_7: for (i = 0; i < num_dp_clocks; i++) { + switch (max_lane_count) { + case 1: + if (i > 1) + return 0; + break; + case 2: + if (i > 3) + return 0; + break; + case 4: + default: + break; + } if (dp_clocks[i] > mode_clock) { if (i < 2) return 1; @@ -83,17 +129,56 @@ int dp_lanes_for_mode_clock(int max_link_bw, int mode_clock) return 0; } -int dp_link_clock_for_mode_clock(int max_link_bw, int mode_clock) +static int dp_link_clock_for_mode_clock(u8 dpcd[DP_DPCD_SIZE], int mode_clock) { int i; + u8 max_link_bw; + u8 max_lane_count; + + if (!dpcd) + return 0; + + max_link_bw = dpcd[DP_MAX_LINK_RATE]; + max_lane_count = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK; switch (max_link_bw) { case DP_LINK_BW_1_62: default: - return 162000; + for (i = 0; i < num_dp_clocks; i++) { + if (i % 2) + continue; + switch (max_lane_count) { + case 1: + if (i > 1) + return 0; + break; + case 2: + if (i > 3) + return 0; + break; + case 4: + default: + break; + } + if (dp_clocks[i] > mode_clock) + return 162000; + } break; case DP_LINK_BW_2_7: for (i = 0; i < num_dp_clocks; i++) { + switch (max_lane_count) { + case 1: + if (i > 1) + return 0; + break; + case 2: + if (i > 3) + return 0; + break; + case 4: + default: + break; + } if (dp_clocks[i] > mode_clock) return (i % 2) ? 270000 : 162000; } @@ -102,6 +187,145 @@ int dp_link_clock_for_mode_clock(int max_link_bw, int mode_clock) return 0; } +int dp_mode_valid(u8 dpcd[DP_DPCD_SIZE], int mode_clock) +{ + int lanes = dp_lanes_for_mode_clock(dpcd, mode_clock); + int bw = dp_lanes_for_mode_clock(dpcd, mode_clock); + + if ((lanes == 0) || (bw == 0)) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static u8 dp_link_status(u8 link_status[DP_LINK_STATUS_SIZE], int r) +{ + return link_status[r - DP_LANE0_1_STATUS]; +} + +static u8 dp_get_lane_status(u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_LANE0_1_STATUS + (lane >> 1); + int s = (lane & 1) * 4; + u8 l = dp_link_status(link_status, i); + return (l >> s) & 0xf; +} + +static bool dp_clock_recovery_ok(u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return false; + } + return true; +} + +static bool dp_channel_eq_ok(u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + u8 lane_align; + u8 lane_status; + int lane; + + lane_align = dp_link_status(link_status, + DP_LANE_ALIGN_STATUS_UPDATED); + if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0) + return false; + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) + return false; + } + return true; +} + +static u8 dp_get_adjust_request_voltage(uint8_t link_status[DP_LINK_STATUS_SIZE], + int lane) + +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : + DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; +} + +static u8 dp_get_adjust_request_pre_emphasis(uint8_t link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; +} + +/* XXX fix me -- chip specific */ +#define DP_VOLTAGE_MAX DP_TRAIN_VOLTAGE_SWING_1200 +static u8 dp_pre_emphasis_max(u8 voltage_swing) +{ + switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_400: + return DP_TRAIN_PRE_EMPHASIS_6; + case DP_TRAIN_VOLTAGE_SWING_600: + return DP_TRAIN_PRE_EMPHASIS_6; + case DP_TRAIN_VOLTAGE_SWING_800: + return DP_TRAIN_PRE_EMPHASIS_3_5; + case DP_TRAIN_VOLTAGE_SWING_1200: + default: + return DP_TRAIN_PRE_EMPHASIS_0; + } +} + +static void dp_get_adjust_train(u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count, + u8 train_set[4]) +{ + u8 v = 0; + u8 p = 0; + int lane; + + for (lane = 0; lane < lane_count; lane++) { + u8 this_v = dp_get_adjust_request_voltage(link_status, lane); + u8 this_p = dp_get_adjust_request_pre_emphasis(link_status, lane); + + DRM_INFO("requested signal parameters: lane %d voltage %s pre_emph %s\n", + lane, + voltage_names[this_v >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[this_p >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + if (this_v > v) + v = this_v; + if (this_p > p) + p = this_p; + } + + if (v >= DP_VOLTAGE_MAX) + v = DP_VOLTAGE_MAX | DP_TRAIN_MAX_SWING_REACHED; + + if (p >= dp_pre_emphasis_max(v)) + p = dp_pre_emphasis_max(v) | DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + DRM_INFO("using signal parameters: voltage %s pre_emph %s\n", + voltage_names[(v & DP_TRAIN_VOLTAGE_SWING_MASK) >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[(p & DP_TRAIN_PRE_EMPHASIS_MASK) >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + for (lane = 0; lane < 4; lane++) + train_set[lane] = v | p; +} + + +/* radeon aux chan functions */ bool radeon_process_aux_ch(struct radeon_i2c_chan *chan, u8 *req_bytes, int num_bytes, u8 *read_byte, u8 read_buf_len, u8 delay) @@ -147,44 +371,10 @@ bool radeon_process_aux_ch(struct radeon_i2c_chan *chan, u8 *req_bytes, return true; } -static u8 radeon_dp_encoder_service(struct radeon_device *rdev, int action, int dp_clock, - uint8_t ucconfig, uint8_t lane_num) -{ - DP_ENCODER_SERVICE_PARAMETERS args; - int index = GetIndexIntoMasterTable(COMMAND, DPEncoderService); - - memset(&args, 0, sizeof(args)); - args.ucLinkClock = dp_clock / 10; - args.ucConfig = ucconfig; - args.ucAction = action; - args.ucLaneNum = lane_num; - args.ucStatus = 0; - - atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args); - return args.ucStatus; -} - -u8 radeon_dp_getsinktype(struct radeon_connector *radeon_connector) -{ - struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; - struct drm_device *dev = radeon_connector->base.dev; - struct radeon_device *rdev = dev->dev_private; - - return radeon_dp_encoder_service(rdev, ATOM_DP_ACTION_GET_SINK_TYPE, 0, - radeon_dig_connector->dp_i2c_bus->rec.i2c_id, 0); -} - -union dig_transmitter_control { - DIG_TRANSMITTER_CONTROL_PS_ALLOCATION v1; - DIG_TRANSMITTER_CONTROL_PARAMETERS_V2 v2; -}; - bool radeon_dp_aux_native_write(struct radeon_connector *radeon_connector, uint16_t address, uint8_t send_bytes, uint8_t *send) { - struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; - struct drm_device *dev = radeon_connector->base.dev; - struct radeon_device *rdev = dev->dev_private; + struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; u8 msg[20]; u8 msg_len, dp_msg_len; bool ret; @@ -201,7 +391,7 @@ bool radeon_dp_aux_native_write(struct radeon_connector *radeon_connector, uint1 memcpy(&msg[4], send, send_bytes); msg_len = 4 + send_bytes; - ret = radeon_process_aux_ch(radeon_dig_connector->dp_i2c_bus, msg, msg_len, NULL, 0, 0); + ret = radeon_process_aux_ch(dig_connector->dp_i2c_bus, msg, msg_len, NULL, 0, 0); return ret; } @@ -209,9 +399,7 @@ bool radeon_dp_aux_native_read(struct radeon_connector *radeon_connector, uint16 uint8_t delay, uint8_t expected_bytes, uint8_t *read_p) { - struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; - struct drm_device *dev = radeon_connector->base.dev; - struct radeon_device *rdev = dev->dev_private; + struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; u8 msg[20]; u8 msg_len, dp_msg_len; bool ret = false; @@ -223,19 +411,47 @@ bool radeon_dp_aux_native_read(struct radeon_connector *radeon_connector, uint16 msg[3] = (dp_msg_len) << 4; msg[3] |= expected_bytes - 1; - ret = radeon_process_aux_ch(radeon_dig_connector->dp_i2c_bus, msg, msg_len, read_p, expected_bytes, delay); + ret = radeon_process_aux_ch(dig_connector->dp_i2c_bus, msg, msg_len, read_p, expected_bytes, delay); return ret; } +/* radeon dp functions */ +static u8 radeon_dp_encoder_service(struct radeon_device *rdev, int action, int dp_clock, + uint8_t ucconfig, uint8_t lane_num) +{ + DP_ENCODER_SERVICE_PARAMETERS args; + int index = GetIndexIntoMasterTable(COMMAND, DPEncoderService); + + memset(&args, 0, sizeof(args)); + args.ucLinkClock = dp_clock / 10; + args.ucConfig = ucconfig; + args.ucAction = action; + args.ucLaneNum = lane_num; + args.ucStatus = 0; + + atom_execute_table(rdev->mode_info.atom_context, index, (uint32_t *)&args); + return args.ucStatus; +} + +u8 radeon_dp_getsinktype(struct radeon_connector *radeon_connector) +{ + struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; + struct drm_device *dev = radeon_connector->base.dev; + struct radeon_device *rdev = dev->dev_private; + + return radeon_dp_encoder_service(rdev, ATOM_DP_ACTION_GET_SINK_TYPE, 0, + dig_connector->dp_i2c_bus->rec.i2c_id, 0); +} + void radeon_dp_getdpcd(struct radeon_connector *radeon_connector) { - struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; u8 msg[25]; int ret; ret = radeon_dp_aux_native_read(radeon_connector, DP_DPCD_REV, 0, 8, msg); if (ret) { - memcpy(radeon_dig_connector->dpcd, msg, 8); + memcpy(dig_connector->dpcd, msg, 8); { int i; printk("DPCD: "); @@ -244,10 +460,38 @@ void radeon_dp_getdpcd(struct radeon_connector *radeon_connector) printk("\n"); } } - radeon_dig_connector->dpcd[0] = 0; + dig_connector->dpcd[0] = 0; return; } +void radeon_dp_set_link_config(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct radeon_connector *radeon_connector; + struct radeon_connector_atom_dig *dig_connector; + + if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) + return; + + radeon_connector = to_radeon_connector(connector); + if (!radeon_connector->con_priv) + return; + dig_connector = radeon_connector->con_priv; + + dig_connector->dp_clock = + dp_link_clock_for_mode_clock(dig_connector->dpcd, mode->clock); + dig_connector->dp_lane_count = + dp_lanes_for_mode_clock(dig_connector->dpcd, mode->clock); +} + +int radeon_dp_mode_valid_helper(struct radeon_connector *radeon_connector, + struct drm_display_mode *mode) +{ + struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; + + return dp_mode_valid(dig_connector->dpcd, mode->clock); +} + static bool atom_dp_get_link_status(struct radeon_connector *radeon_connector, u8 link_status[DP_LINK_STATUS_SIZE]) { @@ -267,21 +511,41 @@ static bool atom_dp_get_link_status(struct radeon_connector *radeon_connector, static void dp_set_power(struct radeon_connector *radeon_connector, u8 power_state) { - struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; - if (radeon_dig_connector->dpcd[0] >= 0x11) { + struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; + + if (dig_connector->dpcd[0] >= 0x11) { radeon_dp_aux_native_write(radeon_connector, DP_SET_POWER, 1, &power_state); } } +static void dp_set_downspread(struct radeon_connector *radeon_connector, u8 downspread) +{ + radeon_dp_aux_native_write(radeon_connector, DP_DOWNSPREAD_CTRL, 1, + &downspread); +} + +static void dp_set_link_bw_lanes(struct radeon_connector *radeon_connector, + u8 link_configuration[DP_LINK_CONFIGURATION_SIZE]) +{ + radeon_dp_aux_native_write(radeon_connector, DP_LINK_BW_SET, 2, + link_configuration); +} + static void dp_update_dpvs_emph(struct radeon_connector *radeon_connector, + struct drm_encoder *encoder, u8 train_set[4]) { - struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; + int i; + + for (i = 0; i < dig_connector->dp_lane_count; i++) + atombios_dig_transmitter_setup(encoder, + ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH, + i, train_set[i]); -// radeon_dp_digtransmitter_setup_vsemph(); radeon_dp_aux_native_write(radeon_connector, DP_TRAINING_LANE0_SET, - 0/* lc */, train_set); + dig_connector->dp_lane_count, train_set); } static void dp_set_training(struct radeon_connector *radeon_connector, @@ -291,6 +555,176 @@ static void dp_set_training(struct radeon_connector *radeon_connector, 1, &training); } +void dp_link_train(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct radeon_device *rdev = dev->dev_private; + struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder); + struct radeon_encoder_atom_dig *dig; + struct radeon_connector *radeon_connector; + struct radeon_connector_atom_dig *dig_connector; + int enc_id = 0; + bool clock_recovery, channel_eq; + u8 link_status[DP_LINK_STATUS_SIZE]; + u8 link_configuration[DP_LINK_CONFIGURATION_SIZE]; + u8 tries, voltage; + u8 train_set[4]; + int i; + + if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) + return; + + if (!radeon_encoder->enc_priv) + return; + dig = radeon_encoder->enc_priv; + + radeon_connector = to_radeon_connector(connector); + if (!radeon_connector->con_priv) + return; + dig_connector = radeon_connector->con_priv; + + if (ASIC_IS_DCE32(rdev)) { + if (dig->dig_block) + enc_id |= ATOM_DP_CONFIG_DIG2_ENCODER; + else + enc_id |= ATOM_DP_CONFIG_DIG1_ENCODER; + if (dig_connector->linkb) + enc_id |= ATOM_DP_CONFIG_LINK_B; + else + enc_id |= ATOM_DP_CONFIG_LINK_A; + } else { + if (dig_connector->linkb) + enc_id |= ATOM_DP_CONFIG_DIG2_ENCODER | ATOM_DP_CONFIG_LINK_B; + else + enc_id |= ATOM_DP_CONFIG_DIG1_ENCODER | ATOM_DP_CONFIG_LINK_A; + } + + memset(link_configuration, 0, DP_LINK_CONFIGURATION_SIZE); + if (dig_connector->dp_clock == 270000) + link_configuration[0] = DP_LINK_BW_2_7; + else + link_configuration[0] = DP_LINK_BW_1_62; + link_configuration[1] = dig_connector->dp_lane_count; + if (dig_connector->dpcd[0] >= 0x11) + link_configuration[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + /* power up the sink */ + dp_set_power(radeon_connector, DP_SET_POWER_D0); + /* disable the training pattern on the sink */ + dp_set_training(radeon_connector, DP_TRAINING_PATTERN_DISABLE); + /* set link bw and lanes on the sink */ + dp_set_link_bw_lanes(radeon_connector, link_configuration); + /* disable downspread on the sink */ + dp_set_downspread(radeon_connector, 0); + /* start training on the source */ + radeon_dp_encoder_service(rdev, ATOM_DP_ACTION_TRAINING_START, + dig_connector->dp_clock, enc_id, 0); + /* set training pattern 1 on the source */ + radeon_dp_encoder_service(rdev, ATOM_DP_ACTION_TRAINING_PATTERN_SEL, + dig_connector->dp_clock, enc_id, 0); + + /* set initial vs/emph */ + memset(train_set, 0, 4); + dp_update_dpvs_emph(radeon_connector, encoder, train_set); + udelay(400); + /* set training pattern 1 on the sink */ + dp_set_training(radeon_connector, DP_TRAINING_PATTERN_1); + + /* clock recovery loop */ + clock_recovery = false; + tries = 0; + voltage = 0xff; + for (;;) { + udelay(100); + if (!atom_dp_get_link_status(radeon_connector, link_status)) + break; + + if (dp_clock_recovery_ok(link_status, dig_connector->dp_lane_count)) { + clock_recovery = true; + break; + } + + for (i = 0; i < dig_connector->dp_lane_count; i++) { + if ((train_set[i] & DP_TRAIN_MAX_SWING_REACHED) == 0) + break; + } + if (i == dig_connector->dp_lane_count) { + DRM_ERROR("clock recovery reached max voltage\n"); + break; + } + + if ((train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage) { + ++tries; + if (tries == 5) { + DRM_ERROR("clock recovery tried 5 times\n"); + break; + } + } else + tries = 0; + + voltage = train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + + /* Compute new train_set as requested by sink */ + dp_get_adjust_train(link_status, dig_connector->dp_lane_count, train_set); + dp_update_dpvs_emph(radeon_connector, encoder, train_set); + } + if (!clock_recovery) + DRM_ERROR("clock recovery failed\n"); + else + DRM_INFO("clock recovery at voltage %d pre-emphasis %d\n", + train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT); + + + /* set training pattern 2 on the sink */ + dp_set_training(radeon_connector, DP_TRAINING_PATTERN_2); + /* set training pattern 2 on the source */ + radeon_dp_encoder_service(rdev, ATOM_DP_ACTION_TRAINING_PATTERN_SEL, + dig_connector->dp_clock, enc_id, 1); + + /* channel equalization loop */ + tries = 0; + channel_eq = false; + for (;;) { + udelay(400); + if (!atom_dp_get_link_status(radeon_connector, link_status)) + break; + + if (dp_channel_eq_ok(link_status, dig_connector->dp_lane_count)) { + channel_eq = true; + break; + } + + /* Try 5 times */ + if (tries > 5) { + DRM_ERROR("channel eq failed: 5 tries\n"); + break; + } + + /* Compute new train_set as requested by sink */ + dp_get_adjust_train(link_status, dig_connector->dp_lane_count, train_set); + dp_update_dpvs_emph(radeon_connector, encoder, train_set); + + tries++; + } + + if (!channel_eq) + DRM_ERROR("channel eq failed\n"); + else + DRM_INFO("channel eq at voltage %d pre-emphasis %d\n", + train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) + >> DP_TRAIN_PRE_EMPHASIS_SHIFT); + + /* disable the training pattern on the sink */ + dp_set_training(radeon_connector, DP_TRAINING_PATTERN_DISABLE); + + radeon_dp_encoder_service(rdev, ATOM_DP_ACTION_TRAINING_COMPLETE, + dig_connector->dp_clock, enc_id, 0); +} + int radeon_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, uint8_t write_byte, uint8_t *read_byte) { @@ -342,3 +776,4 @@ int radeon_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, } return -EREMOTEIO; } + diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index b51e38386cc..3837cc94261 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -934,9 +934,23 @@ static enum drm_connector_status radeon_dp_detect(struct drm_connector *connecto return ret; } +static int radeon_dp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct radeon_connector *radeon_connector = to_radeon_connector(connector); + struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; + + /* XXX check mode bandwidth */ + + if (radeon_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) + return radeon_dp_mode_valid_helper(radeon_connector, mode); + else + return MODE_OK; +} + struct drm_connector_helper_funcs radeon_dp_connector_helper_funcs = { .get_modes = radeon_dp_get_modes, - .mode_valid = radeon_dvi_mode_valid, + .mode_valid = radeon_dp_mode_valid, .best_encoder = radeon_dvi_encoder, }; diff --git a/drivers/gpu/drm/radeon/radeon_encoders.c b/drivers/gpu/drm/radeon/radeon_encoders.c index 8f3d67b6032..397c86f761c 100644 --- a/drivers/gpu/drm/radeon/radeon_encoders.c +++ b/drivers/gpu/drm/radeon/radeon_encoders.c @@ -250,6 +250,12 @@ static bool radeon_atom_mode_fixup(struct drm_encoder *encoder, } } + if (ASIC_IS_DCE3(rdev) && + (radeon_encoder->active_device & (ATOM_DEVICE_DFP_SUPPORT))) { + struct drm_connector *connector = radeon_get_connector_for_encoder(encoder); + radeon_dp_set_link_config(connector, mode); + } + return true; } @@ -719,11 +725,9 @@ atombios_dig_encoder_setup(struct drm_encoder *encoder, int action) args.ucEncoderMode = atombios_get_encoder_mode(encoder); if (args.ucEncoderMode == ATOM_ENCODER_MODE_DP) { - if (dp_link_clock_for_mode_clock(dig_connector->dpcd[1], - radeon_encoder->pixel_clock) == 270000) + if (dig_connector->dp_clock == 270000) args.ucConfig |= ATOM_ENCODER_CONFIG_DPLINKRATE_2_70GHZ; - args.ucLaneNum = dp_lanes_for_mode_clock(dig_connector->dpcd[1], - radeon_encoder->pixel_clock); + args.ucLaneNum = dig_connector->dp_lane_count; } else if (radeon_encoder->pixel_clock > 165000) args.ucLaneNum = 8; else @@ -743,7 +747,7 @@ union dig_transmitter_control { DIG_TRANSMITTER_CONTROL_PARAMETERS_V2 v2; }; -static void +void atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action, uint8_t lane_num, uint8_t lane_set) { struct drm_device *dev = encoder->dev; @@ -803,8 +807,7 @@ atombios_dig_transmitter_setup(struct drm_encoder *encoder, int action, uint8_t } else { if (is_dp) args.v1.usPixelClock = - cpu_to_le16(dp_link_clock_for_mode_clock(dig_connector->dpcd[1], - radeon_encoder->pixel_clock) / 10); + cpu_to_le16(dig_connector->dp_clock / 10); else if (radeon_encoder->pixel_clock > 165000) args.v1.usPixelClock = cpu_to_le16((radeon_encoder->pixel_clock / 2) / 10); else @@ -1198,12 +1201,16 @@ radeon_atom_encoder_mode_set(struct drm_encoder *encoder, struct radeon_device *rdev = dev->dev_private; struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder); struct radeon_crtc *radeon_crtc = to_radeon_crtc(encoder->crtc); + struct drm_connector *connector = radeon_get_connector_for_encoder(encoder); - if (radeon_encoder->enc_priv) { - struct radeon_encoder_atom_dig *dig; + if (radeon_encoder->active_device & + (ATOM_DEVICE_DFP_SUPPORT | ATOM_DEVICE_LCD_SUPPORT)) { + if (radeon_encoder->enc_priv) { + struct radeon_encoder_atom_dig *dig; - dig = radeon_encoder->enc_priv; - dig->dig_block = radeon_crtc->crtc_id; + dig = radeon_encoder->enc_priv; + dig->dig_block = radeon_crtc->crtc_id; + } } radeon_encoder->pixel_clock = adjusted_mode->clock; @@ -1237,6 +1244,7 @@ radeon_atom_encoder_mode_set(struct drm_encoder *encoder, atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_INIT, 0, 0); atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_SETUP, 0, 0); atombios_dig_transmitter_setup(encoder, ATOM_TRANSMITTER_ACTION_ENABLE, 0, 0); + dp_link_train(encoder, connector); break; case ENCODER_OBJECT_ID_INTERNAL_DDI: atombios_ddia_setup(encoder, ATOM_ENABLE); diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index b516401c151..7d03e397149 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -343,6 +343,8 @@ struct radeon_connector_atom_dig { struct radeon_i2c_chan *dp_i2c_bus; u8 dpcd[8]; u8 dp_sink_type; + int dp_clock; + int dp_lane_count; }; struct radeon_connector { @@ -366,10 +368,17 @@ struct radeon_framebuffer { struct drm_gem_object *obj; }; -extern int dp_lanes_for_mode_clock(int max_link_bw, int mode_clock); -extern int dp_link_clock_for_mode_clock(int max_link_bw, int mode_clock); +extern int radeon_dp_mode_valid_helper(struct radeon_connector *radeon_connector, + struct drm_display_mode *mode); +extern void radeon_dp_set_link_config(struct drm_connector *connector, + struct drm_display_mode *mode); +extern void dp_link_train(struct drm_encoder *encoder, + struct drm_connector *connector); extern u8 radeon_dp_getsinktype(struct radeon_connector *radeon_connector); extern void radeon_dp_getdpcd(struct radeon_connector *radeon_connector); +extern void atombios_dig_transmitter_setup(struct drm_encoder *encoder, + int action, uint8_t lane_num, + uint8_t lane_set); extern int radeon_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, uint8_t write_byte, uint8_t *read_byte); diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h index f09b0b2a99b..a49e791db0b 100644 --- a/include/drm/drm_dp_helper.h +++ b/include/drm/drm_dp_helper.h @@ -43,18 +43,41 @@ #define AUX_I2C_REPLY_MASK (0x3 << 6) /* AUX CH addresses */ -#define DP_DPCD_REV 0x0 +/* DPCD */ +#define DP_DPCD_REV 0x000 -#define DP_LINK_BW_SET 0x100 +#define DP_MAX_LINK_RATE 0x001 + +#define DP_MAX_LANE_COUNT 0x002 +# define DP_MAX_LANE_COUNT_MASK 0x1f +# define DP_ENHANCED_FRAME_CAP (1 << 7) + +#define DP_MAX_DOWNSPREAD 0x003 +# define DP_NO_AUX_HANDSHAKE_LINK_TRAINING (1 << 6) + +#define DP_NORP 0x004 + +#define DP_DOWNSTREAMPORT_PRESENT 0x005 +# define DP_DWN_STRM_PORT_PRESENT (1 << 0) +# define DP_DWN_STRM_PORT_TYPE_MASK 0x06 +/* 00b = DisplayPort */ +/* 01b = Analog */ +/* 10b = TMDS or HDMI */ +/* 11b = Other */ +# define DP_FORMAT_CONVERSION (1 << 3) + +#define DP_MAIN_LINK_CHANNEL_CODING 0x006 + +/* link configuration */ +#define DP_LINK_BW_SET 0x100 # define DP_LINK_BW_1_62 0x06 # define DP_LINK_BW_2_7 0x0a -#define DP_LANE_COUNT_SET 0x101 +#define DP_LANE_COUNT_SET 0x101 # define DP_LANE_COUNT_MASK 0x0f # define DP_LANE_COUNT_ENHANCED_FRAME_EN (1 << 7) -#define DP_TRAINING_PATTERN_SET 0x102 - +#define DP_TRAINING_PATTERN_SET 0x102 # define DP_TRAINING_PATTERN_DISABLE 0 # define DP_TRAINING_PATTERN_1 1 # define DP_TRAINING_PATTERN_2 2 @@ -104,11 +127,14 @@ #define DP_LANE0_1_STATUS 0x202 #define DP_LANE2_3_STATUS 0x203 - # define DP_LANE_CR_DONE (1 << 0) # define DP_LANE_CHANNEL_EQ_DONE (1 << 1) # define DP_LANE_SYMBOL_LOCKED (1 << 2) +#define DP_CHANNEL_EQ_BITS (DP_LANE_CR_DONE | \ + DP_LANE_CHANNEL_EQ_DONE | \ + DP_LANE_SYMBOL_LOCKED) + #define DP_LANE_ALIGN_STATUS_UPDATED 0x204 #define DP_INTERLANE_ALIGN_DONE (1 << 0) @@ -122,17 +148,18 @@ #define DP_ADJUST_REQUEST_LANE0_1 0x206 #define DP_ADJUST_REQUEST_LANE2_3 0x207 - -#define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03 -#define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0 -#define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c -#define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2 -#define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30 -#define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4 -#define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0 -#define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6 +# define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03 +# define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0 +# define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c +# define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2 +# define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30 +# define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4 +# define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0 +# define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6 #define DP_SET_POWER 0x600 +# define DP_SET_POWER_D0 0x1 +# define DP_SET_POWER_D3 0x2 #define MODE_I2C_START 1 #define MODE_I2C_WRITE 2 -- cgit v1.2.3-70-g09d2 From a2e68e92d384d37c8cc6bb7206d43b1eb9bc3f08 Mon Sep 17 00:00:00 2001 From: Jerome Glisse Date: Mon, 7 Dec 2009 15:52:56 +0100 Subject: drm: Add search/get functions to get a block in a specific range These are required for changes to TTM. Signed-off-by: Jerome Glisse Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_mm.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ include/drm/drm_mm.h | 34 +++++++++++++++++++ 2 files changed, 122 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/drm_mm.c b/drivers/gpu/drm/drm_mm.c index 1f0d717dbad..a5c2773ccf2 100644 --- a/drivers/gpu/drm/drm_mm.c +++ b/drivers/gpu/drm/drm_mm.c @@ -226,6 +226,44 @@ struct drm_mm_node *drm_mm_get_block_generic(struct drm_mm_node *node, } EXPORT_SYMBOL(drm_mm_get_block_generic); +struct drm_mm_node *drm_mm_get_block_range_generic(struct drm_mm_node *node, + unsigned long size, + unsigned alignment, + unsigned long start, + unsigned long end, + int atomic) +{ + struct drm_mm_node *align_splitoff = NULL; + unsigned tmp = 0; + unsigned wasted = 0; + + if (node->start < start) + wasted += start - node->start; + if (alignment) + tmp = ((node->start + wasted) % alignment); + + if (tmp) + wasted += alignment - tmp; + if (wasted) { + align_splitoff = drm_mm_split_at_start(node, wasted, atomic); + if (unlikely(align_splitoff == NULL)) + return NULL; + } + + if (node->size == size) { + list_del_init(&node->fl_entry); + node->free = 0; + } else { + node = drm_mm_split_at_start(node, size, atomic); + } + + if (align_splitoff) + drm_mm_put_block(align_splitoff); + + return node; +} +EXPORT_SYMBOL(drm_mm_get_block_range_generic); + /* * Put a block. Merge with the previous and / or next block if they are free. * Otherwise add to the free stack. @@ -331,6 +369,56 @@ struct drm_mm_node *drm_mm_search_free(const struct drm_mm *mm, } EXPORT_SYMBOL(drm_mm_search_free); +struct drm_mm_node *drm_mm_search_free_in_range(const struct drm_mm *mm, + unsigned long size, + unsigned alignment, + unsigned long start, + unsigned long end, + int best_match) +{ + struct list_head *list; + const struct list_head *free_stack = &mm->fl_entry; + struct drm_mm_node *entry; + struct drm_mm_node *best; + unsigned long best_size; + unsigned wasted; + + best = NULL; + best_size = ~0UL; + + list_for_each(list, free_stack) { + entry = list_entry(list, struct drm_mm_node, fl_entry); + wasted = 0; + + if (entry->size < size) + continue; + + if (entry->start > end || (entry->start+entry->size) < start) + continue; + + if (entry->start < start) + wasted += start - entry->start; + + if (alignment) { + register unsigned tmp = (entry->start + wasted) % alignment; + if (tmp) + wasted += alignment - tmp; + } + + if (entry->size >= size + wasted) { + if (!best_match) + return entry; + if (size < best_size) { + best = entry; + best_size = entry->size; + } + } + } + + return best; +} +EXPORT_SYMBOL(drm_mm_search_free_in_range); + int drm_mm_clean(struct drm_mm * mm) { struct list_head *head = &mm->ml_entry; diff --git a/include/drm/drm_mm.h b/include/drm/drm_mm.h index 62329f9a42c..b40b2f06203 100644 --- a/include/drm/drm_mm.h +++ b/include/drm/drm_mm.h @@ -66,6 +66,13 @@ extern struct drm_mm_node *drm_mm_get_block_generic(struct drm_mm_node *node, unsigned long size, unsigned alignment, int atomic); +extern struct drm_mm_node *drm_mm_get_block_range_generic( + struct drm_mm_node *node, + unsigned long size, + unsigned alignment, + unsigned long start, + unsigned long end, + int atomic); static inline struct drm_mm_node *drm_mm_get_block(struct drm_mm_node *parent, unsigned long size, unsigned alignment) @@ -78,11 +85,38 @@ static inline struct drm_mm_node *drm_mm_get_block_atomic(struct drm_mm_node *pa { return drm_mm_get_block_generic(parent, size, alignment, 1); } +static inline struct drm_mm_node *drm_mm_get_block_range( + struct drm_mm_node *parent, + unsigned long size, + unsigned alignment, + unsigned long start, + unsigned long end) +{ + return drm_mm_get_block_range_generic(parent, size, alignment, + start, end, 0); +} +static inline struct drm_mm_node *drm_mm_get_block_atomic_range( + struct drm_mm_node *parent, + unsigned long size, + unsigned alignment, + unsigned long start, + unsigned long end) +{ + return drm_mm_get_block_range_generic(parent, size, alignment, + start, end, 1); +} extern void drm_mm_put_block(struct drm_mm_node *cur); extern struct drm_mm_node *drm_mm_search_free(const struct drm_mm *mm, unsigned long size, unsigned alignment, int best_match); +extern struct drm_mm_node *drm_mm_search_free_in_range( + const struct drm_mm *mm, + unsigned long size, + unsigned alignment, + unsigned long start, + unsigned long end, + int best_match); extern int drm_mm_init(struct drm_mm *mm, unsigned long start, unsigned long size); extern void drm_mm_takedown(struct drm_mm *mm); -- cgit v1.2.3-70-g09d2 From ca262a9998d46196750bb19a9dc4bd465b170ff7 Mon Sep 17 00:00:00 2001 From: Jerome Glisse Date: Tue, 8 Dec 2009 15:33:32 +0100 Subject: drm/ttm: Rework validation & memory space allocation (V3) This change allow driver to pass sorted memory placement, from most prefered placement to least prefered placement. In order to avoid long function prototype a structure is used to gather memory placement informations such as range restriction (if you need a buffer to be in given range). Range restriction is determined by fpfn & lpfn which are the first page and last page number btw which allocation can happen. If those fields are set to 0 ttm will assume buffer can be put anywhere in the address space (thus it avoids putting a burden on the driver to always properly set those fields). This patch also factor few functions like evicting first entry of lru list or getting a memory space. This avoid code duplication. V2: Change API to use placement flags and array instead of packing placement order into a quadword. V3: Make sure we set the appropriate mem.placement flag when validating or allocation memory space. [Pending Thomas Hellstrom further review but okay from preliminary review so far]. Signed-off-by: Jerome Glisse Signed-off-by: Dave Airlie --- drivers/gpu/drm/ttm/ttm_bo.c | 463 +++++++++++++++++++--------------------- include/drm/ttm/ttm_bo_api.h | 42 ++-- include/drm/ttm/ttm_bo_driver.h | 20 +- 3 files changed, 256 insertions(+), 269 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index e13fd23f333..60d8179a8bc 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -27,6 +27,14 @@ /* * Authors: Thomas Hellstrom */ +/* Notes: + * + * We store bo pointer in drm_mm_node struct so we know which bo own a + * specific node. There is no protection on the pointer, thus to make + * sure things don't go berserk you have to access this pointer while + * holding the global lru lock and make sure anytime you free a node you + * reset the pointer to NULL. + */ #include "ttm/ttm_module.h" #include "ttm/ttm_bo_driver.h" @@ -247,7 +255,6 @@ EXPORT_SYMBOL(ttm_bo_unreserve); /* * Call bo->mutex locked. */ - static int ttm_bo_add_ttm(struct ttm_buffer_object *bo, bool zero_alloc) { struct ttm_bo_device *bdev = bo->bdev; @@ -329,14 +336,8 @@ static int ttm_bo_handle_move_mem(struct ttm_buffer_object *bo, } if (bo->mem.mem_type == TTM_PL_SYSTEM) { - - struct ttm_mem_reg *old_mem = &bo->mem; - uint32_t save_flags = old_mem->placement; - - *old_mem = *mem; + bo->mem = *mem; mem->mm_node = NULL; - ttm_flag_masked(&save_flags, mem->placement, - TTM_PL_MASK_MEMTYPE); goto moved; } @@ -419,6 +420,7 @@ static int ttm_bo_cleanup_refs(struct ttm_buffer_object *bo, bool remove_all) kref_put(&bo->list_kref, ttm_bo_ref_bug); } if (bo->mem.mm_node) { + bo->mem.mm_node->private = NULL; drm_mm_put_block(bo->mem.mm_node); bo->mem.mm_node = NULL; } @@ -555,17 +557,14 @@ void ttm_bo_unref(struct ttm_buffer_object **p_bo) } EXPORT_SYMBOL(ttm_bo_unref); -static int ttm_bo_evict(struct ttm_buffer_object *bo, unsigned mem_type, - bool interruptible, bool no_wait) +static int ttm_bo_evict(struct ttm_buffer_object *bo, bool interruptible, + bool no_wait) { - int ret = 0; struct ttm_bo_device *bdev = bo->bdev; struct ttm_bo_global *glob = bo->glob; struct ttm_mem_reg evict_mem; - uint32_t proposed_placement; - - if (bo->mem.mem_type != mem_type) - goto out; + struct ttm_placement placement; + int ret = 0; spin_lock(&bo->lock); ret = ttm_bo_wait(bo, false, interruptible, no_wait); @@ -585,14 +584,9 @@ static int ttm_bo_evict(struct ttm_buffer_object *bo, unsigned mem_type, evict_mem = bo->mem; evict_mem.mm_node = NULL; - proposed_placement = bdev->driver->evict_flags(bo); - - ret = ttm_bo_mem_space(bo, proposed_placement, - &evict_mem, interruptible, no_wait); - if (unlikely(ret != 0 && ret != -ERESTART)) - ret = ttm_bo_mem_space(bo, TTM_PL_FLAG_SYSTEM, - &evict_mem, interruptible, no_wait); - + bdev->driver->evict_flags(bo, &placement); + ret = ttm_bo_mem_space(bo, &placement, &evict_mem, interruptible, + no_wait); if (ret) { if (ret != -ERESTART) printk(KERN_ERR TTM_PFX @@ -606,95 +600,117 @@ static int ttm_bo_evict(struct ttm_buffer_object *bo, unsigned mem_type, if (ret) { if (ret != -ERESTART) printk(KERN_ERR TTM_PFX "Buffer eviction failed\n"); + spin_lock(&glob->lru_lock); + if (evict_mem.mm_node) { + evict_mem.mm_node->private = NULL; + drm_mm_put_block(evict_mem.mm_node); + evict_mem.mm_node = NULL; + } + spin_unlock(&glob->lru_lock); goto out; } + bo->evicted = true; +out: + return ret; +} + +static int ttm_mem_evict_first(struct ttm_bo_device *bdev, + uint32_t mem_type, + bool interruptible, bool no_wait) +{ + struct ttm_bo_global *glob = bdev->glob; + struct ttm_mem_type_manager *man = &bdev->man[mem_type]; + struct ttm_buffer_object *bo; + int ret, put_count = 0; spin_lock(&glob->lru_lock); - if (evict_mem.mm_node) { - drm_mm_put_block(evict_mem.mm_node); - evict_mem.mm_node = NULL; - } + bo = list_first_entry(&man->lru, struct ttm_buffer_object, lru); + kref_get(&bo->list_kref); + ret = ttm_bo_reserve_locked(bo, interruptible, no_wait, false, 0); + if (likely(ret == 0)) + put_count = ttm_bo_del_from_lru(bo); spin_unlock(&glob->lru_lock); - bo->evicted = true; -out: + if (unlikely(ret != 0)) + return ret; + while (put_count--) + kref_put(&bo->list_kref, ttm_bo_ref_bug); + ret = ttm_bo_evict(bo, interruptible, no_wait); + ttm_bo_unreserve(bo); + kref_put(&bo->list_kref, ttm_bo_release_list); return ret; } +static int ttm_bo_man_get_node(struct ttm_buffer_object *bo, + struct ttm_mem_type_manager *man, + struct ttm_placement *placement, + struct ttm_mem_reg *mem, + struct drm_mm_node **node) +{ + struct ttm_bo_global *glob = bo->glob; + unsigned long lpfn; + int ret; + + lpfn = placement->lpfn; + if (!lpfn) + lpfn = man->size; + *node = NULL; + do { + ret = drm_mm_pre_get(&man->manager); + if (unlikely(ret)) + return ret; + + spin_lock(&glob->lru_lock); + *node = drm_mm_search_free_in_range(&man->manager, + mem->num_pages, mem->page_alignment, + placement->fpfn, lpfn, 1); + if (unlikely(*node == NULL)) { + spin_unlock(&glob->lru_lock); + return 0; + } + *node = drm_mm_get_block_atomic_range(*node, mem->num_pages, + mem->page_alignment, + placement->fpfn, + lpfn); + spin_unlock(&glob->lru_lock); + } while (*node == NULL); + return 0; +} + /** * Repeatedly evict memory from the LRU for @mem_type until we create enough * space, or we've evicted everything and there isn't enough space. */ -static int ttm_bo_mem_force_space(struct ttm_bo_device *bdev, - struct ttm_mem_reg *mem, - uint32_t mem_type, - bool interruptible, bool no_wait) +static int ttm_bo_mem_force_space(struct ttm_buffer_object *bo, + uint32_t mem_type, + struct ttm_placement *placement, + struct ttm_mem_reg *mem, + bool interruptible, bool no_wait) { + struct ttm_bo_device *bdev = bo->bdev; struct ttm_bo_global *glob = bdev->glob; - struct drm_mm_node *node; - struct ttm_buffer_object *entry; struct ttm_mem_type_manager *man = &bdev->man[mem_type]; - struct list_head *lru; - unsigned long num_pages = mem->num_pages; - int put_count = 0; + struct drm_mm_node *node; int ret; -retry_pre_get: - ret = drm_mm_pre_get(&man->manager); - if (unlikely(ret != 0)) - return ret; - - spin_lock(&glob->lru_lock); do { - node = drm_mm_search_free(&man->manager, num_pages, - mem->page_alignment, 1); + ret = ttm_bo_man_get_node(bo, man, placement, mem, &node); + if (unlikely(ret != 0)) + return ret; if (node) break; - - lru = &man->lru; - if (list_empty(lru)) + spin_lock(&glob->lru_lock); + if (list_empty(&man->lru)) { + spin_unlock(&glob->lru_lock); break; - - entry = list_first_entry(lru, struct ttm_buffer_object, lru); - kref_get(&entry->list_kref); - - ret = - ttm_bo_reserve_locked(entry, interruptible, no_wait, - false, 0); - - if (likely(ret == 0)) - put_count = ttm_bo_del_from_lru(entry); - + } spin_unlock(&glob->lru_lock); - + ret = ttm_mem_evict_first(bdev, mem_type, interruptible, + no_wait); if (unlikely(ret != 0)) return ret; - - while (put_count--) - kref_put(&entry->list_kref, ttm_bo_ref_bug); - - ret = ttm_bo_evict(entry, mem_type, interruptible, no_wait); - - ttm_bo_unreserve(entry); - - kref_put(&entry->list_kref, ttm_bo_release_list); - if (ret) - return ret; - - spin_lock(&glob->lru_lock); } while (1); - - if (!node) { - spin_unlock(&glob->lru_lock); + if (node == NULL) return -ENOMEM; - } - - node = drm_mm_get_block_atomic(node, num_pages, mem->page_alignment); - if (unlikely(!node)) { - spin_unlock(&glob->lru_lock); - goto retry_pre_get; - } - - spin_unlock(&glob->lru_lock); mem->mm_node = node; mem->mem_type = mem_type; return 0; @@ -725,7 +741,6 @@ static uint32_t ttm_bo_select_caching(struct ttm_mem_type_manager *man, return result; } - static bool ttm_bo_mt_compatible(struct ttm_mem_type_manager *man, bool disallow_fixed, uint32_t mem_type, @@ -749,6 +764,18 @@ static bool ttm_bo_mt_compatible(struct ttm_mem_type_manager *man, return true; } +static inline int ttm_mem_type_from_flags(uint32_t flags, uint32_t *mem_type) +{ + int i; + + for (i = 0; i <= TTM_PL_PRIV5; i++) + if (flags & (1 << i)) { + *mem_type = i; + return 0; + } + return -EINVAL; +} + /** * Creates space for memory region @mem according to its type. * @@ -758,66 +785,55 @@ static bool ttm_bo_mt_compatible(struct ttm_mem_type_manager *man, * space. */ int ttm_bo_mem_space(struct ttm_buffer_object *bo, - uint32_t proposed_placement, - struct ttm_mem_reg *mem, - bool interruptible, bool no_wait) + struct ttm_placement *placement, + struct ttm_mem_reg *mem, + bool interruptible, bool no_wait) { struct ttm_bo_device *bdev = bo->bdev; - struct ttm_bo_global *glob = bo->glob; struct ttm_mem_type_manager *man; - - uint32_t num_prios = bdev->driver->num_mem_type_prio; - const uint32_t *prios = bdev->driver->mem_type_prio; - uint32_t i; uint32_t mem_type = TTM_PL_SYSTEM; uint32_t cur_flags = 0; bool type_found = false; bool type_ok = false; bool has_eagain = false; struct drm_mm_node *node = NULL; - int ret; + int i, ret; mem->mm_node = NULL; - for (i = 0; i < num_prios; ++i) { - mem_type = prios[i]; + for (i = 0; i <= placement->num_placement; ++i) { + ret = ttm_mem_type_from_flags(placement->placement[i], + &mem_type); + if (ret) + return ret; man = &bdev->man[mem_type]; type_ok = ttm_bo_mt_compatible(man, - bo->type == ttm_bo_type_user, - mem_type, proposed_placement, - &cur_flags); + bo->type == ttm_bo_type_user, + mem_type, + placement->placement[i], + &cur_flags); if (!type_ok) continue; cur_flags = ttm_bo_select_caching(man, bo->mem.placement, cur_flags); + /* + * Use the access and other non-mapping-related flag bits from + * the memory placement flags to the current flags + */ + ttm_flag_masked(&cur_flags, placement->placement[i], + ~TTM_PL_MASK_MEMTYPE); if (mem_type == TTM_PL_SYSTEM) break; if (man->has_type && man->use_type) { type_found = true; - do { - ret = drm_mm_pre_get(&man->manager); - if (unlikely(ret)) - return ret; - - spin_lock(&glob->lru_lock); - node = drm_mm_search_free(&man->manager, - mem->num_pages, - mem->page_alignment, - 1); - if (unlikely(!node)) { - spin_unlock(&glob->lru_lock); - break; - } - node = drm_mm_get_block_atomic(node, - mem->num_pages, - mem-> - page_alignment); - spin_unlock(&glob->lru_lock); - } while (!node); + ret = ttm_bo_man_get_node(bo, man, placement, mem, + &node); + if (unlikely(ret)) + return ret; } if (node) break; @@ -827,43 +843,48 @@ int ttm_bo_mem_space(struct ttm_buffer_object *bo, mem->mm_node = node; mem->mem_type = mem_type; mem->placement = cur_flags; + if (node) + node->private = bo; return 0; } if (!type_found) return -EINVAL; - num_prios = bdev->driver->num_mem_busy_prio; - prios = bdev->driver->mem_busy_prio; - - for (i = 0; i < num_prios; ++i) { - mem_type = prios[i]; + for (i = 0; i <= placement->num_busy_placement; ++i) { + ret = ttm_mem_type_from_flags(placement->placement[i], + &mem_type); + if (ret) + return ret; man = &bdev->man[mem_type]; - if (!man->has_type) continue; - if (!ttm_bo_mt_compatible(man, - bo->type == ttm_bo_type_user, - mem_type, - proposed_placement, &cur_flags)) + bo->type == ttm_bo_type_user, + mem_type, + placement->placement[i], + &cur_flags)) continue; cur_flags = ttm_bo_select_caching(man, bo->mem.placement, cur_flags); + /* + * Use the access and other non-mapping-related flag bits from + * the memory placement flags to the current flags + */ + ttm_flag_masked(&cur_flags, placement->placement[i], + ~TTM_PL_MASK_MEMTYPE); - ret = ttm_bo_mem_force_space(bdev, mem, mem_type, - interruptible, no_wait); - + ret = ttm_bo_mem_force_space(bo, mem_type, placement, mem, + interruptible, no_wait); if (ret == 0 && mem->mm_node) { mem->placement = cur_flags; + mem->mm_node->private = bo; return 0; } - if (ret == -ERESTART) has_eagain = true; } - ret = (has_eagain) ? -ERESTART : -ENOMEM; return ret; } @@ -886,8 +907,8 @@ int ttm_bo_wait_cpu(struct ttm_buffer_object *bo, bool no_wait) } int ttm_bo_move_buffer(struct ttm_buffer_object *bo, - uint32_t proposed_placement, - bool interruptible, bool no_wait) + struct ttm_placement *placement, + bool interruptible, bool no_wait) { struct ttm_bo_global *glob = bo->glob; int ret = 0; @@ -900,101 +921,82 @@ int ttm_bo_move_buffer(struct ttm_buffer_object *bo, * Have the driver move function wait for idle when necessary, * instead of doing it here. */ - spin_lock(&bo->lock); ret = ttm_bo_wait(bo, false, interruptible, no_wait); spin_unlock(&bo->lock); - if (ret) return ret; - mem.num_pages = bo->num_pages; mem.size = mem.num_pages << PAGE_SHIFT; mem.page_alignment = bo->mem.page_alignment; - /* * Determine where to move the buffer. */ - - ret = ttm_bo_mem_space(bo, proposed_placement, &mem, - interruptible, no_wait); + ret = ttm_bo_mem_space(bo, placement, &mem, interruptible, no_wait); if (ret) goto out_unlock; - ret = ttm_bo_handle_move_mem(bo, &mem, false, interruptible, no_wait); - out_unlock: if (ret && mem.mm_node) { spin_lock(&glob->lru_lock); + mem.mm_node->private = NULL; drm_mm_put_block(mem.mm_node); spin_unlock(&glob->lru_lock); } return ret; } -static int ttm_bo_mem_compat(uint32_t proposed_placement, +static int ttm_bo_mem_compat(struct ttm_placement *placement, struct ttm_mem_reg *mem) { - if ((proposed_placement & mem->placement & TTM_PL_MASK_MEM) == 0) - return 0; - if ((proposed_placement & mem->placement & TTM_PL_MASK_CACHING) == 0) - return 0; - - return 1; + int i; + + for (i = 0; i < placement->num_placement; i++) { + if ((placement->placement[i] & mem->placement & + TTM_PL_MASK_CACHING) && + (placement->placement[i] & mem->placement & + TTM_PL_MASK_MEM)) + return i; + } + return -1; } int ttm_buffer_object_validate(struct ttm_buffer_object *bo, - uint32_t proposed_placement, - bool interruptible, bool no_wait) + struct ttm_placement *placement, + bool interruptible, bool no_wait) { int ret; BUG_ON(!atomic_read(&bo->reserved)); - bo->proposed_placement = proposed_placement; - - TTM_DEBUG("Proposed placement 0x%08lx, Old flags 0x%08lx\n", - (unsigned long)proposed_placement, - (unsigned long)bo->mem.placement); - + /* Check that range is valid */ + if (placement->lpfn || placement->fpfn) + if (placement->fpfn > placement->lpfn || + (placement->lpfn - placement->fpfn) < bo->num_pages) + return -EINVAL; /* * Check whether we need to move buffer. */ - - if (!ttm_bo_mem_compat(bo->proposed_placement, &bo->mem)) { - ret = ttm_bo_move_buffer(bo, bo->proposed_placement, - interruptible, no_wait); - if (ret) { - if (ret != -ERESTART) - printk(KERN_ERR TTM_PFX - "Failed moving buffer. " - "Proposed placement 0x%08x\n", - bo->proposed_placement); - if (ret == -ENOMEM) - printk(KERN_ERR TTM_PFX - "Out of aperture space or " - "DRM memory quota.\n"); + ret = ttm_bo_mem_compat(placement, &bo->mem); + if (ret < 0) { + ret = ttm_bo_move_buffer(bo, placement, interruptible, no_wait); + if (ret) return ret; - } + } else { + /* + * Use the access and other non-mapping-related flag bits from + * the compatible memory placement flags to the active flags + */ + ttm_flag_masked(&bo->mem.placement, placement->placement[ret], + ~TTM_PL_MASK_MEMTYPE); } - /* * We might need to add a TTM. */ - if (bo->mem.mem_type == TTM_PL_SYSTEM && bo->ttm == NULL) { ret = ttm_bo_add_ttm(bo, true); if (ret) return ret; } - /* - * Validation has succeeded, move the access and other - * non-mapping-related flag bits from the proposed flags to - * the active flags - */ - - ttm_flag_masked(&bo->mem.placement, bo->proposed_placement, - ~TTM_PL_MASK_MEMTYPE); - return 0; } EXPORT_SYMBOL(ttm_buffer_object_validate); @@ -1042,8 +1044,10 @@ int ttm_buffer_object_init(struct ttm_bo_device *bdev, size_t acc_size, void (*destroy) (struct ttm_buffer_object *)) { - int ret = 0; + int i, c, ret = 0; unsigned long num_pages; + uint32_t placements[8]; + struct ttm_placement placement; size += buffer_start & ~PAGE_MASK; num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; @@ -1100,7 +1104,16 @@ int ttm_buffer_object_init(struct ttm_bo_device *bdev, goto out_err; } - ret = ttm_buffer_object_validate(bo, flags, interruptible, false); + placement.fpfn = 0; + placement.lpfn = 0; + for (i = 0, c = 0; i <= TTM_PL_PRIV5; i++) + if (flags & (1 << i)) + placements[c++] = (flags & ~TTM_PL_MASK_MEM) | (1 << i); + placement.placement = placements; + placement.num_placement = c; + placement.busy_placement = placements; + placement.num_busy_placement = c; + ret = ttm_buffer_object_validate(bo, &placement, interruptible, false); if (ret) goto out_err; @@ -1135,8 +1148,8 @@ int ttm_buffer_object_create(struct ttm_bo_device *bdev, struct ttm_buffer_object **p_bo) { struct ttm_buffer_object *bo; - int ret; struct ttm_mem_global *mem_glob = bdev->glob->mem_glob; + int ret; size_t acc_size = ttm_bo_size(bdev->glob, (size + PAGE_SIZE - 1) >> PAGE_SHIFT); @@ -1161,66 +1174,32 @@ int ttm_buffer_object_create(struct ttm_bo_device *bdev, return ret; } -static int ttm_bo_leave_list(struct ttm_buffer_object *bo, - uint32_t mem_type, bool allow_errors) -{ - int ret; - - spin_lock(&bo->lock); - ret = ttm_bo_wait(bo, false, false, false); - spin_unlock(&bo->lock); - - if (ret && allow_errors) - goto out; - - if (bo->mem.mem_type == mem_type) - ret = ttm_bo_evict(bo, mem_type, false, false); - - if (ret) { - if (allow_errors) { - goto out; - } else { - ret = 0; - printk(KERN_ERR TTM_PFX "Cleanup eviction failed\n"); - } - } - -out: - return ret; -} - static int ttm_bo_force_list_clean(struct ttm_bo_device *bdev, - struct list_head *head, - unsigned mem_type, bool allow_errors) + unsigned mem_type, bool allow_errors) { + struct ttm_mem_type_manager *man = &bdev->man[mem_type]; struct ttm_bo_global *glob = bdev->glob; - struct ttm_buffer_object *entry; int ret; - int put_count; /* * Can't use standard list traversal since we're unlocking. */ spin_lock(&glob->lru_lock); - - while (!list_empty(head)) { - entry = list_first_entry(head, struct ttm_buffer_object, lru); - kref_get(&entry->list_kref); - ret = ttm_bo_reserve_locked(entry, false, false, false, 0); - put_count = ttm_bo_del_from_lru(entry); + while (!list_empty(&man->lru)) { spin_unlock(&glob->lru_lock); - while (put_count--) - kref_put(&entry->list_kref, ttm_bo_ref_bug); - BUG_ON(ret); - ret = ttm_bo_leave_list(entry, mem_type, allow_errors); - ttm_bo_unreserve(entry); - kref_put(&entry->list_kref, ttm_bo_release_list); + ret = ttm_mem_evict_first(bdev, mem_type, false, false); + if (ret) { + if (allow_errors) { + return ret; + } else { + printk(KERN_ERR TTM_PFX + "Cleanup eviction failed\n"); + } + } spin_lock(&glob->lru_lock); } - spin_unlock(&glob->lru_lock); - return 0; } @@ -1247,7 +1226,7 @@ int ttm_bo_clean_mm(struct ttm_bo_device *bdev, unsigned mem_type) ret = 0; if (mem_type > 0) { - ttm_bo_force_list_clean(bdev, &man->lru, mem_type, false); + ttm_bo_force_list_clean(bdev, mem_type, false); spin_lock(&glob->lru_lock); if (drm_mm_clean(&man->manager)) @@ -1280,12 +1259,12 @@ int ttm_bo_evict_mm(struct ttm_bo_device *bdev, unsigned mem_type) return 0; } - return ttm_bo_force_list_clean(bdev, &man->lru, mem_type, true); + return ttm_bo_force_list_clean(bdev, mem_type, true); } EXPORT_SYMBOL(ttm_bo_evict_mm); int ttm_bo_init_mm(struct ttm_bo_device *bdev, unsigned type, - unsigned long p_offset, unsigned long p_size) + unsigned long p_size) { int ret = -EINVAL; struct ttm_mem_type_manager *man; @@ -1315,7 +1294,7 @@ int ttm_bo_init_mm(struct ttm_bo_device *bdev, unsigned type, type); return ret; } - ret = drm_mm_init(&man->manager, p_offset, p_size); + ret = drm_mm_init(&man->manager, 0, p_size); if (ret) return ret; } @@ -1464,7 +1443,7 @@ int ttm_bo_device_init(struct ttm_bo_device *bdev, * Initialize the system memory buffer type. * Other types need to be driver / IOCTL initialized. */ - ret = ttm_bo_init_mm(bdev, TTM_PL_SYSTEM, 0, 0); + ret = ttm_bo_init_mm(bdev, TTM_PL_SYSTEM, 0); if (unlikely(ret != 0)) goto out_no_sys; diff --git a/include/drm/ttm/ttm_bo_api.h b/include/drm/ttm/ttm_bo_api.h index 49114617052..2f7f56da214 100644 --- a/include/drm/ttm/ttm_bo_api.h +++ b/include/drm/ttm/ttm_bo_api.h @@ -44,6 +44,29 @@ struct ttm_bo_device; struct drm_mm_node; + +/** + * struct ttm_placement + * + * @fpfn: first valid page frame number to put the object + * @lpfn: last valid page frame number to put the object + * @num_placement: number of prefered placements + * @placement: prefered placements + * @num_busy_placement: number of prefered placements when need to evict buffer + * @busy_placement: prefered placements when need to evict buffer + * + * Structure indicating the placement you request for an object. + */ +struct ttm_placement { + unsigned fpfn; + unsigned lpfn; + unsigned num_placement; + const uint32_t *placement; + unsigned num_busy_placement; + const uint32_t *busy_placement; +}; + + /** * struct ttm_mem_reg * @@ -109,10 +132,6 @@ struct ttm_tt; * the object is destroyed. * @event_queue: Queue for processes waiting on buffer object status change. * @lock: spinlock protecting mostly synchronization members. - * @proposed_placement: Proposed placement for the buffer. Changed only by the - * creator prior to validation as opposed to bo->mem.proposed_flags which is - * changed by the implementation prior to a buffer move if it wants to outsmart - * the buffer creator / user. This latter happens, for example, at eviction. * @mem: structure describing current placement. * @persistant_swap_storage: Usually the swap storage is deleted for buffers * pinned in physical memory. If this behaviour is not desired, this member @@ -177,7 +196,6 @@ struct ttm_buffer_object { * Members protected by the bo::reserved lock. */ - uint32_t proposed_placement; struct ttm_mem_reg mem; struct file *persistant_swap_storage; struct ttm_tt *ttm; @@ -293,21 +311,22 @@ extern int ttm_bo_wait(struct ttm_buffer_object *bo, bool lazy, * ttm_buffer_object_validate * * @bo: The buffer object. - * @proposed_placement: Proposed_placement for the buffer object. + * @placement: Proposed placement for the buffer object. * @interruptible: Sleep interruptible if sleeping. * @no_wait: Return immediately if the buffer is busy. * * Changes placement and caching policy of the buffer object - * according to bo::proposed_flags. + * according proposed placement. * Returns - * -EINVAL on invalid proposed_flags. + * -EINVAL on invalid proposed placement. * -ENOMEM on out-of-memory condition. * -EBUSY if no_wait is true and buffer busy. * -ERESTART if interrupted by a signal. */ extern int ttm_buffer_object_validate(struct ttm_buffer_object *bo, - uint32_t proposed_placement, - bool interruptible, bool no_wait); + struct ttm_placement *placement, + bool interruptible, bool no_wait); + /** * ttm_bo_unref * @@ -445,7 +464,6 @@ extern int ttm_bo_check_placement(struct ttm_buffer_object *bo, * * @bdev: Pointer to a ttm_bo_device struct. * @mem_type: The memory type. - * @p_offset: offset for managed area in pages. * @p_size: size managed area in pages. * * Initialize a manager for a given memory type. @@ -458,7 +476,7 @@ extern int ttm_bo_check_placement(struct ttm_buffer_object *bo, */ extern int ttm_bo_init_mm(struct ttm_bo_device *bdev, unsigned type, - unsigned long p_offset, unsigned long p_size); + unsigned long p_size); /** * ttm_bo_clean_mm * diff --git a/include/drm/ttm/ttm_bo_driver.h b/include/drm/ttm/ttm_bo_driver.h index 7a39ab9aa1d..fa5c9e51ee7 100644 --- a/include/drm/ttm/ttm_bo_driver.h +++ b/include/drm/ttm/ttm_bo_driver.h @@ -242,12 +242,6 @@ struct ttm_mem_type_manager { /** * struct ttm_bo_driver * - * @mem_type_prio: Priority array of memory types to place a buffer object in - * if it fits without evicting buffers from any of these memory types. - * @mem_busy_prio: Priority array of memory types to place a buffer object in - * if it needs to evict buffers to make room. - * @num_mem_type_prio: Number of elements in the @mem_type_prio array. - * @num_mem_busy_prio: Number of elements in the @num_mem_busy_prio array. * @create_ttm_backend_entry: Callback to create a struct ttm_backend. * @invalidate_caches: Callback to invalidate read caches when a buffer object * has been evicted. @@ -265,11 +259,6 @@ struct ttm_mem_type_manager { */ struct ttm_bo_driver { - const uint32_t *mem_type_prio; - const uint32_t *mem_busy_prio; - uint32_t num_mem_type_prio; - uint32_t num_mem_busy_prio; - /** * struct ttm_bo_driver member create_ttm_backend_entry * @@ -306,7 +295,8 @@ struct ttm_bo_driver { * finished, they'll end up in bo->mem.flags */ - uint32_t(*evict_flags) (struct ttm_buffer_object *bo); + void(*evict_flags) (struct ttm_buffer_object *bo, + struct ttm_placement *placement); /** * struct ttm_bo_driver member move: * @@ -651,9 +641,9 @@ extern bool ttm_mem_reg_is_pci(struct ttm_bo_device *bdev, * -ERESTART: An interruptible sleep was interrupted by a signal. */ extern int ttm_bo_mem_space(struct ttm_buffer_object *bo, - uint32_t proposed_placement, - struct ttm_mem_reg *mem, - bool interruptible, bool no_wait); + struct ttm_placement *placement, + struct ttm_mem_reg *mem, + bool interruptible, bool no_wait); /** * ttm_bo_wait_for_cpu * -- cgit v1.2.3-70-g09d2 From 98ffc4158e12008102cb6ae242a7fc46f9243f0d Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Mon, 7 Dec 2009 18:36:18 +0100 Subject: drm/ttm: Have the TTM code return -ERESTARTSYS instead of -ERESTART. Return -ERESTARTSYS instead of -ERESTART when interrupted by a signal. The -ERESTARTSYS is converted to an -EINTR by the kernel signal layer before returned to user-space. Signed-off-by: Thomas Hellstrom Signed-off-by: Jerome Glisse Signed-off-by: Dave Airlie --- drivers/gpu/drm/ttm/ttm_bo.c | 29 +++++++++++------------------ drivers/gpu/drm/ttm/ttm_bo_vm.c | 7 +------ include/drm/ttm/ttm_bo_api.h | 14 +++++++------- include/drm/ttm/ttm_bo_driver.h | 8 ++++---- 4 files changed, 23 insertions(+), 35 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 60d8179a8bc..640fb265dd5 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -125,7 +125,7 @@ int ttm_bo_wait_unreserved(struct ttm_buffer_object *bo, bool interruptible) ret = wait_event_interruptible(bo->event_queue, atomic_read(&bo->reserved) == 0); if (unlikely(ret != 0)) - return -ERESTART; + return ret; } else { wait_event(bo->event_queue, atomic_read(&bo->reserved) == 0); } @@ -571,7 +571,7 @@ static int ttm_bo_evict(struct ttm_buffer_object *bo, bool interruptible, spin_unlock(&bo->lock); if (unlikely(ret != 0)) { - if (ret != -ERESTART) { + if (ret != -ERESTARTSYS) { printk(KERN_ERR TTM_PFX "Failed to expire sync object before " "buffer eviction.\n"); @@ -588,7 +588,7 @@ static int ttm_bo_evict(struct ttm_buffer_object *bo, bool interruptible, ret = ttm_bo_mem_space(bo, &placement, &evict_mem, interruptible, no_wait); if (ret) { - if (ret != -ERESTART) + if (ret != -ERESTARTSYS) printk(KERN_ERR TTM_PFX "Failed to find memory space for " "buffer 0x%p eviction.\n", bo); @@ -598,7 +598,7 @@ static int ttm_bo_evict(struct ttm_buffer_object *bo, bool interruptible, ret = ttm_bo_handle_move_mem(bo, &evict_mem, true, interruptible, no_wait); if (ret) { - if (ret != -ERESTART) + if (ret != -ERESTARTSYS) printk(KERN_ERR TTM_PFX "Buffer eviction failed\n"); spin_lock(&glob->lru_lock); if (evict_mem.mm_node) { @@ -795,7 +795,7 @@ int ttm_bo_mem_space(struct ttm_buffer_object *bo, uint32_t cur_flags = 0; bool type_found = false; bool type_ok = false; - bool has_eagain = false; + bool has_erestartsys = false; struct drm_mm_node *node = NULL; int i, ret; @@ -882,28 +882,21 @@ int ttm_bo_mem_space(struct ttm_buffer_object *bo, mem->mm_node->private = bo; return 0; } - if (ret == -ERESTART) - has_eagain = true; + if (ret == -ERESTARTSYS) + has_erestartsys = true; } - ret = (has_eagain) ? -ERESTART : -ENOMEM; + ret = (has_erestartsys) ? -ERESTARTSYS : -ENOMEM; return ret; } EXPORT_SYMBOL(ttm_bo_mem_space); int ttm_bo_wait_cpu(struct ttm_buffer_object *bo, bool no_wait) { - int ret = 0; - if ((atomic_read(&bo->cpu_writers) > 0) && no_wait) return -EBUSY; - ret = wait_event_interruptible(bo->event_queue, - atomic_read(&bo->cpu_writers) == 0); - - if (ret == -ERESTARTSYS) - ret = -ERESTART; - - return ret; + return wait_event_interruptible(bo->event_queue, + atomic_read(&bo->cpu_writers) == 0); } int ttm_bo_move_buffer(struct ttm_buffer_object *bo, @@ -1673,7 +1666,7 @@ int ttm_bo_block_reservation(struct ttm_buffer_object *bo, bool interruptible, ret = wait_event_interruptible (bo->event_queue, atomic_read(&bo->reserved) == 0); if (unlikely(ret != 0)) - return -ERESTART; + return ret; } else { wait_event(bo->event_queue, atomic_read(&bo->reserved) == 0); diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c index 1c040d04033..609a85a4d85 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_vm.c +++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c @@ -114,7 +114,7 @@ static int ttm_bo_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) ret = ttm_bo_wait(bo, false, true, false); spin_unlock(&bo->lock); if (unlikely(ret != 0)) { - retval = (ret != -ERESTART) ? + retval = (ret != -ERESTARTSYS) ? VM_FAULT_SIGBUS : VM_FAULT_NOPAGE; goto out_unlock; } @@ -349,9 +349,6 @@ ssize_t ttm_bo_io(struct ttm_bo_device *bdev, struct file *filp, switch (ret) { case 0: break; - case -ERESTART: - ret = -EINTR; - goto out_unref; case -EBUSY: ret = -EAGAIN; goto out_unref; @@ -421,8 +418,6 @@ ssize_t ttm_bo_fbdev_io(struct ttm_buffer_object *bo, const char __user *wbuf, switch (ret) { case 0: break; - case -ERESTART: - return -EINTR; case -EBUSY: return -EAGAIN; default: diff --git a/include/drm/ttm/ttm_bo_api.h b/include/drm/ttm/ttm_bo_api.h index 2f7f56da214..4fd498523ce 100644 --- a/include/drm/ttm/ttm_bo_api.h +++ b/include/drm/ttm/ttm_bo_api.h @@ -303,7 +303,7 @@ ttm_bo_reference(struct ttm_buffer_object *bo) * Note: It might be necessary to block validations before the * wait by reserving the buffer. * Returns -EBUSY if no_wait is true and the buffer is busy. - * Returns -ERESTART if interrupted by a signal. + * Returns -ERESTARTSYS if interrupted by a signal. */ extern int ttm_bo_wait(struct ttm_buffer_object *bo, bool lazy, bool interruptible, bool no_wait); @@ -321,7 +321,7 @@ extern int ttm_bo_wait(struct ttm_buffer_object *bo, bool lazy, * -EINVAL on invalid proposed placement. * -ENOMEM on out-of-memory condition. * -EBUSY if no_wait is true and buffer busy. - * -ERESTART if interrupted by a signal. + * -ERESTARTSYS if interrupted by a signal. */ extern int ttm_buffer_object_validate(struct ttm_buffer_object *bo, struct ttm_placement *placement, @@ -347,7 +347,7 @@ extern void ttm_bo_unref(struct ttm_buffer_object **bo); * waiting for buffer idle. This lock is recursive. * Returns * -EBUSY if the buffer is busy and no_wait is true. - * -ERESTART if interrupted by a signal. + * -ERESTARTSYS if interrupted by a signal. */ extern int @@ -390,7 +390,7 @@ extern void ttm_bo_synccpu_write_release(struct ttm_buffer_object *bo); * Returns * -ENOMEM: Out of memory. * -EINVAL: Invalid placement flags. - * -ERESTART: Interrupted by signal while sleeping waiting for resources. + * -ERESTARTSYS: Interrupted by signal while sleeping waiting for resources. */ extern int ttm_buffer_object_init(struct ttm_bo_device *bdev, @@ -430,7 +430,7 @@ extern int ttm_buffer_object_init(struct ttm_bo_device *bdev, * Returns * -ENOMEM: Out of memory. * -EINVAL: Invalid placement flags. - * -ERESTART: Interrupted by signal while waiting for resources. + * -ERESTARTSYS: Interrupted by signal while waiting for resources. */ extern int ttm_buffer_object_create(struct ttm_bo_device *bdev, @@ -521,7 +521,7 @@ extern int ttm_bo_clean_mm(struct ttm_bo_device *bdev, unsigned mem_type); * * Returns: * -EINVAL: Invalid or uninitialized memory type. - * -ERESTART: The call was interrupted by a signal while waiting to + * -ERESTARTSYS: The call was interrupted by a signal while waiting to * evict a buffer. */ @@ -624,7 +624,7 @@ extern int ttm_bo_mmap(struct file *filp, struct vm_area_struct *vma, * be called from the fops::read and fops::write method. * Returns: * See man (2) write, man(2) read. In particular, - * the function may return -EINTR if + * the function may return -ERESTARTSYS if * interrupted by a signal. */ diff --git a/include/drm/ttm/ttm_bo_driver.h b/include/drm/ttm/ttm_bo_driver.h index fa5c9e51ee7..ff7664e0c3c 100644 --- a/include/drm/ttm/ttm_bo_driver.h +++ b/include/drm/ttm/ttm_bo_driver.h @@ -638,7 +638,7 @@ extern bool ttm_mem_reg_is_pci(struct ttm_bo_device *bdev, * -EBUSY: No space available (only if no_wait == 1). * -ENOMEM: Could not allocate memory for the buffer object, either due to * fragmentation or concurrent allocators. - * -ERESTART: An interruptible sleep was interrupted by a signal. + * -ERESTARTSYS: An interruptible sleep was interrupted by a signal. */ extern int ttm_bo_mem_space(struct ttm_buffer_object *bo, struct ttm_placement *placement, @@ -653,7 +653,7 @@ extern int ttm_bo_mem_space(struct ttm_buffer_object *bo, * Wait until a buffer object is no longer sync'ed for CPU access. * Returns: * -EBUSY: Buffer object was sync'ed for CPU access. (only if no_wait == 1). - * -ERESTART: An interruptible sleep was interrupted by a signal. + * -ERESTARTSYS: An interruptible sleep was interrupted by a signal. */ extern int ttm_bo_wait_cpu(struct ttm_buffer_object *bo, bool no_wait); @@ -757,7 +757,7 @@ extern void ttm_bo_unmap_virtual(struct ttm_buffer_object *bo); * -EAGAIN: The reservation may cause a deadlock. * Release all buffer reservations, wait for @bo to become unreserved and * try again. (only if use_sequence == 1). - * -ERESTART: A wait for the buffer to become unreserved was interrupted by + * -ERESTARTSYS: A wait for the buffer to become unreserved was interrupted by * a signal. Release all buffer reservations and return to user-space. */ extern int ttm_bo_reserve(struct ttm_buffer_object *bo, @@ -798,7 +798,7 @@ extern int ttm_bo_wait_unreserved(struct ttm_buffer_object *bo, * * Returns: * -EBUSY: If no_wait == 1 and the buffer is already reserved. - * -ERESTART: If interruptible == 1 and the process received a signal + * -ERESTARTSYS: If interruptible == 1 and the process received a signal * while sleeping. */ extern int ttm_bo_block_reservation(struct ttm_buffer_object *bo, -- cgit v1.2.3-70-g09d2 From 99d7e48e8cb867f303439ad40e995e203841bd94 Mon Sep 17 00:00:00 2001 From: Jerome Glisse Date: Wed, 9 Dec 2009 21:55:09 +0100 Subject: drm: Add memory manager debug function drm_mm_debug_table will print the memory manager state in table allowing to give a snapshot of the manager at given point in time. Usefull for debugging. Signed-off-by: Jerome Glisse Signed-off-by: Dave Airlie --- drivers/gpu/drm/drm_mm.c | 20 ++++++++++++++++++++ include/drm/drm_mm.h | 1 + 2 files changed, 21 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/drm_mm.c b/drivers/gpu/drm/drm_mm.c index a5c2773ccf2..d7d7eac3ddd 100644 --- a/drivers/gpu/drm/drm_mm.c +++ b/drivers/gpu/drm/drm_mm.c @@ -469,6 +469,26 @@ void drm_mm_takedown(struct drm_mm * mm) } EXPORT_SYMBOL(drm_mm_takedown); +void drm_mm_debug_table(struct drm_mm *mm, const char *prefix) +{ + struct drm_mm_node *entry; + int total_used = 0, total_free = 0, total = 0; + + list_for_each_entry(entry, &mm->ml_entry, ml_entry) { + printk(KERN_DEBUG "%s 0x%08lx-0x%08lx: %8ld: %s\n", + prefix, entry->start, entry->start + entry->size, + entry->size, entry->free ? "free" : "used"); + total += entry->size; + if (entry->free) + total_free += entry->size; + else + total_used += entry->size; + } + printk(KERN_DEBUG "%s total: %d, used %d free %d\n", prefix, total, + total_used, total_free); +} +EXPORT_SYMBOL(drm_mm_debug_table); + #if defined(CONFIG_DEBUG_FS) int drm_mm_dump_table(struct seq_file *m, struct drm_mm *mm) { diff --git a/include/drm/drm_mm.h b/include/drm/drm_mm.h index b40b2f06203..4c10be39a43 100644 --- a/include/drm/drm_mm.h +++ b/include/drm/drm_mm.h @@ -133,6 +133,7 @@ static inline struct drm_mm *drm_get_mm(struct drm_mm_node *block) return block->mm; } +extern void drm_mm_debug_table(struct drm_mm *mm, const char *prefix); #ifdef CONFIG_DEBUG_FS int drm_mm_dump_table(struct seq_file *m, struct drm_mm *mm); #endif -- cgit v1.2.3-70-g09d2