summaryrefslogtreecommitdiff
path: root/kms++/src
diff options
context:
space:
mode:
Diffstat (limited to 'kms++/src')
-rw-r--r--kms++/src/atomicreq.cpp133
-rw-r--r--kms++/src/blob.cpp50
-rw-r--r--kms++/src/card.cpp430
-rw-r--r--kms++/src/connector.cpp298
-rw-r--r--kms++/src/crtc.cpp196
-rw-r--r--kms++/src/dmabufframebuffer.cpp161
-rw-r--r--kms++/src/drmobject.cpp32
-rw-r--r--kms++/src/drmpropobject.cpp101
-rw-r--r--kms++/src/dumbframebuffer.cpp143
-rw-r--r--kms++/src/encoder.cpp87
-rw-r--r--kms++/src/extframebuffer.cpp64
-rw-r--r--kms++/src/framebuffer.cpp71
-rw-r--r--kms++/src/helpers.cpp72
-rw-r--r--kms++/src/helpers.h12
-rw-r--r--kms++/src/mode_cvt.cpp150
-rw-r--r--kms++/src/modedb.cpp58
-rw-r--r--kms++/src/modedb_cea.cpp465
-rw-r--r--kms++/src/modedb_dmt.cpp203
-rw-r--r--kms++/src/omap/omapcard.cpp23
-rw-r--r--kms++/src/omap/omapframebuffer.cpp193
-rw-r--r--kms++/src/pixelformats.cpp1186
-rw-r--r--kms++/src/plane.cpp154
-rw-r--r--kms++/src/property.cpp85
-rw-r--r--kms++/src/videomode.cpp272
24 files changed, 4639 insertions, 0 deletions
diff --git a/kms++/src/atomicreq.cpp b/kms++/src/atomicreq.cpp
new file mode 100644
index 0000000..1ef4f7d
--- /dev/null
+++ b/kms++/src/atomicreq.cpp
@@ -0,0 +1,133 @@
+#include <cassert>
+#include <stdexcept>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+#ifndef DRM_CLIENT_CAP_ATOMIC
+
+#define DRM_MODE_ATOMIC_TEST_ONLY 0
+#define DRM_MODE_ATOMIC_NONBLOCK 0
+
+struct _drmModeAtomicReq;
+typedef struct _drmModeAtomicReq* drmModeAtomicReqPtr;
+
+static inline drmModeAtomicReqPtr drmModeAtomicAlloc()
+{
+ return 0;
+}
+static inline void drmModeAtomicFree(drmModeAtomicReqPtr)
+{
+}
+static inline int drmModeAtomicAddProperty(drmModeAtomicReqPtr, uint32_t, uint32_t, uint64_t)
+{
+ return 0;
+}
+static inline int drmModeAtomicCommit(int, drmModeAtomicReqPtr, int, void*)
+{
+ return 0;
+}
+
+#endif // DRM_CLIENT_CAP_ATOMIC
+
+using namespace std;
+
+namespace kms
+{
+AtomicReq::AtomicReq(Card& card)
+ : m_card(card)
+{
+ assert(card.has_atomic());
+ m_req = drmModeAtomicAlloc();
+}
+
+AtomicReq::~AtomicReq()
+{
+ drmModeAtomicFree(m_req);
+}
+
+void AtomicReq::add(uint32_t ob_id, uint32_t prop_id, uint64_t value)
+{
+ int r = drmModeAtomicAddProperty(m_req, ob_id, prop_id, value);
+ if (r <= 0)
+ throw std::invalid_argument("foo");
+}
+
+void AtomicReq::add(DrmPropObject* ob, Property* prop, uint64_t value)
+{
+ add(ob->id(), prop->id(), value);
+}
+
+void AtomicReq::add(kms::DrmPropObject* ob, const string& prop, uint64_t value)
+{
+ Property* p = ob->get_prop(prop);
+
+ if (!p)
+ throw runtime_error("Property not found");
+
+ add(ob, p, value);
+}
+
+void AtomicReq::add(kms::DrmPropObject* ob, const map<string, uint64_t>& values)
+{
+ for (const auto& kvp : values)
+ add(ob, kvp.first, kvp.second);
+}
+
+void AtomicReq::add_display(Connector* conn, Crtc* crtc, Blob* videomode, Plane* primary, Framebuffer* fb)
+{
+ add(conn, {
+ { "CRTC_ID", crtc->id() },
+ });
+
+ add(crtc, {
+ { "ACTIVE", 1 },
+ { "MODE_ID", videomode->id() },
+ });
+
+ add(primary, {
+ { "FB_ID", fb->id() },
+ { "CRTC_ID", crtc->id() },
+ { "SRC_X", 0 << 16 },
+ { "SRC_Y", 0 << 16 },
+ { "SRC_W", fb->width() << 16 },
+ { "SRC_H", fb->height() << 16 },
+ { "CRTC_X", 0 },
+ { "CRTC_Y", 0 },
+ { "CRTC_W", fb->width() },
+ { "CRTC_H", fb->height() },
+ });
+}
+
+int AtomicReq::test(bool allow_modeset)
+{
+ uint32_t flags = DRM_MODE_ATOMIC_TEST_ONLY;
+
+ if (allow_modeset)
+ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+
+ return drmModeAtomicCommit(m_card.fd(), m_req, flags, 0);
+}
+
+int AtomicReq::commit(void* data, bool allow_modeset)
+{
+ uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
+
+ if (allow_modeset)
+ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+
+ return drmModeAtomicCommit(m_card.fd(), m_req, flags, data);
+}
+
+int AtomicReq::commit_sync(bool allow_modeset)
+{
+ uint32_t flags = 0;
+
+ if (allow_modeset)
+ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+
+ return drmModeAtomicCommit(m_card.fd(), m_req, flags, 0);
+}
+} // namespace kms
diff --git a/kms++/src/blob.cpp b/kms++/src/blob.cpp
new file mode 100644
index 0000000..69914f6
--- /dev/null
+++ b/kms++/src/blob.cpp
@@ -0,0 +1,50 @@
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+Blob::Blob(Card& card, uint32_t blob_id)
+ : DrmObject(card, blob_id, DRM_MODE_OBJECT_BLOB), m_created(false)
+{
+ // XXX should we verify that the blob_id is a blob object?
+}
+
+Blob::Blob(Card& card, void* data, size_t len)
+ : DrmObject(card, DRM_MODE_OBJECT_BLOB), m_created(true)
+{
+ uint32_t id;
+
+ int r = drmModeCreatePropertyBlob(card.fd(), data, len, &id);
+ if (r)
+ throw invalid_argument("FAILED TO CREATE PROP\n");
+
+ set_id(id);
+}
+
+Blob::~Blob()
+{
+ if (m_created)
+ drmModeDestroyPropertyBlob(card().fd(), id());
+}
+
+vector<uint8_t> Blob::data()
+{
+ drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(card().fd(), id());
+
+ if (!blob)
+ throw invalid_argument("Blob data not available");
+
+ uint8_t* data = static_cast<uint8_t*>(blob->data);
+
+ auto v = vector<uint8_t>(data, data + blob->length);
+
+ drmModeFreePropertyBlob(blob);
+
+ return v;
+}
+
+} // namespace kms
diff --git a/kms++/src/card.cpp b/kms++/src/card.cpp
new file mode 100644
index 0000000..f67f8f8
--- /dev/null
+++ b/kms++/src/card.cpp
@@ -0,0 +1,430 @@
+#include <cstdio>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utility>
+#include <stdexcept>
+#include <cstring>
+#include <algorithm>
+#include <cerrno>
+#include <algorithm>
+#include <glob.h>
+
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+static vector<string> glob(const string& pattern)
+{
+ glob_t glob_result;
+ memset(&glob_result, 0, sizeof(glob_result));
+
+ int r = glob(pattern.c_str(), 0, NULL, &glob_result);
+ if (r != 0) {
+ globfree(&glob_result);
+ throw runtime_error("failed to find DRM cards");
+ }
+
+ vector<string> filenames;
+ for (size_t i = 0; i < glob_result.gl_pathc; ++i)
+ filenames.push_back(string(glob_result.gl_pathv[i]));
+
+ globfree(&glob_result);
+
+ return filenames;
+}
+
+static int open_first_kms_device()
+{
+ vector<string> paths = glob("/dev/dri/card*");
+
+ for (const string& path : paths) {
+ int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
+ if (fd < 0)
+ throw invalid_argument(string(strerror(errno)) + " opening device " + path);
+
+ auto res = drmModeGetResources(fd);
+ if (!res) {
+ close(fd);
+ continue;
+ }
+
+ bool has_kms = res->count_crtcs > 0 && res->count_connectors > 0 && res->count_encoders > 0;
+
+ drmModeFreeResources(res);
+
+ if (has_kms)
+ return fd;
+
+ close(fd);
+ }
+
+ throw runtime_error("No modesetting DRM card found");
+}
+
+static int open_device_by_path(const string& path)
+{
+ int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
+ if (fd < 0)
+ throw invalid_argument(string(strerror(errno)) + " opening device " + path);
+ return fd;
+}
+
+// open Nth DRM card with the given driver name
+static int open_device_by_driver(string name, uint32_t idx)
+{
+ transform(name.begin(), name.end(), name.begin(), ::tolower);
+
+ uint32_t num_matches = 0;
+ vector<string> paths = glob("/dev/dri/card*");
+
+ for (const string& path : paths) {
+ int fd = open_device_by_path(path);
+
+ drmVersionPtr ver = drmGetVersion(fd);
+ string drv_name = string(ver->name, ver->name_len);
+ drmFreeVersion(ver);
+
+ transform(drv_name.begin(), drv_name.end(), drv_name.begin(), ::tolower);
+
+ if (name == drv_name) {
+ if (idx == num_matches)
+ return fd;
+ num_matches++;
+ }
+
+ close(fd);
+ }
+
+ throw invalid_argument("Failed to find a DRM device " + name + ":" + to_string(idx));
+}
+
+std::unique_ptr<Card> Card::open_named_card(const std::string& name)
+{
+ int fd = drmOpen(name.c_str(), 0);
+
+ if (fd < 0)
+ throw invalid_argument(string(strerror(errno)) + " opening card \"" + name + "\"");
+
+ return std::unique_ptr<Card>(new Card(fd, true));
+}
+
+Card::Card(const std::string& dev_path)
+{
+ const char* drv_p = getenv("KMSXX_DRIVER");
+ const char* dev_p = getenv("KMSXX_DEVICE");
+
+ if (!dev_path.empty()) {
+ m_fd = open_device_by_path(dev_path);
+ } else if (dev_p) {
+ string dev(dev_p);
+ m_fd = open_device_by_path(dev);
+ } else if (drv_p) {
+ string drv(drv_p);
+
+ auto isplit = find(drv.begin(), drv.end(), ':');
+
+ if (isplit == drv.begin())
+ throw runtime_error("Invalid KMSXX_DRIVER");
+
+ string name;
+ uint32_t num = 0;
+
+ if (isplit == drv.end()) {
+ name = drv;
+ } else {
+ name = string(drv.begin(), isplit);
+ string numstr = string(isplit + 1, drv.end());
+ num = stoul(numstr);
+ }
+
+ m_fd = open_device_by_driver(name, num);
+ } else {
+ m_fd = open_first_kms_device();
+ }
+
+ setup();
+}
+
+Card::Card(const std::string& driver, uint32_t idx)
+{
+ m_fd = open_device_by_driver(driver, idx);
+
+ setup();
+}
+
+Card::Card(int fd, bool take_ownership)
+{
+ if (take_ownership) {
+ m_fd = fd;
+ } else {
+ m_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+
+ if (m_fd < 0)
+ throw invalid_argument(string(strerror(errno)) + " duplicating fd");
+ }
+
+ setup();
+}
+
+void Card::setup()
+{
+ drmVersionPtr ver = drmGetVersion(m_fd);
+ m_version.major = ver->version_major;
+ m_version.minor = ver->version_minor;
+ m_version.patchlevel = ver->version_patchlevel;
+ m_version.name = string(ver->name, ver->name_len);
+ m_version.date = string(ver->date, ver->date_len);
+ m_version.desc = string(ver->desc, ver->desc_len);
+ drmFreeVersion(ver);
+
+ struct stat stats;
+ int r;
+
+ r = fstat(m_fd, &stats);
+ if (r < 0)
+ throw invalid_argument("Can't stat device (" + string(strerror(errno)) + ")");
+
+ m_minor = minor(stats.st_dev);
+
+ r = drmSetMaster(m_fd);
+ m_is_master = r == 0;
+
+ if (getenv("KMSXX_DISABLE_UNIVERSAL_PLANES") == 0) {
+ r = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+ m_has_universal_planes = r == 0;
+ } else {
+ m_has_universal_planes = false;
+ }
+
+#ifdef DRM_CLIENT_CAP_ATOMIC
+ if (getenv("KMSXX_DISABLE_ATOMIC") == 0) {
+ r = drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1);
+ m_has_atomic = r == 0;
+ } else {
+ m_has_atomic = false;
+ }
+#else
+ m_has_atomic = false;
+#endif
+
+ uint64_t has_dumb;
+ r = drmGetCap(m_fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
+ m_has_dumb = r == 0 && has_dumb;
+
+ auto res = drmModeGetResources(m_fd);
+ if (res) {
+ for (int i = 0; i < res->count_connectors; ++i) {
+ uint32_t id = res->connectors[i];
+ auto ob = new Connector(*this, id, i);
+ m_obmap[id] = ob;
+ m_connectors.push_back(ob);
+ }
+
+ for (int i = 0; i < res->count_crtcs; ++i) {
+ uint32_t id = res->crtcs[i];
+ auto ob = new Crtc(*this, id, i);
+ m_obmap[id] = ob;
+ m_crtcs.push_back(ob);
+ }
+
+ for (int i = 0; i < res->count_encoders; ++i) {
+ uint32_t id = res->encoders[i];
+ auto ob = new Encoder(*this, id, i);
+ m_obmap[id] = ob;
+ m_encoders.push_back(ob);
+ }
+
+ drmModeFreeResources(res);
+
+ auto planeRes = drmModeGetPlaneResources(m_fd);
+ if (planeRes) {
+ for (uint i = 0; i < planeRes->count_planes; ++i) {
+ uint32_t id = planeRes->planes[i];
+ auto ob = new Plane(*this, id, i);
+ m_obmap[id] = ob;
+ m_planes.push_back(ob);
+ }
+
+ drmModeFreePlaneResources(planeRes);
+ }
+ }
+
+ // collect all possible props
+ for (auto ob : get_objects()) {
+ auto props = drmModeObjectGetProperties(m_fd, ob->id(), ob->object_type());
+
+ if (props == nullptr)
+ continue;
+
+ for (unsigned i = 0; i < props->count_props; ++i) {
+ uint32_t prop_id = props->props[i];
+
+ if (m_obmap.find(prop_id) == m_obmap.end()) {
+ auto prop = new Property(*this, prop_id);
+ m_obmap[prop_id] = prop;
+ m_properties.push_back(prop);
+ }
+ }
+
+ drmModeFreeObjectProperties(props);
+ }
+
+ for (auto pair : m_obmap)
+ pair.second->setup();
+}
+
+Card::~Card()
+{
+ restore_modes();
+
+ while (m_framebuffers.size() > 0)
+ delete m_framebuffers.back();
+
+ for (auto pair : m_obmap)
+ delete pair.second;
+
+ close(m_fd);
+}
+
+void Card::drop_master()
+{
+ drmDropMaster(fd());
+ m_is_master = false;
+}
+
+bool Card::has_kms() const
+{
+ return m_connectors.size() > 0 && m_encoders.size() > 0 && m_crtcs.size() > 0;
+}
+
+void Card::restore_modes()
+{
+ for (auto conn : get_connectors())
+ conn->restore_mode();
+}
+
+Connector* Card::get_first_connected_connector() const
+{
+ for (auto c : m_connectors) {
+ if (c->connected())
+ return c;
+ }
+
+ throw invalid_argument("no connected connectors");
+}
+
+DrmObject* Card::get_object(uint32_t id) const
+{
+ auto iter = m_obmap.find(id);
+ if (iter != m_obmap.end())
+ return iter->second;
+ return nullptr;
+}
+
+std::vector<kms::DrmObject*> Card::get_objects() const
+{
+ vector<DrmObject*> v;
+ for (auto pair : m_obmap)
+ v.push_back(pair.second);
+ return v;
+}
+
+Connector* Card::get_connector(uint32_t id) const
+{
+ return dynamic_cast<Connector*>(get_object(id));
+}
+Crtc* Card::get_crtc(uint32_t id) const
+{
+ return dynamic_cast<Crtc*>(get_object(id));
+}
+Encoder* Card::get_encoder(uint32_t id) const
+{
+ return dynamic_cast<Encoder*>(get_object(id));
+}
+Property* Card::get_prop(uint32_t id) const
+{
+ return dynamic_cast<Property*>(get_object(id));
+}
+Plane* Card::get_plane(uint32_t id) const
+{
+ return dynamic_cast<Plane*>(get_object(id));
+}
+
+std::vector<kms::Pipeline> Card::get_connected_pipelines()
+{
+ vector<Pipeline> outputs;
+
+ for (auto conn : get_connectors()) {
+ if (conn->connected() == false)
+ continue;
+
+ Crtc* crtc = conn->get_current_crtc();
+
+ if (!crtc) {
+ for (auto possible : conn->get_possible_crtcs()) {
+ if (find_if(outputs.begin(), outputs.end(), [possible](Pipeline out) { return out.crtc == possible; }) == outputs.end()) {
+ crtc = possible;
+ break;
+ }
+ }
+ }
+
+ if (!crtc)
+ throw invalid_argument(string("Connector #") +
+ to_string(conn->idx()) +
+ " has no possible crtcs");
+
+ outputs.push_back(Pipeline{ crtc, conn });
+ }
+
+ return outputs;
+}
+
+static void page_flip_handler(int fd, unsigned int frame,
+ unsigned int sec, unsigned int usec,
+ void* data)
+{
+ auto handler = static_cast<PageFlipHandlerBase*>(data);
+ double time = sec + usec / 1000000.0;
+ handler->handle_page_flip(frame, time);
+}
+
+void Card::call_page_flip_handlers()
+{
+ drmEventContext ev{};
+ ev.version = DRM_EVENT_CONTEXT_VERSION;
+ ev.page_flip_handler = page_flip_handler;
+
+ drmHandleEvent(fd(), &ev);
+}
+
+int Card::disable_all()
+{
+ AtomicReq req(*this);
+
+ for (Crtc* c : m_crtcs) {
+ req.add(c, {
+ { "ACTIVE", 0 },
+ });
+ }
+
+ for (Plane* p : m_planes) {
+ req.add(p, {
+ { "FB_ID", 0 },
+ { "CRTC_ID", 0 },
+ });
+ }
+
+ return req.commit_sync(true);
+}
+
+} // namespace kms
diff --git a/kms++/src/connector.cpp b/kms++/src/connector.cpp
new file mode 100644
index 0000000..cf6243c
--- /dev/null
+++ b/kms++/src/connector.cpp
@@ -0,0 +1,298 @@
+#include <cstdio>
+#include <unistd.h>
+#include <fcntl.h>
+#include <cassert>
+#include <cmath>
+
+#include <kms++/kms++.h>
+#include "helpers.h"
+
+using namespace std;
+
+namespace kms
+{
+#ifndef DRM_MODE_CONNECTOR_DPI
+#define DRM_MODE_CONNECTOR_DPI 17
+#endif
+#ifndef DRM_MODE_CONNECTOR_WRITEBACK
+#define DRM_MODE_CONNECTOR_WRITEBACK 18
+#endif
+#ifndef DRM_MODE_CONNECTOR_SPI
+#define DRM_MODE_CONNECTOR_SPI 19
+#endif
+#ifndef DRM_MODE_CONNECTOR_USB
+#define DRM_MODE_CONNECTOR_USB 20
+#endif
+
+static const map<int, string> connector_names = {
+ { DRM_MODE_CONNECTOR_Unknown, "Unknown" },
+ { DRM_MODE_CONNECTOR_VGA, "VGA" },
+ { DRM_MODE_CONNECTOR_DVII, "DVI-I" },
+ { DRM_MODE_CONNECTOR_DVID, "DVI-D" },
+ { DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
+ { DRM_MODE_CONNECTOR_Composite, "Composite" },
+ { DRM_MODE_CONNECTOR_SVIDEO, "S-Video" },
+ { DRM_MODE_CONNECTOR_LVDS, "LVDS" },
+ { DRM_MODE_CONNECTOR_Component, "Component" },
+ { DRM_MODE_CONNECTOR_9PinDIN, "9-Pin-DIN" },
+ { DRM_MODE_CONNECTOR_DisplayPort, "DP" },
+ { DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
+ { DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
+ { DRM_MODE_CONNECTOR_TV, "TV" },
+ { DRM_MODE_CONNECTOR_eDP, "eDP" },
+ { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
+ { DRM_MODE_CONNECTOR_DSI, "DSI" },
+ { DRM_MODE_CONNECTOR_DPI, "DPI" },
+ { DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
+ { DRM_MODE_CONNECTOR_SPI, "SPI" },
+ { DRM_MODE_CONNECTOR_USB, "USB" },
+};
+
+static const map<int, string> connection_str = {
+ { 0, "<unknown>" },
+ { DRM_MODE_CONNECTED, "Connected" },
+ { DRM_MODE_DISCONNECTED, "Disconnected" },
+ { DRM_MODE_UNKNOWNCONNECTION, "Unknown" },
+};
+
+static const map<int, string> subpix_str = {
+#define DEF_SUBPIX(c) \
+ { \
+ DRM_MODE_SUBPIXEL_##c, #c \
+ }
+ DEF_SUBPIX(UNKNOWN),
+ DEF_SUBPIX(HORIZONTAL_RGB),
+ DEF_SUBPIX(HORIZONTAL_BGR),
+ DEF_SUBPIX(VERTICAL_RGB),
+ DEF_SUBPIX(VERTICAL_BGR),
+ DEF_SUBPIX(NONE),
+#undef DEF_SUBPIX
+};
+
+struct ConnectorPriv {
+ drmModeConnectorPtr drm_connector;
+};
+
+Connector::Connector(Card& card, uint32_t id, uint32_t idx)
+ : DrmPropObject(card, id, DRM_MODE_OBJECT_CONNECTOR, idx)
+{
+ m_priv = new ConnectorPriv();
+
+ m_priv->drm_connector = drmModeGetConnector(this->card().fd(), this->id());
+ assert(m_priv->drm_connector);
+
+ // XXX drmModeGetConnector() does forced probe, which seems to change (at least) EDID blob id.
+ // XXX So refresh the props again here.
+ refresh_props();
+
+ const auto& name = connector_names.at(m_priv->drm_connector->connector_type);
+ m_fullname = name + "-" + to_string(m_priv->drm_connector->connector_type_id);
+}
+
+Connector::~Connector()
+{
+ drmModeFreeConnector(m_priv->drm_connector);
+ delete m_priv;
+}
+
+void Connector::refresh()
+{
+ drmModeFreeConnector(m_priv->drm_connector);
+
+ m_priv->drm_connector = drmModeGetConnector(this->card().fd(), this->id());
+ assert(m_priv->drm_connector);
+
+ // XXX drmModeGetConnector() does forced probe, which seems to change (at least) EDID blob id.
+ // XXX So refresh the props again here.
+ refresh_props();
+
+ const auto& name = connector_names.at(m_priv->drm_connector->connector_type);
+ m_fullname = name + "-" + to_string(m_priv->drm_connector->connector_type_id);
+}
+
+void Connector::setup()
+{
+ if (m_priv->drm_connector->encoder_id != 0)
+ m_current_encoder = card().get_encoder(m_priv->drm_connector->encoder_id);
+ else
+ m_current_encoder = 0;
+
+ if (m_current_encoder)
+ m_saved_crtc = m_current_encoder->get_crtc();
+ else
+ m_saved_crtc = 0;
+}
+
+void Connector::restore_mode()
+{
+ if (m_saved_crtc)
+ m_saved_crtc->restore_mode(this);
+}
+
+Videomode Connector::get_default_mode() const
+{
+ if (m_priv->drm_connector->count_modes == 0)
+ return Videomode();
+
+ drmModeModeInfo drmmode = m_priv->drm_connector->modes[0];
+
+ return drm_mode_to_video_mode(drmmode);
+}
+
+Videomode Connector::get_mode(const string& mode) const
+{
+ auto c = m_priv->drm_connector;
+
+ size_t idx = mode.find('@');
+
+ string name = idx == string::npos ? mode : mode.substr(0, idx);
+ float vrefresh = idx == string::npos ? 0.0 : stod(mode.substr(idx + 1));
+
+ for (int i = 0; i < c->count_modes; i++) {
+ Videomode m = drm_mode_to_video_mode(c->modes[i]);
+
+ if (m.name != name)
+ continue;
+
+ if (vrefresh && vrefresh != m.calculated_vrefresh())
+ continue;
+
+ return m;
+ }
+
+ throw invalid_argument(mode + ": mode not found");
+}
+
+Videomode Connector::get_mode(unsigned xres, unsigned yres, float vrefresh, bool ilace) const
+{
+ auto c = m_priv->drm_connector;
+
+ for (int i = 0; i < c->count_modes; i++) {
+ Videomode m = drm_mode_to_video_mode(c->modes[i]);
+
+ if (m.hdisplay != xres || m.vdisplay != yres)
+ continue;
+
+ if (ilace != m.interlace())
+ continue;
+
+ if (vrefresh && vrefresh != m.calculated_vrefresh())
+ continue;
+
+ return m;
+ }
+
+ // If not found, do another round using rounded vrefresh
+
+ for (int i = 0; i < c->count_modes; i++) {
+ Videomode m = drm_mode_to_video_mode(c->modes[i]);
+
+ if (m.hdisplay != xres || m.vdisplay != yres)
+ continue;
+
+ if (ilace != m.interlace())
+ continue;
+
+ if (vrefresh && vrefresh != roundf(m.calculated_vrefresh()))
+ continue;
+
+ return m;
+ }
+
+ throw invalid_argument("mode not found");
+}
+
+bool Connector::connected() const
+{
+ return m_priv->drm_connector->connection == DRM_MODE_CONNECTED ||
+ m_priv->drm_connector->connection == DRM_MODE_UNKNOWNCONNECTION;
+}
+
+ConnectorStatus Connector::connector_status() const
+{
+ switch (m_priv->drm_connector->connection) {
+ case DRM_MODE_CONNECTED:
+ return ConnectorStatus::Connected;
+ case DRM_MODE_DISCONNECTED:
+ return ConnectorStatus::Disconnected;
+ default:
+ return ConnectorStatus::Unknown;
+ }
+}
+
+vector<Crtc*> Connector::get_possible_crtcs() const
+{
+ vector<Crtc*> crtcs;
+
+ for (int i = 0; i < m_priv->drm_connector->count_encoders; ++i) {
+ auto enc = card().get_encoder(m_priv->drm_connector->encoders[i]);
+
+ auto l = enc->get_possible_crtcs();
+
+ crtcs.insert(crtcs.end(), l.begin(), l.end());
+ }
+
+ return crtcs;
+}
+
+Crtc* Connector::get_current_crtc() const
+{
+ if (m_current_encoder)
+ return m_current_encoder->get_crtc();
+ else
+ return 0;
+}
+
+uint32_t Connector::connector_type() const
+{
+ return m_priv->drm_connector->connector_type;
+}
+
+uint32_t Connector::connector_type_id() const
+{
+ return m_priv->drm_connector->connector_type_id;
+}
+
+uint32_t Connector::mmWidth() const
+{
+ return m_priv->drm_connector->mmWidth;
+}
+
+uint32_t Connector::mmHeight() const
+{
+ return m_priv->drm_connector->mmHeight;
+}
+
+uint32_t Connector::subpixel() const
+{
+ return m_priv->drm_connector->subpixel;
+}
+
+const string& Connector::subpixel_str() const
+{
+ return subpix_str.at(subpixel());
+}
+
+std::vector<Videomode> Connector::get_modes() const
+{
+ vector<Videomode> modes;
+
+ for (int i = 0; i < m_priv->drm_connector->count_modes; i++)
+ modes.push_back(drm_mode_to_video_mode(
+ m_priv->drm_connector->modes[i]));
+
+ return modes;
+}
+
+std::vector<Encoder*> Connector::get_encoders() const
+{
+ vector<Encoder*> encoders;
+
+ for (int i = 0; i < m_priv->drm_connector->count_encoders; i++) {
+ auto enc = card().get_encoder(m_priv->drm_connector->encoders[i]);
+ encoders.push_back(enc);
+ }
+ return encoders;
+}
+
+} // namespace kms
diff --git a/kms++/src/crtc.cpp b/kms++/src/crtc.cpp
new file mode 100644
index 0000000..ca00252
--- /dev/null
+++ b/kms++/src/crtc.cpp
@@ -0,0 +1,196 @@
+#include <cstdio>
+#include <unistd.h>
+#include <fcntl.h>
+#include <cassert>
+
+#include <kms++/kms++.h>
+#include "helpers.h"
+
+using namespace std;
+
+namespace kms
+{
+struct CrtcPriv {
+ drmModeCrtcPtr drm_crtc;
+};
+
+Crtc::Crtc(Card& card, uint32_t id, uint32_t idx)
+ : DrmPropObject(card, id, DRM_MODE_OBJECT_CRTC, idx)
+{
+ m_priv = new CrtcPriv();
+ m_priv->drm_crtc = drmModeGetCrtc(this->card().fd(), this->id());
+ assert(m_priv->drm_crtc);
+}
+
+Crtc::~Crtc()
+{
+ drmModeFreeCrtc(m_priv->drm_crtc);
+ delete m_priv;
+}
+
+void Crtc::refresh()
+{
+ drmModeFreeCrtc(m_priv->drm_crtc);
+
+ m_priv->drm_crtc = drmModeGetCrtc(this->card().fd(), this->id());
+ assert(m_priv->drm_crtc);
+}
+
+void Crtc::setup()
+{
+ for (Plane* plane : card().get_planes()) {
+ if (plane->supports_crtc(this))
+ m_possible_planes.push_back(plane);
+ }
+}
+
+void Crtc::restore_mode(Connector* conn)
+{
+ auto c = m_priv->drm_crtc;
+
+ uint32_t conns[] = { conn->id() };
+
+ drmModeSetCrtc(card().fd(), id(), c->buffer_id,
+ c->x, c->y,
+ conns, 1, &c->mode);
+}
+
+int Crtc::set_mode(Connector* conn, const Videomode& mode)
+{
+ AtomicReq req(card());
+
+ unique_ptr<Blob> blob = mode.to_blob(card());
+
+ req.add(conn, {
+ { "CRTC_ID", this->id() },
+ });
+
+ req.add(this, {
+ { "ACTIVE", 1 },
+ { "MODE_ID", blob->id() },
+ });
+
+ int r = req.commit_sync(true);
+
+ refresh();
+
+ return r;
+}
+
+int Crtc::set_mode(Connector* conn, Framebuffer& fb, const Videomode& mode)
+{
+ uint32_t conns[] = { conn->id() };
+ drmModeModeInfo drmmode = video_mode_to_drm_mode(mode);
+
+ return drmModeSetCrtc(card().fd(), id(), fb.id(),
+ 0, 0,
+ conns, 1, &drmmode);
+}
+
+int Crtc::disable_mode()
+{
+ return drmModeSetCrtc(card().fd(), id(), 0, 0, 0, 0, 0, 0);
+}
+
+static inline uint32_t conv(float x)
+{
+ // XXX fix the conversion for fractional part
+ return ((uint32_t)x) << 16;
+}
+
+int Crtc::set_plane(Plane* plane, Framebuffer& fb,
+ int32_t dst_x, int32_t dst_y, uint32_t dst_w, uint32_t dst_h,
+ float src_x, float src_y, float src_w, float src_h)
+{
+ return drmModeSetPlane(card().fd(), plane->id(), id(), fb.id(), 0,
+ dst_x, dst_y, dst_w, dst_h,
+ conv(src_x), conv(src_y), conv(src_w), conv(src_h));
+}
+
+int Crtc::disable_plane(Plane* plane)
+{
+ return drmModeSetPlane(card().fd(), plane->id(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+}
+
+Plane* Crtc::get_primary_plane()
+{
+ Plane* primary = nullptr;
+
+ for (Plane* p : get_possible_planes()) {
+ if (p->plane_type() != PlaneType::Primary)
+ continue;
+
+ if (p->crtc_id() == id())
+ return p;
+
+ primary = p;
+ }
+
+ if (primary)
+ return primary;
+
+ throw invalid_argument(string("No primary plane for crtc ") + to_string(id()));
+}
+
+int Crtc::page_flip(Framebuffer& fb, void* data)
+{
+ return drmModePageFlip(card().fd(), id(), fb.id(), DRM_MODE_PAGE_FLIP_EVENT, data);
+}
+
+uint32_t Crtc::buffer_id() const
+{
+ return m_priv->drm_crtc->buffer_id;
+}
+
+uint32_t Crtc::x() const
+{
+ return m_priv->drm_crtc->x;
+}
+
+uint32_t Crtc::y() const
+{
+ return m_priv->drm_crtc->y;
+}
+
+uint32_t Crtc::width() const
+{
+ return m_priv->drm_crtc->width;
+}
+
+uint32_t Crtc::height() const
+{
+ return m_priv->drm_crtc->height;
+}
+
+int Crtc::mode_valid() const
+{
+ return m_priv->drm_crtc->mode_valid;
+}
+
+Videomode Crtc::mode() const
+{
+ return drm_mode_to_video_mode(m_priv->drm_crtc->mode);
+}
+
+int Crtc::legacy_gamma_size() const
+{
+ return m_priv->drm_crtc->gamma_size;
+}
+
+void Crtc::legacy_gamma_set(vector<tuple<uint16_t, uint16_t, uint16_t>> v)
+{
+ uint32_t len = v.size();
+ vector<uint16_t> red(len);
+ vector<uint16_t> green(len);
+ vector<uint16_t> blue(len);
+
+ for (uint32_t i = 0; i < len; ++i) {
+ red[i] = get<0>(v[i]);
+ green[i] = get<1>(v[i]);
+ blue[i] = get<2>(v[i]);
+ }
+
+ drmModeCrtcSetGamma(card().fd(), id(), len, red.data(), green.data(), blue.data());
+}
+
+} // namespace kms
diff --git a/kms++/src/dmabufframebuffer.cpp b/kms++/src/dmabufframebuffer.cpp
new file mode 100644
index 0000000..33d65f7
--- /dev/null
+++ b/kms++/src/dmabufframebuffer.cpp
@@ -0,0 +1,161 @@
+
+#include <cstring>
+#include <cerrno>
+
+#include <stdexcept>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <linux/dma-buf.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+DmabufFramebuffer::DmabufFramebuffer(Card& card, uint32_t width, uint32_t height, const string& format,
+ vector<int> fds, vector<uint32_t> pitches, vector<uint32_t> offsets, vector<uint64_t> modifiers)
+ : DmabufFramebuffer(card, width, height, fourcc_str_to_pixel_format(format), fds, pitches, offsets, modifiers)
+{
+}
+
+DmabufFramebuffer::DmabufFramebuffer(Card& card, uint32_t width, uint32_t height, PixelFormat format,
+ vector<int> fds, vector<uint32_t> pitches, vector<uint32_t> offsets, vector<uint64_t> modifiers)
+ : Framebuffer(card, width, height)
+{
+ int r;
+
+ m_format = format;
+
+ const PixelFormatInfo& format_info = get_pixel_format_info(format);
+
+ m_num_planes = format_info.num_planes;
+
+ if (fds.size() != m_num_planes || pitches.size() != m_num_planes || offsets.size() != m_num_planes)
+ throw std::invalid_argument("the size of fds, pitches and offsets has to match number of planes");
+
+ for (int i = 0; i < format_info.num_planes; ++i) {
+ FramebufferPlane& plane = m_planes.at(i);
+
+ plane.prime_fd = dup(fds[i]);
+
+ r = drmPrimeFDToHandle(card.fd(), plane.prime_fd, &plane.handle);
+ if (r)
+ throw invalid_argument(string("drmPrimeFDToHandle: ") + strerror(errno));
+
+ plane.stride = pitches[i];
+ plane.offset = offsets[i];
+ plane.modifier = modifiers.empty() ? 0 : modifiers[i];
+ plane.size = plane.stride * height;
+ plane.map = 0;
+ }
+
+ uint32_t id;
+ uint32_t bo_handles[4] = { m_planes[0].handle, m_planes[1].handle, m_planes[2].handle, m_planes[3].handle };
+ pitches.resize(4);
+ offsets.resize(4);
+
+ if (modifiers.empty()) {
+ r = drmModeAddFB2(card.fd(), width, height, pixel_format_to_fourcc(format),
+ bo_handles, pitches.data(), offsets.data(), &id, 0);
+ if (r)
+ throw invalid_argument(string("drmModeAddFB2 failed: ") + strerror(errno));
+ } else {
+ modifiers.resize(4);
+ r = drmModeAddFB2WithModifiers(card.fd(), width, height, pixel_format_to_fourcc(format),
+ bo_handles, pitches.data(), offsets.data(), modifiers.data(), &id, DRM_MODE_FB_MODIFIERS);
+ if (r)
+ throw invalid_argument(string("drmModeAddFB2WithModifiers failed: ") + strerror(errno));
+ }
+
+ set_id(id);
+}
+
+DmabufFramebuffer::~DmabufFramebuffer()
+{
+ drmModeRmFB(card().fd(), id());
+
+ for (uint i = 0; i < m_num_planes; ++i) {
+ FramebufferPlane& plane = m_planes.at(i);
+
+ if (plane.map)
+ munmap(plane.map, plane.size);
+
+ if (plane.prime_fd >= 0)
+ ::close(plane.prime_fd);
+ }
+}
+
+uint8_t* DmabufFramebuffer::map(unsigned plane)
+{
+ FramebufferPlane& p = m_planes.at(plane);
+
+ if (p.map)
+ return p.map;
+
+ p.map = static_cast<uint8_t*>(mmap(0, p.size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ p.prime_fd, 0));
+ if (p.map == MAP_FAILED)
+ throw invalid_argument(string("mmap failed: ") + strerror(errno));
+
+ return p.map;
+}
+
+int DmabufFramebuffer::prime_fd(unsigned plane)
+{
+ FramebufferPlane& p = m_planes.at(plane);
+
+ return p.prime_fd;
+}
+
+void DmabufFramebuffer::begin_cpu_access(CpuAccess access)
+{
+ if (m_sync_flags != 0)
+ throw runtime_error("begin_cpu sync already started");
+
+ switch (access) {
+ case CpuAccess::Read:
+ m_sync_flags = DMA_BUF_SYNC_READ;
+ break;
+ case CpuAccess::Write:
+ m_sync_flags = DMA_BUF_SYNC_WRITE;
+ break;
+ case CpuAccess::ReadWrite:
+ m_sync_flags = DMA_BUF_SYNC_RW;
+ break;
+ }
+
+ dma_buf_sync dbs{
+ .flags = DMA_BUF_SYNC_START | m_sync_flags
+ };
+
+ for (uint32_t p = 0; p < m_num_planes; ++p) {
+ int r = ioctl(prime_fd(p), DMA_BUF_IOCTL_SYNC, &dbs);
+ if (r)
+ throw runtime_error("DMA_BUF_IOCTL_SYNC failed");
+ }
+}
+
+void DmabufFramebuffer::end_cpu_access()
+{
+ if (m_sync_flags == 0)
+ throw runtime_error("begin_cpu sync not started");
+
+ dma_buf_sync dbs{
+ .flags = DMA_BUF_SYNC_END | m_sync_flags
+ };
+
+ for (uint32_t p = 0; p < m_num_planes; ++p) {
+ int r = ioctl(prime_fd(p), DMA_BUF_IOCTL_SYNC, &dbs);
+ if (r)
+ throw runtime_error("DMA_BUF_IOCTL_SYNC failed");
+ }
+
+ m_sync_flags = 0;
+}
+
+} // namespace kms
diff --git a/kms++/src/drmobject.cpp b/kms++/src/drmobject.cpp
new file mode 100644
index 0000000..f01c8af
--- /dev/null
+++ b/kms++/src/drmobject.cpp
@@ -0,0 +1,32 @@
+#include <cstring>
+#include <iostream>
+#include <stdexcept>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+DrmObject::DrmObject(Card& card, uint32_t object_type)
+ : m_card(card), m_id(-1), m_object_type(object_type), m_idx(0)
+{
+}
+
+DrmObject::DrmObject(Card& card, uint32_t id, uint32_t object_type, uint32_t idx)
+ : m_card(card), m_id(id), m_object_type(object_type), m_idx(idx)
+{
+}
+
+DrmObject::~DrmObject()
+{
+}
+
+void DrmObject::set_id(uint32_t id)
+{
+ m_id = id;
+}
+} // namespace kms
diff --git a/kms++/src/drmpropobject.cpp b/kms++/src/drmpropobject.cpp
new file mode 100644
index 0000000..616aef7
--- /dev/null
+++ b/kms++/src/drmpropobject.cpp
@@ -0,0 +1,101 @@
+#include <cstring>
+#include <iostream>
+#include <stdexcept>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+DrmPropObject::DrmPropObject(Card& card, uint32_t object_type)
+ : DrmObject(card, object_type)
+{
+}
+
+DrmPropObject::DrmPropObject(Card& card, uint32_t id, uint32_t object_type, uint32_t idx)
+ : DrmObject(card, id, object_type, idx)
+{
+ refresh_props();
+}
+
+DrmPropObject::~DrmPropObject()
+{
+}
+
+void DrmPropObject::refresh_props()
+{
+ auto props = drmModeObjectGetProperties(card().fd(), this->id(), this->object_type());
+
+ if (props == nullptr)
+ return;
+
+ for (unsigned i = 0; i < props->count_props; ++i) {
+ uint32_t prop_id = props->props[i];
+ uint64_t prop_value = props->prop_values[i];
+
+ m_prop_values[prop_id] = prop_value;
+ }
+
+ drmModeFreeObjectProperties(props);
+}
+
+Property* DrmPropObject::get_prop(const string& name) const
+{
+ for (auto pair : m_prop_values) {
+ auto prop = card().get_prop(pair.first);
+
+ if (name == prop->name())
+ return prop;
+ }
+
+ return nullptr;
+}
+
+uint64_t DrmPropObject::get_prop_value(uint32_t id) const
+{
+ return m_prop_values.at(id);
+}
+
+uint64_t DrmPropObject::get_prop_value(const string& name) const
+{
+ for (auto pair : m_prop_values) {
+ auto prop = card().get_prop(pair.first);
+ if (name == prop->name())
+ return m_prop_values.at(prop->id());
+ }
+
+ throw invalid_argument("property not found: " + name);
+}
+
+unique_ptr<Blob> DrmPropObject::get_prop_value_as_blob(const string& name) const
+{
+ uint32_t blob_id = (uint32_t)get_prop_value(name);
+
+ return unique_ptr<Blob>(new Blob(card(), blob_id));
+}
+
+int DrmPropObject::set_prop_value(Property* prop, uint64_t value)
+{
+ return drmModeObjectSetProperty(card().fd(), this->id(), this->object_type(), prop->id(), value);
+}
+
+int DrmPropObject::set_prop_value(uint32_t id, uint64_t value)
+{
+ return drmModeObjectSetProperty(card().fd(), this->id(), this->object_type(), id, value);
+}
+
+int DrmPropObject::set_prop_value(const string& name, uint64_t value)
+{
+ Property* prop = get_prop(name);
+
+ if (prop == nullptr)
+ throw invalid_argument("property not found: " + name);
+
+ return set_prop_value(prop->id(), value);
+}
+
+} // namespace kms
diff --git a/kms++/src/dumbframebuffer.cpp b/kms++/src/dumbframebuffer.cpp
new file mode 100644
index 0000000..421dac4
--- /dev/null
+++ b/kms++/src/dumbframebuffer.cpp
@@ -0,0 +1,143 @@
+
+#include <cstring>
+#include <cerrno>
+#include <stdexcept>
+#include <sys/mman.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <drm_fourcc.h>
+#include <drm.h>
+#include <drm_mode.h>
+
+#include <kms++/kms++.h>
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+using namespace std;
+
+namespace kms
+{
+DumbFramebuffer::DumbFramebuffer(Card& card, uint32_t width, uint32_t height, const string& fourcc)
+ : DumbFramebuffer(card, width, height, fourcc_str_to_pixel_format(fourcc))
+{
+}
+
+DumbFramebuffer::DumbFramebuffer(Card& card, uint32_t width, uint32_t height, PixelFormat format)
+ : Framebuffer(card, width, height), m_format(format)
+{
+ int r;
+
+ const PixelFormatInfo& format_info = get_pixel_format_info(m_format);
+
+ m_num_planes = format_info.num_planes;
+
+ for (int i = 0; i < format_info.num_planes; ++i) {
+ FramebufferPlane& plane = m_planes.at(i);
+
+ auto [w, h, bpp] = format_info.dumb_size(width, height, i);
+
+ /* create dumb buffer */
+ struct drm_mode_create_dumb creq = drm_mode_create_dumb();
+ creq.width = w;
+ creq.height = h;
+ creq.bpp = bpp;
+ r = drmIoctl(card.fd(), DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+ if (r)
+ throw invalid_argument(string("DRM_IOCTL_MODE_CREATE_DUMB failed: ") + strerror(errno));
+
+ plane.handle = creq.handle;
+ plane.stride = creq.pitch;
+ plane.size = creq.height * creq.pitch;
+ plane.offset = 0;
+ plane.map = 0;
+ plane.prime_fd = -1;
+ }
+
+ /* create framebuffer object for the dumb-buffer */
+ uint32_t bo_handles[4] = {
+ m_planes[0].handle,
+ m_planes[1].handle,
+ m_planes[2].handle,
+ m_planes[3].handle,
+ };
+ uint32_t pitches[4] = {
+ m_planes[0].stride,
+ m_planes[1].stride,
+ m_planes[2].stride,
+ m_planes[3].stride,
+ };
+ uint32_t offsets[4] = {
+ m_planes[0].offset,
+ m_planes[1].offset,
+ m_planes[2].offset,
+ m_planes[3].offset,
+ };
+ uint32_t id;
+ r = drmModeAddFB2(card.fd(), width, height, pixel_format_to_fourcc(format),
+ bo_handles, pitches, offsets, &id, 0);
+ if (r)
+ throw invalid_argument(string("drmModeAddFB2 failed: ") + strerror(errno));
+
+ set_id(id);
+}
+
+DumbFramebuffer::~DumbFramebuffer()
+{
+ /* delete framebuffer */
+ drmModeRmFB(card().fd(), id());
+
+ for (uint i = 0; i < m_num_planes; ++i) {
+ FramebufferPlane& plane = m_planes.at(i);
+
+ /* unmap buffer */
+ if (plane.map)
+ munmap(plane.map, plane.size);
+
+ /* delete dumb buffer */
+ struct drm_mode_destroy_dumb dreq = drm_mode_destroy_dumb();
+ dreq.handle = plane.handle;
+ drmIoctl(card().fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ if (plane.prime_fd >= 0)
+ ::close(plane.prime_fd);
+ }
+}
+
+uint8_t* DumbFramebuffer::map(unsigned plane)
+{
+ FramebufferPlane& p = m_planes.at(plane);
+
+ if (p.map)
+ return p.map;
+
+ /* prepare buffer for memory mapping */
+ struct drm_mode_map_dumb mreq = drm_mode_map_dumb();
+ mreq.handle = p.handle;
+ int r = drmIoctl(card().fd(), DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+ if (r)
+ throw invalid_argument(string("DRM_IOCTL_MODE_MAP_DUMB failed: ") + strerror(errno));
+
+ /* perform actual memory mapping */
+ p.map = static_cast<uint8_t*>(mmap(0, p.size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ card().fd(), mreq.offset));
+ if (p.map == MAP_FAILED)
+ throw invalid_argument(string("mmap failed: ") + strerror(errno));
+
+ return p.map;
+}
+
+int DumbFramebuffer::prime_fd(unsigned int plane)
+{
+ if (m_planes.at(plane).prime_fd >= 0)
+ return m_planes.at(plane).prime_fd;
+
+ int r = drmPrimeHandleToFD(card().fd(), m_planes.at(plane).handle,
+ DRM_CLOEXEC | O_RDWR, &m_planes.at(plane).prime_fd);
+ if (r)
+ throw std::runtime_error("drmPrimeHandleToFD failed");
+
+ return m_planes.at(plane).prime_fd;
+}
+
+} // namespace kms
diff --git a/kms++/src/encoder.cpp b/kms++/src/encoder.cpp
new file mode 100644
index 0000000..8144b9f
--- /dev/null
+++ b/kms++/src/encoder.cpp
@@ -0,0 +1,87 @@
+#include <cstdio>
+#include <iostream>
+#include <unistd.h>
+#include <fcntl.h>
+#include <cassert>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+struct EncoderPriv {
+ drmModeEncoderPtr drm_encoder;
+};
+
+static const map<int, string> encoder_types = {
+#define DEF_ENC(c) \
+ { \
+ DRM_MODE_ENCODER_##c, #c \
+ }
+ DEF_ENC(NONE),
+ DEF_ENC(DAC),
+ DEF_ENC(TMDS),
+ DEF_ENC(LVDS),
+ DEF_ENC(TVDAC),
+ DEF_ENC(VIRTUAL),
+ DEF_ENC(DSI),
+ { 7, "DPMST" },
+ { 8, "DPI" },
+#undef DEF_ENC
+};
+
+Encoder::Encoder(Card& card, uint32_t id, uint32_t idx)
+ : DrmPropObject(card, id, DRM_MODE_OBJECT_ENCODER, idx)
+{
+ m_priv = new EncoderPriv();
+ m_priv->drm_encoder = drmModeGetEncoder(this->card().fd(), this->id());
+ assert(m_priv->drm_encoder);
+}
+
+Encoder::~Encoder()
+{
+ drmModeFreeEncoder(m_priv->drm_encoder);
+ delete m_priv;
+}
+
+void Encoder::refresh()
+{
+ drmModeFreeEncoder(m_priv->drm_encoder);
+
+ m_priv->drm_encoder = drmModeGetEncoder(this->card().fd(), this->id());
+ assert(m_priv->drm_encoder);
+}
+
+Crtc* Encoder::get_crtc() const
+{
+ if (m_priv->drm_encoder->crtc_id)
+ return card().get_crtc(m_priv->drm_encoder->crtc_id);
+ else
+ return 0;
+}
+
+vector<Crtc*> Encoder::get_possible_crtcs() const
+{
+ unsigned bits = m_priv->drm_encoder->possible_crtcs;
+ vector<Crtc*> crtcs;
+
+ for (int idx = 0; bits; idx++, bits >>= 1) {
+ if ((bits & 1) == 0)
+ continue;
+
+ auto crtc = card().get_crtcs()[idx];
+ crtcs.push_back(crtc);
+ }
+
+ return crtcs;
+}
+
+const string& Encoder::get_encoder_type() const
+{
+ return encoder_types.at(m_priv->drm_encoder->encoder_type);
+}
+
+} // namespace kms
diff --git a/kms++/src/extframebuffer.cpp b/kms++/src/extframebuffer.cpp
new file mode 100644
index 0000000..d67cbad
--- /dev/null
+++ b/kms++/src/extframebuffer.cpp
@@ -0,0 +1,64 @@
+
+#include <cstring>
+#include <cerrno>
+
+#include <stdexcept>
+#include <sys/mman.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+ExtFramebuffer::ExtFramebuffer(Card& card, uint32_t width, uint32_t height, PixelFormat format,
+ vector<uint32_t> handles, vector<uint32_t> pitches, vector<uint32_t> offsets, vector<uint64_t> modifiers)
+ : Framebuffer(card, width, height)
+{
+ m_format = format;
+
+ const PixelFormatInfo& format_info = get_pixel_format_info(format);
+
+ m_num_planes = format_info.num_planes;
+
+ if (handles.size() != m_num_planes || pitches.size() != m_num_planes || offsets.size() != m_num_planes)
+ throw std::invalid_argument("the size of handles, pitches and offsets has to match number of planes");
+
+ for (int i = 0; i < format_info.num_planes; ++i) {
+ FramebufferPlane& plane = m_planes.at(i);
+
+ plane.handle = handles[i];
+ plane.stride = pitches[i];
+ plane.offset = offsets[i];
+ plane.modifier = modifiers.empty() ? 0 : modifiers[i];
+ plane.size = plane.stride * height;
+ plane.map = 0;
+ }
+
+ uint32_t id;
+ handles.resize(4);
+ pitches.resize(4);
+ offsets.resize(4);
+ int r;
+
+ if (modifiers.empty()) {
+ r = drmModeAddFB2(card.fd(), width, height, pixel_format_to_fourcc(format), handles.data(), pitches.data(), offsets.data(), &id, 0);
+ } else {
+ modifiers.resize(4);
+ r = drmModeAddFB2WithModifiers(card.fd(), width, height, pixel_format_to_fourcc(format), handles.data(), pitches.data(), offsets.data(), modifiers.data(), &id, DRM_MODE_FB_MODIFIERS);
+ }
+
+ if (r)
+ throw std::invalid_argument(string("Failed to create ExtFramebuffer: ") + strerror(r));
+
+ set_id(id);
+}
+
+ExtFramebuffer::~ExtFramebuffer()
+{
+ drmModeRmFB(card().fd(), id());
+}
+
+} // namespace kms
diff --git a/kms++/src/framebuffer.cpp b/kms++/src/framebuffer.cpp
new file mode 100644
index 0000000..3d251ce
--- /dev/null
+++ b/kms++/src/framebuffer.cpp
@@ -0,0 +1,71 @@
+#include <algorithm>
+#include <cstring>
+#include <stdexcept>
+#include <sys/mman.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+Framebuffer::Framebuffer(Card& card, uint32_t width, uint32_t height)
+ : DrmObject(card, DRM_MODE_OBJECT_FB), m_width(width), m_height(height)
+{
+ card.m_framebuffers.push_back(this);
+}
+
+Framebuffer::Framebuffer(Card& card, uint32_t id)
+ : DrmObject(card, id, DRM_MODE_OBJECT_FB)
+{
+ auto fb = drmModeGetFB2(card.fd(), id);
+
+ if (fb) {
+ m_width = fb->width;
+ m_height = fb->height;
+ m_fourcc = fb->pixel_format;
+ try {
+ m_format = fourcc_to_pixel_format(m_fourcc);
+ } catch (const invalid_argument& e) {
+ m_format = PixelFormat::Undefined;
+ }
+
+ drmModeFreeFB2(fb);
+ } else {
+ m_width = m_height = 0;
+ }
+
+ card.m_framebuffers.push_back(this);
+}
+
+void Framebuffer::flush(uint32_t x, uint32_t y, uint32_t width, uint32_t height)
+{
+ drmModeClip clip{};
+ clip.x1 = x;
+ clip.y1 = y;
+ clip.x2 = x + width;
+ clip.y2 = y + height;
+
+ drmModeDirtyFB(card().fd(), id(), &clip, 1);
+}
+
+void Framebuffer::flush()
+{
+ drmModeClip clip{};
+ clip.x1 = clip.y1 = 0;
+ clip.x2 = width();
+ clip.y2 = height();
+
+ drmModeDirtyFB(card().fd(), id(), &clip, 1);
+}
+
+Framebuffer::~Framebuffer()
+{
+ auto& fbs = card().m_framebuffers;
+ auto iter = find(fbs.begin(), fbs.end(), this);
+ card().m_framebuffers.erase(iter);
+}
+
+} // namespace kms
diff --git a/kms++/src/helpers.cpp b/kms++/src/helpers.cpp
new file mode 100644
index 0000000..9079a23
--- /dev/null
+++ b/kms++/src/helpers.cpp
@@ -0,0 +1,72 @@
+
+#include <kms++/kms++.h>
+#include "helpers.h"
+#include <cstring>
+
+#define CPY(field) dst.field = src.field
+
+namespace kms
+{
+Videomode drm_mode_to_video_mode(const drmModeModeInfo& drmmode)
+{
+ Videomode mode = {};
+
+ auto& src = drmmode;
+ auto& dst = mode;
+
+ CPY(clock);
+
+ CPY(hdisplay);
+ CPY(hsync_start);
+ CPY(hsync_end);
+ CPY(htotal);
+ CPY(hskew);
+
+ CPY(vdisplay);
+ CPY(vsync_start);
+ CPY(vsync_end);
+ CPY(vtotal);
+ CPY(vscan);
+
+ CPY(vrefresh);
+
+ CPY(flags);
+ CPY(type);
+
+ mode.name = drmmode.name;
+
+ return mode;
+}
+
+drmModeModeInfo video_mode_to_drm_mode(const Videomode& mode)
+{
+ drmModeModeInfo drmmode = {};
+
+ auto& src = mode;
+ auto& dst = drmmode;
+
+ CPY(clock);
+
+ CPY(hdisplay);
+ CPY(hsync_start);
+ CPY(hsync_end);
+ CPY(htotal);
+ CPY(hskew);
+
+ CPY(vdisplay);
+ CPY(vsync_start);
+ CPY(vsync_end);
+ CPY(vtotal);
+ CPY(vscan);
+
+ CPY(vrefresh);
+
+ CPY(flags);
+ CPY(type);
+
+ strncpy(drmmode.name, mode.name.c_str(), sizeof(drmmode.name));
+ drmmode.name[sizeof(drmmode.name) - 1] = 0;
+
+ return drmmode;
+}
+} // namespace kms
diff --git a/kms++/src/helpers.h b/kms++/src/helpers.h
new file mode 100644
index 0000000..58926bf
--- /dev/null
+++ b/kms++/src/helpers.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+namespace kms
+{
+struct Videomode;
+
+Videomode drm_mode_to_video_mode(const drmModeModeInfo& drmmode);
+drmModeModeInfo video_mode_to_drm_mode(const Videomode& mode);
+} // namespace kms
diff --git a/kms++/src/mode_cvt.cpp b/kms++/src/mode_cvt.cpp
new file mode 100644
index 0000000..a7a10b8
--- /dev/null
+++ b/kms++/src/mode_cvt.cpp
@@ -0,0 +1,150 @@
+// Supports CVT 1.2 reduced blanking modes v1 and v2
+
+#include <kms++/kms++.h>
+#include <cmath>
+
+using namespace std;
+
+namespace kms
+{
+static float CELL_GRAN = 8;
+static float CELL_GRAN_RND = round(CELL_GRAN);
+
+struct CVTConsts {
+ float CLOCK_STEP;
+ float MIN_V_BPORCH;
+ float RB_H_BLANK;
+ float RB_H_FPORCH;
+ float RB_H_SYNC;
+ float RB_H_BPORCH;
+ float RB_MIN_V_BLANK;
+ float RB_V_FPORCH;
+ float REFRESH_MULTIPLIER;
+};
+
+static const CVTConsts cvt_consts_v1 = {
+ .CLOCK_STEP = 0.25, // Fixed
+ .MIN_V_BPORCH = 6, // Min
+ .RB_H_BLANK = 160, // Fixed
+ .RB_H_FPORCH = 48, // Fixed
+ .RB_H_SYNC = 32, // Fixed
+ .RB_H_BPORCH = 80, // Fixed
+ .RB_MIN_V_BLANK = 460, // Min
+ .RB_V_FPORCH = 3, // Fixed
+ .REFRESH_MULTIPLIER = 1, // Fixed
+};
+
+static const CVTConsts cvt_consts_v2 = {
+ .CLOCK_STEP = 0.001, // Fixed
+ .MIN_V_BPORCH = 6, // Fixed
+ .RB_H_BLANK = 80, // Fixed
+ .RB_H_FPORCH = 8, // Fixed
+ .RB_H_SYNC = 32, // Fixed
+ .RB_H_BPORCH = 40, // Fixed
+ .RB_MIN_V_BLANK = 460, // Min
+ .RB_V_FPORCH = 1, // Min
+ .REFRESH_MULTIPLIER = 1, // or 1000/1001
+};
+
+Videomode videomode_from_cvt(uint32_t hact, uint32_t vact, uint32_t refresh, bool ilace, bool reduced_v2, bool video_optimized)
+{
+ CVTConsts c = reduced_v2 ? cvt_consts_v2 : cvt_consts_v1;
+
+ if (video_optimized)
+ c.REFRESH_MULTIPLIER = 1000.0 / 1001.0;
+
+ bool INT_RQD = ilace;
+
+ float H_PIXELS = hact;
+ float V_LINES = vact;
+ float IP_FREQ_RQD = refresh ? refresh : 60;
+ if (ilace)
+ IP_FREQ_RQD /= 2;
+
+ float V_SYNC_RND;
+
+ if (reduced_v2) {
+ V_SYNC_RND = 8;
+ } else {
+ if (hact * 3 == vact * 4)
+ V_SYNC_RND = 4;
+ else if (hact * 9 == vact * 16)
+ V_SYNC_RND = 5;
+ else if (hact * 10 == vact * 16)
+ V_SYNC_RND = 6;
+ else if (hact == 1280 && (vact == 1024 || vact == 768))
+ V_SYNC_RND = 7;
+ else
+ V_SYNC_RND = 10;
+ }
+
+ // 5.2.1
+ float V_FIELD_RATE_RQD = INT_RQD ? IP_FREQ_RQD * 2 : IP_FREQ_RQD;
+
+ // 5.2.2
+ float H_PIXELS_RND = floor(H_PIXELS / CELL_GRAN_RND) * CELL_GRAN_RND;
+
+ // 5.2.3
+ float LEFT_MARGIN = 0;
+ float RIGHT_MARGIN = 0;
+
+ // 5.2.4
+ float TOTAL_ACTIVE_PIXELS = H_PIXELS_RND + LEFT_MARGIN + RIGHT_MARGIN;
+
+ // 5.2.5
+ float V_LINES_RND = INT_RQD ? floor(V_LINES / 2) : floor(V_LINES);
+
+ // 5.2.6
+ float TOP_MARGIN = 0;
+ float BOT_MARGIN = 0;
+
+ // 5.2.7
+ float INTERLACE = INT_RQD ? 0.5 : 0;
+
+ // 5.4.8
+ float H_PERIOD_EST = ((1000000 / V_FIELD_RATE_RQD) - c.RB_MIN_V_BLANK) / (V_LINES_RND + TOP_MARGIN + BOT_MARGIN);
+
+ // 5.4.9
+ float VBI_LINES = floor(c.RB_MIN_V_BLANK / H_PERIOD_EST) + 1;
+
+ // 5.4.10
+ float RB_MIN_VBI = c.RB_V_FPORCH + V_SYNC_RND + c.MIN_V_BPORCH;
+ float ACT_VBI_LINES = VBI_LINES < RB_MIN_VBI ? RB_MIN_VBI : VBI_LINES;
+
+ // 5.4.11
+ float TOTAL_V_LINES = ACT_VBI_LINES + V_LINES_RND + TOP_MARGIN + BOT_MARGIN + INTERLACE;
+
+ // 5.4.12
+ float TOTAL_PIXELS = c.RB_H_BLANK + TOTAL_ACTIVE_PIXELS;
+
+ // 5.4.13
+ float ACT_PIXEL_FREQ = c.CLOCK_STEP * floor((V_FIELD_RATE_RQD * TOTAL_V_LINES * TOTAL_PIXELS / 1000000 * c.REFRESH_MULTIPLIER) / c.CLOCK_STEP);
+
+ // 5.4.14
+ //float ACT_H_FREQ = 1000 * ACT_PIXEL_FREQ / TOTAL_PIXELS;
+
+ // 5.4.15
+ //float ACT_FIELD_RATE = 1000 * ACT_H_FREQ / TOTAL_V_LINES;
+
+ // 5.4.16
+ //float ACT_FRAME_RATE = INT_RQD ? ACT_FIELD_RATE / 2 : ACT_FIELD_RATE;
+
+ // 3.4.3.7 Adjust vfp
+ if (reduced_v2)
+ c.RB_V_FPORCH = ACT_VBI_LINES - V_SYNC_RND - c.MIN_V_BPORCH;
+
+ Videomode mode;
+
+ mode = videomode_from_timings(ACT_PIXEL_FREQ * 1000,
+ H_PIXELS_RND, c.RB_H_FPORCH, c.RB_H_SYNC, c.RB_H_BPORCH,
+ V_LINES_RND * (INT_RQD ? 2 : 1), c.RB_V_FPORCH, V_SYNC_RND, ACT_VBI_LINES - V_SYNC_RND - c.RB_V_FPORCH);
+
+ mode.set_hsync(SyncPolarity::Positive);
+ mode.set_vsync(SyncPolarity::Negative);
+
+ mode.set_interlace(INT_RQD);
+
+ return mode;
+}
+
+} // namespace kms
diff --git a/kms++/src/modedb.cpp b/kms++/src/modedb.cpp
new file mode 100644
index 0000000..081100a
--- /dev/null
+++ b/kms++/src/modedb.cpp
@@ -0,0 +1,58 @@
+#include <xf86drm.h>
+#include <stdexcept>
+#include <cmath>
+
+#include <kms++/modedb.h>
+
+using namespace std;
+
+namespace kms
+{
+static const Videomode& find_from_table(const Videomode* modes, uint32_t width, uint32_t height, float vrefresh, bool ilace)
+{
+ for (unsigned i = 0; modes[i].clock; ++i) {
+ const Videomode& m = modes[i];
+
+ if (m.hdisplay != width || m.vdisplay != height)
+ continue;
+
+ if (ilace != m.interlace())
+ continue;
+
+ if (vrefresh && vrefresh != m.calculated_vrefresh())
+ continue;
+
+ return m;
+ }
+
+ // If not found, do another round using rounded vrefresh
+
+ for (unsigned i = 0; modes[i].clock; ++i) {
+ const Videomode& m = modes[i];
+
+ if (m.hdisplay != width || m.vdisplay != height)
+ continue;
+
+ if (ilace != m.interlace())
+ continue;
+
+ if (vrefresh && vrefresh != roundf(m.calculated_vrefresh()))
+ continue;
+
+ return m;
+ }
+
+ throw invalid_argument("mode not found");
+}
+
+const Videomode& find_dmt(uint32_t width, uint32_t height, float vrefresh, bool ilace)
+{
+ return find_from_table(dmt_modes, width, height, vrefresh, ilace);
+}
+
+const Videomode& find_cea(uint32_t width, uint32_t height, float vrefresh, bool ilace)
+{
+ return find_from_table(cea_modes, width, height, vrefresh, ilace);
+}
+
+} // namespace kms
diff --git a/kms++/src/modedb_cea.cpp b/kms++/src/modedb_cea.cpp
new file mode 100644
index 0000000..b09472f
--- /dev/null
+++ b/kms++/src/modedb_cea.cpp
@@ -0,0 +1,465 @@
+/* From Linux kernel: drm_edid.c */
+/*
+ * Copyright (c) 2006 Luc Verhaegen (quirks list)
+ * Copyright (c) 2007-2008 Intel Corporation
+ * Jesse Barnes <jesse.barnes@intel.com>
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from
+ * FB layer.
+ * Copyright (C) 2006 Dennis Munsie <dmunsie@cecropia.com>
+ *
+ * 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.
+ */
+
+#include <kms++/modedb.h>
+
+#include <xf86drm.h>
+
+namespace kms
+{
+#define DIV_ROUND(n, d) (((n) + (d) / 2) / (d))
+
+#define DRM_MODE(nm, c, hd, hss, hse, ht, hsk, vd, vss, vse, vt, vs, f) \
+ .name = nm, .clock = (c), \
+ .hdisplay = (hd), .hsync_start = (hss), .hsync_end = (hse), .htotal = (ht), .hskew = (hsk), \
+ .vdisplay = (vd), .vsync_start = (vss), .vsync_end = (vse), .vtotal = (vt), .vscan = (vs), \
+ .vrefresh = DIV_ROUND(c * 1000, ht * vt) * (((f)&DRM_MODE_FLAG_INTERLACE) ? 2 : 1), \
+ .flags = (f), .type = 0
+
+/*
+ * Probably taken from CEA-861 spec.
+ * This table is converted from xorg's hw/xfree86/modes/xf86EdidModes.c.
+ */
+const Videomode cea_modes[] = {
+ /* 1 - 640x480@60Hz */
+ {
+ DRM_MODE("640x480", 25175, 640, 656,
+ 752, 800, 0, 480, 490, 492, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 2 - 720x480@60Hz */
+ {
+ DRM_MODE("720x480", 27000, 720, 736,
+ 798, 858, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 3 - 720x480@60Hz */
+ {
+ DRM_MODE("720x480", 27000, 720, 736,
+ 798, 858, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 4 - 1280x720@60Hz */
+ {
+ DRM_MODE("1280x720", 74250, 1280, 1390,
+ 1430, 1650, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 5 - 1920x1080i@60Hz */
+ {
+ DRM_MODE("1920x1080i", 74250, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1094, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 6 - 720(1440)x480i@60Hz */
+ {
+ DRM_MODE("720x480i", 13500, 720, 739,
+ 801, 858, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 7 - 720(1440)x480i@60Hz */
+ {
+ DRM_MODE("720x480i", 13500, 720, 739,
+ 801, 858, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 8 - 720(1440)x240@60Hz */
+ {
+ DRM_MODE("720x240", 13500, 720, 739,
+ 801, 858, 0, 240, 244, 247, 262, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 9 - 720(1440)x240@60Hz */
+ {
+ DRM_MODE("720x240", 13500, 720, 739,
+ 801, 858, 0, 240, 244, 247, 262, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 10 - 2880x480i@60Hz */
+ {
+ DRM_MODE("2880x480i", 54000, 2880, 2956,
+ 3204, 3432, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 11 - 2880x480i@60Hz */
+ {
+ DRM_MODE("2880x480i", 54000, 2880, 2956,
+ 3204, 3432, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 12 - 2880x240@60Hz */
+ {
+ DRM_MODE("2880x240", 54000, 2880, 2956,
+ 3204, 3432, 0, 240, 244, 247, 262, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 13 - 2880x240@60Hz */
+ {
+ DRM_MODE("2880x240", 54000, 2880, 2956,
+ 3204, 3432, 0, 240, 244, 247, 262, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 14 - 1440x480@60Hz */
+ {
+ DRM_MODE("1440x480", 54000, 1440, 1472,
+ 1596, 1716, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 15 - 1440x480@60Hz */
+ {
+ DRM_MODE("1440x480", 54000, 1440, 1472,
+ 1596, 1716, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 16 - 1920x1080@60Hz */
+ {
+ DRM_MODE("1920x1080", 148500, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 17 - 720x576@50Hz */
+ {
+ DRM_MODE("720x576", 27000, 720, 732,
+ 796, 864, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 18 - 720x576@50Hz */
+ {
+ DRM_MODE("720x576", 27000, 720, 732,
+ 796, 864, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 19 - 1280x720@50Hz */
+ {
+ DRM_MODE("1280x720", 74250, 1280, 1720,
+ 1760, 1980, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 20 - 1920x1080i@50Hz */
+ {
+ DRM_MODE("1920x1080i", 74250, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1094, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 21 - 720(1440)x576i@50Hz */
+ {
+ DRM_MODE("720x576i", 13500, 720, 732,
+ 795, 864, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 22 - 720(1440)x576i@50Hz */
+ {
+ DRM_MODE("720x576i", 13500, 720, 732,
+ 795, 864, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 23 - 720(1440)x288@50Hz */
+ {
+ DRM_MODE("720x288", 13500, 720, 732,
+ 795, 864, 0, 288, 290, 293, 312, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 24 - 720(1440)x288@50Hz */
+ {
+ DRM_MODE("720x288", 13500, 720, 732,
+ 795, 864, 0, 288, 290, 293, 312, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 25 - 2880x576i@50Hz */
+ {
+ DRM_MODE("2880x576i", 54000, 2880, 2928,
+ 3180, 3456, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 26 - 2880x576i@50Hz */
+ {
+ DRM_MODE("2880x576i", 54000, 2880, 2928,
+ 3180, 3456, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 27 - 2880x288@50Hz */
+ {
+ DRM_MODE("2880x288", 54000, 2880, 2928,
+ 3180, 3456, 0, 288, 290, 293, 312, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 28 - 2880x288@50Hz */
+ {
+ DRM_MODE("2880x288", 54000, 2880, 2928,
+ 3180, 3456, 0, 288, 290, 293, 312, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 29 - 1440x576@50Hz */
+ {
+ DRM_MODE("1440x576", 54000, 1440, 1464,
+ 1592, 1728, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 30 - 1440x576@50Hz */
+ {
+ DRM_MODE("1440x576", 54000, 1440, 1464,
+ 1592, 1728, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 31 - 1920x1080@50Hz */
+ {
+ DRM_MODE("1920x1080", 148500, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 32 - 1920x1080@24Hz */
+ {
+ DRM_MODE("1920x1080", 74250, 1920, 2558,
+ 2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 33 - 1920x1080@25Hz */
+ {
+ DRM_MODE("1920x1080", 74250, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 34 - 1920x1080@30Hz */
+ {
+ DRM_MODE("1920x1080", 74250, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 35 - 2880x480@60Hz */
+ {
+ DRM_MODE("2880x480", 108000, 2880, 2944,
+ 3192, 3432, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 36 - 2880x480@60Hz */
+ {
+ DRM_MODE("2880x480", 108000, 2880, 2944,
+ 3192, 3432, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 37 - 2880x576@50Hz */
+ {
+ DRM_MODE("2880x576", 108000, 2880, 2928,
+ 3184, 3456, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 38 - 2880x576@50Hz */
+ {
+ DRM_MODE("2880x576", 108000, 2880, 2928,
+ 3184, 3456, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 39 - 1920x1080i@50Hz */
+ {
+ DRM_MODE("1920x1080i", 72000, 1920, 1952,
+ 2120, 2304, 0, 1080, 1126, 1136, 1250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 40 - 1920x1080i@100Hz */
+ {
+ DRM_MODE("1920x1080i", 148500, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1094, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 41 - 1280x720@100Hz */
+ {
+ DRM_MODE("1280x720", 148500, 1280, 1720,
+ 1760, 1980, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 42 - 720x576@100Hz */
+ {
+ DRM_MODE("720x576", 54000, 720, 732,
+ 796, 864, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 43 - 720x576@100Hz */
+ {
+ DRM_MODE("720x576", 54000, 720, 732,
+ 796, 864, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 44 - 720(1440)x576i@100Hz */
+ {
+ DRM_MODE("720x576i", 27000, 720, 732,
+ 795, 864, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 45 - 720(1440)x576i@100Hz */
+ {
+ DRM_MODE("720x576i", 27000, 720, 732,
+ 795, 864, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 46 - 1920x1080i@120Hz */
+ {
+ DRM_MODE("1920x1080i", 148500, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1094, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
+ DRM_MODE_FLAG_INTERLACE),
+ },
+ /* 47 - 1280x720@120Hz */
+ {
+ DRM_MODE("1280x720", 148500, 1280, 1390,
+ 1430, 1650, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 48 - 720x480@120Hz */
+ {
+ DRM_MODE("720x480", 54000, 720, 736,
+ 798, 858, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 49 - 720x480@120Hz */
+ {
+ DRM_MODE("720x480", 54000, 720, 736,
+ 798, 858, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 50 - 720(1440)x480i@120Hz */
+ {
+ DRM_MODE("720x480i", 27000, 720, 739,
+ 801, 858, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 51 - 720(1440)x480i@120Hz */
+ {
+ DRM_MODE("720x480i", 27000, 720, 739,
+ 801, 858, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 52 - 720x576@200Hz */
+ {
+ DRM_MODE("720x576", 108000, 720, 732,
+ 796, 864, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 53 - 720x576@200Hz */
+ {
+ DRM_MODE("720x576", 108000, 720, 732,
+ 796, 864, 0, 576, 581, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 54 - 720(1440)x576i@200Hz */
+ {
+ DRM_MODE("720x576i", 54000, 720, 732,
+ 795, 864, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 55 - 720(1440)x576i@200Hz */
+ {
+ DRM_MODE("720x576i", 54000, 720, 732,
+ 795, 864, 0, 576, 580, 586, 625, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 56 - 720x480@240Hz */
+ {
+ DRM_MODE("720x480", 108000, 720, 736,
+ 798, 858, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 57 - 720x480@240Hz */
+ {
+ DRM_MODE("720x480", 108000, 720, 736,
+ 798, 858, 0, 480, 489, 495, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ },
+ /* 58 - 720(1440)x480i@240 */
+ {
+ DRM_MODE("720x480i", 54000, 720, 739,
+ 801, 858, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 59 - 720(1440)x480i@240 */
+ {
+ DRM_MODE("720x480i", 54000, 720, 739,
+ 801, 858, 0, 480, 488, 494, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+ DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),
+ },
+ /* 60 - 1280x720@24Hz */
+ {
+ DRM_MODE("1280x720", 59400, 1280, 3040,
+ 3080, 3300, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 61 - 1280x720@25Hz */
+ {
+ DRM_MODE("1280x720", 74250, 1280, 3700,
+ 3740, 3960, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 62 - 1280x720@30Hz */
+ {
+ DRM_MODE("1280x720", 74250, 1280, 3040,
+ 3080, 3300, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 63 - 1920x1080@120Hz */
+ {
+ DRM_MODE("1920x1080", 297000, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* 64 - 1920x1080@100Hz */
+ {
+ DRM_MODE("1920x1080", 297000, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1094, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ },
+ /* TERMINATOR */
+ {},
+};
+
+} // namespace kms
diff --git a/kms++/src/modedb_dmt.cpp b/kms++/src/modedb_dmt.cpp
new file mode 100644
index 0000000..21fe1c2
--- /dev/null
+++ b/kms++/src/modedb_dmt.cpp
@@ -0,0 +1,203 @@
+/* Generated from DMTr1 v13.pdf */
+
+#include <kms++/modedb.h>
+
+#include <xf86drm.h>
+
+namespace kms
+{
+#define DIV_ROUND(n, d) (((n) + (d) / 2) / (d))
+
+// hd, hss, hse, ht, vd, vss, vse, vt
+
+#define DRM_MODE(nm, c, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp, f) \
+ { \
+ .name = nm, .clock = c, \
+ .hdisplay = (hact), .hsync_start = (hact) + (hfp), .hsync_end = (hact) + (hfp) + (hsw), .htotal = (hact) + (hfp) + (hsw) + (hbp), .hskew = 0, \
+ .vdisplay = (vact), .vsync_start = (vact) + (vfp), .vsync_end = (vact) + (vfp) + (vsw), .vtotal = (vact) + (vfp) + (vsw) + (vbp), .vscan = 0, \
+ .vrefresh = DIV_ROUND(c * 1000, ((hact) + (hfp) + (hsw) + (hbp)) * ((vact) + (vfp) + (vsw) + (vbp))) * (((f)&DRM_MODE_FLAG_INTERLACE) ? 2 : 1), \
+ .flags = (f), .type = 0 \
+ }
+
+const Videomode dmt_modes[] = {
+ // 0x1 - 640 x 350 @ 85Hz
+ DRM_MODE("640 x 350 @ 85Hz", 31500, 640, 32, 64, 96, 350, 32, 3, 60, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x2 - 640 x 400 @ 85Hz
+ DRM_MODE("640 x 400 @ 85Hz", 31500, 640, 32, 64, 96, 400, 1, 3, 41, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x3 - 720 x 400 @ 85Hz
+ DRM_MODE("720 x 400 @ 85Hz", 35500, 720, 36, 72, 108, 400, 1, 3, 42, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x4 - 640 x 480 @ 60Hz
+ DRM_MODE("640 x 480 @ 60Hz", 25175, 640, 8, 96, 40, 480, 2, 2, 25, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x5 - 640 x 480 @ 72Hz
+ DRM_MODE("640 x 480 @ 72Hz", 31500, 640, 16, 40, 120, 480, 1, 3, 20, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x6 - 640 x 480 @ 75Hz
+ DRM_MODE("640 x 480 @ 75Hz", 31500, 640, 16, 64, 120, 480, 1, 3, 16, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x7 - 640 x 480 @ 85Hz
+ DRM_MODE("640 x 480 @ 85Hz", 36000, 640, 56, 56, 80, 480, 1, 3, 25, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x8 - 800 x 600 @ 56Hz
+ DRM_MODE("800 x 600 @ 56Hz", 36000, 800, 24, 72, 128, 600, 1, 2, 22, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x9 - 800 x 600 @ 60Hz
+ DRM_MODE("800 x 600 @ 60Hz", 40000, 800, 40, 128, 88, 600, 1, 4, 23, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0xa - 800 x 600 @ 72Hz
+ DRM_MODE("800 x 600 @ 72Hz", 50000, 800, 56, 120, 64, 600, 37, 6, 23, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0xb - 800 x 600 @ 75Hz
+ DRM_MODE("800 x 600 @ 75Hz", 49500, 800, 16, 80, 160, 600, 1, 3, 21, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0xc - 800 x 600 @ 85Hz
+ DRM_MODE("800 x 600 @ 85Hz", 56250, 800, 32, 64, 152, 600, 1, 3, 27, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0xd - 800 x 600 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("800 x 600 @ 120Hz CVT (Reduced Blanking)", 73250, 800, 48, 32, 80, 600, 3, 4, 29, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0xe - 848 x 480 @ 60Hz
+ DRM_MODE("848 x 480 @ 60Hz", 33750, 848, 16, 112, 112, 480, 6, 8, 23, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0xf - 1024 x 768 @ 43Hz (Interlaced)
+ DRM_MODE("1024 x 768 @ 43Hz (Interlaced)", 44900, 1024, 8, 176, 56, 768, 0, 4, 20, DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x10 - 1024 x 768 @ 60Hz
+ DRM_MODE("1024 x 768 @ 60Hz", 65000, 1024, 24, 136, 160, 768, 3, 6, 29, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x11 - 1024 x 768 @ 70Hz
+ DRM_MODE("1024 x 768 @ 70Hz", 75000, 1024, 24, 136, 144, 768, 3, 6, 29, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x12 - 1024 x 768 @ 75Hz
+ DRM_MODE("1024 x 768 @ 75Hz", 78750, 1024, 16, 96, 176, 768, 1, 3, 28, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x13 - 1024 x 768 @ 85Hz
+ DRM_MODE("1024 x 768 @ 85Hz", 94500, 1024, 48, 96, 208, 768, 1, 3, 36, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x14 - 1024 x 768 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1024 x 768 @ 120Hz CVT (Reduced Blanking)", 115500, 1024, 48, 32, 80, 768, 3, 4, 38, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x15 - 1152 x 864 @ 75Hz
+ DRM_MODE("1152 x 864 @ 75Hz", 108000, 1152, 64, 128, 256, 864, 1, 3, 32, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x55 - 1280 x 720 @ 60Hz
+ DRM_MODE("1280 x 720 @ 60Hz", 74250, 1280, 110, 40, 220, 720, 5, 5, 20, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x16 - 1280 x 768 @ 60Hz CVT (Reduced Blanking)
+ DRM_MODE("1280 x 768 @ 60Hz CVT (Reduced Blanking)", 68250, 1280, 48, 32, 80, 768, 3, 7, 12, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x17 - 1280 x 768 @ 60Hz
+ DRM_MODE("1280 x 768 @ 60Hz", 79500, 1280, 64, 128, 192, 768, 3, 7, 20, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x18 - 1280 x 768 @ 75Hz
+ DRM_MODE("1280 x 768 @ 75Hz", 102250, 1280, 80, 128, 208, 768, 3, 7, 27, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x19 - 1280 x 768 @ 85Hz
+ DRM_MODE("1280 x 768 @ 85Hz", 117500, 1280, 80, 136, 216, 768, 3, 7, 31, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x1a - 1280 x 768 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1280 x 768 @ 120Hz CVT (Reduced Blanking)", 140250, 1280, 48, 32, 80, 768, 3, 7, 35, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x1b - 1280 x 800 @ 60Hz CVT (Reduced Blanking)
+ DRM_MODE("1280 x 800 @ 60Hz CVT (Reduced Blanking)", 71000, 1280, 48, 32, 80, 800, 3, 6, 14, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x1c - 1280 x 800 @ 60Hz
+ DRM_MODE("1280 x 800 @ 60Hz", 83500, 1280, 72, 128, 200, 800, 3, 6, 22, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x1d - 1280 x 800 @ 75Hz
+ DRM_MODE("1280 x 800 @ 75Hz", 106500, 1280, 80, 128, 208, 800, 3, 6, 29, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x1e - 1280 x 800 @ 85Hz
+ DRM_MODE("1280 x 800 @ 85Hz", 122500, 1280, 80, 136, 216, 800, 3, 6, 34, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x1f - 1280 x 800 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1280 x 800 @ 120Hz CVT (Reduced Blanking)", 146250, 1280, 48, 32, 80, 800, 3, 6, 38, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x20 - 1280 x 960 @ 60Hz
+ DRM_MODE("1280 x 960 @ 60Hz", 108000, 1280, 96, 112, 312, 960, 1, 3, 36, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x21 - 1280 x 960 @ 85Hz
+ DRM_MODE("1280 x 960 @ 85Hz", 148500, 1280, 64, 160, 224, 960, 1, 3, 47, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x22 - 1280 x 960 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1280 x 960 @ 120Hz CVT (Reduced Blanking)", 175500, 1280, 48, 32, 80, 960, 3, 4, 50, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x23 - 1280 x 1024 @ 60Hz
+ DRM_MODE("1280 x 1024 @ 60Hz", 108000, 1280, 48, 112, 248, 1024, 1, 3, 38, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x24 - 1280 x 1024 @ 75Hz
+ DRM_MODE("1280 x 1024 @ 75Hz", 135000, 1280, 16, 144, 248, 1024, 1, 3, 38, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x25 - 1280 x 1024 @ 85Hz
+ DRM_MODE("1280 x 1024 @ 85Hz", 157500, 1280, 64, 160, 224, 1024, 1, 3, 44, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x26 - 1280 x 1024 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1280 x 1024 @ 120Hz CVT (Reduced Blanking)", 187250, 1280, 48, 32, 80, 1024, 3, 7, 50, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x27 - 1360 x 768 @ 60Hz
+ DRM_MODE("1360 x 768 @ 60Hz", 85500, 1360, 64, 112, 256, 768, 3, 6, 18, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x28 - 1360 x 768 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1360 x 768 @ 120Hz CVT (Reduced Blanking)", 148250, 1360, 48, 32, 80, 768, 3, 5, 37, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x51 - 1366 x 768 @ 60Hz
+ DRM_MODE("1366 x 768 @ 60Hz", 85500, 1366, 70, 143, 213, 768, 3, 3, 24, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x56 - 1366 x 768 @ 60Hz
+ DRM_MODE("1366 x 768 @ 60Hz", 72000, 1366, 14, 56, 64, 768, 1, 3, 28, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x29 - 1400 x 1050 @ 60Hz CVT (Reduced Blanking)
+ DRM_MODE("1400 x 1050 @ 60Hz CVT (Reduced Blanking)", 101000, 1400, 48, 32, 80, 1050, 3, 4, 23, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x2a - 1400 x 1050 @ 60Hz
+ DRM_MODE("1400 x 1050 @ 60Hz", 121750, 1400, 88, 144, 232, 1050, 3, 4, 32, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x2b - 1400 x 1050 @ 75Hz
+ DRM_MODE("1400 x 1050 @ 75Hz", 156000, 1400, 104, 144, 248, 1050, 3, 4, 42, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x2c - 1400 x 1050 @ 85Hz
+ DRM_MODE("1400 x 1050 @ 85Hz", 179500, 1400, 104, 152, 256, 1050, 3, 4, 48, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x2d - 1400 x 1050 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1400 x 1050 @ 120Hz CVT (Reduced Blanking)", 208000, 1400, 48, 32, 80, 1050, 3, 4, 55, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x2e - 1440 x 900 @ 60Hz CVT (Reduced Blanking)
+ DRM_MODE("1440 x 900 @ 60Hz CVT (Reduced Blanking)", 88750, 1440, 48, 32, 80, 900, 3, 6, 17, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x2f - 1440 x 900 @ 60Hz
+ DRM_MODE("1440 x 900 @ 60Hz", 106500, 1440, 80, 152, 232, 900, 3, 6, 25, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x30 - 1440 x 900 @ 75Hz
+ DRM_MODE("1440 x 900 @ 75Hz", 136750, 1440, 96, 152, 248, 900, 3, 6, 33, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x31 - 1440 x 900 @ 85Hz
+ DRM_MODE("1440 x 900 @ 85Hz", 157000, 1440, 104, 152, 256, 900, 3, 6, 39, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x32 - 1440 x 900 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1440 x 900 @ 120Hz CVT (Reduced Blanking)", 182750, 1440, 48, 32, 80, 900, 3, 6, 44, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x53 - 1600 x 900 @ 60Hz
+ DRM_MODE("1600 x 900 @ 60Hz", 108000, 1600, 24, 80, 96, 900, 1, 3, 96, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x33 - 1600 x 1200 @ 60Hz
+ DRM_MODE("1600 x 1200 @ 60Hz", 162000, 1600, 64, 192, 304, 1200, 1, 3, 46, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x34 - 1600 x 1200 @ 65Hz
+ DRM_MODE("1600 x 1200 @ 65Hz", 175500, 1600, 64, 192, 304, 1200, 1, 3, 46, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x35 - 1600 x 1200 @ 70Hz
+ DRM_MODE("1600 x 1200 @ 70Hz", 189000, 1600, 64, 192, 304, 1200, 1, 3, 46, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x36 - 1600 x 1200 @ 75Hz
+ DRM_MODE("1600 x 1200 @ 75Hz", 202500, 1600, 64, 192, 304, 1200, 1, 3, 46, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x37 - 1600 x 1200 @ 85Hz
+ DRM_MODE("1600 x 1200 @ 85Hz", 229500, 1600, 64, 192, 304, 1200, 1, 3, 46, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x38 - 1600 x 1200 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1600 x 1200 @ 120Hz CVT (Reduced Blanking)", 268250, 1600, 48, 32, 80, 1200, 3, 4, 64, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x39 - 1680 x 1050 @ 60Hz CVT (Reduced Blanking)
+ DRM_MODE("1680 x 1050 @ 60Hz CVT (Reduced Blanking)", 119000, 1680, 48, 32, 80, 1050, 3, 6, 21, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x3a - 1680 x 1050 @ 60Hz
+ DRM_MODE("1680 x 1050 @ 60Hz", 146250, 1680, 104, 176, 280, 1050, 3, 6, 30, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x3b - 1680 x 1050 @ 75Hz
+ DRM_MODE("1680 x 1050 @ 75Hz", 187000, 1680, 120, 176, 296, 1050, 3, 6, 40, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x3c - 1680 x 1050 @ 85Hz
+ DRM_MODE("1680 x 1050 @ 85Hz", 214750, 1680, 128, 176, 304, 1050, 3, 6, 46, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x3d - 1680 x 1050 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1680 x 1050 @ 120Hz CVT (Reduced Blanking)", 245500, 1680, 48, 32, 80, 1050, 3, 6, 53, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x3e - 1792 x 1344 @ 60 Hz
+ DRM_MODE("1792 x 1344 @ 60 Hz", 204750, 1792, 128, 200, 328, 1344, 1, 3, 46, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x3f - 1792 x 1344 @ 75Hz
+ DRM_MODE("1792 x 1344 @ 75Hz", 261000, 1792, 96, 216, 352, 1344, 1, 3, 69, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x40 - 1792 x 1344 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1792 x 1344 @ 120Hz CVT (Reduced Blanking)", 333250, 1792, 48, 32, 80, 1344, 3, 4, 72, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x41 - 1856 x 1392 at 60Hz
+ DRM_MODE("1856 x 1392 at 60Hz", 218250, 1856, 96, 224, 352, 1392, 1, 3, 43, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x42 - 1856 x 1392 @ 75Hz
+ DRM_MODE("1856 x 1392 @ 75Hz", 288000, 1856, 128, 224, 352, 1392, 1, 3, 104, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x43 - 1856 x 1392 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1856 x 1392 @ 120Hz CVT (Reduced Blanking)", 356500, 1856, 48, 32, 80, 1392, 3, 4, 75, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x52 - 1920 x 1080 @ 60Hz
+ DRM_MODE("1920 x 1080 @ 60Hz", 148500, 1920, 88, 44, 148, 1080, 4, 5, 36, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x44 - 1920 x 1200 @ 60Hz CVT (Reduced Blanking)
+ DRM_MODE("1920 x 1200 @ 60Hz CVT (Reduced Blanking)", 154000, 1920, 48, 32, 80, 1200, 3, 6, 26, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x45 - 1920 x 1200 @ 60Hz
+ DRM_MODE("1920 x 1200 @ 60Hz", 193250, 1920, 136, 200, 336, 1200, 3, 6, 36, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x46 - 1920 x 1200 @ 75Hz
+ DRM_MODE("1920 x 1200 @ 75Hz", 245250, 1920, 136, 208, 344, 1200, 3, 6, 46, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x47 - 1920 x 1200 @ 85Hz
+ DRM_MODE("1920 x 1200 @ 85Hz", 281250, 1920, 144, 208, 352, 1200, 3, 6, 53, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x48 - 1920 x 1200 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1920 x 1200 @ 120Hz CVT (Reduced Blanking)", 317000, 1920, 48, 32, 80, 1200, 3, 6, 62, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x49 - 1920 x 1440 @ 60Hz
+ DRM_MODE("1920 x 1440 @ 60Hz", 234000, 1920, 128, 208, 344, 1440, 1, 3, 56, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x4a - 1920 x 1440 @ 75Hz
+ DRM_MODE("1920 x 1440 @ 75Hz", 297000, 1920, 144, 224, 352, 1440, 1, 3, 56, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x4b - 1920 x 1440 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("1920 x 1440 @ 120Hz CVT (Reduced Blanking)", 380500, 1920, 48, 32, 80, 1440, 3, 4, 78, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x54 - 2048 x 1152 @ 60Hz
+ DRM_MODE("2048 x 1152 @ 60Hz", 162000, 2048, 26, 80, 96, 1152, 1, 3, 44, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x4c - 2560 x 1600 @ 60Hz CVT (Reduced Blanking)
+ DRM_MODE("2560 x 1600 @ 60Hz CVT (Reduced Blanking)", 268500, 2560, 48, 32, 80, 1600, 3, 6, 37, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x4d - 2560 x 1600 @ 60Hz
+ DRM_MODE("2560 x 1600 @ 60Hz", 348500, 2560, 192, 280, 472, 1600, 3, 6, 49, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x4e - 2560 x 1600 @ 75Hz
+ DRM_MODE("2560 x 1600 @ 75Hz", 443250, 2560, 208, 280, 488, 1600, 3, 6, 63, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x4f - 2560 x 1600 @ 85Hz
+ DRM_MODE("2560 x 1600 @ 85Hz", 505250, 2560, 208, 280, 488, 1600, 3, 6, 73, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC),
+ // 0x50 - 2560 x 1600 @ 120Hz CVT (Reduced Blanking)
+ DRM_MODE("2560 x 1600 @ 120Hz CVT (Reduced Blanking)", 552750, 2560, 48, 32, 80, 1600, 3, 6, 85, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x57 - 4096 x 2160 @ 60Hz CVT (Reduced Blanking v2)
+ DRM_MODE("4096 x 2160 @ 60Hz CVT (Reduced Blanking v2)", 556744, 4096, 8, 32, 40, 2160, 48, 8, 6, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ // 0x58 - 4096 x 2160 @ 59.94 Hz CVT (Reduced Blanking v2)
+ DRM_MODE("4096 x 2160 @ 59.94 Hz CVT (Reduced Blanking v2)", 556188, 4096, 8, 32, 40, 2160, 48, 8, 6, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC),
+ /* TERMINATOR */
+ {},
+};
+
+} // namespace kms
diff --git a/kms++/src/omap/omapcard.cpp b/kms++/src/omap/omapcard.cpp
new file mode 100644
index 0000000..500bb72
--- /dev/null
+++ b/kms++/src/omap/omapcard.cpp
@@ -0,0 +1,23 @@
+
+#include <kms++/omap/omapcard.h>
+
+extern "C" {
+#include <omap_drmif.h>
+}
+
+using namespace std;
+
+namespace kms
+{
+OmapCard::OmapCard(const string& device)
+ : Card(device)
+{
+ m_omap_dev = omap_device_new(fd());
+}
+
+OmapCard::~OmapCard()
+{
+ omap_device_del(m_omap_dev);
+}
+
+} // namespace kms
diff --git a/kms++/src/omap/omapframebuffer.cpp b/kms++/src/omap/omapframebuffer.cpp
new file mode 100644
index 0000000..4a94202
--- /dev/null
+++ b/kms++/src/omap/omapframebuffer.cpp
@@ -0,0 +1,193 @@
+
+#include <cstring>
+#include <stdexcept>
+#include <sys/mman.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <drm_fourcc.h>
+#include <drm.h>
+#include <drm_mode.h>
+
+#include <kms++/kms++.h>
+#include <kms++/omap/omapkms++.h>
+
+extern "C" {
+#include <omap_drmif.h>
+}
+
+#define __round_mask(x, y) ((__typeof__(x))((y)-1))
+#define round_up(x, y) ((((x)-1) | __round_mask(x, y)) + 1)
+#define PAGE_SIZE 4096
+
+using namespace std;
+
+namespace kms
+{
+OmapFramebuffer::OmapFramebuffer(OmapCard& card, uint32_t width, uint32_t height, const string& fourcc, Flags flags)
+ : OmapFramebuffer(card, width, height, fourcc_str_to_pixel_format(fourcc), flags)
+{
+}
+
+OmapFramebuffer::OmapFramebuffer(OmapCard& card, uint32_t width, uint32_t height, PixelFormat format, Flags flags)
+ : Framebuffer(card, width, height), m_omap_card(card), m_format(format)
+{
+ Create(width, height, format, flags);
+}
+
+OmapFramebuffer::~OmapFramebuffer()
+{
+ Destroy();
+}
+
+void OmapFramebuffer::Create(uint32_t width, uint32_t height, PixelFormat format, Flags buffer_flags)
+{
+ const PixelFormatInfo& format_info = get_pixel_format_info(format);
+
+ m_num_planes = format_info.num_planes;
+
+ for (int i = 0; i < format_info.num_planes; ++i) {
+ const PixelFormatPlaneInfo& pi = format_info.planes[i];
+ FramebufferPlane& plane = m_planes[i];
+
+ uint32_t flags = OMAP_BO_SCANOUT | OMAP_BO_WC;
+
+#if defined(OMAP_BO_MEM_CONTIG)
+ if (buffer_flags & Flags::MemContig)
+ flags |= OMAP_BO_MEM_CONTIG;
+ if (buffer_flags & Flags::MemTiler)
+ flags |= OMAP_BO_MEM_TILER;
+ if (buffer_flags & Flags::MemPin)
+ flags |= OMAP_BO_MEM_PIN;
+#endif
+
+ struct omap_bo* bo;
+
+ uint32_t stride;
+
+ if (!(buffer_flags & Flags::Tiled)) {
+ stride = format_info.stride(width, i);
+
+ uint32_t size = format_info.planesize(stride, height, i);
+
+ bo = omap_bo_new(m_omap_card.dev(), size, flags);
+ if (!bo)
+ throw invalid_argument(string("omap_bo_new failed: ") + strerror(errno));
+ } else {
+ unsigned bitspertiler;
+
+ switch (format) {
+ case PixelFormat::NV12:
+ bitspertiler = i == 0 ? 8 : 16;
+ break;
+ case PixelFormat::YUYV:
+ case PixelFormat::UYVY:
+ bitspertiler = 32;
+ break;
+ case PixelFormat::ARGB8888:
+ case PixelFormat::XRGB8888:
+ bitspertiler = 32;
+ break;
+ case PixelFormat::RGB565:
+ bitspertiler = 16;
+ break;
+ default:
+ throw invalid_argument("unimplemented format");
+ }
+
+ switch (bitspertiler) {
+ case 8:
+ flags |= OMAP_BO_TILED_8;
+ break;
+ case 16:
+ flags |= OMAP_BO_TILED_16;
+ break;
+ case 32:
+ flags |= OMAP_BO_TILED_32;
+ break;
+ default:
+ throw invalid_argument("bad bitspertiler");
+ }
+
+ uint32_t width_tiler = format_info.stride(width, i) / pi.bytes_per_block;
+
+ bo = omap_bo_new_tiled(m_omap_card.dev(), width_tiler, height, flags);
+ if (!bo)
+ throw invalid_argument(string("omap_bo_new_tiled failed: ") + strerror(errno));
+
+ stride = format_info.stride(width, i, PAGE_SIZE);
+ }
+
+ plane.omap_bo = bo;
+ plane.handle = omap_bo_handle(bo);
+ plane.stride = stride;
+ plane.size = omap_bo_size(bo);
+ plane.offset = 0;
+ plane.map = 0;
+ plane.prime_fd = -1;
+ }
+
+ /* create framebuffer object */
+ uint32_t bo_handles[4] = { m_planes[0].handle, m_planes[1].handle };
+ uint32_t pitches[4] = { m_planes[0].stride, m_planes[1].stride };
+ uint32_t offsets[4] = { m_planes[0].offset, m_planes[1].offset };
+ uint32_t id;
+ int r = drmModeAddFB2(card().fd(), width, height, pixel_format_to_fourcc(format),
+ bo_handles, pitches, offsets, &id, 0);
+ if (r)
+ throw invalid_argument(string("drmModeAddFB2 failed: ") + strerror(errno));
+
+ set_id(id);
+}
+
+void OmapFramebuffer::Destroy()
+{
+ /* delete framebuffer */
+ drmModeRmFB(card().fd(), id());
+
+ for (uint i = 0; i < m_num_planes; ++i) {
+ FramebufferPlane& plane = m_planes[i];
+
+ /* unmap buffer */
+ if (plane.map)
+ munmap(plane.map, plane.size);
+
+ omap_bo_del(plane.omap_bo);
+
+ if (plane.prime_fd >= 0)
+ ::close(plane.prime_fd);
+ }
+}
+
+uint8_t* OmapFramebuffer::map(unsigned plane)
+{
+ FramebufferPlane& p = m_planes[plane];
+
+ if (p.map)
+ return p.map;
+
+ p.map = static_cast<uint8_t*>(omap_bo_map(p.omap_bo));
+ if (p.map == MAP_FAILED)
+ throw invalid_argument(string("mmap failed: ") + strerror(errno));
+
+ return p.map;
+}
+
+int OmapFramebuffer::prime_fd(unsigned int plane)
+{
+ FramebufferPlane& p = m_planes[plane];
+
+ if (p.prime_fd >= 0)
+ return p.prime_fd;
+
+ int fd = omap_bo_dmabuf(p.omap_bo);
+ if (fd < 0)
+ throw std::runtime_error("omap_bo_dmabuf failed\n");
+
+ p.prime_fd = fd;
+
+ return fd;
+}
+
+} // namespace kms
diff --git a/kms++/src/pixelformats.cpp b/kms++/src/pixelformats.cpp
new file mode 100644
index 0000000..126a614
--- /dev/null
+++ b/kms++/src/pixelformats.cpp
@@ -0,0 +1,1186 @@
+#include <map>
+#include <stdexcept>
+#include <cassert>
+
+#include <kms++/pixelformats.h>
+
+using namespace std;
+
+namespace kms
+{
+static map<PixelFormat, PixelFormatInfo> format_info_array = {
+ {
+ PixelFormat::R8, {
+ PixelFormatInfo {
+ "R8",
+ "R8 ",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGB332, {
+ PixelFormatInfo {
+ "RGB332",
+ "RGB8",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGB565, {
+ PixelFormatInfo {
+ "RGB565",
+ "RG16",
+ "RGBP",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::BGR565, {
+ PixelFormatInfo {
+ "BGR565",
+ "BG16",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XRGB1555, {
+ PixelFormatInfo {
+ "XRGB1555",
+ "XR15",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XBGR1555, {
+ PixelFormatInfo {
+ "XBGR1555",
+ "XB15",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGBX4444, {
+ PixelFormatInfo {
+ "RGBX4444",
+ "RX12",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XRGB4444, {
+ PixelFormatInfo {
+ "XRGB4444",
+ "XR12",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XBGR4444, {
+ PixelFormatInfo {
+ "XBGR4444",
+ "XB12",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ARGB1555, {
+ PixelFormatInfo {
+ "ARGB1555",
+ "AR15",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ABGR1555, {
+ PixelFormatInfo {
+ "ABGR1555",
+ "AB15",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGBA4444, {
+ PixelFormatInfo {
+ "RGBA4444",
+ "RA12",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ARGB4444, {
+ PixelFormatInfo {
+ "ARGB4444",
+ "AR12",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ABGR4444, {
+ PixelFormatInfo {
+ "ABGR4444",
+ "AB12",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGB888, {
+ PixelFormatInfo {
+ "RGB888",
+ "RG24",
+ "BGR3",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 3, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::BGR888, {
+ PixelFormatInfo {
+ "BGR888",
+ "BG24",
+ "RGB3",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 3, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XRGB8888, {
+ PixelFormatInfo {
+ "XRGB8888",
+ "XR24",
+ "XR24",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XBGR8888, {
+ PixelFormatInfo {
+ "XBGR8888",
+ "XB24",
+ "XB24",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGBX8888, {
+ PixelFormatInfo {
+ "RGBX8888",
+ "RX24",
+ "RX24",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::BGRX8888, {
+ PixelFormatInfo {
+ "BGRX8888",
+ "BX24",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XBGR2101010, {
+ PixelFormatInfo {
+ "XBGR2101010",
+ "XB30",
+ "RX30",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XRGB2101010, {
+ PixelFormatInfo {
+ "XRGB2101010",
+ "XR30",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGBX1010102, {
+ PixelFormatInfo {
+ "RGBX1010102",
+ "RX30",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::BGRX1010102, {
+ PixelFormatInfo {
+ "BGRX1010102",
+ "BX30",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ARGB8888, {
+ PixelFormatInfo {
+ "ARGB8888",
+ "AR24",
+ "AR24",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ABGR8888, {
+ PixelFormatInfo {
+ "ABGR8888",
+ "AB24",
+ "AB24",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGBA8888, {
+ PixelFormatInfo {
+ "RGBA8888",
+ "RA24",
+ "RA24",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::BGRA8888, {
+ PixelFormatInfo {
+ "BGRA8888",
+ "BA24",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ABGR2101010, {
+ PixelFormatInfo {
+ "ABGR2101010",
+ "AB30",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::ARGB2101010, {
+ PixelFormatInfo {
+ "ARGB2101010",
+ "AR30",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::RGBA1010102, {
+ PixelFormatInfo {
+ "RGBA1010102",
+ "RA30",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::BGRA1010102, {
+ PixelFormatInfo {
+ "BGRA1010102",
+ "BA30",
+ "",
+ PixelColorType::RGB,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YUYV, {
+ PixelFormatInfo {
+ "YUYV",
+ "YUYV",
+ "YUYV",
+ PixelColorType::YUV,
+ { 2, 1 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::UYVY, {
+ PixelFormatInfo {
+ "UYVY",
+ "UYVY",
+ "UYVY",
+ PixelColorType::YUV,
+ { 2, 1 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YVYU, {
+ PixelFormatInfo {
+ "YVYU",
+ "YVYU",
+ "YVYU",
+ PixelColorType::YUV,
+ { 2, 1 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::VYUY, {
+ PixelFormatInfo {
+ "VYUY",
+ "VYUY",
+ "VYUY",
+ PixelColorType::YUV,
+ { 2, 1 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::VUY888, {
+ PixelFormatInfo {
+ "VUY888",
+ "VU24",
+ "YUV3",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 3, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XVUY8888, {
+ PixelFormatInfo {
+ "XVUY8888",
+ "XVUY",
+ "YUVX",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y210, {
+ PixelFormatInfo {
+ "Y210",
+ "Y210",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 8, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y212, {
+ PixelFormatInfo {
+ "Y212",
+ "Y212",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 8, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y216, {
+ PixelFormatInfo {
+ "Y216",
+ "Y216",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 8, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::NV12, {
+ PixelFormatInfo {
+ "NV12",
+ "NV12",
+ "NM12",
+ PixelColorType::YUV,
+ { 2, 2 },
+ { { 1, 1, 1, 1 }, { 2, 1, 2, 2 } },
+ }
+ }
+ },
+ {
+ PixelFormat::NV21, {
+ PixelFormatInfo {
+ "NV21",
+ "NV21",
+ "NM21",
+ PixelColorType::YUV,
+ { 2, 2 },
+ { { 1, 1, 1, 1 }, { 2, 1, 2, 2 } },
+ }
+ }
+ },
+ {
+ PixelFormat::NV16, {
+ PixelFormatInfo {
+ "NV16",
+ "NV16",
+ "NM16",
+ PixelColorType::YUV,
+ { 2, 1 },
+ { { 1, 1, 1, 1 }, { 2, 1, 2, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::NV61, {
+ PixelFormatInfo {
+ "NV61",
+ "NV61",
+ "NM61",
+ PixelColorType::YUV,
+ { 2, 1 },
+ { { 1, 1, 1, 1 }, { 2, 1, 2, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::P030, {
+ PixelFormatInfo {
+ "P030",
+ "P030",
+ "",
+ PixelColorType::YUV,
+ { 6, 2 },
+ { { 4, 3, 1, 1 }, { 8, 3, 2, 2 } },
+ }
+ }
+ },
+ {
+ PixelFormat::P230, {
+ PixelFormatInfo {
+ "P230",
+ "P230",
+ "",
+ PixelColorType::YUV,
+ { 6, 2 },
+ { { 4, 3, 1, 1 }, { 8, 3, 2, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XVUY2101010, {
+ PixelFormatInfo {
+ "XVUY2101010",
+ "XY30",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YUV420, {
+ PixelFormatInfo {
+ "YUV420",
+ "YU12",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 1, 1, 1, 1 }, { 1, 1, 2, 2 }, { 1, 1, 2, 2 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YVU420, {
+ PixelFormatInfo {
+ "YVU420",
+ "YV12",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 1, 1, 1, 1 }, { 1, 1, 2, 2 }, { 1, 1, 2, 2 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YUV422, {
+ PixelFormatInfo {
+ "YUV422",
+ "YU16",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 1, 1, 1, 1 }, { 1, 1, 2, 1 }, { 1, 1, 2, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YVU422, {
+ PixelFormatInfo {
+ "YVU422",
+ "YV16",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 1, 1, 1, 1 }, { 1, 1, 2, 1 }, { 1, 1, 2, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YUV444, {
+ PixelFormatInfo {
+ "YUV444",
+ "YU24",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::YVU444, {
+ PixelFormatInfo {
+ "YVU444",
+ "YV24",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::T430, {
+ PixelFormatInfo {
+ "T430",
+ "T430",
+ "",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 4, 1, 1, 1 }, { 4, 1, 1, 1 }, { 4, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y8, {
+ PixelFormatInfo {
+ "Y8",
+ "GREY",
+ "GREY",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y10, {
+ PixelFormatInfo {
+ "Y10",
+ "",
+ "Y10 ",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y10P, {
+ PixelFormatInfo {
+ "Y10P",
+ "",
+ "Y10P",
+ PixelColorType::YUV,
+ { 4, 1 },
+ { { 5, 4, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y12, {
+ PixelFormatInfo {
+ "Y12",
+ "",
+ "Y12 ",
+ PixelColorType::YUV,
+ { 1, 1 },
+ { { 2, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::Y12P, {
+ PixelFormatInfo {
+ "Y12P",
+ "",
+ "Y12P",
+ PixelColorType::YUV,
+ { 2, 1 },
+ { { 3, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::XYYY2101010, {
+ PixelFormatInfo {
+ "XYYY2101010",
+ "YPA4",
+ "",
+ PixelColorType::YUV,
+ { 3, 1 },
+ { { 4, 3, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SBGGR8, {
+ PixelFormatInfo {
+ "SBGGR8",
+ "",
+ "BA81",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGBRG8, {
+ PixelFormatInfo {
+ "SGBRG8",
+ "",
+ "GBRG",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGRBG8, {
+ PixelFormatInfo {
+ "SGRBG8",
+ "",
+ "GRBG",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SRGGB8, {
+ PixelFormatInfo {
+ "SRGGB8",
+ "",
+ "RGGB",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SBGGR10, {
+ PixelFormatInfo {
+ "SBGGR10",
+ "",
+ "BG10",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGBRG10, {
+ PixelFormatInfo {
+ "SGBRG10",
+ "",
+ "GB10",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGRBG10, {
+ PixelFormatInfo {
+ "SGRBG10",
+ "",
+ "BA10",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SRGGB10, {
+ PixelFormatInfo {
+ "SRGGB10",
+ "",
+ "RG10",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SBGGR10P, {
+ PixelFormatInfo {
+ "SBGGR10P",
+ "",
+ "pBAA",
+ PixelColorType::RAW,
+ { 4, 2 },
+ { { 5, 4, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGBRG10P, {
+ PixelFormatInfo {
+ "SGBRG10P",
+ "",
+ "pGAA",
+ PixelColorType::RAW,
+ { 4, 2 },
+ { { 5, 4, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGRBG10P, {
+ PixelFormatInfo {
+ "SGRBG10P",
+ "",
+ "pgAA",
+ PixelColorType::RAW,
+ { 4, 2 },
+ { { 5, 4, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SRGGB10P, {
+ PixelFormatInfo {
+ "SRGGB10P",
+ "",
+ "pRAA",
+ PixelColorType::RAW,
+ { 4, 2 },
+ { { 5, 4, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SBGGR12, {
+ PixelFormatInfo {
+ "SBGGR12",
+ "",
+ "BG12",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGBRG12, {
+ PixelFormatInfo {
+ "SGBRG12",
+ "",
+ "GB12",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGRBG12, {
+ PixelFormatInfo {
+ "SGRBG12",
+ "",
+ "BA12",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SRGGB12, {
+ PixelFormatInfo {
+ "SRGGB12",
+ "",
+ "RG12",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SBGGR12P, {
+ PixelFormatInfo {
+ "SBGGR12P",
+ "",
+ "pBCC",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 3, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGBRG12P, {
+ PixelFormatInfo {
+ "SGBRG12P",
+ "",
+ "pGCC",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 3, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGRBG12P, {
+ PixelFormatInfo {
+ "SGRBG12P",
+ "",
+ "pgCC",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 3, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SRGGB12P, {
+ PixelFormatInfo {
+ "SRGGB12P",
+ "",
+ "pRCC",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 3, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SBGGR16, {
+ PixelFormatInfo {
+ "SBGGR16",
+ "",
+ "BYR2",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGBRG16, {
+ PixelFormatInfo {
+ "SGBRG16",
+ "",
+ "GB16",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SGRBG16, {
+ PixelFormatInfo {
+ "SGRBG16",
+ "",
+ "GR16",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::SRGGB16, {
+ PixelFormatInfo {
+ "SRGGB16",
+ "",
+ "RG16",
+ PixelColorType::RAW,
+ { 2, 2 },
+ { { 4, 2, 1, 1 } },
+ }
+ }
+ },
+ {
+ PixelFormat::MJPEG, {
+ PixelFormatInfo {
+ "MJPEG",
+ "MJPG",
+ "MJPG",
+ PixelColorType::Undefined,
+ { 1, 1 },
+ { { 1, 1, 1, 1 } },
+ }
+ }
+ },
+};
+
+const struct PixelFormatInfo& get_pixel_format_info(PixelFormat format)
+{
+ if (!format_info_array.count(format))
+ throw invalid_argument("get_pixel_format_info: Unsupported pixelformat");
+
+ return format_info_array.at(format);
+}
+
+PixelFormat fourcc_to_pixel_format(uint32_t fourcc)
+{
+ for (const auto& [fmt, pfi] : format_info_array) {
+ if (pfi.drm_fourcc == fourcc)
+ return fmt;
+ }
+
+ throw invalid_argument("FourCC not supported");
+}
+
+uint32_t pixel_format_to_fourcc(PixelFormat f)
+{
+ return format_info_array.at(f).drm_fourcc;
+}
+
+PixelFormat fourcc_str_to_pixel_format(const std::string& fourcc)
+{
+ return fourcc_to_pixel_format(str_to_fourcc(fourcc));
+}
+
+std::string pixel_format_to_fourcc_str(PixelFormat f)
+{
+ return fourcc_to_str(format_info_array.at(f).drm_fourcc);
+}
+
+PixelFormat find_pixel_format_by_name(const std::string& name)
+{
+ for (const auto& [fmt, pfi] : format_info_array) {
+ if (pfi.name == name)
+ return fmt;
+ }
+
+ throw invalid_argument("Unsupported pixelformat");
+}
+
+static constexpr uint32_t _div_round_up(uint32_t a, uint32_t b)
+{
+ return (a + b - 1) / b;
+}
+
+static constexpr uint32_t _align_up(uint32_t a, uint32_t b)
+{
+ return _div_round_up(a, b) * b;
+}
+
+std::tuple<uint32_t, uint32_t> PixelFormatInfo::align_pixels(uint32_t width,
+ uint32_t height) const
+{
+ return { _align_up(width, std::get<0>(pixel_align)),
+ _align_up(height, std::get<1>(pixel_align)) };
+}
+
+uint32_t PixelFormatInfo::stride(uint32_t width, uint32_t plane, uint32_t align) const
+{
+ if (plane >= num_planes)
+ throw std::runtime_error("Invalid plane number");
+
+ assert(width % std::get<0>(pixel_align) == 0);
+
+ const auto& pi = planes[plane];
+
+ assert(width % pi.pixels_per_block == 0);
+ uint32_t stride = width / pi.pixels_per_block * pi.bytes_per_block;
+
+ assert(stride % pi.hsub == 0);
+ stride = stride / pi.hsub;
+
+ stride = _align_up(stride, align);
+
+ return stride;
+}
+
+uint32_t PixelFormatInfo::planesize(uint32_t stride, uint32_t height,
+ uint32_t plane) const
+{
+ assert(height % std::get<1>(pixel_align) == 0);
+
+ const auto& pi = planes[plane];
+
+ assert(height % pi.vsub == 0);
+
+ return stride * (height / pi.vsub);
+}
+
+uint32_t PixelFormatInfo::framesize(uint32_t width, uint32_t height, uint32_t align) const
+{
+ uint32_t size = 0;
+
+ for (uint32_t i = 0; i < num_planes; ++i) {
+ uint32_t s = stride(width, i, align);
+ size += planesize(s, height, i);
+ }
+
+ return size;
+}
+
+std::tuple<uint32_t, uint32_t, uint32_t> PixelFormatInfo::dumb_size(uint32_t width,
+ uint32_t height,
+ uint32_t plane,
+ uint32_t align) const
+{
+ /*
+ Helper function mainly for DRM dumb framebuffer
+ Returns (width, height, bitspp) tuple which results in a suitable plane
+ size.
+
+ DRM_IOCTL_MODE_CREATE_DUMB takes a 'bpp' (bits-per-pixel) argument,
+ which is then used with the width and height to allocate the buffer.
+ This doesn't work for pixel formats where the average bits-per-pixel
+ is not an integer (e.g. P030)
+
+ So, we instead use the bytes_per_block (in bits) as
+ the 'bpp' argument, and adjust the width accordingly.
+ */
+
+ assert(height % std::get<1>(pixel_align) == 0);
+
+ const auto& pi = planes[plane];
+
+ assert(height % pi.vsub == 0);
+
+ uint32_t stride = PixelFormatInfo::stride(width, plane, align);
+
+ assert(stride % pi.bytes_per_block == 0);
+
+ width = stride / pi.bytes_per_block;
+ height = height / pi.vsub;
+ uint32_t bitspp = pi.bytes_per_block * 8;
+
+ return { width, height, bitspp };
+}
+
+} // namespace kms
diff --git a/kms++/src/plane.cpp b/kms++/src/plane.cpp
new file mode 100644
index 0000000..3e17024
--- /dev/null
+++ b/kms++/src/plane.cpp
@@ -0,0 +1,154 @@
+#include <cstdio>
+#include <iostream>
+#include <unistd.h>
+#include <fcntl.h>
+#include <cassert>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <algorithm>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+struct PlanePriv {
+ drmModePlanePtr drm_plane;
+};
+
+Plane::Plane(Card& card, uint32_t id, uint32_t idx)
+ : DrmPropObject(card, id, DRM_MODE_OBJECT_PLANE, idx)
+{
+ m_priv = new PlanePriv();
+ m_priv->drm_plane = drmModeGetPlane(this->card().fd(), this->id());
+ assert(m_priv->drm_plane);
+}
+
+Plane::~Plane()
+{
+ drmModeFreePlane(m_priv->drm_plane);
+ delete m_priv;
+}
+
+bool Plane::supports_crtc(Crtc* crtc) const
+{
+ return m_priv->drm_plane->possible_crtcs & (1 << crtc->idx());
+}
+
+bool Plane::supports_format(PixelFormat fmt) const
+{
+ auto p = m_priv->drm_plane;
+
+ uint32_t fcc = pixel_format_to_fourcc(fmt);
+
+ for (unsigned i = 0; i < p->count_formats; ++i)
+ if (fcc == p->formats[i])
+ return true;
+
+ return false;
+}
+
+PlaneType Plane::plane_type() const
+{
+ if (card().has_universal_planes()) {
+ switch (get_prop_value("type")) {
+ case DRM_PLANE_TYPE_OVERLAY:
+ return PlaneType::Overlay;
+ case DRM_PLANE_TYPE_PRIMARY:
+ return PlaneType::Primary;
+ case DRM_PLANE_TYPE_CURSOR:
+ return PlaneType::Cursor;
+ default:
+ throw invalid_argument("Bad plane type");
+ }
+ } else {
+ return PlaneType::Overlay;
+ }
+}
+
+vector<Crtc*> Plane::get_possible_crtcs() const
+{
+ unsigned idx = 0;
+ vector<Crtc*> v;
+ auto crtcs = card().get_crtcs();
+
+ for (uint32_t crtc_mask = m_priv->drm_plane->possible_crtcs;
+ crtc_mask;
+ idx++, crtc_mask >>= 1) {
+ if ((crtc_mask & 1) == 0)
+ continue;
+
+ auto iter = find_if(crtcs.begin(), crtcs.end(), [idx](Crtc* crtc) { return crtc->idx() == idx; });
+
+ if (iter == crtcs.end())
+ continue;
+
+ v.push_back(*iter);
+ }
+
+ return v;
+}
+
+vector<uint32_t> Plane::get_fourccs() const
+{
+ auto p = m_priv->drm_plane;
+ vector<uint32_t> r;
+
+ for (unsigned i = 0; i < p->count_formats; ++i)
+ r.push_back(p->formats[i]);
+
+ return r;
+}
+
+vector<PixelFormat> Plane::get_formats() const
+{
+ auto p = m_priv->drm_plane;
+ vector<PixelFormat> r;
+
+ for (unsigned i = 0; i < p->count_formats; ++i)
+ try {
+ r.push_back(fourcc_to_pixel_format(p->formats[i]));
+ } catch (const std::invalid_argument&) {
+ // skip formats that are not supported
+ }
+
+ return r;
+}
+
+uint32_t Plane::crtc_id() const
+{
+ return m_priv->drm_plane->crtc_id;
+}
+
+uint32_t Plane::fb_id() const
+{
+ return m_priv->drm_plane->fb_id;
+}
+
+uint32_t Plane::crtc_x() const
+{
+ return m_priv->drm_plane->crtc_x;
+}
+
+uint32_t Plane::crtc_y() const
+{
+ return m_priv->drm_plane->crtc_y;
+}
+
+uint32_t Plane::x() const
+{
+ return m_priv->drm_plane->x;
+}
+
+uint32_t Plane::y() const
+{
+ return m_priv->drm_plane->y;
+}
+
+uint32_t Plane::gamma_size() const
+{
+ return m_priv->drm_plane->gamma_size;
+}
+
+} // namespace kms
diff --git a/kms++/src/property.cpp b/kms++/src/property.cpp
new file mode 100644
index 0000000..0484f0e
--- /dev/null
+++ b/kms++/src/property.cpp
@@ -0,0 +1,85 @@
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <kms++/kms++.h>
+
+using namespace std;
+
+namespace kms
+{
+struct PropertyPriv {
+ drmModePropertyPtr drm_prop;
+};
+
+Property::Property(Card& card, uint32_t id)
+ : DrmObject(card, id, DRM_MODE_OBJECT_PROPERTY)
+{
+ m_priv = new PropertyPriv();
+ m_priv->drm_prop = drmModeGetProperty(card.fd(), id);
+ m_name = m_priv->drm_prop->name;
+
+ PropertyType t;
+ drmModePropertyPtr p = m_priv->drm_prop;
+ if (drm_property_type_is(p, DRM_MODE_PROP_BITMASK))
+ t = PropertyType::Bitmask;
+ else if (drm_property_type_is(p, DRM_MODE_PROP_BLOB))
+ t = PropertyType::Blob;
+ else if (drm_property_type_is(p, DRM_MODE_PROP_ENUM))
+ t = PropertyType::Enum;
+ else if (drm_property_type_is(p, DRM_MODE_PROP_OBJECT))
+ t = PropertyType::Object;
+ else if (drm_property_type_is(p, DRM_MODE_PROP_RANGE))
+ t = PropertyType::Range;
+ else if (drm_property_type_is(p, DRM_MODE_PROP_SIGNED_RANGE))
+ t = PropertyType::SignedRange;
+ else
+ throw invalid_argument("Invalid property type");
+
+ m_type = t;
+}
+
+Property::~Property()
+{
+ drmModeFreeProperty(m_priv->drm_prop);
+ delete m_priv;
+}
+
+const string& Property::name() const
+{
+ return m_name;
+}
+
+bool Property::is_immutable() const
+{
+ return m_priv->drm_prop->flags & DRM_MODE_PROP_IMMUTABLE;
+}
+
+bool Property::is_pending() const
+{
+ return m_priv->drm_prop->flags & DRM_MODE_PROP_PENDING;
+}
+
+vector<uint64_t> Property::get_values() const
+{
+ drmModePropertyPtr p = m_priv->drm_prop;
+ return vector<uint64_t>(p->values, p->values + p->count_values);
+}
+
+map<uint64_t, string> Property::get_enums() const
+{
+ drmModePropertyPtr p = m_priv->drm_prop;
+
+ map<uint64_t, string> map;
+
+ for (int i = 0; i < p->count_enums; ++i)
+ map[p->enums[i].value] = string(p->enums[i].name);
+
+ return map;
+}
+
+vector<uint32_t> Property::get_blob_ids() const
+{
+ drmModePropertyPtr p = m_priv->drm_prop;
+ return vector<uint32_t>(p->blob_ids, p->blob_ids + p->count_blobs);
+}
+} // namespace kms
diff --git a/kms++/src/videomode.cpp b/kms++/src/videomode.cpp
new file mode 100644
index 0000000..b7b1b28
--- /dev/null
+++ b/kms++/src/videomode.cpp
@@ -0,0 +1,272 @@
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <cmath>
+#include <sstream>
+#include <kms++/format.h>
+
+#include <kms++/kms++.h>
+#include "helpers.h"
+
+using namespace std;
+
+namespace kms
+{
+bool Videomode::valid() const
+{
+ return !!clock;
+}
+
+unique_ptr<Blob> Videomode::to_blob(Card& card) const
+{
+ drmModeModeInfo drm_mode = video_mode_to_drm_mode(*this);
+
+ return unique_ptr<Blob>(new Blob(card, &drm_mode, sizeof(drm_mode)));
+}
+
+float Videomode::calculated_vrefresh() const
+{
+ // XXX interlace should only halve visible vertical lines, not blanking
+ float refresh = (clock * 1000.0) / (htotal * vtotal) * (interlace() ? 2 : 1);
+ return roundf(refresh * 100.0) / 100.0;
+}
+
+bool Videomode::interlace() const
+{
+ return flags & DRM_MODE_FLAG_INTERLACE;
+}
+
+SyncPolarity Videomode::hsync() const
+{
+ if (flags & DRM_MODE_FLAG_PHSYNC)
+ return SyncPolarity::Positive;
+ if (flags & DRM_MODE_FLAG_NHSYNC)
+ return SyncPolarity::Negative;
+ return SyncPolarity::Undefined;
+}
+
+SyncPolarity Videomode::vsync() const
+{
+ if (flags & DRM_MODE_FLAG_PVSYNC)
+ return SyncPolarity::Positive;
+ if (flags & DRM_MODE_FLAG_NVSYNC)
+ return SyncPolarity::Negative;
+ return SyncPolarity::Undefined;
+}
+
+void Videomode::set_interlace(bool ilace)
+{
+ if (ilace)
+ flags |= DRM_MODE_FLAG_INTERLACE;
+ else
+ flags &= ~DRM_MODE_FLAG_INTERLACE;
+}
+
+void Videomode::set_hsync(SyncPolarity pol)
+{
+ flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC);
+
+ switch (pol) {
+ case SyncPolarity::Positive:
+ flags |= DRM_MODE_FLAG_PHSYNC;
+ break;
+ case SyncPolarity::Negative:
+ flags |= DRM_MODE_FLAG_NHSYNC;
+ break;
+ default:
+ break;
+ }
+}
+
+void Videomode::set_vsync(SyncPolarity pol)
+{
+ flags &= ~(DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC);
+
+ switch (pol) {
+ case SyncPolarity::Positive:
+ flags |= DRM_MODE_FLAG_PVSYNC;
+ break;
+ case SyncPolarity::Negative:
+ flags |= DRM_MODE_FLAG_NVSYNC;
+ break;
+ default:
+ break;
+ }
+}
+
+string Videomode::to_string_short() const
+{
+ return fmt::format("{}x{}{}@{:.2f}", hdisplay, vdisplay, interlace() ? "i" : "", calculated_vrefresh());
+}
+
+static char sync_to_char(SyncPolarity pol)
+{
+ switch (pol) {
+ case SyncPolarity::Positive:
+ return '+';
+ case SyncPolarity::Negative:
+ return '-';
+ default:
+ return '?';
+ }
+}
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+template<typename T>
+std::string join(const T& values, const std::string& delim)
+{
+ std::ostringstream ss;
+ for (const auto& v : values) {
+ if (&v != &values[0])
+ ss << delim;
+ ss << v;
+ }
+ return ss.str();
+}
+
+static const map<int, string> mode_type_map = {
+ // the deprecated ones don't care about a short name
+ { DRM_MODE_TYPE_BUILTIN, "builtin" }, // deprecated
+ { DRM_MODE_TYPE_CLOCK_C, "clock_c" }, // deprecated
+ { DRM_MODE_TYPE_CRTC_C, "crtc_c" }, // deprecated
+ { DRM_MODE_TYPE_PREFERRED, "P" },
+ { DRM_MODE_TYPE_DEFAULT, "default" }, // deprecated
+ { DRM_MODE_TYPE_USERDEF, "U" },
+ { DRM_MODE_TYPE_DRIVER, "D" },
+};
+
+static const map<int, string> mode_flag_map = {
+ // the first 5 flags are displayed elsewhere
+ { DRM_MODE_FLAG_PHSYNC, "" },
+ { DRM_MODE_FLAG_NHSYNC, "" },
+ { DRM_MODE_FLAG_PVSYNC, "" },
+ { DRM_MODE_FLAG_NVSYNC, "" },
+ { DRM_MODE_FLAG_INTERLACE, "" },
+ { DRM_MODE_FLAG_DBLSCAN, "dblscan" },
+ { DRM_MODE_FLAG_CSYNC, "csync" },
+ { DRM_MODE_FLAG_PCSYNC, "pcsync" },
+ { DRM_MODE_FLAG_NCSYNC, "ncsync" },
+ { DRM_MODE_FLAG_HSKEW, "hskew" },
+ { DRM_MODE_FLAG_BCAST, "bcast" }, // deprecated
+ { DRM_MODE_FLAG_PIXMUX, "pixmux" }, // deprecated
+ { DRM_MODE_FLAG_DBLCLK, "2x" },
+ { DRM_MODE_FLAG_CLKDIV2, "clkdiv2" },
+};
+
+static const map<int, string> mode_3d_map = {
+ { DRM_MODE_FLAG_3D_NONE, "" },
+ { DRM_MODE_FLAG_3D_FRAME_PACKING, "3dfp" },
+ { DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE, "3dfa" },
+ { DRM_MODE_FLAG_3D_LINE_ALTERNATIVE, "3dla" },
+ { DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL, "3dsbs" },
+ { DRM_MODE_FLAG_3D_L_DEPTH, "3dldepth" },
+ { DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH, "3dgfx" },
+ { DRM_MODE_FLAG_3D_TOP_AND_BOTTOM, "3dtab" },
+ { DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF, "3dsbs" },
+};
+
+static const map<int, string> mode_aspect_map = {
+ { DRM_MODE_FLAG_PIC_AR_NONE, "" },
+ { DRM_MODE_FLAG_PIC_AR_4_3, "4:3" },
+ { DRM_MODE_FLAG_PIC_AR_16_9, "16:9" },
+ { DRM_MODE_FLAG_PIC_AR_64_27, "64:27" },
+ { DRM_MODE_FLAG_PIC_AR_256_135, "256:135" },
+};
+
+static string mode_type_str(uint32_t val)
+{
+ vector<string> s;
+ for (const auto& [k, v] : mode_type_map) {
+ if (val & k) {
+ if (!v.empty())
+ s.push_back(v);
+ val &= ~k;
+ }
+ }
+ // any unknown bits
+ if (val != 0)
+ s.push_back(fmt::format("{:#x}", val));
+ return join(s, "|");
+}
+
+static string mode_flag_str(uint32_t val)
+{
+ vector<string> s;
+ for (const auto& [k, v] : mode_flag_map) {
+ if (val & k) {
+ if (!v.empty())
+ s.push_back(v);
+ val &= ~k;
+ }
+ }
+ auto it = mode_3d_map.find(val & DRM_MODE_FLAG_3D_MASK);
+ if (it != mode_3d_map.end()) {
+ if (!it->second.empty())
+ s.push_back(it->second);
+ val &= ~DRM_MODE_FLAG_3D_MASK;
+ }
+ it = mode_aspect_map.find(val & DRM_MODE_FLAG_PIC_AR_MASK);
+ if (it != mode_aspect_map.end()) {
+ if (!it->second.empty())
+ s.push_back(it->second);
+ val &= ~DRM_MODE_FLAG_PIC_AR_MASK;
+ }
+ // any unknown bits
+ if (val != 0)
+ s.push_back(fmt::format("{:#x}", val));
+ return join(s, "|");
+}
+
+string Videomode::to_string_long() const
+{
+ string h = fmt::format("{}/{}/{}/{}/{}", hdisplay, hfp(), hsw(), hbp(), sync_to_char(hsync()));
+ string v = fmt::format("{}/{}/{}/{}/{}", vdisplay, vfp(), vsw(), vbp(), sync_to_char(vsync()));
+
+ string str = fmt::format("{} {:.3f} {} {} {} ({:.2f}) {} {}",
+ to_string_short(),
+ clock / 1000.0,
+ h, v,
+ vrefresh, calculated_vrefresh(),
+ mode_type_str(type),
+ mode_flag_str(flags));
+
+ return str;
+}
+
+string Videomode::to_string_long_padded() const
+{
+ string h = fmt::format("{}/{}/{}/{}/{}", hdisplay, hfp(), hsw(), hbp(), sync_to_char(hsync()));
+ string v = fmt::format("{}/{}/{}/{}/{}", vdisplay, vfp(), vsw(), vbp(), sync_to_char(vsync()));
+
+ string str = fmt::format("{:<16} {:7.3f} {:<18} {:<18} {:2} ({:.2f}) {:<5} {}",
+ to_string_short(),
+ clock / 1000.0,
+ h, v,
+ vrefresh, calculated_vrefresh(),
+ mode_type_str(type),
+ mode_flag_str(flags));
+
+ return str;
+}
+
+Videomode videomode_from_timings(uint32_t clock_khz,
+ uint16_t hact, uint16_t hfp, uint16_t hsw, uint16_t hbp,
+ uint16_t vact, uint16_t vfp, uint16_t vsw, uint16_t vbp)
+{
+ Videomode m{};
+ m.clock = clock_khz;
+
+ m.hdisplay = hact;
+ m.hsync_start = hact + hfp;
+ m.hsync_end = hact + hfp + hsw;
+ m.htotal = hact + hfp + hsw + hbp;
+
+ m.vdisplay = vact;
+ m.vsync_start = vact + vfp;
+ m.vsync_end = vact + vfp + vsw;
+ m.vtotal = vact + vfp + vsw + vbp;
+
+ return m;
+}
+
+} // namespace kms