Last active
September 27, 2015 12:05
-
-
Save notro/59e0c064bc512e85e9b2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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