diff options
Diffstat (limited to 'drivers/gpu/drm/openchrome/via_h1_cmdbuf.c')
-rw-r--r-- | drivers/gpu/drm/openchrome/via_h1_cmdbuf.c | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/drivers/gpu/drm/openchrome/via_h1_cmdbuf.c b/drivers/gpu/drm/openchrome/via_h1_cmdbuf.c new file mode 100644 index 000000000000..fbd211b1c55f --- /dev/null +++ b/drivers/gpu/drm/openchrome/via_h1_cmdbuf.c @@ -0,0 +1,664 @@ +/* via_h1_cmdbuf.c -- DMA support for the VIA Unichrome/Pro + * + * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas. + * All Rights Reserved. + * + * Copyright 2004 Digeo, Inc., Palo Alto, CA, U.S.A. + * All Rights Reserved. + * + * Copyright 2004 The Unichrome project. + * 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: + * Tungsten Graphics, + * Erdi Chen, + * Thomas Hellstrom. + */ + +#include <drm/drmP.h> +#include "via_drv.h" +#include "via_3d_reg.h" + +#define CMDBUF_ALIGNMENT_SIZE (0x100) +#define CMDBUF_ALIGNMENT_MASK (0x0ff) + +static void via_cmdbuf_start(struct via_device *dev_priv); +static void via_cmdbuf_pause(struct via_device *dev_priv); +static void via_cmdbuf_reset(struct via_device *dev_priv); +static void via_cmdbuf_rewind(struct via_device *dev_priv); +static void via_pad_cache(struct via_device *dev_priv, int qwords); + +/* + * Free space in command buffer. + */ + +static uint32_t via_cmdbuf_space(struct via_device *dev_priv) +{ + uint32_t gart_base = dev_priv->dma_offset; + uint32_t hw_addr = ioread32(dev_priv->hw_addr_ptr) - gart_base; + + return ((hw_addr <= dev_priv->dma_low) ? + (dev_priv->dma_high + hw_addr - dev_priv->dma_low) : + (hw_addr - dev_priv->dma_low)); +} + +/* + * How much does the command regulator lag behind? + */ + +static uint32_t via_cmdbuf_lag(struct via_device *dev_priv) +{ + uint32_t gart_base = dev_priv->dma_offset; + uint32_t hw_addr = ioread32(dev_priv->hw_addr_ptr) - gart_base; + + return ((hw_addr <= dev_priv->dma_low) ? + (dev_priv->dma_low - hw_addr) : + (dev_priv->dma_wrap + dev_priv->dma_low - hw_addr)); +} + +/* + * Check that the given size fits in the buffer, otherwise wait. + */ + +static inline int +via_cmdbuf_wait(struct via_device *dev_priv, unsigned int size) +{ + uint32_t gart_base = dev_priv->dma_offset; + uint32_t cur_addr, hw_addr, next_addr; + volatile uint32_t *hw_addr_ptr; + uint32_t count; + hw_addr_ptr = dev_priv->hw_addr_ptr; + cur_addr = dev_priv->dma_low; + next_addr = cur_addr + size + 512 * 1024; + count = 1000000; + do { + hw_addr = *hw_addr_ptr - gart_base; + if (count-- == 0) { + DRM_ERROR + ("via_cmdbuf_wait timed out hw %x cur_addr %x next_addr %x\n", + hw_addr, cur_addr, next_addr); + return -1; + } + if ((cur_addr < hw_addr) && (next_addr >= hw_addr)) + usleep_range(500, 2000); + } while ((cur_addr < hw_addr) && (next_addr >= hw_addr)); + return 0; +} + +/* + * Checks whether buffer head has reach the end. Rewind the ring buffer + * when necessary. + * + * Returns virtual pointer to ring buffer. + */ + +static inline uint32_t *via_check_dma(struct via_device *dev_priv, + unsigned int size) +{ + if ((dev_priv->dma_low + size + 4 * CMDBUF_ALIGNMENT_SIZE) > + dev_priv->dma_high) { + via_cmdbuf_rewind(dev_priv); + } + if (via_cmdbuf_wait(dev_priv, size) != 0) + return NULL; + + return (uint32_t *) (dev_priv->dmabuf.virtual + dev_priv->dma_low); +} + +int via_dma_cleanup(struct drm_device *dev) +{ + if (dev->dev_private) { + struct via_device *dev_priv = dev->dev_private; + + if (dev_priv->dmabuf.virtual) { + struct ttm_buffer_object *bo = dev_priv->dmabuf.bo; + + via_cmdbuf_reset(dev_priv); + + via_bo_unpin(bo, &dev_priv->dmabuf); + ttm_bo_unref(&bo); + dev_priv->dmabuf.virtual = NULL; + } + } + return 0; +} + +static int via_initialize(struct drm_device *dev, + struct via_device *dev_priv, + drm_via_dma_init_t *init) +{ + struct ttm_buffer_object *bo; + int ret = -EFAULT; + + if (!dev_priv) { + DRM_ERROR("via_dma_init called before via_map_init\n"); + return ret; + } + + if (dev_priv->dmabuf.virtual != NULL) { + DRM_ERROR("called again without calling cleanup\n"); + return ret; + } + + ret = via_bo_create(&dev_priv->bdev, init->size, ttm_bo_type_kernel, + TTM_PL_FLAG_TT, VIA_MM_ALIGN_SIZE, PAGE_SIZE, + false, NULL, NULL, &bo); + if (!ret) { + ret = via_bo_pin(bo, &dev_priv->dmabuf); + if (ret) + goto out_err; + } + + dev_priv->dma_low = 0; + dev_priv->dma_high = bo->num_pages << PAGE_SHIFT; + dev_priv->dma_wrap = dev_priv->dma_high; + dev_priv->dma_offset = bo->offset; + dev_priv->last_pause_ptr = NULL; + dev_priv->hw_addr_ptr = + (void *)(dev_priv->mmio.virtual + init->reg_pause_addr); + + via_cmdbuf_start(dev_priv); +out_err: + if (ret) + DRM_ERROR("can not ioremap TTM DMA ring buffer\n"); + return ret; +} + +int via_dma_init(struct drm_device *dev, void *data, struct drm_file *file_priv) +{ + struct via_device *dev_priv = dev->dev_private; + drm_via_dma_init_t *init = data; + int retcode = 0; + + switch (init->func) { + case VIA_INIT_DMA: + if (!capable(CAP_SYS_ADMIN)) + retcode = -EPERM; + else + retcode = via_initialize(dev, dev_priv, init); + break; + case VIA_CLEANUP_DMA: + if (!capable(CAP_SYS_ADMIN)) + retcode = -EPERM; + else + retcode = via_dma_cleanup(dev); + break; + case VIA_DMA_INITIALIZED: + retcode = (dev_priv->dmabuf.virtual != NULL) ? + 0 : -EFAULT; + break; + default: + retcode = -EINVAL; + break; + } + + return retcode; +} + +int via_dispatch_cmdbuffer(struct drm_device *dev, drm_via_cmdbuffer_t *cmd) +{ + struct via_device *dev_priv = dev->dev_private; + uint32_t *vb; + int ret; + + if (dev_priv->dmabuf.virtual == NULL) { + DRM_ERROR("called without initializing AGP ring buffer.\n"); + return -EFAULT; + } + + if (cmd->size > VIA_PCI_BUF_SIZE) + return -ENOMEM; + + if (copy_from_user(dev_priv->pci_buf, cmd->buf, cmd->size)) + return -EFAULT; + + /* + * Running this function on AGP memory is dead slow. Therefore + * we run it on a temporary cacheable system memory buffer and + * copy it to AGP memory when ready. + */ + ret = via_verify_command_stream((uint32_t *) dev_priv->pci_buf, + cmd->size, dev, 1); + if (ret) + return ret; + + vb = via_check_dma(dev_priv, (cmd->size < 0x100) ? 0x102 : cmd->size); + if (vb == NULL) + return -EAGAIN; + + memcpy(vb, dev_priv->pci_buf, cmd->size); + + dev_priv->dma_low += cmd->size; + + /* + * Small submissions somehow stalls the CPU. (AGP cache effects?) + * pad to greater size. + */ + + if (cmd->size < 0x100) + via_pad_cache(dev_priv, (0x100 - cmd->size) >> 3); + via_cmdbuf_pause(dev_priv); + + return 0; +} + +int via_driver_dma_quiescent(struct drm_device *dev) +{ + struct via_device *dev_priv = dev->dev_private; + + if (!via_wait_idle(dev_priv)) + return -EBUSY; + return 0; +} + +int via_flush_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) +{ + + //LOCK_TEST_WITH_RETURN(dev, file_priv); + + return via_driver_dma_quiescent(dev); +} + +int via_cmdbuffer(struct drm_device *dev, void *data, struct drm_file *file_priv) +{ + drm_via_cmdbuffer_t *cmdbuf = data; + int ret; + + //LOCK_TEST_WITH_RETURN(dev, file_priv); + + DRM_DEBUG("buf %p size %lu\n", cmdbuf->buf, cmdbuf->size); + + ret = via_dispatch_cmdbuffer(dev, cmdbuf); + return ret; +} + +static int via_dispatch_pci_cmdbuffer(struct drm_device *dev, + drm_via_cmdbuffer_t *cmd) +{ + struct via_device *dev_priv = dev->dev_private; + int ret; + + if (cmd->size > VIA_PCI_BUF_SIZE) + return -ENOMEM; + if (copy_from_user(dev_priv->pci_buf, cmd->buf, cmd->size)) + return -EFAULT; + + ret = via_verify_command_stream((uint32_t *) dev_priv->pci_buf, + cmd->size, dev, 0); + if (ret) + return ret; + + ret = + via_parse_command_stream(dev, (const uint32_t *)dev_priv->pci_buf, + cmd->size); + return ret; +} + +int via_pci_cmdbuffer(struct drm_device *dev, void *data, struct drm_file *file_priv) +{ + drm_via_cmdbuffer_t *cmdbuf = data; + int ret; + + //LOCK_TEST_WITH_RETURN(dev, file_priv); + + DRM_DEBUG("buf %p size %lu\n", cmdbuf->buf, cmdbuf->size); + + ret = via_dispatch_pci_cmdbuffer(dev, cmdbuf); + return ret; +} + +static inline uint32_t *via_align_buffer(struct via_device *dev_priv, + uint32_t *vb, int qw_count) +{ + for (; qw_count > 0; --qw_count) + VIA_OUT_RING_QW(HC_DUMMY, HC_DUMMY); + return vb; +} + +/* + * This function is used internally by ring buffer management code. + * + * Returns virtual pointer to ring buffer. + */ +static inline uint32_t *via_get_dma(struct via_device *dev_priv) +{ + return (uint32_t *) (dev_priv->dmabuf.virtual + dev_priv->dma_low); +} + +/* + * Hooks a segment of data into the tail of the ring-buffer by + * modifying the pause address stored in the buffer itself. If + * the regulator has already paused, restart it. + */ +static int via_hook_segment(struct via_device *dev_priv, + uint32_t pause_addr_hi, uint32_t pause_addr_lo, + int no_pci_fire) +{ + int paused, count; + volatile uint32_t *paused_at = dev_priv->last_pause_ptr; + uint32_t reader, ptr; + uint32_t diff; + + paused = 0; + mb(); + (void) *(volatile uint32_t *)(via_get_dma(dev_priv) - 1); + + *paused_at = pause_addr_lo; + mb(); + (void) *paused_at; + + reader = ioread32(dev_priv->hw_addr_ptr); + ptr = ((volatile void *)paused_at - dev_priv->dmabuf.virtual) + + dev_priv->dma_offset + 4; + + dev_priv->last_pause_ptr = via_get_dma(dev_priv) - 1; + + /* + * If there is a possibility that the command reader will + * miss the new pause address and pause on the old one, + * In that case we need to program the new start address + * using PCI. + */ + + diff = (uint32_t) (ptr - reader) - dev_priv->dma_diff; + count = 10000000; + + while ((diff < CMDBUF_ALIGNMENT_SIZE) && count--) { + paused = (VIA_READ(0x41c) & 0x80000000); + if (paused) + break; + reader = ioread32(dev_priv->hw_addr_ptr); + diff = (uint32_t) (ptr - reader) - dev_priv->dma_diff; + } + + paused = VIA_READ(0x41c) & 0x80000000; + if (paused && !no_pci_fire) { + reader = ioread32(dev_priv->hw_addr_ptr); + diff = (uint32_t) (ptr - reader) - dev_priv->dma_diff; + diff &= (dev_priv->dma_high - 1); + if (diff != 0 && diff < (dev_priv->dma_high >> 1)) { + DRM_ERROR("Paused at incorrect address. " + "0x%08x, 0x%08x 0x%08x\n", + ptr, reader, dev_priv->dma_diff); + } else if (diff == 0) { + /* + * There is a concern that these writes may stall the PCI bus + * if the GPU is not idle. However, idling the GPU first + * doesn't make a difference. + */ + + VIA_WRITE(VIA_REG_TRANSET, (HC_ParaType_PreCR << 16)); + VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_hi); + VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_lo); + VIA_READ(VIA_REG_TRANSPACE); + } + } + return paused; +} + +int via_wait_idle(struct via_device *dev_priv) +{ + int count = 10000000; + + while (!(VIA_READ(VIA_REG_STATUS) & VIA_VR_QUEUE_EMPTY) && --count) + ; + + while (count && (VIA_READ(VIA_REG_STATUS) & + (VIA_CMD_RGTR_BUSY | VIA_2D_ENG_BUSY | + VIA_3D_ENG_BUSY))) + --count; + return count; +} + +static uint32_t *via_align_cmd(struct via_device *dev_priv, uint32_t cmd_type, + uint32_t addr, uint32_t *cmd_addr_hi, + uint32_t *cmd_addr_lo, int skip_wait) +{ + uint32_t gart_base; + uint32_t cmd_addr, addr_lo, addr_hi; + uint32_t *vb; + uint32_t qw_pad_count; + + if (!skip_wait) + via_cmdbuf_wait(dev_priv, 2 * CMDBUF_ALIGNMENT_SIZE); + + vb = via_get_dma(dev_priv); + VIA_OUT_RING_QW(HC_HEADER2 | ((VIA_REG_TRANSET >> 2) << 12) | + (VIA_REG_TRANSPACE >> 2), HC_ParaType_PreCR << 16); + + gart_base = dev_priv->dma_offset; + qw_pad_count = (CMDBUF_ALIGNMENT_SIZE >> 3) - + ((dev_priv->dma_low & CMDBUF_ALIGNMENT_MASK) >> 3); + + cmd_addr = (addr) ? addr : + gart_base + dev_priv->dma_low - 8 + (qw_pad_count << 3); + addr_lo = ((HC_SubA_HAGPBpL << 24) | (cmd_type & HC_HAGPBpID_MASK) | + (cmd_addr & HC_HAGPBpL_MASK)); + addr_hi = ((HC_SubA_HAGPBpH << 24) | (cmd_addr >> 24)); + + vb = via_align_buffer(dev_priv, vb, qw_pad_count - 1); + VIA_OUT_RING_QW(*cmd_addr_hi = addr_hi, *cmd_addr_lo = addr_lo); + return vb; +} + +static void via_cmdbuf_start(struct via_device *dev_priv) +{ + uint32_t pause_addr_lo, pause_addr_hi; + uint32_t start_addr, start_addr_lo; + uint32_t end_addr, end_addr_lo; + uint32_t command; + uint32_t gart_base; + uint32_t ptr; + uint32_t reader; + int count; + + dev_priv->dma_low = 0; + + gart_base = dev_priv->dma_offset; + start_addr = gart_base; + end_addr = gart_base + dev_priv->dma_high; + + start_addr_lo = ((HC_SubA_HAGPBstL << 24) | (start_addr & 0xFFFFFF)); + end_addr_lo = ((HC_SubA_HAGPBendL << 24) | (end_addr & 0xFFFFFF)); + command = ((HC_SubA_HAGPCMNT << 24) | (start_addr >> 24) | + ((end_addr & 0xff000000) >> 16)); + + dev_priv->last_pause_ptr = + via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, + &pause_addr_hi, &pause_addr_lo, 1) - 1; + + mb(); + (void) *(volatile uint32_t *) dev_priv->last_pause_ptr; + + VIA_WRITE(VIA_REG_TRANSET, (HC_ParaType_PreCR << 16)); + VIA_WRITE(VIA_REG_TRANSPACE, command); + VIA_WRITE(VIA_REG_TRANSPACE, start_addr_lo); + VIA_WRITE(VIA_REG_TRANSPACE, end_addr_lo); + + VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_hi); + VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_lo); + wmb(); + VIA_WRITE(VIA_REG_TRANSPACE, command | HC_HAGPCMNT_MASK); + VIA_READ(VIA_REG_TRANSPACE); + + dev_priv->dma_diff = 0; + + count = 10000000; + while (!(VIA_READ(0x41c) & 0x80000000) && count--); + + reader = ioread32(dev_priv->hw_addr_ptr); + ptr = ((volatile void *)dev_priv->last_pause_ptr - dev_priv->dmabuf.virtual) + + dev_priv->dma_offset + 4; + + /* + * This is the difference between where we tell the + * command reader to pause and where it actually pauses. + * This differs between hw implementation so we need to + * detect it. + */ + + dev_priv->dma_diff = ptr - reader; +} + +static void via_pad_cache(struct via_device *dev_priv, int qwords) +{ + uint32_t *vb; + + via_cmdbuf_wait(dev_priv, qwords + 2); + vb = via_get_dma(dev_priv); + VIA_OUT_RING_QW(HC_HEADER2, HC_ParaType_NotTex << 16); + via_align_buffer(dev_priv, vb, qwords); +} + +static inline void via_dummy_bitblt(struct via_device *dev_priv) +{ + uint32_t *vb = via_get_dma(dev_priv); + VIA_OUT_RING_H1(0x0C, (0 | (0 << 16))); + VIA_OUT_RING_H1(0x10, 0 | (0 << 16)); + VIA_OUT_RING_H1(0x0, 0x1 | 0x2000 | 0xAA000000); +} + +static void via_cmdbuf_rewind(struct via_device *dev_priv) +{ + uint32_t pause_addr_lo, pause_addr_hi; + uint32_t jump_addr_lo, jump_addr_hi; + volatile uint32_t *last_pause_ptr; + uint32_t dma_low_save1, dma_low_save2; + + via_align_cmd(dev_priv, HC_HAGPBpID_JUMP, 0, &jump_addr_hi, + &jump_addr_lo, 0); + + dev_priv->dma_wrap = dev_priv->dma_low; + + /* + * Wrap command buffer to the beginning. + */ + + dev_priv->dma_low = 0; + if (via_cmdbuf_wait(dev_priv, CMDBUF_ALIGNMENT_SIZE) != 0) + DRM_ERROR("via_cmdbuf_rewind failed\n"); + + via_dummy_bitblt(dev_priv); + via_dummy_bitblt(dev_priv); + + last_pause_ptr = + via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi, + &pause_addr_lo, 0) - 1; + via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi, + &pause_addr_lo, 0); + + *last_pause_ptr = pause_addr_lo; + dma_low_save1 = dev_priv->dma_low; + + /* + * Now, set a trap that will pause the regulator if it tries to rerun the old + * command buffer. (Which may happen if via_hook_segment detecs a command regulator pause + * and reissues the jump command over PCI, while the regulator has already taken the jump + * and actually paused at the current buffer end). + * There appears to be no other way to detect this condition, since the hw_addr_pointer + * does not seem to get updated immediately when a jump occurs. + */ + + last_pause_ptr = + via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi, + &pause_addr_lo, 0) - 1; + via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi, + &pause_addr_lo, 0); + *last_pause_ptr = pause_addr_lo; + + dma_low_save2 = dev_priv->dma_low; + dev_priv->dma_low = dma_low_save1; + via_hook_segment(dev_priv, jump_addr_hi, jump_addr_lo, 0); + dev_priv->dma_low = dma_low_save2; + via_hook_segment(dev_priv, pause_addr_hi, pause_addr_lo, 0); +} + +static void via_cmdbuf_flush(struct via_device *dev_priv, uint32_t cmd_type) +{ + uint32_t pause_addr_lo, pause_addr_hi; + + via_align_cmd(dev_priv, cmd_type, 0, &pause_addr_hi, &pause_addr_lo, 0); + via_hook_segment(dev_priv, pause_addr_hi, pause_addr_lo, 0); +} + +static void via_cmdbuf_pause(struct via_device *dev_priv) +{ + via_cmdbuf_flush(dev_priv, HC_HAGPBpID_PAUSE); +} + +static void via_cmdbuf_reset(struct via_device *dev_priv) +{ + via_cmdbuf_flush(dev_priv, HC_HAGPBpID_STOP); + via_wait_idle(dev_priv); +} + +/* + * User interface to the space and lag functions. + */ + +int via_cmdbuf_size(struct drm_device *dev, void *data, struct drm_file *file_priv) +{ + struct via_device *dev_priv = dev->dev_private; + drm_via_cmdbuf_size_t *d_siz = data; + int ret = 0; + uint32_t tmp_size, count; + + DRM_DEBUG("\n"); + //LOCK_TEST_WITH_RETURN(dev, file_priv); + + if (dev_priv->dmabuf.virtual == NULL) { + DRM_ERROR("called without initializing AGP ring buffer.\n"); + return -EFAULT; + } + + count = 1000000; + tmp_size = d_siz->size; + switch (d_siz->func) { + case VIA_CMDBUF_SPACE: + while (((tmp_size = via_cmdbuf_space(dev_priv)) < d_siz->size) + && --count) { + if (!d_siz->wait) + break; + } + if (!count) { + DRM_ERROR("VIA_CMDBUF_SPACE timed out.\n"); + ret = -EAGAIN; + } + break; + case VIA_CMDBUF_LAG: + while (((tmp_size = via_cmdbuf_lag(dev_priv)) > d_siz->size) + && --count) { + if (!d_siz->wait) + break; + } + if (!count) { + DRM_ERROR("VIA_CMDBUF_LAG timed out.\n"); + ret = -EAGAIN; + } + break; + default: + ret = -EFAULT; + } + d_siz->size = tmp_size; + + return ret; +} |