You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
ndg/lib/lv_drivers/display/drm.c

808 lines
18 KiB
C

/**
* @file drm.c
*
*/
/*********************
* INCLUDES
*********************/
#include "drm.h"
#if USE_DRM
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#define DBG_TAG "drm"
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
#define print(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__);
#define err(msg, ...) print("error: " msg "\n", ##__VA_ARGS__)
#define info(msg, ...) print(msg "\n", ##__VA_ARGS__)
#define dbg(msg, ...) {} //print(DBG_TAG ": " msg "\n", ##__VA_ARGS__)
struct drm_buffer {
uint32_t handle;
uint32_t pitch;
uint32_t offset;
unsigned long int size;
uint8_t * map;
uint32_t fb_handle;
};
struct drm_dev {
int fd;
uint32_t conn_id, enc_id, crtc_id, plane_id, crtc_idx;
uint32_t width, height;
uint32_t mmWidth, mmHeight;
uint32_t fourcc;
drmModeModeInfo mode;
uint32_t blob_id;
drmModeCrtc *saved_crtc;
drmModeAtomicReq *req;
drmEventContext drm_event_ctx;
drmModePlane *plane;
drmModeCrtc *crtc;
drmModeConnector *conn;
uint32_t count_plane_props;
uint32_t count_crtc_props;
uint32_t count_conn_props;
drmModePropertyPtr plane_props[128];
drmModePropertyPtr crtc_props[128];
drmModePropertyPtr conn_props[128];
struct drm_buffer drm_bufs[2]; /*DUMB buffers*/
uint8_t active_drm_buf_idx; /*Double buffering handling*/
lv_disp_draw_buf_t draw_buf;
} drm_dev;
static uint32_t get_plane_property_id(const char *name)
{
uint32_t i;
dbg("Find plane property: %s", name);
for (i = 0; i < drm_dev.count_plane_props; ++i)
if (!strcmp(drm_dev.plane_props[i]->name, name))
return drm_dev.plane_props[i]->prop_id;
dbg("Unknown plane property: %s", name);
return 0;
}
static uint32_t get_crtc_property_id(const char *name)
{
uint32_t i;
dbg("Find crtc property: %s", name);
for (i = 0; i < drm_dev.count_crtc_props; ++i)
if (!strcmp(drm_dev.crtc_props[i]->name, name))
return drm_dev.crtc_props[i]->prop_id;
dbg("Unknown crtc property: %s", name);
return 0;
}
static uint32_t get_conn_property_id(const char *name)
{
uint32_t i;
dbg("Find conn property: %s", name);
for (i = 0; i < drm_dev.count_conn_props; ++i)
if (!strcmp(drm_dev.conn_props[i]->name, name))
return drm_dev.conn_props[i]->prop_id;
dbg("Unknown conn property: %s", name);
return 0;
}
static void page_flip_handler(int fd, unsigned int sequence, unsigned int tv_sec,
unsigned int tv_usec, void *user_data)
{
LV_UNUSED(fd);
LV_UNUSED(sequence);
LV_UNUSED(tv_sec);
LV_UNUSED(tv_usec);
LV_UNUSED(user_data);
dbg("flip");
if(drm_dev.req) {
drmModeAtomicFree(drm_dev.req);
drm_dev.req = NULL;
}
}
static int drm_get_plane_props(void)
{
uint32_t i;
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.plane_id,
DRM_MODE_OBJECT_PLANE);
if (!props) {
err("drmModeObjectGetProperties failed");
return -1;
}
dbg("Found %u plane props", props->count_props);
drm_dev.count_plane_props = props->count_props;
for (i = 0; i < props->count_props; i++) {
drm_dev.plane_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]);
dbg("Added plane prop %u:%s", drm_dev.plane_props[i]->prop_id, drm_dev.plane_props[i]->name);
}
drmModeFreeObjectProperties(props);
return 0;
}
static int drm_get_crtc_props(void)
{
uint32_t i;
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.crtc_id,
DRM_MODE_OBJECT_CRTC);
if (!props) {
err("drmModeObjectGetProperties failed");
return -1;
}
dbg("Found %u crtc props", props->count_props);
drm_dev.count_crtc_props = props->count_props;
for (i = 0; i < props->count_props; i++) {
drm_dev.crtc_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]);
dbg("Added crtc prop %u:%s", drm_dev.crtc_props[i]->prop_id, drm_dev.crtc_props[i]->name);
}
drmModeFreeObjectProperties(props);
return 0;
}
static int drm_get_conn_props(void)
{
uint32_t i;
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.conn_id,
DRM_MODE_OBJECT_CONNECTOR);
if (!props) {
err("drmModeObjectGetProperties failed");
return -1;
}
dbg("Found %u connector props", props->count_props);
drm_dev.count_conn_props = props->count_props;
for (i = 0; i < props->count_props; i++) {
drm_dev.conn_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]);
dbg("Added connector prop %u:%s", drm_dev.conn_props[i]->prop_id, drm_dev.conn_props[i]->name);
}
drmModeFreeObjectProperties(props);
return 0;
}
static int drm_add_plane_property(const char *name, uint64_t value)
{
int ret;
uint32_t prop_id = get_plane_property_id(name);
if (!prop_id) {
err("Couldn't find plane prop %s", name);
return -1;
}
ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.plane_id, get_plane_property_id(name), value);
if (ret < 0) {
err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret);
return ret;
}
return 0;
}
static int drm_add_crtc_property(const char *name, uint64_t value)
{
int ret;
uint32_t prop_id = get_crtc_property_id(name);
if (!prop_id) {
err("Couldn't find crtc prop %s", name);
return -1;
}
ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.crtc_id, get_crtc_property_id(name), value);
if (ret < 0) {
err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret);
return ret;
}
return 0;
}
static int drm_add_conn_property(const char *name, uint64_t value)
{
int ret;
uint32_t prop_id = get_conn_property_id(name);
if (!prop_id) {
err("Couldn't find conn prop %s", name);
return -1;
}
ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.conn_id, get_conn_property_id(name), value);
if (ret < 0) {
err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret);
return ret;
}
return 0;
}
static int drm_dmabuf_set_plane(struct drm_buffer *buf)
{
int ret;
static int first = 1;
uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
drm_dev.req = drmModeAtomicAlloc();
/* On first Atomic commit, do a modeset */
if (first) {
drm_add_conn_property("CRTC_ID", drm_dev.crtc_id);
drm_add_crtc_property("MODE_ID", drm_dev.blob_id);
drm_add_crtc_property("ACTIVE", 1);
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
first = 0;
}
drm_add_plane_property("FB_ID", buf->fb_handle);
drm_add_plane_property("CRTC_ID", drm_dev.crtc_id);
drm_add_plane_property("SRC_X", 0);
drm_add_plane_property("SRC_Y", 0);
drm_add_plane_property("SRC_W", drm_dev.width << 16);
drm_add_plane_property("SRC_H", drm_dev.height << 16);
drm_add_plane_property("CRTC_X", 0);
drm_add_plane_property("CRTC_Y", 0);
drm_add_plane_property("CRTC_W", drm_dev.width);
drm_add_plane_property("CRTC_H", drm_dev.height);
ret = drmModeAtomicCommit(drm_dev.fd, drm_dev.req, flags, NULL);
if (ret) {
err("drmModeAtomicCommit failed: %s", strerror(errno));
drmModeAtomicFree(drm_dev.req);
return ret;
}
return 0;
}
static int find_plane(unsigned int fourcc, uint32_t *plane_id, uint32_t crtc_id, uint32_t crtc_idx)
{
LV_UNUSED(crtc_id);
drmModePlaneResPtr planes;
drmModePlanePtr plane;
unsigned int i;
unsigned int j;
int ret = 0;
unsigned int format = fourcc;
planes = drmModeGetPlaneResources(drm_dev.fd);
if (!planes) {
err("drmModeGetPlaneResources failed");
return -1;
}
dbg("drm: found planes %u", planes->count_planes);
for (i = 0; i < planes->count_planes; ++i) {
plane = drmModeGetPlane(drm_dev.fd, planes->planes[i]);
if (!plane) {
err("drmModeGetPlane failed: %s", strerror(errno));
break;
}
if (!(plane->possible_crtcs & (1 << crtc_idx))) {
drmModeFreePlane(plane);
continue;
}
for (j = 0; j < plane->count_formats; ++j) {
if (plane->formats[j] == format)
break;
}
if (j == plane->count_formats) {
drmModeFreePlane(plane);
continue;
}
*plane_id = plane->plane_id;
drmModeFreePlane(plane);
dbg("found plane %d", *plane_id);
break;
}
if (i == planes->count_planes)
ret = -1;
drmModeFreePlaneResources(planes);
return ret;
}
static int drm_find_connector(void)
{
drmModeConnector *conn = NULL;
drmModeEncoder *enc = NULL;
drmModeRes *res;
int i;
if ((res = drmModeGetResources(drm_dev.fd)) == NULL) {
err("drmModeGetResources() failed");
return -1;
}
if (res->count_crtcs <= 0) {
err("no Crtcs");
goto free_res;
}
/* find all available connectors */
for (i = 0; i < res->count_connectors; i++) {
conn = drmModeGetConnector(drm_dev.fd, res->connectors[i]);
if (!conn)
continue;
#if DRM_CONNECTOR_ID >= 0
if (conn->connector_id != DRM_CONNECTOR_ID) {
drmModeFreeConnector(conn);
continue;
}
#endif
if (conn->connection == DRM_MODE_CONNECTED) {
dbg("drm: connector %d: connected", conn->connector_id);
} else if (conn->connection == DRM_MODE_DISCONNECTED) {
dbg("drm: connector %d: disconnected", conn->connector_id);
} else if (conn->connection == DRM_MODE_UNKNOWNCONNECTION) {
dbg("drm: connector %d: unknownconnection", conn->connector_id);
} else {
dbg("drm: connector %d: unknown", conn->connector_id);
}
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0)
break;
drmModeFreeConnector(conn);
conn = NULL;
};
if (!conn) {
err("suitable connector not found");
goto free_res;
}
drm_dev.conn_id = conn->connector_id;
dbg("conn_id: %d", drm_dev.conn_id);
drm_dev.mmWidth = conn->mmWidth;
drm_dev.mmHeight = conn->mmHeight;
lv_memcpy(&drm_dev.mode, &conn->modes[0], sizeof(drmModeModeInfo));
if (drmModeCreatePropertyBlob(drm_dev.fd, &drm_dev.mode, sizeof(drm_dev.mode),
&drm_dev.blob_id)) {
err("error creating mode blob");
goto free_res;
}
drm_dev.width = conn->modes[0].hdisplay;
drm_dev.height = conn->modes[0].vdisplay;
for (i = 0 ; i < res->count_encoders; i++) {
enc = drmModeGetEncoder(drm_dev.fd, res->encoders[i]);
if (!enc)
continue;
dbg("enc%d enc_id %d conn enc_id %d", i, enc->encoder_id, conn->encoder_id);
if (enc->encoder_id == conn->encoder_id)
break;
drmModeFreeEncoder(enc);
enc = NULL;
}
if (enc) {
drm_dev.enc_id = enc->encoder_id;
dbg("enc_id: %d", drm_dev.enc_id);
drm_dev.crtc_id = enc->crtc_id;
dbg("crtc_id: %d", drm_dev.crtc_id);
drmModeFreeEncoder(enc);
} else {
/* Encoder hasn't been associated yet, look it up */
for (i = 0; i < conn->count_encoders; i++) {
int crtc, crtc_id = -1;
enc = drmModeGetEncoder(drm_dev.fd, conn->encoders[i]);
if (!enc)
continue;
for (crtc = 0 ; crtc < res->count_crtcs; crtc++) {
uint32_t crtc_mask = 1 << crtc;
crtc_id = res->crtcs[crtc];
dbg("enc_id %d crtc%d id %d mask %x possible %x", enc->encoder_id, crtc, crtc_id, crtc_mask, enc->possible_crtcs);
if (enc->possible_crtcs & crtc_mask)
break;
}
if (crtc_id > 0) {
drm_dev.enc_id = enc->encoder_id;
dbg("enc_id: %d", drm_dev.enc_id);
drm_dev.crtc_id = crtc_id;
dbg("crtc_id: %d", drm_dev.crtc_id);
break;
}
drmModeFreeEncoder(enc);
enc = NULL;
}
if (!enc) {
err("suitable encoder not found");
goto free_res;
}
drmModeFreeEncoder(enc);
}
drm_dev.crtc_idx = UINT32_MAX;
for (i = 0; i < res->count_crtcs; ++i) {
if (drm_dev.crtc_id == res->crtcs[i]) {
drm_dev.crtc_idx = i;
break;
}
}
if (drm_dev.crtc_idx == UINT32_MAX) {
err("drm: CRTC not found");
goto free_res;
}
dbg("crtc_idx: %d", drm_dev.crtc_idx);
return 0;
free_res:
drmModeFreeResources(res);
return -1;
}
static int drm_open(const char *path)
{
int fd, flags;
uint64_t has_dumb;
int ret;
fd = open(path, O_RDWR);
if (fd < 0) {
err("cannot open \"%s\"", path);
return -1;
}
/* set FD_CLOEXEC flag */
if ((flags = fcntl(fd, F_GETFD)) < 0 ||
fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
err("fcntl FD_CLOEXEC failed");
goto err;
}
/* check capability */
ret = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
if (ret < 0 || has_dumb == 0) {
err("drmGetCap DRM_CAP_DUMB_BUFFER failed or \"%s\" doesn't have dumb "
"buffer", path);
goto err;
}
return fd;
err:
close(fd);
return -1;
}
static int drm_setup(unsigned int fourcc)
{
int ret;
const char *device_path = NULL;
device_path = getenv("DRM_CARD");
if (!device_path)
device_path = DRM_CARD;
drm_dev.fd = drm_open(device_path);
if (drm_dev.fd < 0)
return -1;
ret = drmSetClientCap(drm_dev.fd, DRM_CLIENT_CAP_ATOMIC, 1);
if (ret) {
err("No atomic modesetting support: %s", strerror(errno));
goto err;
}
ret = drm_find_connector();
if (ret) {
err("available drm devices not found");
goto err;
}
ret = find_plane(fourcc, &drm_dev.plane_id, drm_dev.crtc_id, drm_dev.crtc_idx);
if (ret) {
err("Cannot find plane");
goto err;
}
drm_dev.plane = drmModeGetPlane(drm_dev.fd, drm_dev.plane_id);
if (!drm_dev.plane) {
err("Cannot get plane");
goto err;
}
drm_dev.crtc = drmModeGetCrtc(drm_dev.fd, drm_dev.crtc_id);
if (!drm_dev.crtc) {
err("Cannot get crtc");
goto err;
}
drm_dev.conn = drmModeGetConnector(drm_dev.fd, drm_dev.conn_id);
if (!drm_dev.conn) {
err("Cannot get connector");
goto err;
}
ret = drm_get_plane_props();
if (ret) {
err("Cannot get plane props");
goto err;
}
ret = drm_get_crtc_props();
if (ret) {
err("Cannot get crtc props");
goto err;
}
ret = drm_get_conn_props();
if (ret) {
err("Cannot get connector props");
goto err;
}
drm_dev.drm_event_ctx.version = DRM_EVENT_CONTEXT_VERSION;
drm_dev.drm_event_ctx.page_flip_handler = page_flip_handler;
drm_dev.fourcc = fourcc;
info("drm: Found plane_id: %u connector_id: %d crtc_id: %d",
drm_dev.plane_id, drm_dev.conn_id, drm_dev.crtc_id);
info("drm: %dx%d (%dmm X% dmm) pixel format %c%c%c%c",
drm_dev.width, drm_dev.height, drm_dev.mmWidth, drm_dev.mmHeight,
(fourcc>>0)&0xff, (fourcc>>8)&0xff, (fourcc>>16)&0xff, (fourcc>>24)&0xff);
return 0;
err:
close(drm_dev.fd);
return -1;
}
static int drm_allocate_dumb(struct drm_buffer *buf)
{
struct drm_mode_create_dumb creq;
struct drm_mode_map_dumb mreq;
uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
int ret;
/* create dumb buffer */
lv_memset(&creq, 0, sizeof(creq));
creq.width = drm_dev.width;
creq.height = drm_dev.height;
creq.bpp = LV_COLOR_DEPTH;
ret = drmIoctl(drm_dev.fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
if (ret < 0) {
err("DRM_IOCTL_MODE_CREATE_DUMB fail");
return -1;
}
buf->handle = creq.handle;
buf->pitch = creq.pitch;
buf->size = creq.size;
/* prepare buffer for memory mapping */
lv_memset(&mreq, 0, sizeof(mreq));
mreq.handle = creq.handle;
ret = drmIoctl(drm_dev.fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
if (ret) {
err("DRM_IOCTL_MODE_MAP_DUMB fail");
return -1;
}
buf->offset = mreq.offset;
info("size %lu pitch %u offset %u", buf->size, buf->pitch, buf->offset);
/* perform actual memory mapping */
buf->map = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_dev.fd, mreq.offset);
if (buf->map == MAP_FAILED) {
err("mmap fail");
return -1;
}
/* clear the framebuffer to 0 (= full transparency in ARGB8888) */
lv_memset(buf->map, 0, creq.size);
/* create framebuffer object for the dumb-buffer */
handles[0] = creq.handle;
pitches[0] = creq.pitch;
offsets[0] = 0;
ret = drmModeAddFB2(drm_dev.fd, drm_dev.width, drm_dev.height, drm_dev.fourcc,
handles, pitches, offsets, &buf->fb_handle, 0);
if (ret) {
err("drmModeAddFB fail");
return -1;
}
return 0;
}
static int drm_setup_buffers(void)
{
int ret;
/*Allocate DUMB buffers*/
ret = drm_allocate_dumb(&drm_dev.drm_bufs[0]);
if (ret)
return ret;
ret = drm_allocate_dumb(&drm_dev.drm_bufs[1]);
if (ret)
return ret;
return 0;
}
void drm_wait_vsync(lv_disp_drv_t * disp_drv)
{
while(drm_dev.req) {
struct pollfd pfd;
pfd.fd = drm_dev.fd;
pfd.events = POLLIN;
int ret;
do {
ret = poll(&pfd, 1, -1);
} while (ret == -1 && errno == EINTR);
if(ret > 0)
drmHandleEvent(drm_dev.fd, &drm_dev.drm_event_ctx);
else {
err("poll failed: %s", strerror(errno));
return;
}
}
lv_disp_flush_ready(disp_drv);
}
void drm_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
struct drm_buffer *fbuf = &drm_dev.drm_bufs[drm_dev.active_drm_buf_idx ^ 1];
if(!disp_drv->direct_mode) {
/*Backwards compatibility: Non-direct flush */
uint32_t w = (area->x2 - area->x1) + 1;
for (int y = 0, i = area->y1; i <= area->y2 ; ++i, ++y) {
lv_memcpy(fbuf->map + (area->x1 * (LV_COLOR_SIZE / 8)) + (fbuf->pitch * i),
(uint8_t *)color_p + (w * (LV_COLOR_SIZE / 8) * y),
w * (LV_COLOR_SIZE / 8));
}
}
if(lv_disp_flush_is_last(disp_drv)) {
/*Request buffer swap*/
if(drm_dmabuf_set_plane(fbuf)) {
err("Flush fail");
return;
}
else
dbg("Flush done");
drm_dev.active_drm_buf_idx ^= 1;
}
}
#if LV_COLOR_DEPTH == 32
#define DRM_FOURCC DRM_FORMAT_XRGB8888
#elif LV_COLOR_DEPTH == 16
#define DRM_FOURCC DRM_FORMAT_RGB565
#else
#error LV_COLOR_DEPTH not supported
#endif
void drm_get_sizes(lv_coord_t *width, lv_coord_t *height, uint32_t *dpi)
{
if (width)
*width = drm_dev.width;
if (height)
*height = drm_dev.height;
if (dpi && drm_dev.mmWidth)
*dpi = DIV_ROUND_UP(drm_dev.width * 25400, drm_dev.mmWidth * 1000);
}
int drm_init(void)
{
int ret;
ret = drm_setup(DRM_FOURCC);
if (ret) {
close(drm_dev.fd);
drm_dev.fd = -1;
return -1;
}
ret = drm_setup_buffers();
if (ret) {
err("DRM buffer allocation failed");
close(drm_dev.fd);
drm_dev.fd = -1;
return -1;
}
info("DRM subsystem and buffer mapped successfully");
return 0;
}
int drm_disp_drv_init(lv_disp_drv_t * disp_drv)
{
lv_disp_drv_init(disp_drv);
int ret = drm_init();
if(ret) return ret;
lv_disp_draw_buf_init(&drm_dev.draw_buf, drm_dev.drm_bufs[1].map, drm_dev.drm_bufs[0].map, drm_dev.width * drm_dev.height);
disp_drv->draw_buf = &drm_dev.draw_buf;
disp_drv->direct_mode = true;
disp_drv->hor_res = drm_dev.width;
disp_drv->ver_res = drm_dev.height;
disp_drv->flush_cb = drm_flush;
disp_drv->wait_cb = drm_wait_vsync;
return 0;
}
void drm_exit(void)
{
close(drm_dev.fd);
drm_dev.fd = -1;
}
#endif