diff options
Diffstat (limited to 'drivers/gpu/drm/xilinx/xilinx_drm_drv.c')
-rw-r--r-- | drivers/gpu/drm/xilinx/xilinx_drm_drv.c | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/drivers/gpu/drm/xilinx/xilinx_drm_drv.c b/drivers/gpu/drm/xilinx/xilinx_drm_drv.c new file mode 100644 index 000000000000..8298ad2caa0b --- /dev/null +++ b/drivers/gpu/drm/xilinx/xilinx_drm_drv.c @@ -0,0 +1,614 @@ +/* + * Xilinx DRM KMS support for Xilinx + * + * Copyright (C) 2013 Xilinx, Inc. + * + * Author: Hyun Woo Kwon <hyunk@xilinx.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include <linux/component.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> + +#include "xilinx_drm_connector.h" +#include "xilinx_drm_crtc.h" +#include "xilinx_drm_drv.h" +#include "xilinx_drm_encoder.h" +#include "xilinx_drm_fb.h" +#include "xilinx_drm_gem.h" + +#define DRIVER_NAME "xilinx_drm" +#define DRIVER_DESC "Xilinx DRM KMS support for Xilinx" +#define DRIVER_DATE "20130509" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static uint xilinx_drm_fbdev_vres = 2; +module_param_named(fbdev_vres, xilinx_drm_fbdev_vres, uint, 0444); +MODULE_PARM_DESC(fbdev_vres, + "fbdev virtual resolution multiplier for fb (default: 2)"); + +/* + * TODO: The possible pipeline configurations are numerous with Xilinx soft IPs. + * It's not too bad for now, but the more proper way(Common Display Framework, + * or some internal abstraction) should be considered, when it reaches a point + * that such thing is required. + */ + +struct xilinx_drm_private { + struct drm_device *drm; + struct drm_crtc *crtc; + struct drm_fb_helper *fb; + struct platform_device *pdev; + bool is_master; +}; + +/** + * struct xilinx_video_format_desc - Xilinx Video IP video format description + * @name: Xilinx video format name + * @depth: color depth + * @bpp: bits per pixel + * @xilinx_format: xilinx format code + * @drm_format: drm format code + */ +struct xilinx_video_format_desc { + const char *name; + unsigned int depth; + unsigned int bpp; + unsigned int xilinx_format; + u32 drm_format; +}; + +static const struct xilinx_video_format_desc xilinx_video_formats[] = { + { "yuv420", 16, 16, XILINX_VIDEO_FORMAT_YUV420, DRM_FORMAT_YUV420 }, + { "uvy422", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_UYVY }, + { "vuy422", 16, 16, XILINX_VIDEO_FORMAT_YUV422, DRM_FORMAT_VYUY }, + { "yuv422", 16, 16, XILINX_VIDEO_FORMAT_YUV422, DRM_FORMAT_YUYV }, + { "yvu422", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_YVYU }, + { "yuv444", 24, 24, XILINX_VIDEO_FORMAT_YUV444, DRM_FORMAT_YUV444 }, + { "nv12", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_NV12 }, + { "nv21", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_NV21 }, + { "nv16", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_NV16 }, + { "nv61", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_NV61 }, + { "abgr1555", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_ABGR1555 }, + { "argb1555", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_ARGB1555 }, + { "rgba4444", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_RGBA4444 }, + { "bgra4444", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_BGRA4444 }, + { "bgr565", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_BGR565 }, + { "rgb565", 16, 16, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_RGB565 }, + { "bgr888", 24, 24, XILINX_VIDEO_FORMAT_RGB, DRM_FORMAT_BGR888 }, + { "rgb888", 24, 24, XILINX_VIDEO_FORMAT_RGB, DRM_FORMAT_RGB888 }, + { "xbgr8888", 24, 32, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_XBGR8888 }, + { "xrgb8888", 24, 32, XILINX_VIDEO_FORMAT_XRGB, DRM_FORMAT_XRGB8888 }, + { "abgr8888", 32, 32, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_ABGR8888 }, + { "argb8888", 32, 32, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_ARGB8888 }, + { "bgra8888", 32, 32, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_BGRA8888 }, + { "rgba8888", 32, 32, XILINX_VIDEO_FORMAT_NONE, DRM_FORMAT_RGBA8888 }, +}; + +/** + * xilinx_drm_check_format - Check if the given format is supported + * @drm: DRM device + * @fourcc: format fourcc + * + * Check if the given format @fourcc is supported by the current pipeline + * + * Return: true if the format is supported, or false + */ +bool xilinx_drm_check_format(struct drm_device *drm, u32 fourcc) +{ + struct xilinx_drm_private *private = drm->dev_private; + + return xilinx_drm_crtc_check_format(private->crtc, fourcc); +} + +/** + * xilinx_drm_get_format - Get the current device format + * @drm: DRM device + * + * Get the current format of pipeline + * + * Return: the corresponding DRM_FORMAT_XXX + */ +u32 xilinx_drm_get_format(struct drm_device *drm) +{ + struct xilinx_drm_private *private = drm->dev_private; + + return xilinx_drm_crtc_get_format(private->crtc); +} + +/** + * xilinx_drm_get_align - Get the alignment value for pitch + * @drm: DRM object + * + * Get the alignment value for pitch from the plane + * + * Return: The alignment value if successful, or the error code. + */ +unsigned int xilinx_drm_get_align(struct drm_device *drm) +{ + struct xilinx_drm_private *private = drm->dev_private; + + return xilinx_drm_crtc_get_align(private->crtc); +} + +/* poll changed handler */ +static void xilinx_drm_output_poll_changed(struct drm_device *drm) +{ + struct xilinx_drm_private *private = drm->dev_private; + + xilinx_drm_fb_hotplug_event(private->fb); +} + +static const struct drm_mode_config_funcs xilinx_drm_mode_config_funcs = { + .fb_create = xilinx_drm_fb_create, + .output_poll_changed = xilinx_drm_output_poll_changed, +}; + +/* enable vblank */ +static int xilinx_drm_enable_vblank(struct drm_device *drm, unsigned int crtc) +{ + struct xilinx_drm_private *private = drm->dev_private; + + xilinx_drm_crtc_enable_vblank(private->crtc); + + return 0; +} + +/* disable vblank */ +static void xilinx_drm_disable_vblank(struct drm_device *drm, unsigned int crtc) +{ + struct xilinx_drm_private *private = drm->dev_private; + + xilinx_drm_crtc_disable_vblank(private->crtc); +} + +/* initialize mode config */ +static void xilinx_drm_mode_config_init(struct drm_device *drm) +{ + struct xilinx_drm_private *private = drm->dev_private; + + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + + drm->mode_config.max_width = + xilinx_drm_crtc_get_max_width(private->crtc); + drm->mode_config.max_height = 4096; + + drm->mode_config.funcs = &xilinx_drm_mode_config_funcs; +} + +/* convert xilinx format to drm format by code */ +int xilinx_drm_format_by_code(unsigned int xilinx_format, u32 *drm_format) +{ + const struct xilinx_video_format_desc *format; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xilinx_video_formats); i++) { + format = &xilinx_video_formats[i]; + if (format->xilinx_format == xilinx_format) { + *drm_format = format->drm_format; + return 0; + } + } + + DRM_ERROR("Unknown Xilinx video format: %d\n", xilinx_format); + + return -EINVAL; +} + +/* convert xilinx format to drm format by name */ +int xilinx_drm_format_by_name(const char *name, u32 *drm_format) +{ + const struct xilinx_video_format_desc *format; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xilinx_video_formats); i++) { + format = &xilinx_video_formats[i]; + if (strcmp(format->name, name) == 0) { + *drm_format = format->drm_format; + return 0; + } + } + + DRM_ERROR("Unknown Xilinx video format: %s\n", name); + + return -EINVAL; +} + +/* get bpp of given format */ +unsigned int xilinx_drm_format_bpp(u32 drm_format) +{ + const struct xilinx_video_format_desc *format; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xilinx_video_formats); i++) { + format = &xilinx_video_formats[i]; + if (format->drm_format == drm_format) + return format->bpp; + } + + return 0; +} + +/* get color depth of given format */ +unsigned int xilinx_drm_format_depth(u32 drm_format) +{ + const struct xilinx_video_format_desc *format; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xilinx_video_formats); i++) { + format = &xilinx_video_formats[i]; + if (format->drm_format == drm_format) + return format->depth; + } + + return 0; +} + +static int xilinx_drm_bind(struct device *dev) +{ + struct xilinx_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + + return component_bind_all(dev, drm); +} + +static void xilinx_drm_unbind(struct device *dev) +{ + dev_set_drvdata(dev, NULL); +} + +static const struct component_master_ops xilinx_drm_ops = { + .bind = xilinx_drm_bind, + .unbind = xilinx_drm_unbind, +}; + +static int compare_of(struct device *dev, void *data) +{ + struct device_node *np = data; + + return dev->of_node == np; +} + +static int xilinx_drm_open(struct drm_device *dev, struct drm_file *file) +{ + struct xilinx_drm_private *private = dev->dev_private; + + /* This is a hack way to allow the root user to run as a master */ + if (!(drm_is_primary_client(file) && !dev->master) && + !file->is_master && capable(CAP_SYS_ADMIN)) { + file->is_master = 1; + private->is_master = true; + } + + return 0; +} + +static int xilinx_drm_release(struct inode *inode, struct file *filp) +{ + struct drm_file *file = filp->private_data; + struct drm_minor *minor = file->minor; + struct drm_device *drm = minor->dev; + struct xilinx_drm_private *private = drm->dev_private; + + if (private->is_master) { + private->is_master = false; + file->is_master = 0; + } + + return drm_release(inode, filp); +} + +/* restore the default mode when xilinx drm is released */ +static void xilinx_drm_lastclose(struct drm_device *drm) +{ + struct xilinx_drm_private *private = drm->dev_private; + + xilinx_drm_crtc_restore(private->crtc); + + xilinx_drm_fb_restore_mode(private->fb); +} + +static const struct file_operations xilinx_drm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = xilinx_drm_release, + .unlocked_ioctl = drm_ioctl, + .mmap = drm_gem_cma_mmap, + .poll = drm_poll, + .read = drm_read, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .llseek = noop_llseek, +}; + +static struct drm_driver xilinx_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | + DRIVER_LEGACY, + .open = xilinx_drm_open, + .lastclose = xilinx_drm_lastclose, + + .enable_vblank = xilinx_drm_enable_vblank, + .disable_vblank = xilinx_drm_disable_vblank, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .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, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = xilinx_drm_gem_cma_dumb_create, + + .fops = &xilinx_drm_fops, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +#if defined(CONFIG_PM_SLEEP) +/* suspend xilinx drm */ +static int xilinx_drm_pm_suspend(struct device *dev) +{ + struct xilinx_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + struct drm_connector *connector; + + drm_kms_helper_poll_disable(drm); + drm_modeset_lock_all(drm); + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + int old_dpms = connector->dpms; + + if (connector->funcs->dpms) + connector->funcs->dpms(connector, + DRM_MODE_DPMS_SUSPEND); + + connector->dpms = old_dpms; + } + drm_modeset_unlock_all(drm); + + return 0; +} + +/* resume xilinx drm */ +static int xilinx_drm_pm_resume(struct device *dev) +{ + struct xilinx_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + struct drm_connector *connector; + + drm_modeset_lock_all(drm); + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + if (connector->funcs->dpms) { + int dpms = connector->dpms; + + connector->dpms = DRM_MODE_DPMS_OFF; + connector->funcs->dpms(connector, dpms); + } + } + drm_modeset_unlock_all(drm); + + drm_helper_resume_force_mode(drm); + + drm_modeset_lock_all(drm); + drm_kms_helper_poll_enable(drm); + drm_modeset_unlock_all(drm); + + return 0; +} +#endif + +static const struct dev_pm_ops xilinx_drm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xilinx_drm_pm_suspend, xilinx_drm_pm_resume) +}; + +/* init xilinx drm platform */ +static int xilinx_drm_platform_probe(struct platform_device *pdev) +{ + struct xilinx_drm_private *private; + struct drm_device *drm; + struct drm_encoder *encoder; + struct drm_connector *connector; + const struct drm_format_info *info; + struct device_node *encoder_node, *ep = NULL, *remote; + struct component_match *match = NULL; + unsigned int align, i = 0; + int ret; + u32 format; + + drm = drm_dev_alloc(&xilinx_drm_driver, &pdev->dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + private = devm_kzalloc(drm->dev, sizeof(*private), GFP_KERNEL); + if (!private) { + ret = -ENOMEM; + goto err_drm; + } + + drm_mode_config_init(drm); + + /* create a xilinx crtc */ + private->crtc = xilinx_drm_crtc_create(drm); + if (IS_ERR(private->crtc)) { + DRM_DEBUG_DRIVER("failed to create xilinx crtc\n"); + ret = PTR_ERR(private->crtc); + goto err_config; + } + + while ((encoder_node = of_parse_phandle(drm->dev->of_node, + "xlnx,encoder-slave", i))) { + encoder = xilinx_drm_encoder_create(drm, encoder_node); + of_node_put(encoder_node); + if (IS_ERR(encoder)) { + DRM_DEBUG_DRIVER("failed to create xilinx encoder\n"); + ret = PTR_ERR(encoder); + goto err_config; + } + + connector = xilinx_drm_connector_create(drm, encoder, i); + if (IS_ERR(connector)) { + DRM_DEBUG_DRIVER("failed to create xilinx connector\n"); + ret = PTR_ERR(connector); + goto err_config; + } + + i++; + } + + while (1) { + ep = of_graph_get_next_endpoint(drm->dev->of_node, ep); + if (!ep) + break; + + of_node_put(ep); + remote = of_graph_get_remote_port_parent(ep); + if (!remote || !of_device_is_available(remote)) { + of_node_put(remote); + continue; + } + + component_match_add(drm->dev, &match, compare_of, remote); + of_node_put(remote); + i++; + } + + if (i == 0) { + DRM_ERROR("failed to get an encoder slave node\n"); + return -ENODEV; + } + + ret = drm_vblank_init(drm, 1); + if (ret) { + dev_err(&pdev->dev, "failed to initialize vblank\n"); + goto err_master; + } + + /* enable irq to enable vblank feature */ + drm->irq_enabled = 1; + + drm->dev_private = private; + private->drm = drm; + xilinx_drm_mode_config_init(drm); + + format = xilinx_drm_crtc_get_format(private->crtc); + info = drm_format_info(format); + if (info && info->depth && info->cpp[0]) { + align = xilinx_drm_crtc_get_align(private->crtc); + private->fb = xilinx_drm_fb_init(drm, info->cpp[0] * 8, 1, + align, xilinx_drm_fbdev_vres); + if (IS_ERR(private->fb)) { + DRM_ERROR("failed to initialize drm fb\n"); + private->fb = NULL; + } + } else { + dev_info(&pdev->dev, "fbdev is not initialized\n"); + } + + drm_kms_helper_poll_init(drm); + + drm_helper_disable_unused_functions(drm); + + platform_set_drvdata(pdev, private); + + if (match) { + ret = component_master_add_with_match(drm->dev, + &xilinx_drm_ops, match); + if (ret) + goto err_master; + } + + ret = dma_set_coherent_mask(&pdev->dev, + DMA_BIT_MASK(sizeof(dma_addr_t) * 8)); + if (ret) { + dev_info(&pdev->dev, "failed to set coherent mask (%zu)\n", + sizeof(dma_addr_t)); + } + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto err_master; + + return 0; + +err_master: + component_master_del(drm->dev, &xilinx_drm_ops); +err_config: + drm_mode_config_cleanup(drm); + if (ret == -EPROBE_DEFER) + DRM_INFO("load() is defered & will be called again\n"); +err_drm: + drm_dev_put(drm); + return ret; +} + +/* exit xilinx drm platform */ +static int xilinx_drm_platform_remove(struct platform_device *pdev) +{ + struct xilinx_drm_private *private = platform_get_drvdata(pdev); + struct drm_device *drm = private->drm; + + component_master_del(drm->dev, &xilinx_drm_ops); + drm_kms_helper_poll_fini(drm); + xilinx_drm_fb_fini(private->fb); + drm_mode_config_cleanup(drm); + drm->dev_private = NULL; + drm_dev_put(private->drm); + + return 0; +} + +static void xilinx_drm_platform_shutdown(struct platform_device *pdev) +{ + struct xilinx_drm_private *private = platform_get_drvdata(pdev); + + drm_put_dev(private->drm); +} + +static const struct of_device_id xilinx_drm_of_match[] = { + { .compatible = "xlnx,drm", }, + { /* end of table */ }, +}; +MODULE_DEVICE_TABLE(of, xilinx_drm_of_match); + +static struct platform_driver xilinx_drm_private_driver = { + .probe = xilinx_drm_platform_probe, + .remove = xilinx_drm_platform_remove, + .shutdown = xilinx_drm_platform_shutdown, + .driver = { + .name = "xilinx-drm", + .pm = &xilinx_drm_pm_ops, + .of_match_table = xilinx_drm_of_match, + }, +}; + +module_platform_driver(xilinx_drm_private_driver); + +MODULE_AUTHOR("Xilinx, Inc."); +MODULE_DESCRIPTION("Xilinx DRM KMS Driver"); +MODULE_LICENSE("GPL v2"); |