#include <drm/drmP.h>
#include <drm/radeon_drm.h>
#include "radeon.h"
#include "hmm.h"
#include "bitmap.h"


#define DRIVER_CAPS_0   HW_BIT_BLIT

//#define DRIVER_CAPS_0   0
#define DRIVER_CAPS_1   0

struct context *context_map[256];

struct hmm bm_mm;

extern struct drm_device *main_drm_device;

#if 0

void __attribute__((regparm(1))) destroy_bitmap(bitmap_t *bitmap)
{
//    dma_addr_t *pages = bitmap->obj->allocated_pages;
    int i;

    free_handle(&bm_mm, bitmap->handle);
    bitmap->handle = 0;
//    bitmap->obj->base.read_domains = I915_GEM_DOMAIN_GTT;
//    bitmap->obj->base.write_domain = I915_GEM_DOMAIN_CPU;

//    mutex_lock(&main_device->struct_mutex);
//    drm_gem_object_unreference(&bitmap->obj->base);
//    mutex_unlock(&main_device->struct_mutex);

//    if(pages != NULL)
//    {
//        for (i = 0; i < bitmap->page_count; i++)
//            FreePage(pages[i]);

//        DRM_DEBUG("%s release %d pages\n", __FUNCTION__, bitmap->page_count);

//        free(pages);
//    };
    UserFree(bitmap->uaddr);
    __DestroyObject(bitmap);
};

static int bitmap_get_pages_gtt(struct drm_i915_gem_object *obj)
{
    int page_count;

    /* Get the list of pages out of our struct file.  They'll be pinned
     * at this point until we release them.
     */

    page_count = obj->base.size / PAGE_SIZE;
    BUG_ON(obj->allocated_pages == NULL);
    BUG_ON(obj->pages.page != NULL);

    obj->pages.page = obj->allocated_pages;
    obj->pages.nents = page_count;


//   if (obj->tiling_mode != I915_TILING_NONE)
//       i915_gem_object_do_bit_17_swizzle(obj);

    return 0;
}

static void bitmap_put_pages_gtt(struct drm_i915_gem_object *obj)
{
    int ret, i;

    BUG_ON(obj->madv == __I915_MADV_PURGED);

    ret = i915_gem_object_set_to_cpu_domain(obj, true);
    if (ret) {
        /* In the event of a disaster, abandon all caches and
         * hope for the best.
         */
        WARN_ON(ret != -EIO);
        i915_gem_clflush_object(obj);
        obj->base.read_domains = obj->base.write_domain = I915_GEM_DOMAIN_CPU;
    }

    if (obj->madv == I915_MADV_DONTNEED)
        obj->dirty = 0;

    obj->dirty = 0;
}

static const struct drm_i915_gem_object_ops bitmap_object_ops = {
    .get_pages = bitmap_get_pages_gtt,
    .put_pages = bitmap_put_pages_gtt,
};



struct  io_call_10         /*     SRV_CREATE_SURFACE    */
{
    u32     handle;       // ignored
    void   *data;         // ignored

    u32     width;
    u32     height;
    u32     pitch;        // ignored

    u32     max_width;
    u32     max_height;
    u32     format;       // reserved mbz
};


int create_surface(struct drm_device *dev, struct io_call_10 *pbitmap)
{
    struct radeon_device *rdev = dev->dev_private;
    struct radeon_bo     *obj = NULL;

    bitmap_t   *bitmap;
    u32         handle;
    u32         width, max_width;
    u32         height, max_height;
    u32         size,  max_size;
    u32         pitch, max_pitch;
    void       *uaddr;
    dma_addr_t *pages;
    u32         page_count;

    int         i;

    int   ret;

    pbitmap->handle = 0;
    pbitmap->data   = (void*)-1;

    width  = pbitmap->width;
    height = pbitmap->height;

    if((width == 0)||(height == 0)||(width > 4096)||(height > 4096))
        goto err1;

    max_width  = (pbitmap->max_width ==0) ? width  : pbitmap->max_width;
    max_height = (pbitmap->max_height==0) ? height : pbitmap->max_height;

    handle = alloc_handle(&bm_mm);
//    printf("%s %d\n",__FUNCTION__, handle);

    if(handle == 0)
        goto err1;

    bitmap = CreateObject(GetPid(), sizeof(*bitmap));
//    printf("bitmap %x\n", bitmap);
    if( bitmap == NULL)
        goto err2;

    bitmap->handle = handle;
    bitmap->header.destroy = destroy_bitmap;
    bitmap->obj    = NULL;

    hmm_set_data(&bm_mm, handle, bitmap);

    pitch = ALIGN(width*4,64);
    size =  roundup(pitch*height, PAGE_SIZE);

//    printf("pitch %d size %d\n", pitch, size);

    max_pitch = ALIGN(max_width*4,64);
    max_size =  roundup(max_pitch*max_height, PAGE_SIZE);

//    printf("max_pitch %d max_size %d\n", max_pitch, max_size);

    ret = radeon_bo_create(rdev, size, PAGE_SIZE, false, RADEON_GEM_DOMAIN_GTT, NULL, &obj);

    if (unlikely(ret != 0))
        goto err3;

    ret = radeon_bo_reserve(obj, false);
    if (unlikely(ret != 0))
        goto err3;

    ret = radeon_bo_pin(obj, RADEON_GEM_DOMAIN_GTT, NULL);
    if (unlikely(ret != 0))
        goto err3;

#ifndef __TTM__
    ret = radeon_bo_user_map(obj, (void**)&uaddr);
    if (unlikely(ret != 0))
        goto err3;
#endif

    bitmap->page_count = size/PAGE_SIZE;
    bitmap->max_count  =  max_size/PAGE_SIZE;

//    DRM_DEBUG("%s alloc %d pages\n", __FUNCTION__, page_count);

    bitmap->handle = handle;
    bitmap->uaddr  = uaddr;
    bitmap->pitch  = pitch;
    bitmap->gaddr  = radeon_bo_gpu_offset(obj);

    bitmap->width  = width;
    bitmap->height = height;
    bitmap->max_width  = max_width;
    bitmap->max_height = max_height;

    bitmap->obj    = obj;
    bitmap->header.destroy = destroy_bitmap;

    pbitmap->handle = handle;
    pbitmap->data   = uaddr;
    pbitmap->pitch  = pitch;


    DRM_DEBUG("%s handle: %d pitch: %d gpu_addr: %x user_addr: %x\n",
            __FUNCTION__, handle, pitch, bitmap->gaddr, uaddr);

    return 0;

err5:
//    mutex_lock(&dev->struct_mutex);
//    drm_gem_object_unreference(&obj->base);
//    mutex_unlock(&dev->struct_mutex);

err4:
//    while (i--)
//        FreePage(pages[i]);
//    free(pages);
//    UserFree(uaddr);

err3:
    __DestroyObject(bitmap);
err2:
    free_handle(&bm_mm, handle);
err1:
    return -1;
};


int lock_surface(struct io_call_12 *pbitmap)
{
    int ret;

    bitmap_t  *bitmap;

    if(unlikely(pbitmap->handle == 0))
        return -1;

    bitmap = (bitmap_t*)hmm_get_data(&bm_mm, pbitmap->handle);

    if(unlikely(bitmap==NULL))
        return -1;
/*
    mutex_lock(&main_device->struct_mutex);
    ret = i915_gem_object_set_to_cpu_domain(bitmap->obj, true);
    mutex_unlock(&main_device->struct_mutex);

    if(ret != 0 )
    {
        pbitmap->data  = NULL;
        pbitmap->pitch = 0;

        dbgprintf("%s fail\n", __FUNCTION__);
        return ret;
    };
*/
    pbitmap->data  = bitmap->uaddr;
    pbitmap->pitch = bitmap->pitch;

    return 0;
};


#if 0

int resize_surface(struct io_call_14 *pbitmap)
{
    bitmap_t  *bitmap;
    dma_addr_t page, *pages;
    u32        size, page_count;
    u32        width, height;
    u32        pitch;
    int        i;
    int        ret = 0;


    if(unlikely(pbitmap->handle == 0))
        return -1;

    bitmap = (bitmap_t*)hmm_get_data(&bm_mm, pbitmap->handle);

    if(unlikely(bitmap==NULL))
        return -1;

    if( pbitmap->new_width > bitmap->max_width ||
        pbitmap->new_height > bitmap->max_height)
        return -1;

    width  = pbitmap->new_width;
    height = pbitmap->new_height;

    pitch = ALIGN(width*4,64);
    size =  roundup(pitch * height, PAGE_SIZE);
    page_count = size/PAGE_SIZE;

    DRM_DEBUG("new width %d height %d pitch %d size %d\n",
            width, height, pitch, size);

    if(page_count > bitmap->page_count)
    {
        char *vaddr = bitmap->uaddr + PAGE_SIZE * bitmap->page_count;

        pages = bitmap->obj->allocated_pages;

        DRM_DEBUG("old pages %d new_pages %d vaddr %x\n",
                bitmap->page_count, page_count, vaddr);

        for(i = bitmap->page_count; i < page_count; i++, vaddr+= PAGE_SIZE)
        {
            page = AllocPage();
            if ( page == 0 )
                goto err4;
            pages[i] = page;
            MapPage(vaddr, page, 0x207);        //map as shared page
        };

        DRM_DEBUG("%s alloc %d pages\n", __FUNCTION__,
                  page_count - bitmap->page_count);

        i915_gem_object_unpin(bitmap->obj);
        i915_gem_object_unbind(bitmap->obj);
        bitmap->obj->base.size = size;
        bitmap->obj->pages.nents = page_count;

        ret = i915_gem_object_pin(bitmap->obj, PAGE_SIZE, true,true);
        if (ret)
            goto err4;

        bitmap->page_count = page_count;
        bitmap->gaddr  = bitmap->obj->gtt_offset;
    }
    else if(page_count < bitmap->page_count)
    {
        char *vaddr = bitmap->uaddr + PAGE_SIZE * page_count;

        i915_gem_object_unpin(bitmap->obj);
        i915_gem_object_unbind(bitmap->obj);

        pages = bitmap->obj->allocated_pages;

        DRM_DEBUG("old pages %d new_pages %d vaddr %x\n",
                bitmap->page_count, page_count, vaddr);

        for(i = page_count; i < bitmap->page_count; i++, vaddr+= PAGE_SIZE)
        {
            MapPage(vaddr, 0, 0);        //unmap

            FreePage(pages[i]);
            pages[i] = 0;
        };

        DRM_DEBUG("%s release %d pages\n", __FUNCTION__,
                  bitmap->page_count - page_count);

        bitmap->obj->base.size = size;
        bitmap->obj->pages.nents = page_count;

        ret = i915_gem_object_pin(bitmap->obj, PAGE_SIZE, true,true);
        if (ret)
            goto err3;

        bitmap->page_count = page_count;
        bitmap->gaddr  = bitmap->obj->gtt_offset;
    };

    bitmap->width  = width;
    bitmap->height = height;
    bitmap->pitch  = pitch;


    pbitmap->data  = bitmap->uaddr;
    pbitmap->pitch = bitmap->pitch;

    return 0;

err4:
    while (i-- > bitmap->page_count)
        FreePage(pages[i]);

err3:
    return -1;
};
#endif


int init_bitmaps()
{
    int ret;

    ret = init_hmm(&bm_mm, 1024);

    return ret;
};



int get_driver_caps(hwcaps_t *caps)
{
    int ret = 0;

    ENTER();
    switch(caps->idx)
    {
        case 0:
            caps->opt[0] = DRIVER_CAPS_0;
            caps->opt[1] = DRIVER_CAPS_1;
            break;

        case 1:
            caps->cap1.max_tex_width  = 4096;
            caps->cap1.max_tex_height = 4096;
            break;
        default:
            ret = 1;
    };
    caps->idx = 1;
    LEAVE();
    return ret;
}


void __attribute__((regparm(1))) destroy_context(struct context *context)
{
    struct radeon_device *rdev = main_drm_device->dev_private;

    DRM_DEBUG("destroy context %x\n", context);

    context_map[context->slot] = NULL;

    radeon_ib_free(rdev, &context->ib);

    __DestroyObject(context);
};


#define CURRENT_TASK             (0x80003000)

struct context *get_context(struct drm_device *dev)
{
    struct radeon_device *rdev = dev->dev_private;
    struct context *context;
    struct io_call_10 io_10;
    int    slot = *((u8*)CURRENT_TASK);
    int    ret;

    context = context_map[slot];

    if( context != NULL)
        return context;

    context = CreateObject(GetPid(), sizeof(*context));

    if( context != NULL)
    {
        ret = radeon_ib_get(rdev, RADEON_RING_TYPE_GFX_INDEX, &context->ib, NULL, 4096);
        if (ret) {
            DRM_ERROR("radeon: failed to get ib (%d).\n", ret);
            goto err;
        };

        context->cmd_buffer = context->ib.ptr;

        context->header.destroy = destroy_context;
        context->mask  = NULL;
        context->seqno = 0;
        context->slot  = slot;

        context_map[slot] = context;
    };
    return context;

err:
    __DestroyObject(context);
    return NULL;
};

#endif