blob: 7a912b4cebb142a109c8b4a6d9d9dfb39070d4af [file] [log] [blame]
/*
* Copyright (c) 2012-2017 Etnaviv Project
*
* 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 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 _POSIX_C_SOURCE 200112L
#include "etna_fb.h"
#include "etna_util.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <string.h>
#include <linux/videodev2.h>
#include <errno.h>
#include "hw/state_3d.xml.h"
#ifdef ANDROID
#define FBDEV_DEV "/dev/graphics/fb%i"
#else
#define FBDEV_DEV "/dev/fb%i"
#endif
/* Structure to convert framebuffer format to RS destination conf */
struct etna_fb_format_desc
{
unsigned bits_per_pixel;
unsigned red_offset;
unsigned red_length;
unsigned green_offset;
unsigned green_length;
unsigned blue_offset;
unsigned blue_length;
unsigned alpha_offset;
unsigned alpha_length;
unsigned grayscale;
unsigned rs_format;
bool swap_rb;
};
static const struct etna_fb_format_desc etna_fb_formats[] = {
/* bpp ro rl go gl bo bl ao al gs rs_format swap_rb */
{32, 16, 8, 8, 8, 0 , 8, 0, 0, 0, RS_FORMAT_X8R8G8B8, false},
{32, 0 , 8, 8, 8, 16, 8, 0, 0, 0, RS_FORMAT_X8R8G8B8, true},
{32, 16, 8, 8, 8, 0 , 8, 24, 8, 0, RS_FORMAT_A8R8G8B8, false},
{32, 16, 8, 8, 8, 0 , 8, 24, 8, V4L2_PIX_FMT_ARGB32, RS_FORMAT_A8R8G8B8, false},
{32, 0 , 8, 8, 8, 16, 8, 24, 8, 0, RS_FORMAT_A8R8G8B8, true},
{16, 8 , 4, 4, 4, 0, 4, 0, 0, 0, RS_FORMAT_X4R4G4B4, false},
{16, 0 , 4, 4, 4, 8, 4, 0, 0, 0, RS_FORMAT_X4R4G4B4, true},
{16, 8 , 4, 4, 4, 0, 4, 12, 4, 0, RS_FORMAT_A4R4G4B4, false},
{16, 0 , 4, 4, 4, 8, 4, 12, 4, 0, RS_FORMAT_A4R4G4B4, true},
{16, 10, 5, 5, 5, 0, 5, 0, 0, 0, RS_FORMAT_X1R5G5B5, false},
{16, 0, 5, 5, 5, 10, 5, 0, 0, 0, RS_FORMAT_X1R5G5B5, true},
{16, 10, 5, 5, 5, 0, 5, 15, 1, 0, RS_FORMAT_A1R5G5B5, false},
{16, 0, 5, 5, 5, 10, 5, 15, 1, 0, RS_FORMAT_A1R5G5B5, true},
{16, 11, 5, 5, 6, 0, 5, 0, 0, 0, RS_FORMAT_R5G6B5, false},
{16, 0, 5, 5, 6, 11, 5, 0, 0, 0, RS_FORMAT_R5G6B5, true},
{16, 0, 0, 0, 0, 0 , 0, 0, 0, V4L2_PIX_FMT_YUYV, RS_FORMAT_YUY2, false},
};
#define NUM_FB_FORMATS (sizeof(etna_fb_formats) / sizeof(etna_fb_formats[0]))
// Align height to 8 to make sure we can use the buffer as target
// for resolve even on GPUs with two pixel pipes
#define ETNA_FB_HEIGHT_ALIGN (8)
/* Get resolve format and swap red/blue format based on report on red/green/blue
* bit positions from kernel.
*/
static bool etna_fb_get_format(const struct fb_var_screeninfo *fb_var, unsigned *rs_format, bool *swap_rb)
{
unsigned fmt_idx=0;
/* linear scan of table to find matching format */
for(fmt_idx=0; fmt_idx<NUM_FB_FORMATS; ++fmt_idx)
{
const struct etna_fb_format_desc *desc = &etna_fb_formats[fmt_idx];
if(desc->red_offset == fb_var->red.offset &&
desc->red_length == fb_var->red.length &&
desc->green_offset == fb_var->green.offset &&
desc->green_length == fb_var->green.length &&
desc->blue_offset == fb_var->blue.offset &&
desc->blue_length == fb_var->blue.length &&
(desc->alpha_offset == fb_var->transp.offset || desc->alpha_length == 0) &&
desc->alpha_length == fb_var->transp.length &&
desc->grayscale == fb_var->grayscale)
{
break;
}
}
if(fmt_idx == NUM_FB_FORMATS)
{
printf("Unsupported framebuffer format: red_offset=%i red_length=%i green_offset=%i green_length=%i blue_offset=%i blue_length=%i trans_offset=%i transp_length=%i grayscale=%08x\n",
(int)fb_var->red.offset, (int)fb_var->red.length,
(int)fb_var->green.offset, (int)fb_var->green.length,
(int)fb_var->blue.offset, (int)fb_var->blue.length,
(int)fb_var->transp.offset, (int)fb_var->transp.length,
(int)fb_var->grayscale);
return false;
} else {
printf("Framebuffer format: %i, flip_rb=%i\n",
etna_fb_formats[fmt_idx].rs_format,
etna_fb_formats[fmt_idx].swap_rb);
*rs_format = etna_fb_formats[fmt_idx].rs_format;
*swap_rb = etna_fb_formats[fmt_idx].swap_rb;
return true;
}
}
/* Open framebuffer and get information */
int fb_open(struct etna_device *conn, int num, struct fb_info **out_p)
{
#ifndef LIBETNAVIV_BO_EXTENSIONS
printf("Error: fb_open needs libetnaviv extensions\n");
return -1;
#else
char devname[256];
struct fb_info *out = ETNA_CALLOC_STRUCT(fb_info);
snprintf(devname, 256, FBDEV_DEV, num);
int fd = open(devname, O_RDWR);
if (fd == -1) {
printf("Error: failed to open %s: %s\n",
devname, strerror(errno));
return errno;
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &out->fb_var) ||
ioctl(fd, FBIOGET_FSCREENINFO, &out->fb_fix)) {
printf("Error: failed to run ioctl on %s: %s\n",
devname, strerror(errno));
close(fd);
return errno;
}
printf("fix smem_start %08x\n", (unsigned)out->fb_fix.smem_start);
printf(" smem_len %08x\n", (unsigned)out->fb_fix.smem_len);
printf(" line_length %08x\n", (unsigned)out->fb_fix.line_length);
printf("\n");
printf("var x_res %i\n", (unsigned)out->fb_var.xres);
printf(" y_res %i\n", (unsigned)out->fb_var.yres);
printf(" x_res_virtual %i\n", (unsigned)out->fb_var.xres_virtual);
printf(" y_res_virtual %i\n", (unsigned)out->fb_var.yres_virtual);
printf(" bits_per_pixel %i\n", (unsigned)out->fb_var.bits_per_pixel);
printf(" red.offset %i\n", (unsigned)out->fb_var.red.offset);
printf(" red.length %i\n", (unsigned)out->fb_var.red.length);
printf(" green.offset %i\n", (unsigned)out->fb_var.green.offset);
printf(" green.length %i\n", (unsigned)out->fb_var.green.length);
printf(" blue.offset %i\n", (unsigned)out->fb_var.blue.offset);
printf(" blue.length %i\n", (unsigned)out->fb_var.blue.length);
printf(" transp.offset %i\n", (unsigned)out->fb_var.transp.offset);
printf(" transp.length %i\n", (unsigned)out->fb_var.transp.length);
printf(" grayscale 0x%08x\n", (unsigned)out->fb_var.grayscale);
out->fd = fd;
out->stride = out->fb_fix.line_length;
out->width = out->fb_var.xres;
out->height = out->fb_var.yres;
// Align height to make sure we can use the buffer as target
// for resolve even on GPUs with two pixel pipes
out->padded_height = etna_align_up(out->fb_var.yres, ETNA_FB_HEIGHT_ALIGN);
out->buffer_stride = out->stride * out->padded_height;
out->num_buffers = out->fb_fix.smem_len / out->buffer_stride;
if(out->num_buffers > ETNA_FB_MAX_BUFFERS)
out->num_buffers = ETNA_FB_MAX_BUFFERS;
char *num_buffers_str = getenv("EGL_FBDEV_BUFFERS");
if(num_buffers_str != NULL)
{
int num_buffers_env = atoi(num_buffers_str);
if(num_buffers_env >= 1 && out->num_buffers > num_buffers_env)
out->num_buffers = num_buffers_env;
}
printf("number of fb buffers: %i\n", out->num_buffers);
int req_virth = (out->num_buffers * out->padded_height);
if(out->fb_var.yres_virtual < (unsigned)req_virth)
{
printf("required virtual h is %i, current virtual h is %i: requesting change\n",
req_virth, out->fb_var.yres_virtual);
out->fb_var.yres_virtual = req_virth;
if (ioctl(out->fd, FBIOPUT_VSCREENINFO, &out->fb_var))
{
printf("Error: failed to run ioctl to change virtual height for buffering: %s. Rendering may fail.\n", strerror(errno));
return -1;
}
}
out->bo = etna_bo_from_fbdev(conn, fd, 0, out->fb_fix.smem_len);
if(!out->bo)
{
printf("Error: Unable to map framebuffer to GPU\n");
return -1;
}
for(int idx=0; idx<out->num_buffers; ++idx)
{
out->buffer[idx].bo = out->bo;
out->buffer[idx].offset = idx * out->buffer_stride;
out->buffer[idx].flags = ETNA_RELOC_READ|ETNA_RELOC_WRITE;
}
/* determine resolve format */
if(!etna_fb_get_format(&out->fb_var, (unsigned*)&out->rs_format, &out->swap_rb))
{
/* no match */
printf("Error: No matching framebuffer format\n");
out->rs_format = -1;
out->swap_rb = false;
return -1;
}
*out_p = out;
return 0;
#endif
}
/* Set currently visible buffer id */
int fb_set_buffer(struct fb_info *fb, int buffer)
{
fb->fb_var.yoffset = buffer * fb->fb_var.yres;
/* Android uses FBIOPUT_VSCREENINFO for this; however on some hardware this does a
* reconfiguration of the DC every time it is called which causes flicker and slowness.
* On the other hand, FBIOPAN_DISPLAY causes a smooth scroll on some hardware,
* according to the Android rationale. Choose the least of both evils.
*/
if (ioctl(fb->fd, FBIOPAN_DISPLAY, &fb->fb_var))
{
printf("Error: failed to run ioctl to pan display: %s\n", strerror(errno));
return errno;
}
return 0;
}
int fb_close(struct fb_info *fb)
{
etna_bo_del(fb->bo);
close(fb->fd);
return 0;
}