blob: 6619c0795bd5b8ee26cd65dc48d03fb34c3fdd52 [file] [log] [blame]
/*
*
* (C) COPYRIGHT 2012-2015 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU licence.
*
* A copy of the licence is included with the program, and can also be obtained
* from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
/**
* pl111_drm_device.c
* Implementation of the Linux device driver entrypoints for PL111 DRM
*/
#include <linux/amba/bus.h>
#include <linux/amba/clcd.h>
#include <linux/version.h>
#include <linux/shmem_fs.h>
#include <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "pl111_drm.h"
struct pl111_drm_dev_private priv;
#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
static void initial_kds_obtained(void *cb1, void *cb2)
{
wait_queue_head_t *wait = (wait_queue_head_t *) cb1;
bool *cb_has_called = (bool *) cb2;
*cb_has_called = true;
wake_up(wait);
}
/* Must be called from within current_displaying_lock spinlock */
void release_kds_resource_and_display(struct pl111_drm_flip_resource *flip_res)
{
struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc);
pl111_crtc->displaying_fb = flip_res->fb;
/* Release the previous buffer */
if (pl111_crtc->old_kds_res_set != NULL) {
/*
* Can flip to the same buffer, but must not release the current
* resource set
*/
BUG_ON(pl111_crtc->old_kds_res_set == flip_res->kds_res_set);
kds_resource_set_release(&pl111_crtc->old_kds_res_set);
}
/* Record the current buffer, to release on the next buffer flip */
pl111_crtc->old_kds_res_set = flip_res->kds_res_set;
}
#endif
void pl111_drm_preclose(struct drm_device *dev, struct drm_file *file_priv)
{
DRM_DEBUG_KMS("DRM %s on dev=%p\n", __func__, dev);
}
void pl111_drm_lastclose(struct drm_device *dev)
{
DRM_DEBUG_KMS("DRM %s on dev=%p\n", __func__, dev);
}
/*
* pl111 does not have a proper HW counter for vblank IRQs so enable_vblank
* and disable_vblank are just no op callbacks.
*/
static int pl111_enable_vblank(struct drm_device *dev, int crtc)
{
DRM_DEBUG_KMS("%s: dev=%p, crtc=%d", __func__, dev, crtc);
return 0;
}
static void pl111_disable_vblank(struct drm_device *dev, int crtc)
{
DRM_DEBUG_KMS("%s: dev=%p, crtc=%d", __func__, dev, crtc);
}
struct drm_mode_config_funcs mode_config_funcs = {
.fb_create = pl111_fb_create,
};
static int pl111_modeset_init(struct drm_device *dev)
{
struct drm_mode_config *mode_config;
struct pl111_drm_dev_private *priv = dev->dev_private;
struct pl111_drm_connector *pl111_connector;
struct pl111_drm_encoder *pl111_encoder;
int ret = 0;
if (priv == NULL)
return -EINVAL;
drm_mode_config_init(dev);
mode_config = &dev->mode_config;
mode_config->funcs = &mode_config_funcs;
mode_config->min_width = 1;
mode_config->max_width = 1024;
mode_config->min_height = 1;
mode_config->max_height = 768;
priv->pl111_crtc = pl111_crtc_create(dev);
if (priv->pl111_crtc == NULL) {
pr_err("Failed to create pl111_drm_crtc\n");
ret = -ENOMEM;
goto out_config;
}
priv->number_crtcs = 1;
pl111_connector = pl111_connector_create(dev);
if (pl111_connector == NULL) {
pr_err("Failed to create pl111_drm_connector\n");
ret = -ENOMEM;
goto out_config;
}
pl111_encoder = pl111_encoder_create(dev, 1);
if (pl111_encoder == NULL) {
pr_err("Failed to create pl111_drm_encoder\n");
ret = -ENOMEM;
goto out_config;
}
ret = drm_mode_connector_attach_encoder(&pl111_connector->connector,
&pl111_encoder->encoder);
if (ret != 0) {
DRM_ERROR("Failed to attach encoder\n");
goto out_config;
}
pl111_connector->connector.encoder = &pl111_encoder->encoder;
pl111_encoder->encoder.crtc = &priv->pl111_crtc->crtc;
goto finish;
out_config:
drm_mode_config_cleanup(dev);
finish:
DRM_DEBUG("%s returned %d\n", __func__, ret);
return ret;
}
static void pl111_modeset_fini(struct drm_device *dev)
{
drm_mode_config_cleanup(dev);
}
static int pl111_drm_load(struct drm_device *dev, unsigned long chipset)
{
int ret = 0;
pr_info("DRM %s\n", __func__);
mutex_init(&priv.export_dma_buf_lock);
atomic_set(&priv.nr_flips_in_flight, 0);
init_waitqueue_head(&priv.wait_for_flips);
#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
ret = kds_callback_init(&priv.kds_cb, 1, show_framebuffer_on_crtc_cb);
if (ret != 0) {
pr_err("Failed to initialise KDS callback\n");
goto finish;
}
ret = kds_callback_init(&priv.kds_obtain_current_cb, 1,
initial_kds_obtained);
if (ret != 0) {
pr_err("Failed to init KDS obtain callback\n");
kds_callback_term(&priv.kds_cb);
goto finish;
}
#endif
/* Create a cache for page flips */
priv.page_flip_slab = kmem_cache_create("page flip slab",
sizeof(struct pl111_drm_flip_resource), 0, 0, NULL);
if (priv.page_flip_slab == NULL) {
DRM_ERROR("Failed to create slab\n");
ret = -ENOMEM;
goto out_kds_callbacks;
}
dev->dev_private = &priv;
ret = pl111_modeset_init(dev);
if (ret != 0) {
pr_err("Failed to init modeset\n");
goto out_slab;
}
ret = pl111_device_init(dev);
if (ret != 0) {
DRM_ERROR("Failed to init MMIO and IRQ\n");
goto out_modeset;
}
ret = drm_vblank_init(dev, 1);
if (ret != 0) {
DRM_ERROR("Failed to init vblank\n");
goto out_vblank;
}
#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 13, 0))
platform_set_drvdata(dev->platformdev, dev);
#endif
goto finish;
out_vblank:
pl111_device_fini(dev);
out_modeset:
pl111_modeset_fini(dev);
out_slab:
kmem_cache_destroy(priv.page_flip_slab);
out_kds_callbacks:
#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
kds_callback_term(&priv.kds_obtain_current_cb);
kds_callback_term(&priv.kds_cb);
#endif
finish:
DRM_DEBUG_KMS("pl111_drm_load returned %d\n", ret);
return ret;
}
static int pl111_drm_unload(struct drm_device *dev)
{
pr_info("DRM %s\n", __func__);
kmem_cache_destroy(priv.page_flip_slab);
drm_vblank_cleanup(dev);
pl111_modeset_fini(dev);
pl111_device_fini(dev);
#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
kds_callback_term(&priv.kds_obtain_current_cb);
kds_callback_term(&priv.kds_cb);
#endif
return 0;
}
static struct vm_operations_struct pl111_gem_vm_ops = {
.fault = pl111_gem_fault,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0))
.open = drm_gem_vm_open,
.close = drm_gem_vm_close,
#else
.open = pl111_gem_vm_open,
.close = pl111_gem_vm_close,
#endif
};
static const struct file_operations drm_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.mmap = pl111_gem_mmap,
.poll = drm_poll,
.read = drm_read,
};
static struct drm_ioctl_desc pl111_ioctls[] = {
DRM_IOCTL_DEF_DRV(PL111_GEM_CREATE, pl111_drm_gem_create_ioctl,
DRM_CONTROL_ALLOW | DRM_UNLOCKED),
};
static struct drm_driver driver = {
.driver_features =
DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
.load = pl111_drm_load,
.unload = pl111_drm_unload,
.context_dtor = NULL,
.preclose = pl111_drm_preclose,
.lastclose = pl111_drm_lastclose,
.suspend = pl111_drm_suspend,
.resume = pl111_drm_resume,
.get_vblank_counter = drm_vblank_count,
.enable_vblank = pl111_enable_vblank,
.disable_vblank = pl111_disable_vblank,
.ioctls = pl111_ioctls,
.fops = &drm_fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
.dumb_create = pl111_dumb_create,
.dumb_destroy = pl111_dumb_destroy,
.dumb_map_offset = pl111_dumb_map_offset,
.gem_free_object = pl111_gem_free_object,
.gem_vm_ops = &pl111_gem_vm_ops,
.prime_handle_to_fd = &pl111_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_export = &pl111_gem_prime_export,
.gem_prime_import = &pl111_gem_prime_import,
};
int pl111_drm_init(struct platform_device *dev)
{
int ret;
pr_info("DRM %s\n", __func__);
pr_info("PL111 DRM initialize, driver name: %s, version %d.%d\n",
DRIVER_NAME, DRIVER_MAJOR, DRIVER_MINOR);
driver.num_ioctls = ARRAY_SIZE(pl111_ioctls);
ret = 0;
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 15, 0))
driver.kdriver.platform_device = dev;
#endif
return drm_platform_init(&driver, dev);
}
void pl111_drm_exit(struct platform_device *dev)
{
pr_info("DRM %s\n", __func__);
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 13, 0))
drm_platform_exit(&driver, dev);
#else
drm_put_dev(platform_get_drvdata(dev));
#endif
}