| /* |
| * Copyright (C) 2011 Chia-I Wu <[email protected]> |
| * Copyright (C) 2011 LunarG Inc. |
| * |
| * Based on xf86-video-nouveau, which has |
| * |
| * Copyright © 2007 Red Hat, Inc. |
| * Copyright © 2008 Maarten Maathuis |
| * |
| * 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 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. |
| */ |
| |
| #define LOG_TAG "GRALLOC-NOUVEAU" |
| |
| #include <cutils/log.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <drm.h> |
| #include <nouveau_drmif.h> |
| #include <nouveau_channel.h> |
| #include <nouveau_bo.h> |
| |
| #include "gralloc_drm.h" |
| #include "gralloc_drm_priv.h" |
| |
| #define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
| |
| #define NVC0_TILE_HEIGHT(m) (8 << ((m) >> 4)) |
| |
| enum { |
| NvDmaFB = 0xd8000001, |
| NvDmaTT = 0xd8000002, |
| }; |
| |
| struct nouveau_info { |
| struct gralloc_drm_drv_t base; |
| |
| int fd; |
| struct nouveau_device *dev; |
| struct nouveau_channel *chan; |
| int arch; |
| int tiled_scanout; |
| }; |
| |
| struct nouveau_buffer { |
| struct gralloc_drm_bo_t base; |
| |
| struct nouveau_bo *bo; |
| }; |
| |
| static struct nouveau_bo *alloc_bo(struct nouveau_info *info, |
| int width, int height, int cpp, int usage, int *pitch) |
| { |
| struct nouveau_bo *bo = NULL; |
| int flags, tile_mode, tile_flags; |
| int tiled, scanout; |
| unsigned int align; |
| |
| flags = NOUVEAU_BO_MAP | NOUVEAU_BO_VRAM; |
| tile_mode = 0; |
| tile_flags = 0; |
| |
| scanout = !!(usage & GRALLOC_USAGE_HW_FB); |
| |
| tiled = !(usage & (GRALLOC_USAGE_SW_READ_OFTEN | |
| GRALLOC_USAGE_SW_WRITE_OFTEN)); |
| if (!info->chan) |
| tiled = 0; |
| else if (scanout && info->tiled_scanout) |
| tiled = 1; |
| |
| /* calculate pitch align */ |
| align = 64; |
| if (info->arch >= 0x50) { |
| if (scanout && !info->tiled_scanout) |
| align = 256; |
| else |
| tiled = 1; |
| } |
| |
| *pitch = ALIGN(width * cpp, align); |
| |
| if (tiled) { |
| if (info->arch >= 0xc0) { |
| if (height > 64) |
| tile_mode = 0x40; |
| else if (height > 32) |
| tile_mode = 0x30; |
| else if (height > 16) |
| tile_mode = 0x20; |
| else if (height > 8) |
| tile_mode = 0x10; |
| else |
| tile_mode = 0x00; |
| |
| tile_flags = 0xfe00; |
| |
| align = NVC0_TILE_HEIGHT(tile_mode); |
| height = ALIGN(height, align); |
| } |
| else if (info->arch >= 0x50) { |
| if (height > 32) |
| tile_mode = 4; |
| else if (height > 16) |
| tile_mode = 3; |
| else if (height > 8) |
| tile_mode = 2; |
| else if (height > 4) |
| tile_mode = 1; |
| else |
| tile_mode = 0; |
| |
| tile_flags = (scanout && cpp != 2) ? 0x7a00 : 0x7000; |
| |
| align = 1 << (tile_mode + 2); |
| height = ALIGN(height, align); |
| } |
| else { |
| align = *pitch / 4; |
| |
| /* round down to the previous power of two */ |
| align >>= 1; |
| align |= align >> 1; |
| align |= align >> 2; |
| align |= align >> 4; |
| align |= align >> 8; |
| align |= align >> 16; |
| align++; |
| |
| align = MAX((info->dev->chipset >= 0x40) ? 1024 : 256, |
| align); |
| |
| /* adjust pitch */ |
| *pitch = ALIGN(*pitch, align); |
| |
| tile_mode = *pitch; |
| } |
| } |
| |
| if (cpp == 4) |
| tile_flags |= NOUVEAU_BO_TILE_32BPP; |
| else if (cpp == 2) |
| tile_flags |= NOUVEAU_BO_TILE_16BPP; |
| |
| if (scanout) |
| tile_flags |= NOUVEAU_BO_TILE_SCANOUT; |
| |
| if (nouveau_bo_new_tile(info->dev, flags, 0, *pitch * height, |
| tile_mode, tile_flags, &bo)) { |
| ALOGE("failed to allocate bo (flags 0x%x, size %d, tile_mode 0x%x, tile_flags 0x%x)", |
| flags, *pitch * height, tile_mode, tile_flags); |
| bo = NULL; |
| } |
| |
| return bo; |
| } |
| |
| static struct gralloc_drm_bo_t * |
| nouveau_alloc(struct gralloc_drm_drv_t *drv, struct gralloc_drm_handle_t *handle) |
| { |
| struct nouveau_info *info = (struct nouveau_info *) drv; |
| struct nouveau_buffer *nb; |
| int cpp; |
| |
| cpp = gralloc_drm_get_bpp(handle->format); |
| if (!cpp) { |
| ALOGE("unrecognized format 0x%x", handle->format); |
| return NULL; |
| } |
| |
| nb = calloc(1, sizeof(*nb)); |
| if (!nb) |
| return NULL; |
| |
| if (handle->name) { |
| if (nouveau_bo_handle_ref(info->dev, handle->name, &nb->bo)) { |
| ALOGE("failed to create nouveau bo from name %u", |
| handle->name); |
| free(nb); |
| return NULL; |
| } |
| } |
| else { |
| int width, height, pitch; |
| |
| width = handle->width; |
| height = handle->height; |
| gralloc_drm_align_geometry(handle->format, &width, &height); |
| |
| nb->bo = alloc_bo(info, width, height, |
| cpp, handle->usage, &pitch); |
| if (!nb->bo) { |
| ALOGE("failed to allocate nouveau bo %dx%dx%d", |
| handle->width, handle->height, cpp); |
| free(nb); |
| return NULL; |
| } |
| |
| if (nouveau_bo_handle_get(nb->bo, |
| (uint32_t *) &handle->name)) { |
| ALOGE("failed to flink nouveau bo"); |
| nouveau_bo_ref(NULL, &nb->bo); |
| free(nb); |
| return NULL; |
| } |
| |
| handle->stride = pitch; |
| } |
| |
| if (handle->usage & GRALLOC_USAGE_HW_FB) |
| nb->base.fb_handle = nb->bo->handle; |
| |
| nb->base.handle = handle; |
| |
| return &nb->base; |
| } |
| |
| static void nouveau_free(struct gralloc_drm_drv_t *drv, |
| struct gralloc_drm_bo_t *bo) |
| { |
| struct nouveau_buffer *nb = (struct nouveau_buffer *) bo; |
| nouveau_bo_ref(NULL, &nb->bo); |
| free(nb); |
| } |
| |
| static int nouveau_map(struct gralloc_drm_drv_t *drv, |
| struct gralloc_drm_bo_t *bo, int x, int y, int w, int h, |
| int enable_write, void **addr) |
| { |
| struct nouveau_buffer *nb = (struct nouveau_buffer *) bo; |
| uint32_t flags; |
| int err; |
| |
| flags = NOUVEAU_BO_RD; |
| if (enable_write) |
| flags |= NOUVEAU_BO_WR; |
| |
| /* TODO if tiled, allocate a linear copy of bo in GART and map it */ |
| err = nouveau_bo_map(nb->bo, flags); |
| if (!err) |
| *addr = nb->bo->map; |
| |
| return err; |
| } |
| |
| static void nouveau_unmap(struct gralloc_drm_drv_t *drv, |
| struct gralloc_drm_bo_t *bo) |
| { |
| struct nouveau_buffer *nb = (struct nouveau_buffer *) bo; |
| /* TODO if tiled, unmap the linear bo and copy back */ |
| nouveau_bo_unmap(nb->bo); |
| } |
| |
| static void nouveau_destroy(struct gralloc_drm_drv_t *drv) |
| { |
| struct nouveau_info *info = (struct nouveau_info *) drv; |
| |
| if (info->chan) |
| nouveau_channel_free(&info->chan); |
| nouveau_device_close(&info->dev); |
| free(info); |
| } |
| |
| static int nouveau_init(struct nouveau_info *info) |
| { |
| int err = 0; |
| |
| switch (info->dev->chipset & 0xf0) { |
| case 0x00: |
| info->arch = 0x04; |
| break; |
| case 0x10: |
| info->arch = 0x10; |
| break; |
| case 0x20: |
| info->arch = 0x20; |
| break; |
| case 0x30: |
| info->arch = 0x30; |
| break; |
| case 0x40: |
| case 0x60: |
| info->arch = 0x40; |
| break; |
| case 0x50: |
| case 0x80: |
| case 0x90: |
| case 0xa0: |
| info->arch = 0x50; |
| break; |
| case 0xc0: |
| info->arch = 0xc0; |
| break; |
| default: |
| ALOGE("unknown nouveau chipset 0x%x", info->dev->chipset); |
| err = -EINVAL; |
| break; |
| } |
| |
| info->tiled_scanout = (info->chan != NULL); |
| |
| return err; |
| } |
| |
| struct gralloc_drm_drv_t *gralloc_drm_drv_create_for_nouveau(int fd) |
| { |
| struct nouveau_info *info; |
| int err; |
| |
| info = calloc(1, sizeof(*info)); |
| if (!info) |
| return NULL; |
| |
| info->fd = fd; |
| err = nouveau_device_open_existing(&info->dev, 0, info->fd, 0); |
| if (err) { |
| ALOGE("failed to create nouveau device"); |
| free(info); |
| return NULL; |
| } |
| |
| err = nouveau_channel_alloc(info->dev, NvDmaFB, NvDmaTT, |
| 24 * 1024, &info->chan); |
| if (err) { |
| /* make it non-fatal temporarily as it may require firmwares */ |
| ALOGW("failed to create nouveau channel"); |
| info->chan = NULL; |
| } |
| |
| err = nouveau_init(info); |
| if (err) { |
| if (info->chan) |
| nouveau_channel_free(&info->chan); |
| nouveau_device_close(&info->dev); |
| free(info); |
| return NULL; |
| } |
| |
| info->base.destroy = nouveau_destroy; |
| info->base.alloc = nouveau_alloc; |
| info->base.free = nouveau_free; |
| info->base.map = nouveau_map; |
| info->base.unmap = nouveau_unmap; |
| |
| return &info->base; |
| } |