Skip to content

Instantly share code, notes, and snippets.

@notro
Last active September 27, 2015 12:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notro/59e0c064bc512e85e9b2 to your computer and use it in GitHub Desktop.
Save notro/59e0c064bc512e85e9b2 to your computer and use it in GitHub Desktop.
/*
* Copyright (C) 2015 Noralf Trønnes
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/device.h>
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/tinydrm/tinydrm.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/spi/spi.h>
#include "internal.h"
/*
*
* Connector
*
*/
static inline struct tinydrm_device *connector_to_tinydrm(struct drm_connector *connector)
{
return container_of(connector, struct tinydrm_device, connector);
}
static int tinydrm_connector_get_modes(struct drm_connector *connector)
{
struct tinydrm_device *tdev = connector_to_tinydrm(connector);
struct drm_display_mode *mode;
// this is destroyed by tinydrm_connector_destroy -> drm_connector_cleanup -> drm_mode_remove -> drm_mode_destroy
mode = drm_cvt_mode(connector->dev, tdev->width, tdev->height, 60, false, false, false);
if (!mode)
return 0;
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
return 1; // number of modes
}
struct drm_encoder *tinydrm_connector_best_encoder(struct drm_connector *connector)
{
struct tinydrm_device *tdev = connector_to_tinydrm(connector);
return &tdev->encoder;
}
static const struct drm_connector_helper_funcs tinydrm_connector_helper_funcs = {
.get_modes = tinydrm_connector_get_modes,
// .mode_valid = tinydrm_connector_mode_valid, // optional
.best_encoder = tinydrm_connector_best_encoder,
};
static enum drm_connector_status
tinydrm_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static void tinydrm_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs tinydrm_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.reset = drm_atomic_helper_connector_reset,
.detect = tinydrm_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tinydrm_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
/*
*
* Encoder
*
*/
static void tinydrm_encoder_disable(struct drm_encoder *encoder)
{
}
static void tinydrm_encoder_enable(struct drm_encoder *encoder)
{
}
static int tinydrm_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
const struct drm_display_mode *mode = &crtc_state->mode;
struct drm_connector *connector = conn_state->connector;
const struct drm_display_mode *panel_mode;
struct drm_device *ddev = encoder->dev;
if (list_empty(&connector->modes)) {
dev_dbg(ddev->dev, "encoder: empty modes list\n");
return -EINVAL;
}
panel_mode = list_first_entry(&connector->modes,
struct drm_display_mode, head);
/* We're not allowed to modify the resolution. */
if (mode->hdisplay != panel_mode->hdisplay ||
mode->vdisplay != panel_mode->vdisplay)
return -EINVAL;
/* The flat panel mode is fixed, just copy it to the adjusted mode. */
drm_mode_copy(adjusted_mode, panel_mode);
return 0;
}
static const struct drm_encoder_helper_funcs tinydrm_encoder_helper_funcs = {
// .mode_set = tinydrm_encoder_mode_set, // optional, not sure if we need it. Called from crtc_set_mode()
.disable = tinydrm_encoder_disable,
.enable = tinydrm_encoder_enable,
.atomic_check = tinydrm_encoder_atomic_check,
};
static const struct drm_encoder_funcs tinydrm_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
/*
*
* Crtc
*
*/
static void tinydrm_crtc_enable(struct drm_crtc *crtc)
{
struct drm_device *ddev = crtc->dev;
struct tinydrm_device *tdev = ddev->dev_private;
dev_info(ddev->dev, "%s: enabled=%u\n", __func__, tdev->enabled);
if (tdev->enabled)
return;
if (tdev->enable) {
int ret = tdev->enable(tdev);
if (ret)
dev_err(ddev->dev, "enable() failed %d\n", ret);
}
tdev->enabled = true;
}
static void tinydrm_crtc_disable(struct drm_crtc *crtc)
{
struct drm_device *ddev = crtc->dev;
struct tinydrm_device *tdev = ddev->dev_private;
dev_info(ddev->dev, "%s: enabled=%u\n", __func__, tdev->enabled);
if (!tdev->enabled)
return;
if (tdev->disable) {
int ret = tdev->disable(tdev);
if (ret)
dev_err(ddev->dev, "disable() failed %d\n", ret);
}
tdev->enabled = false;
}
static bool tinydrm_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static const struct drm_crtc_helper_funcs tinydrm_crtc_helper_funcs = {
.mode_fixup = tinydrm_crtc_mode_fixup,
.disable = tinydrm_crtc_disable,
.enable = tinydrm_crtc_enable,
};
static const struct drm_crtc_funcs tinydrm_crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
};
/*
*
* Framebuffer
*
*/
static inline struct tinydrm_framebuffer *to_tinydrm_framebuffer(struct drm_framebuffer *fb)
{
return container_of(fb, struct tinydrm_framebuffer, base);
}
static void tinydrm_framebuffer_destroy(struct drm_framebuffer *fb)
{
struct tinydrm_framebuffer *tinydrm_fb = to_tinydrm_framebuffer(fb);
if (tinydrm_fb->cma_obj)
drm_gem_cma_free_object(&tinydrm_fb->cma_obj->base);
drm_framebuffer_cleanup(fb);
kfree(tinydrm_fb);
}
static int tinydrm_framebuffer_dirty(struct drm_framebuffer *fb,
struct drm_file *file_priv,
unsigned flags, unsigned color,
struct drm_clip_rect *clips,
unsigned num_clips)
{
struct tinydrm_framebuffer *tfb = to_tinydrm_framebuffer(fb);
struct tinydrm_device *tdev = fb->dev->dev_private;
return tdev->dirty(fb, tfb->cma_obj, flags, color, clips, num_clips);
}
static const struct drm_framebuffer_funcs tinydrm_fb_funcs = {
.destroy = tinydrm_framebuffer_destroy,
.dirty = tinydrm_framebuffer_dirty,
};
/*
*
* Mode
*
*/
static struct drm_framebuffer *
tinydrm_fb_create(struct drm_device *ddev, struct drm_file *file_priv,
struct drm_mode_fb_cmd2 *mode_cmd)
{
struct tinydrm_framebuffer *tinydrm_fb;
struct drm_gem_object *obj;
int ret;
/* Validate the pixel format, size and pitches */
DRM_DEBUG_KMS("%s: pixel_format=%s\n", __func__, drm_get_format_name(mode_cmd->pixel_format));
DRM_DEBUG_KMS("%s: width=%u\n", __func__, mode_cmd->width);
DRM_DEBUG_KMS("%s: height=%u\n", __func__, mode_cmd->height);
DRM_DEBUG_KMS("%s: pitches[0]=%u\n", __func__, mode_cmd->pitches[0]);
obj = drm_gem_object_lookup(ddev, file_priv, mode_cmd->handles[0]);
if (!obj)
return NULL;
tinydrm_fb = kzalloc(sizeof(*tinydrm_fb), GFP_KERNEL);
if (!tinydrm_fb)
return NULL;
tinydrm_fb->cma_obj = to_drm_gem_cma_obj(obj);
ret = drm_framebuffer_init(ddev, &tinydrm_fb->base, &tinydrm_fb_funcs);
if (ret) {
kfree(tinydrm_fb);
drm_gem_object_unreference_unlocked(obj);
return NULL;
}
drm_helper_mode_fill_fb_struct(&tinydrm_fb->base, mode_cmd);
return &tinydrm_fb->base;
}
static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
.fb_create = tinydrm_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
/*
*
* Plane
*
*/
static const uint32_t tinydrm_formats[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
};
static const struct drm_plane_funcs tinydrm_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_primary_helper_destroy,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static int tinydrm_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
return 0;
}
static void tinydrm_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
DRM_DEBUG("handle 0x%x, crtc %dx%d+%d+%d\n", 0,
plane->state->crtc_w, plane->state->crtc_h,
plane->state->crtc_x, plane->state->crtc_y);
}
static const struct drm_plane_helper_funcs tinydrm_plane_helper_funcs = {
.atomic_check = tinydrm_plane_atomic_check,
.atomic_update = tinydrm_plane_atomic_update,
};
static int tinydrm_load(struct drm_device *ddev, unsigned long flags)
{
struct tinydrm_device *tdev = ddev->dev_private;
int ret;
drm_mode_config_init(ddev);
ddev->mode_config.min_width = tdev->width;
ddev->mode_config.min_height = tdev->height;
ddev->mode_config.max_width = tdev->width;
ddev->mode_config.max_height = tdev->height;
ddev->mode_config.funcs = &tinydrm_mode_config_funcs;
drm_plane_helper_add(&tdev->plane, &tinydrm_plane_helper_funcs);
ret = drm_universal_plane_init(ddev, &tdev->plane, 0,
&tinydrm_plane_funcs, tinydrm_formats,
ARRAY_SIZE(tinydrm_formats),
DRM_PLANE_TYPE_PRIMARY);
if (ret)
return ret;
drm_crtc_helper_add(&tdev->crtc, &tinydrm_crtc_helper_funcs);
ret = drm_crtc_init_with_planes(ddev, &tdev->crtc, &tdev->plane, NULL,
&tinydrm_crtc_funcs);
if (ret)
return ret;
tdev->encoder.possible_crtcs = 1 << drm_crtc_index(&tdev->crtc);
drm_encoder_helper_add(&tdev->encoder, &tinydrm_encoder_helper_funcs);
ret = drm_encoder_init(ddev, &tdev->encoder, &tinydrm_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL);
if (ret)
return ret;
tdev->connector.status = connector_status_connected;
drm_connector_helper_add(&tdev->connector, &tinydrm_connector_helper_funcs);
ret = drm_connector_init(ddev, &tdev->connector, &tinydrm_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
if (ret)
return ret;
ret = drm_mode_connector_attach_encoder(&tdev->connector, &tdev->encoder);
if (ret)
return ret;
ret = drm_connector_register(&tdev->connector);
if (ret)
return ret;
drm_mode_config_reset(ddev);
ret = tinydrm_fbdev_init(tdev);
if (ret)
return ret;
return 0;
}
static const struct file_operations tinydrm_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = drm_compat_ioctl,
#endif
.poll = drm_poll,
.read = drm_read,
.llseek = no_llseek,
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver tinydrm_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
| DRIVER_ATOMIC,
.load = tinydrm_load,
.get_vblank_counter = drm_vblank_count,
.gem_free_object = drm_gem_cma_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = drm_gem_prime_import,
.gem_prime_export = drm_gem_prime_export,
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
.gem_prime_vmap = drm_gem_cma_prime_vmap,
.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
.gem_prime_mmap = drm_gem_cma_prime_mmap,
.dumb_create = drm_gem_cma_dumb_create,
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
.dumb_destroy = drm_gem_dumb_destroy,
.fops = &tinydrm_fops,
.name = "tinydrm",
.desc = "tinydrm",
.date = "20150916",
.major = 1,
.minor = 0,
};
static void tinydrm_release(struct tinydrm_device *tdev)
{
tinydrm_fbdev_fini(tdev);
drm_dev_unregister(tdev->base);
drm_dev_unref(tdev->base);
}
static int tinydrm_register(struct device *dev, struct tinydrm_device *tdev)
{
struct drm_driver *driver = &tinydrm_driver;
struct drm_device *ddev;
int ret;
DRM_DEBUG("\n");
ddev = drm_dev_alloc(driver, dev);
if (!ddev)
return -ENOMEM;
tdev->base = ddev;
ddev->dev_private = tdev;
ret = drm_dev_set_unique(ddev, dev_name(ddev->dev));
if (ret)
goto err_free;
ret = drm_dev_register(ddev, 0);
if (ret)
goto err_free;
DRM_INFO("Device: %s\n", dev_name(dev));
DRM_INFO("Initialized %s %d.%d.%d on minor %d\n",
driver->name, driver->major, driver->minor, driver->patchlevel,
ddev->primary->index);
return 0;
err_free:
drm_dev_unref(ddev);
return ret;
}
static int drv_enable(struct tinydrm_device *tdev)
{
dev_info(tdev->base->dev, "%s()\n", __func__);
return 0;
}
static int drv_disable(struct tinydrm_device *tdev)
{
dev_info(tdev->base->dev, "%s()\n", __func__);
return 0;
}
static inline void tinydrm_reset_clip(struct drm_clip_rect *clip)
{
clip->x1 = ~0;
clip->x2 = 0;
clip->y1 = ~0;
clip->y2 = 0;
}
void tinydrm_merge_clips(struct drm_clip_rect *dst, struct drm_clip_rect *clips, unsigned num_clips, unsigned flags)
{
int i;
for (i = 0; i < num_clips; i++) {
dst->x1 = min(dst->x1, clips[i].x1);
dst->x2 = max(dst->x2, clips[i].x2);
dst->y1 = min(dst->y1, clips[i].y1);
dst->y2 = max(dst->y2, clips[i].y2);
if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY) {
i++;
dst->x2 = max(dst->x2, clips[i].x2);
dst->y2 = max(dst->y2, clips[i].y2);
}
}
}
#include <linux/workqueue.h>
struct tinydrm_lcdctrl {
struct tinydrm_device tdev;
struct delayed_work deferred_work;
unsigned defer_ms;
// clips_lock
struct drm_clip_rect *clips;
unsigned num_clips;
};
static inline struct tinydrm_lcdctrl *to_tinydrm_lcdctrl(struct tinydrm_device *tdev)
{
return container_of(tdev, struct tinydrm_lcdctrl, tdev);
}
#include <linux/delay.h>
static void tinydrm_deferred_io_work(struct work_struct *work)
{
struct tinydrm_lcdctrl *tctrl = container_of(work, struct tinydrm_lcdctrl, deferred_work.work);
struct drm_clip_rect clip;
clip = *tctrl->clips;
tinydrm_reset_clip(tctrl->clips);
dev_info(tctrl->tdev.base->dev, "%s: x1=%u, x2=%u, y1=%u, y2=%u\n\n", __func__, clip.x1, clip.x2, clip.y1, clip.y2);
msleep(25);
}
static int drv_dirty(struct drm_framebuffer *fb, struct drm_gem_cma_object *cma_obj, unsigned flags,
unsigned color, struct drm_clip_rect *clips, unsigned num_clips)
{
struct tinydrm_device *tdev = fb->dev->dev_private;
struct tinydrm_lcdctrl *tctrl = to_tinydrm_lcdctrl(tdev);
int i;
dev_info(tdev->base->dev, "%s(cma_obj=%p, flags=0x%x, color=0x%x, clips=%p, num_clips=%u)\n", __func__, cma_obj, flags, color, clips, num_clips);
for (i = 0; i < num_clips; i++)
dev_info(tdev->base->dev, " x1=%u, x2=%u, y1=%u, y2=%u\n", clips[i].x1, clips[i].x2, clips[i].y1, clips[i].y2);
tinydrm_merge_clips(tctrl->clips, clips, num_clips, flags);
schedule_delayed_work(&tctrl->deferred_work, msecs_to_jiffies(tctrl->defer_ms));
return 0;
}
enum adafruit_displays {
ADAFRUIT_358 = 358,
ADAFRUIT_797 = 797,
ADAFRUIT_1480 = 1480,
ADAFRUIT_1601 = 1601,
};
static const struct of_device_id ada_mipifb_ids[] = {
{ .compatible = "adafruit,ada358", .data = (void *)ADAFRUIT_358 },
{ .compatible = "adafruit,ada797", .data = (void *)ADAFRUIT_797 },
{ .compatible = "adafruit,ada1480", .data = (void *)ADAFRUIT_1480 },
{ .compatible = "adafruit,ada1601", .data = (void *)ADAFRUIT_1601 },
{ .compatible = "sainsmart18", .data = (void *)ADAFRUIT_358 },
{},
};
MODULE_DEVICE_TABLE(of, ada_mipifb_ids);
static int ada_mipifb_probe(struct spi_device *spi)
{
struct tinydrm_lcdctrl *tctrl;
struct tinydrm_device *tdev;
struct device *dev = &spi->dev;
const struct of_device_id *of_id;
of_id = of_match_device(ada_mipifb_ids, dev);
if (!of_id)
return -EINVAL;
tctrl = devm_kzalloc(dev, sizeof(*tctrl), GFP_KERNEL);
if (!tctrl)
return -ENOMEM;
tctrl->defer_ms = 50;
tctrl->clips = devm_kzalloc(dev, sizeof(*tctrl->clips), GFP_KERNEL);
if (!tctrl->clips)
return -ENOMEM;
INIT_DELAYED_WORK(&tctrl->deferred_work, tinydrm_deferred_io_work);
tinydrm_reset_clip(tctrl->clips);
tdev = &tctrl->tdev;
tdev->enable = drv_enable;
tdev->disable = drv_disable;
tdev->dirty = drv_dirty;
switch ((int)of_id->data) {
case ADAFRUIT_358:
tdev->width = 240;
tdev->height = 240;
break;
case ADAFRUIT_797:
tdev->width = 320;
tdev->height = 320;
break;
case ADAFRUIT_1480:
case ADAFRUIT_1601:
tdev->width = 240;
tdev->height = 320;
break;
default:
return -EINVAL;
}
spi_set_drvdata(spi, tdev);
return tinydrm_register(dev, tdev);
}
static int ada_mipifb_remove(struct spi_device *spi)
{
struct tinydrm_device *tdev = spi_get_drvdata(spi);
tinydrm_release(tdev);
return 0;
}
static struct spi_driver ada_mipifb_spi_driver = {
.driver = {
.name = "ada-mipifb",
.owner = THIS_MODULE,
.of_match_table = ada_mipifb_ids,
},
.probe = ada_mipifb_probe,
.remove = ada_mipifb_remove,
};
module_spi_driver(ada_mipifb_spi_driver);
MODULE_LICENSE("GPL");
/*
* Copyright (C) 2015 Noralf Trønnes
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/tinydrm/tinydrm.h>
#define TINYDRM_FBDEV_DIRTY_DELAY HZ/100
static void tinydrm_fbdev_dirty(struct fb_info *info, unsigned flags,
unsigned color, struct drm_clip_rect *clips,
unsigned num_clips)
{
struct drm_fb_helper *helper = info->par;
struct tinydrm_device *tdev = helper->dev->dev_private;
struct drm_framebuffer *fb = helper->fb;
struct drm_gem_cma_object *cma_obj;
int ret;
cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
if (!cma_obj)
return;
ret = tdev->dirty(fb, cma_obj, flags, color, clips, num_clips);
if (ret)
dev_err_once(fb->dev->dev, "dirty() failed: %d\n", ret);
}
#define TINYDRM_DEF_IO_CLIPS 32
static void tinydrm_fbdev_deferred_io(struct fb_info *info,
struct list_head *pagelist)
{
struct drm_clip_rect clips[TINYDRM_DEF_IO_CLIPS];
unsigned long start, end, next, min, max;
struct page *page;
int i = 0;
int count = 0;
min = ULONG_MAX;
max = 0;
next = 0;
list_for_each_entry(page, pagelist, lru) {
start = page->index << PAGE_SHIFT;
end = start + PAGE_SIZE - 1;
if (next && start != next && i < TINYDRM_DEF_IO_CLIPS - 1) {
clips[i].x1 = 0;
clips[i].x2 = info->var.xres - 1;
clips[i].y1 = min / info->fix.line_length;
clips[i].y2 = max / info->fix.line_length;
++i;
min = ULONG_MAX;
max = 0;
}
next = end + 1;
min = min(min, start);
max = max(max, end);
count++;
}
pr_debug("%s: count=%d\n", __func__, count);
if (min < max) {
clips[i].x1 = 0;
clips[i].x2 = info->var.xres - 1;
clips[i].y1 = min / info->fix.line_length;
clips[i].y2 = min_t(u32, max / info->fix.line_length,
info->var.yres - 1);
tinydrm_fbdev_dirty(info, 0, 0, clips, i + 1);
}
}
static struct fb_deferred_io tinydrm_fbdev_defio = {
.delay = TINYDRM_FBDEV_DIRTY_DELAY,
.deferred_io = tinydrm_fbdev_deferred_io,
};
static void tinydrm_fbdev_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
struct drm_clip_rect clip = {
.x1 = rect->dx,
.x2 = rect->dx + rect->width - 1,
.y1 = rect->dy,
.y2 = rect->dy + rect->height - 1,
};
dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n",
__func__, rect->dx, rect->dy, rect->width, rect->height);
sys_fillrect(info, rect);
tinydrm_fbdev_dirty(info, DRM_MODE_FB_DIRTY_ANNOTATE_FILL, rect->color, &clip, 1);
}
static void tinydrm_fbdev_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
struct drm_clip_rect clips[2] = {
{
.x1 = area->sx,
.x2 = area->sx + area->width - 1,
.y1 = area->sy,
.y2 = area->sy + area->height - 1,
}, {
.x1 = area->dx,
.x2 = area->dx + area->width - 1,
.y1 = area->dy,
.y2 = area->dy + area->height - 1,
}
};
dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n",
__func__, area->dx, area->dy, area->width, area->height);
sys_copyarea(info, area);
tinydrm_fbdev_dirty(info, DRM_MODE_FB_DIRTY_ANNOTATE_COPY, 0, clips, 2);
}
static void tinydrm_fbdev_imageblit(struct fb_info *info,
const struct fb_image *image)
{
struct drm_clip_rect clip = {
.x1 = image->dx,
.x2 = image->dx + image->width - 1,
.y1 = image->dy,
.y2 = image->dy + image->height - 1,
};
dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n",
__func__, image->dx, image->dy, image->width, image->height);
sys_imageblit(info, image);
tinydrm_fbdev_dirty(info, 0, 0, &clip, 1);
}
static struct fb_ops tinydrm_fbdev_cma_ops = {
.owner = THIS_MODULE,
.fb_fillrect = tinydrm_fbdev_fillrect,
.fb_copyarea = tinydrm_fbdev_copyarea,
.fb_imageblit = tinydrm_fbdev_imageblit,
.fb_check_var = drm_fb_helper_check_var,
.fb_set_par = drm_fb_helper_set_par,
.fb_blank = drm_fb_helper_blank,
.fb_pan_display = drm_fb_helper_pan_display,
.fb_setcmap = drm_fb_helper_setcmap,
//.fb_blank
};
static int tinydrm_fbdev_event_notify(struct notifier_block *self,
unsigned long action, void *data)
{
struct fb_event *event = data;
struct fb_info *info = event->info;
struct drm_fb_helper *helper = info->par;
struct tinydrm_device *tdev = helper->dev->dev_private;
struct fb_ops *fbops;
/* Make sure it's a tinydrm fbdev */
if (!(info == helper->fbdev && helper->dev == tdev->base))
return 0;
DRM_DEBUG("action=%lu, info=%p\n", action, info);
switch (action) {
case FB_EVENT_FB_REGISTERED:
info->flags |= FBINFO_VIRTFB;
strcpy(info->fix.id, "tinydrm");
/*
* Without this change, the get_page() call in
* fb_deferred_io_fault() results in an oops:
* Unable to handle kernel paging request at virtual address
*/
info->fix.smem_start = __pa(info->screen_base);
/*
* a per device fbops structure is needed because
* fb_deferred_io_cleanup() clears fbops.fb_mmap
*/
fbops = devm_kzalloc(info->device, sizeof(*fbops), GFP_KERNEL);
if (!fbops)
return -ENOMEM;
memcpy(fbops, &tinydrm_fbdev_cma_ops, sizeof(*fbops));
info->fbops = fbops;
info->fbdefio = &tinydrm_fbdev_defio;
fb_deferred_io_init(info);
break;
case FB_EVENT_FB_UNREGISTERED:
if (info->fbdefio)
fb_deferred_io_cleanup(info);
break;
}
return 0;
}
static struct notifier_block tinydrm_fbdev_event_notifier = {
.notifier_call = tinydrm_fbdev_event_notify,
.priority = 100, /* place before the fbcon notifier */
};
int tinydrm_fbdev_init(struct tinydrm_device *tdev)
{
struct drm_device *ddev = tdev->base;
fb_register_client(&tinydrm_fbdev_event_notifier);
tdev->fbdev_cma = drm_fbdev_cma_init(ddev, 16,
ddev->mode_config.num_crtc,
ddev->mode_config.num_connector);
fb_unregister_client(&tinydrm_fbdev_event_notifier);
return PTR_ERR_OR_ZERO(tdev->fbdev_cma);
}
void tinydrm_fbdev_fini(struct tinydrm_device *tdev)
{
fb_register_client(&tinydrm_fbdev_event_notifier);
drm_fbdev_cma_fini(tdev->fbdev_cma);
fb_unregister_client(&tinydrm_fbdev_event_notifier);
}
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
struct tinydrm_framebuffer {
struct drm_framebuffer base;
struct drm_gem_object *obj;
struct drm_gem_cma_object *cma_obj;
};
struct tinydrm_device {
struct drm_device *base;
struct drm_plane plane;
struct drm_crtc crtc;
struct drm_encoder encoder;
struct drm_connector connector;
struct drm_fbdev_cma *fbdev_cma;
bool enabled;
u32 width, height;
void *dev_private;
int (*enable)(struct tinydrm_device *tdev);
int (*disable)(struct tinydrm_device *tdev);
int (*dirty)(struct drm_framebuffer *fb,
struct drm_gem_cma_object *cma_obj,
unsigned flags, unsigned color,
struct drm_clip_rect *clips, unsigned num_clips);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment