diff options
Diffstat (limited to 'drivers/gpu/drm')
40 files changed, 7861 insertions, 262 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index b16c50ee769c..a9ca07916a94 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -79,6 +79,8 @@ config DRM_TDFX Choose this option if you have a 3dfx Banshee or Voodoo3 (or later), graphics card. If M is selected, the module will be called tdfx. +source "drivers/gpu/drm/arm/Kconfig" + config DRM_R128 tristate "ATI Rage 128" depends on DRM && PCI diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1c9f24396002..daa41fcb63a8 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -30,6 +30,7 @@ CFLAGS_drm_trace_points.o := -I$(src) obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_USB) += drm_usb.o +obj-$(CONFIG_DRM_ARM) += arm/ obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_TDFX) += tdfx/ obj-$(CONFIG_DRM_R128) += r128/ diff --git a/drivers/gpu/drm/arm/Kconfig b/drivers/gpu/drm/arm/Kconfig new file mode 100644 index 000000000000..acb046fec516 --- /dev/null +++ b/drivers/gpu/drm/arm/Kconfig @@ -0,0 +1,26 @@ +config DRM_ARM + bool "ARM Ltd. drivers" + depends on DRM && OF && (ARM || ARM64) + select DMA_CMA + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select VIDEOMODE_HELPERS + help + Choose this option to select drivers for ARM's devices + +config DRM_HDLCD + tristate "ARM HDLCD" + depends on DRM_ARM + select I2C + help + Choose this option if you have an ARM High Definition Colour LCD + controller. + + If M is selected the module will be called hdlcd. + +config DRM_VIRTUAL_HDLCD + bool "Support for virtual HDLCD" + depends on DRM_HDLCD + help + Enable support for virtual HDLCD as emulated by ARM's Fast Models. diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile new file mode 100644 index 000000000000..429644c28a2f --- /dev/null +++ b/drivers/gpu/drm/arm/Makefile @@ -0,0 +1,4 @@ + +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_hdmi_encoder.o hdlcd_vexpress_encoder.o hdlcd_fb.o +hdlcd-$(CONFIG_DRM_VIRTUAL_HDLCD) += hdlcd_virt_encoder.o +obj-$(CONFIG_DRM_HDLCD) += hdlcd.o diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c new file mode 100644 index 000000000000..9a81467d72f4 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of a CRTC class for the HDLCD driver. + */ + +#include <linux/clk.h> +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +/* + * The HDLCD controller is a dumb RGB streamer that gets connected to + * a single HDMI transmitter or in the case of the ARM Models it gets + * emulated by the software that does the actual rendering. + * + */ +static void hdlcd_crtc_destroy(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); +} + +void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait) +{ + struct drm_framebuffer *fb = hdlcd->crtc.fb; + struct hdlcd_bo *bo; + unsigned int depth, bpp; + dma_addr_t scanout_start; + int ret; + + drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); + bo = hdlcd->bo; + + scanout_start = bo->dma_addr + fb->offsets[0] + + (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8); + + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + + if (wait && hdlcd->dpms == DRM_MODE_DPMS_ON) { + drm_vblank_get(fb->dev, 0); + hdlcd->frame_completion.done = 0; + do { + ret = wait_for_completion_interruptible_timeout(&hdlcd->frame_completion, + msecs_to_jiffies(50)); + } while (ret <= 0); + drm_vblank_put(fb->dev, 0); + } else { + dev_info(fb->dev->dev, "%s: wait called with DPMS set to %d\n", + __func__, hdlcd->dpms); + } +} + +static int hdlcd_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + if (hdlcd->dpms == DRM_MODE_DPMS_ON) { + /* don't schedule any page flipping if one is in progress */ + if (hdlcd->event) + return -EBUSY; + + hdlcd->event = event; + drm_vblank_get(crtc->dev, 0); + } + + crtc->fb = fb; + + if (hdlcd->dpms == DRM_MODE_DPMS_ON) { + hdlcd_set_scanout(hdlcd, true); + } else { + unsigned long flags; + + /* not active, update registers immediately */ + hdlcd_set_scanout(hdlcd, false); + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (event) + drm_send_vblank_event(crtc->dev, 0, event); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } + + return 0; +} + +static const struct drm_crtc_funcs hdlcd_crtc_funcs = { + .destroy = hdlcd_crtc_destroy, + .set_config = drm_crtc_helper_set_config, + .page_flip = hdlcd_crtc_page_flip, +}; + +static void hdlcd_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + hdlcd->dpms = mode; + if (mode == DRM_MODE_DPMS_ON) + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 1); + else + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); +} + +static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void hdlcd_crtc_prepare(struct drm_crtc *crtc) +{ + drm_vblank_pre_modeset(crtc->dev, 0); + hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_crtc_commit(struct drm_crtc *crtc) +{ + drm_vblank_post_modeset(crtc->dev, 0); + hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static bool hdlcd_fb_mode_equal(struct drm_framebuffer *oldfb, + struct drm_framebuffer *newfb) +{ + if (!oldfb || !newfb) + return false; + + if (oldfb->pixel_format == newfb->pixel_format && + oldfb->width == newfb->width && + oldfb->height == newfb->height) + return true; + + return false; +} + +static int hdlcd_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, struct drm_framebuffer *oldfb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + unsigned int depth, bpp, polarities; + unsigned char red_width = 0, green_width = 0, blue_width = 0, alpha_width = 0; + unsigned int default_color = 0x00000000; + +#ifdef HDLCD_SHOW_UNDERRUN + default_color = 0x00ff000000; +#endif + + /* This function gets called when the only change is the start of + the scanout buffer. Detect that and bail out early */ + if (hdlcd->initialised && hdlcd_fb_mode_equal(oldfb, crtc->fb)) { + hdlcd_set_scanout(hdlcd, true); + return 0; + } + + /* Preset the number of bits per colour */ + drm_fb_get_bpp_depth(crtc->fb->pixel_format, &depth, &bpp); + switch (depth) { + case 32: + alpha_width = 8; + case 24: + case 8: /* pseudocolor */ + red_width = 8; green_width = 8; blue_width = 8; + break; + case 16: /* 565 format */ + red_width = 5; green_width = 6; blue_width = 5; + break; + } + + /* switch to using the more useful bytes per pixel */ + bpp = (bpp + 7) / 8; + + polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA; + + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + polarities |= HDLCD_POLARITY_HSYNC; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + polarities |= HDLCD_POLARITY_VSYNC; + + /* Allow max number of outstanding requests and largest burst size */ + hdlcd_write(hdlcd, HDLCD_REG_BUS_OPTIONS, + HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); + + hdlcd_write(hdlcd, HDLCD_REG_PIXEL_FORMAT, (bpp - 1) << 3); + + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, crtc->fb->width * bpp); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, crtc->fb->height - 1); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, crtc->fb->width * bpp); + hdlcd_write(hdlcd, HDLCD_REG_V_BACK_PORCH, + mode->vtotal - mode->vsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_FRONT_PORCH, + mode->vsync_start - mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_SYNC, + mode->vsync_end - mode->vsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_DATA, mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_BACK_PORCH, + mode->htotal - mode->hsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_FRONT_PORCH, + mode->hsync_start - mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_SYNC, + mode->hsync_end - mode->hsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_DATA, mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_POLARITIES, polarities); + + /* + * The format of the HDLCD_REG_<color>_SELECT register is: + * - bits[23:16] - default value for that color component + * - bits[11:8] - number of bits to extract for each color component + * - bits[4:0] - index of the lowest bit to extract + * + * The default color value is used when bits[11:8] read zero, when the + * pixel is outside the visible frame area or when there is a + * buffer underrun. + */ + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color | + alpha_width | /* offset */ + (blue_width & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color | + (blue_width + alpha_width) | /* offset */ + ((green_width & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color | + (blue_width + green_width + alpha_width) | /* offset */ + ((red_width & 0xf) << 8)); + + clk_prepare(hdlcd->clk); + clk_set_rate(hdlcd->clk, mode->clock * 1000); + clk_enable(hdlcd->clk); + + hdlcd_set_scanout(hdlcd, false); + hdlcd->initialised = true; + + return 0; +} + +int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *oldfb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + hdlcd_set_scanout(hdlcd, true); + return 0; +} + +static void hdlcd_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = { + .dpms = hdlcd_crtc_dpms, + .mode_fixup = hdlcd_crtc_mode_fixup, + .prepare = hdlcd_crtc_prepare, + .commit = hdlcd_crtc_commit, + .mode_set = hdlcd_crtc_mode_set, + .mode_set_base = hdlcd_crtc_mode_set_base, + .load_lut = hdlcd_crtc_load_lut, +}; + +int hdlcd_setup_crtc(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + int ret; + + drm_mode_config_init(dev); + hdlcd_drm_mode_config_init(dev); + + ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs); + if (ret < 0) + goto crtc_setup_err; + + drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs); + + return 0; + +crtc_setup_err: + drm_mode_config_cleanup(dev); + + return ret; +} + diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c new file mode 100644 index 000000000000..a3019ab75a40 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Driver + */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/completion.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + + +static int hdlcd_unload(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + drm_kms_helper_poll_fini(dev); + if (hdlcd->fb_helper) + drm_fb_helper_fini(hdlcd->fb_helper); + + drm_vblank_cleanup(dev); + drm_mode_config_cleanup(dev); + + drm_irq_uninstall(dev); + + if (!IS_ERR(hdlcd->clk)) + clk_put(hdlcd->clk); + + platform_set_drvdata(dev->platformdev, NULL); + + if (hdlcd->mmio) + iounmap(hdlcd->mmio); + + dev->dev_private = NULL; + kfree(hdlcd); + + return 0; +} + +static int hdlcd_load(struct drm_device *dev, unsigned long flags) +{ + struct platform_device *pdev = dev->platformdev; + struct hdlcd_drm_private *hdlcd; + struct resource *res; + phandle slave_phandle; + u32 version; + int ret; + + hdlcd = kzalloc(sizeof(*hdlcd), GFP_KERNEL); + if (!hdlcd) { + dev_err(dev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + +#ifdef CONFIG_DEBUG_FS + atomic_set(&hdlcd->buffer_underrun_count, 0); + atomic_set(&hdlcd->bus_error_count, 0); + atomic_set(&hdlcd->vsync_count, 0); + atomic_set(&hdlcd->dma_end_count, 0); +#endif + hdlcd->initialised = false; + dev->dev_private = hdlcd; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev->dev, "failed to get memory resource\n"); + ret = -EINVAL; + goto fail; + } + + hdlcd->mmio = ioremap_nocache(res->start, resource_size(res)); + if (!hdlcd->mmio) { + dev_err(dev->dev, "failed to map control registers area\n"); + ret = -ENOMEM; + goto fail; + } + + hdlcd->clk = clk_get(dev->dev, "pxlclk"); + if (IS_ERR(hdlcd->clk)) { + dev_err(dev->dev, "unable to get an usable clock\n"); + ret = PTR_ERR(hdlcd->clk); + goto fail; + } + + if (of_property_read_u32(pdev->dev.of_node, "i2c-slave", &slave_phandle)) { + dev_warn(dev->dev, "no i2c-slave handle provided, disabling physical connector\n"); + hdlcd->slave_node = NULL; + } else + hdlcd->slave_node = of_find_node_by_phandle(slave_phandle); + + version = hdlcd_read(hdlcd, HDLCD_REG_VERSION); + if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) { + dev_err(dev->dev, "unknown product id: 0x%x\n", version); + ret = -EINVAL; + goto fail; + } + dev_info(dev->dev, "found ARM HDLCD version r%dp%d\n", + (version & HDLCD_VERSION_MAJOR_MASK) >> 8, + version & HDLCD_VERSION_MINOR_MASK); + + ret = hdlcd_setup_crtc(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to create crtc\n"); + goto fail; + } + + /* + * It only makes sense to create the virtual connector if we don't have + * a physical way of controlling output + */ + if (hdlcd->slave_node) { + ret = hdlcd_create_digital_connector(dev, hdlcd); + if (ret < 0) { + dev_err(dev->dev, "failed to create digital connector, trying board setup: %d\n", ret); + ret = hdlcd_create_vexpress_connector(dev, hdlcd); + } + + if (ret < 0) { + dev_err(dev->dev, "failed to create board connector: %d\n", ret); + goto fail; + } + } else { + ret = hdlcd_create_virtual_connector(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to create virtual connector: %d\n", ret); + goto fail; + } + } + + ret = hdlcd_fbdev_init(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to init the framebuffer (%d)\n", ret); + goto fail; + } + + platform_set_drvdata(pdev, dev); + + ret = drm_irq_install(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to install IRQ handler\n"); + goto fail; + } + + init_completion(&hdlcd->frame_completion); + ret = drm_vblank_init(dev, 1); + if (ret < 0) { + dev_err(dev->dev, "failed to initialise vblank\n"); + goto fail; + } else { + dev_info(dev->dev, "initialised vblank\n"); + } + + drm_kms_helper_poll_init(dev); + + return 0; + +fail: + hdlcd_unload(dev); + return ret; +} + +static void hdlcd_preclose(struct drm_device *dev, struct drm_file *file) +{ +} + +static void hdlcd_lastclose(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + drm_modeset_lock_all(dev); + if (hdlcd->fb_helper) + drm_fb_helper_restore_fbdev_mode(hdlcd->fb_helper); + drm_modeset_unlock_all(dev); +} + +static irqreturn_t hdlcd_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + unsigned long irq_status; + + irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS); + +#ifdef CONFIG_DEBUG_FS + if (irq_status & HDLCD_INTERRUPT_UNDERRUN) { + atomic_inc(&hdlcd->buffer_underrun_count); + } + if (irq_status & HDLCD_INTERRUPT_DMA_END) { + atomic_inc(&hdlcd->dma_end_count); + } + if (irq_status & HDLCD_INTERRUPT_BUS_ERROR) { + atomic_inc(&hdlcd->bus_error_count); + } + if (irq_status & HDLCD_INTERRUPT_VSYNC) { + atomic_inc(&hdlcd->vsync_count); + } +#endif + if (irq_status & HDLCD_INTERRUPT_VSYNC) { + struct drm_pending_vblank_event *event; + unsigned long flags; + + drm_handle_vblank(dev, 0); + + spin_lock_irqsave(&dev->event_lock, flags); + if (hdlcd->event) { + event = hdlcd->event; + hdlcd->event = NULL; + drm_send_vblank_event(dev, 0, event); + drm_vblank_put(dev, 0); + } + spin_unlock_irqrestore(&dev->event_lock, flags); + } + if (irq_status & HDLCD_INTERRUPT_DMA_END) { + // send completion when reading the frame has finished + complete_all(&hdlcd->frame_completion); + } + + /* acknowledge interrupt(s) */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status); + + return IRQ_HANDLED; +} + +static void hdlcd_irq_preinstall(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + /* Ensure interrupts are disabled */ + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, ~0); +} + +static int hdlcd_irq_postinstall(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + unsigned int irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + +#ifdef CONFIG_DEBUG_FS + /* enable debug interrupts */ + irq_mask |= HDLCD_DEBUG_INT_MASK; +#endif + + /* enable DMA completion interrupts */ + irq_mask |= HDLCD_INTERRUPT_DMA_END; + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); + + return 0; +} + +static void hdlcd_irq_uninstall(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + /* disable all the interrupts that we might have enabled */ + unsigned int irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + +#ifdef CONFIG_DEBUG_FS + /* disable debug interrupts */ + irq_mask &= ~HDLCD_DEBUG_INT_MASK; +#endif + + /* disable vsync and dma interrupts */ + irq_mask &= ~(HDLCD_INTERRUPT_VSYNC | HDLCD_INTERRUPT_DMA_END); + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); +} + +static int hdlcd_enable_vblank(struct drm_device *dev, int crtc) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask | HDLCD_INTERRUPT_VSYNC); + + return 0; +} + +static void hdlcd_disable_vblank(struct drm_device *dev, int crtc) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask & ~HDLCD_INTERRUPT_VSYNC); +} + +#ifdef CONFIG_DEBUG_FS +static int hdlcd_show_underrun_count(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + seq_printf(m, "underrun : %d\n", atomic_read(&hdlcd->buffer_underrun_count)); + seq_printf(m, "dma_end : %d\n", atomic_read(&hdlcd->dma_end_count)); + seq_printf(m, "bus_error: %d\n", atomic_read(&hdlcd->bus_error_count)); + seq_printf(m, "vsync : %d\n", atomic_read(&hdlcd->vsync_count)); + return 0; +} + +static struct drm_info_list hdlcd_debugfs_list[] = { + { "interrupt_count", hdlcd_show_underrun_count, 0 }, +}; + +static int hdlcd_debugfs_init(struct drm_minor *minor) +{ + return drm_debugfs_create_files(hdlcd_debugfs_list, + ARRAY_SIZE(hdlcd_debugfs_list), minor->debugfs_root, minor); +} + +static void hdlcd_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(hdlcd_debugfs_list, + ARRAY_SIZE(hdlcd_debugfs_list), minor); +} +#endif + +static const struct file_operations hdlcd_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .mmap = drm_gem_cma_mmap, + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, +}; + +int drm_gem_dumb_destroy(struct drm_file *file, + struct drm_device *dev, + uint32_t handle) +{ + return drm_gem_handle_delete(file, handle); +} + +static struct drm_driver hdlcd_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | + DRIVER_MODESET | DRIVER_PRIME, + .load = hdlcd_load, + .unload = hdlcd_unload, + .preclose = hdlcd_preclose, + .lastclose = hdlcd_lastclose, + .irq_handler = hdlcd_irq, + .irq_preinstall = hdlcd_irq_preinstall, + .irq_postinstall = hdlcd_irq_postinstall, + .irq_uninstall = hdlcd_irq_uninstall, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = hdlcd_enable_vblank, + .disable_vblank = hdlcd_disable_vblank, + + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .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, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = hdlcd_debugfs_init, + .debugfs_cleanup = hdlcd_debugfs_cleanup, +#endif + .fops = &hdlcd_fops, + + .name = "hdlcd", + .desc = "ARM HDLCD Controller DRM", + .date = "20130505", + .major = 1, + .minor = 0, +}; + + +static int hdlcd_probe(struct platform_device *pdev) +{ + return drm_platform_init(&hdlcd_driver, pdev); +} + +static int hdlcd_remove(struct platform_device *pdev) +{ + drm_put_dev(platform_get_drvdata(pdev)); + return 0; +} + +static struct of_device_id hdlcd_of_match[] = { + { .compatible = "arm,hdlcd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hdlcd_of_match); + +static struct platform_driver hdlcd_platform_driver = { + .probe = hdlcd_probe, + .remove = hdlcd_remove, + .driver = { + .name = "hdlcd", + .owner = THIS_MODULE, + .of_match_table = hdlcd_of_match, + }, +}; + +static int __init hdlcd_init(void) +{ + return platform_driver_register(&hdlcd_platform_driver); +} + +static void __exit hdlcd_exit(void) +{ + platform_driver_unregister(&hdlcd_platform_driver); +} + +/* need late_initcall() so we load after i2c driver */ +late_initcall(hdlcd_init); +module_exit(hdlcd_exit); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("ARM HDLCD DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h new file mode 100644 index 000000000000..3fc3bddbb841 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -0,0 +1,82 @@ +/* + * ARM HDLCD Controller register definition + */ + +#ifndef __HDLCD_DRV_H__ +#define __HDLCD_DRV_H__ + +struct hdlcd_bo { + struct drm_gem_object gem; + dma_addr_t dma_addr; + void *cpu_addr; +}; + +struct hdlcd_drm_private { + void __iomem *mmio; + struct clk *clk; + struct drm_fb_helper *fb_helper; + struct hdlcd_bo *bo; + struct drm_pending_vblank_event *event; + struct drm_crtc crtc; + struct device_node *slave_node; + struct completion frame_completion; +#ifdef CONFIG_DEBUG_FS + atomic_t buffer_underrun_count; + atomic_t bus_error_count; + atomic_t vsync_count; + atomic_t dma_end_count; +#endif + int dpms; + bool initialised; +}; + +#define to_hdlcd_bo_obj(x) container_of(x, struct hdlcd_bo, gem) +#define crtc_to_hdlcd_priv(x) container_of(x, struct hdlcd_drm_private, crtc) + +static inline void +hdlcd_write(struct hdlcd_drm_private *hdlcd, unsigned int reg, u32 value) +{ + writel(value, hdlcd->mmio + reg); +} + +static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg) +{ + return readl(hdlcd->mmio + reg); +} + +/* + * Developers using HDLCD may wish to enable these settings if + * display disruption is apparent and you suspect HDLCD + * access to RAM may be starved. + * + * Turn HDLCD default color to red instead of default black so + * that it's easier to see data underruns (compared to other + * visual disruptions) + */ +#define HDLCD_SHOW_UNDERRUN + +/* setup the crtc subclass */ +int hdlcd_setup_crtc(struct drm_device *dev); + +/* functions for creating a suitable connector */ +extern int hdlcd_create_digital_connector(struct drm_device *dev, + struct hdlcd_drm_private *hdlcd); +extern int hdlcd_create_vexpress_connector(struct drm_device *dev, + struct hdlcd_drm_private *hdlcd); +#ifdef CONFIG_DRM_VIRTUAL_HDLCD +extern int hdlcd_create_virtual_connector(struct drm_device *dev); +#else +static inline int hdlcd_create_virtual_connector(struct drm_device *dev) +{ + return -ENXIO; +} +#endif + +void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait); +void hdlcd_drm_mode_config_init(struct drm_device *dev); +int hdlcd_fbdev_init(struct drm_device *dev); + +/* common function used by all connectors */ +extern struct drm_encoder *hdlcd_connector_best_encoder(struct drm_connector *con); + +#endif /* __HDLCD_DRV_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_fb.c b/drivers/gpu/drm/arm/hdlcd_fb.c new file mode 100644 index 000000000000..0e96361dc2a3 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb.c @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of the DRM fbdev compatibility mode for the HDLCD driver. + * Mainly used for doing dumb double buffering as expected by the ARM Mali driver. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <linux/dma-buf.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +#define MAX_FRAMES 2 + +#define to_hdlcd_fb(x) container_of(x, struct hdlcd_fb, fb) + +struct hdlcd_dmabuf_attachment { + struct sg_table *sgt; + enum dma_data_direction dir; +}; + +static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd); + +static int hdlcd_dmabuf_attach(struct dma_buf *dma_buf, + struct device *target_dev, struct dma_buf_attachment *attach) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach; + + hdlcd_attach = kzalloc(sizeof(*hdlcd_attach), GFP_KERNEL); + if (!hdlcd_attach) + return -ENOMEM; + + hdlcd_attach->dir = DMA_NONE; + attach->priv = hdlcd_attach; + + return 0; +} + +static void hdlcd_dmabuf_detach(struct dma_buf *dma_buf, + struct dma_buf_attachment *attach) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv; + struct sg_table *sgt; + + if (!hdlcd_attach) + return; + + sgt = hdlcd_attach->sgt; + if (sgt) { + sg_free_table(sgt); + } + + kfree(sgt); + kfree(hdlcd_attach); + attach->priv = NULL; +} + +static void hdlcd_dmabuf_release(struct dma_buf *dma_buf) +{ + struct hdlcd_drm_private *hdlcd = dma_buf->priv; + struct drm_gem_object *obj = &hdlcd->bo->gem; + + if (obj->export_dma_buf == dma_buf) { + /* drop the reference the export fd holds */ + obj->export_dma_buf = NULL; + drm_gem_object_unreference_unlocked(obj); + } +} + +static struct sg_table *hdlcd_map_dma_buf(struct dma_buf_attachment *attach, + enum dma_data_direction dir) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv; + struct hdlcd_drm_private *hdlcd = attach->dmabuf->priv; + struct sg_table *sgt; + int size, ret; + + if (dir == DMA_NONE || !hdlcd_attach) + return ERR_PTR(-EINVAL); + + /* return the cached mapping when possible */ + if (hdlcd_attach->dir == dir) + return hdlcd_attach->sgt; + + /* don't allow two different directions for the same attachment */ + if (hdlcd_attach->dir != DMA_NONE) + return ERR_PTR(-EBUSY); + + size = hdlcd->bo->gem.size; + + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + DRM_ERROR("Failed to allocate sg_table\n"); + return ERR_PTR(-ENOMEM); + } + + ret = sg_alloc_table(sgt, 1, GFP_KERNEL); + if (ret < 0) { + DRM_ERROR("Failed to allocate page table\n"); + kfree(sgt); + return ERR_PTR(-ENOMEM); + } + + sg_dma_len(sgt->sgl) = size; + sg_set_page(sgt->sgl, pfn_to_page(PFN_DOWN(hdlcd->bo->dma_addr)), size, 0); + sg_dma_address(sgt->sgl) = hdlcd->bo->dma_addr; + ret = dma_map_sg(attach->dev, sgt->sgl, sgt->orig_nents, dir); + if (!ret) { + DRM_ERROR("failed to map sgl with IOMMU\n"); + sg_free_table(sgt); + kfree(sgt); + return ERR_PTR(-EIO); + } + hdlcd_attach->sgt = sgt; + hdlcd_attach->dir = dir; + + return sgt; +} + +static void hdlcd_unmap_dma_buf(struct dma_buf_attachment *attach, + struct sg_table *sgt, enum dma_data_direction dir) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv; + + if (hdlcd_attach->dir != DMA_NONE) + dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir); + sg_free_table(sgt); + kfree(sgt); + hdlcd_attach->sgt = NULL; + hdlcd_attach->dir = DMA_NONE; +} + +static void *hdlcd_dmabuf_kmap(struct dma_buf *dma_buf, unsigned long page_num) +{ + return NULL; +} + +static void hdlcd_dmabuf_kunmap(struct dma_buf *dma_buf, + unsigned long page_num, void *addr) +{ +} + +static int hdlcd_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) +{ + struct hdlcd_drm_private *hdlcd = dma_buf->priv; + struct drm_gem_object *obj = &hdlcd->bo->gem; + struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj); + int ret; + + mutex_lock(&obj->dev->struct_mutex); + ret = drm_gem_mmap_obj(obj, obj->size, vma); + mutex_unlock(&obj->dev->struct_mutex); + if (ret < 0) + return ret; + + /* + * Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the + * vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map + * the whole buffer. + */ + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_pgoff = 0; + ret = dma_mmap_writecombine(obj->dev->dev, vma, bo->cpu_addr, bo->dma_addr, + vma->vm_end - vma->vm_start); + if (ret) + drm_gem_vm_close(vma); + + return 0; +} + +static void *hdlcd_dmabuf_vmap(struct dma_buf *dma_buf) +{ + return ERR_PTR(-EINVAL); +} + +static void hdlcd_dmabuf_vunmap(struct dma_buf *dma_buf, void *cpu_addr) +{ +} + +struct dma_buf_ops hdlcd_buf_ops = { + .attach = hdlcd_dmabuf_attach, + .detach = hdlcd_dmabuf_detach, + .map_dma_buf = hdlcd_map_dma_buf, + .unmap_dma_buf = hdlcd_unmap_dma_buf, + .release = hdlcd_dmabuf_release, + .kmap = hdlcd_dmabuf_kmap, + .kmap_atomic = hdlcd_dmabuf_kmap, + .kunmap = hdlcd_dmabuf_kunmap, + .kunmap_atomic = hdlcd_dmabuf_kunmap, + .mmap = hdlcd_dmabuf_mmap, + .vmap = hdlcd_dmabuf_vmap, + .vunmap = hdlcd_dmabuf_vunmap, +}; + +/* + * Used for sharing buffers with Mali userspace + */ +struct fb_dmabuf_export { + uint32_t fd; + uint32_t flags; +}; + +#define FBIOGET_DMABUF _IOR('F', 0x21, struct fb_dmabuf_export) + +static int hdlcd_get_dmabuf_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct fb_dmabuf_export ebuf; + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct dma_buf *dma_buf; + uint32_t fd; + + if (copy_from_user(&ebuf, argp, sizeof(ebuf))) + return -EFAULT; + + dma_buf = dma_buf_export(hdlcd, &hdlcd_buf_ops, + info->screen_size, ebuf.flags | O_RDWR); + if (!dma_buf) { + dev_info(info->dev, "Failed to export DMA buffer\n"); + goto err_export; + } + + fd = dma_buf_fd(dma_buf, O_CLOEXEC); + if (fd < 0) { + dev_info(info->dev, "Failed to get file descriptor for DMA buffer\n"); + goto err_export_fd; + } + ebuf.fd = fd; + + if (copy_to_user(argp, &ebuf, sizeof(ebuf))) + goto err_export_fd; + + return 0; + +err_export_fd: + dma_buf_put(dma_buf); +err_export: + return -EFAULT; +} + +static int hdlcd_wait_for_vsync(struct fb_info *info) +{ +#if 0 + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + int ret; + + drm_vblank_get(helper->dev, 0); + reinit_completion(&hdlcd->frame_completion); + do { + ret = wait_for_completion_interruptible_timeout(&hdlcd->frame_completion, + msecs_to_jiffies(50)); + } while (ret == -ERESTARTSYS); + drm_vblank_put(helper->dev, 0); + if (!ret) + return -ETIMEDOUT; +#endif + + return 0; +} + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FBIOGET_DMABUF: + return hdlcd_get_dmabuf_ioctl(info, cmd, arg); + case FBIO_WAITFORVSYNC: + return hdlcd_wait_for_vsync(info); + default: + printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd); + } + + return -EFAULT; +} + +static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct drm_gem_object *obj = &hdlcd->bo->gem; + struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj); + DEFINE_DMA_ATTRS(attrs); + size_t vm_size; + + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + + vma->vm_flags |= VM_IO | VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP; + vm_size = vma->vm_end - vma->vm_start; + + if (vm_size > bo->gem.size) + return -EINVAL; + + return dma_mmap_attrs(helper->dev->dev, vma, bo->cpu_addr, + bo->dma_addr, bo->gem.size, &attrs); +} + +static int hdlcd_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *fb_helper = info->par; + struct drm_framebuffer *fb = fb_helper->fb; + int depth; + + if (var->pixclock != 0 || in_dbg_master()) + return -EINVAL; + + /* Need to resize the fb object !!! */ + if (var->bits_per_pixel > fb->bits_per_pixel || + var->xres > fb->width || var->yres > fb->height || + var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) { + DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb " + "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n", + var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual, + fb->width, fb->height, fb->bits_per_pixel); + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 16: + depth = (var->green.length == 6) ? 16 : 15; + break; + case 32: + depth = (var->transp.length > 0) ? 32 : 24; + break; + default: + depth = var->bits_per_pixel; + break; + } + + switch (depth) { + case 8: + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 15: + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + var->transp.length = 1; + var->transp.offset = 15; + break; + case 16: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 32: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + var->transp.offset = 24; + break; + default: + return -EINVAL; + } + return 0; +} + +static struct fb_ops hdlcd_fb_ops = { + .owner = THIS_MODULE, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_check_var = hdlcd_fb_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_ioctl = hdlcd_fb_ioctl, + .fb_compat_ioctl = hdlcd_fb_ioctl, + .fb_mmap = hdlcd_fb_mmap, +}; + +static struct hdlcd_bo *hdlcd_fb_bo_create(struct drm_device *drm, size_t size) +{ + int err; + struct hdlcd_bo *bo; + struct hdlcd_drm_private *hdlcd = drm->dev_private; + DEFINE_DMA_ATTRS(attrs); + + bo = kzalloc(sizeof(*bo), GFP_KERNEL); + if (!bo) + return ERR_PTR(-ENOMEM); + + size = round_up(size, PAGE_SIZE); + err = drm_gem_object_init(drm, &bo->gem, size); + if (err) + goto err_init; + + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + bo->cpu_addr = dma_alloc_attrs(drm->dev, size, &bo->dma_addr, + GFP_KERNEL | __GFP_NOWARN, &attrs); + + if (!bo->cpu_addr) { + dev_err(drm->dev, "failed to allocate buffer of size %zu\n", size); + err = -ENOMEM; + goto err_dma; + } + +#if 0 + err = drm_gem_create_mmap_offset(&bo->gem); + if (err) + goto err_map; +#endif + + hdlcd->bo = bo; + return bo; + +#if 0 +err_map: + dma_free_attrs(drm->dev, size, bo->cpu_addr, bo->dma_addr, &attrs); +#endif +err_dma: + drm_gem_object_release(&bo->gem); +err_init: + kfree(bo); + + return ERR_PTR(err); +} + +void hdlcd_fb_bo_free(struct hdlcd_bo *bo) +{ + DEFINE_DMA_ATTRS(attrs); + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + + drm_gem_object_release(&bo->gem); + dma_free_attrs(bo->gem.dev->dev, bo->gem.size, bo->cpu_addr, bo->dma_addr, &attrs); + kfree(bo); +} + +static int hdlcd_fb_probe(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct fb_info *info; + struct hdlcd_bo *bo = hdlcd->bo; + struct drm_mode_fb_cmd2 cmd = { 0 }; + unsigned int bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + unsigned long offset; + size_t size; + int err; + + cmd.width = sizes->surface_width; + cmd.height = sizes->surface_height; + cmd.pitches[0] = ALIGN(sizes->surface_width * bytes_per_pixel, 64); + cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + size = PAGE_ALIGN(cmd.pitches[0] * cmd.height * MAX_FRAMES); + + bo = hdlcd_fb_bo_create(helper->dev, size); + if (IS_ERR(bo)) + return PTR_ERR(bo); + + info = framebuffer_alloc(0, helper->dev->dev); + if (!info) { + dev_err(helper->dev->dev, "failed to allocate framebuffer info\n"); + hdlcd_fb_bo_free(bo); + return -ENOMEM; + } + + helper->fb = hdlcd_fb_alloc(helper->dev, &cmd); + helper->fbdev = info; + + info->par = helper; + info->flags = FBINFO_FLAG_DEFAULT; + info->fbops = &hdlcd_fb_ops; + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err < 0) { + dev_err(helper->dev->dev, "failed to allocate color map: %d\n", err); + goto cleanup; + } + + drm_fb_helper_fill_fix(info, helper->fb->pitches[0], helper->fb->depth); + drm_fb_helper_fill_var(info, helper, helper->fb->width, sizes->surface_height); + + offset = info->var.xoffset * bytes_per_pixel + + info->var.yoffset * helper->fb->pitches[0]; + + helper->dev->mode_config.fb_base = (resource_size_t)bo->dma_addr; + info->screen_base = (void __iomem *)bo->cpu_addr + offset; + info->screen_size = size; + info->var.yres_virtual = info->var.yres * MAX_FRAMES; + info->fix.smem_start = (unsigned long)bo->dma_addr + offset; + info->fix.smem_len = size; + + return 0; + +cleanup: + drm_framebuffer_unregister_private(helper->fb); + hdlcd_fb_bo_free(bo); + framebuffer_release(info); + return err; +} + +static struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = { + .fb_probe = hdlcd_fb_probe, +}; + +static void hdlcd_fb_destroy(struct drm_framebuffer *fb) +{ + struct hdlcd_drm_private *hdlcd = fb->dev->dev_private; + drm_gem_object_unreference_unlocked(&hdlcd->bo->gem); + drm_framebuffer_cleanup(fb); + kfree(hdlcd->bo); +} + +static int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct hdlcd_drm_private *hdlcd = fb->dev->dev_private; + return drm_gem_handle_create(file_priv, &hdlcd->bo->gem, handle); +} + +static int hdlcd_fb_dirty(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned flags, + unsigned color, struct drm_clip_rect *clips, + unsigned num_clips) +{ + return 0; +} + +static struct drm_framebuffer_funcs hdlcd_fb_funcs = { + .destroy = hdlcd_fb_destroy, + .create_handle = hdlcd_fb_create_handle, + .dirty = hdlcd_fb_dirty, +}; + +static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + int err; + struct drm_framebuffer *fb; + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (!fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + err = drm_framebuffer_init(dev, fb, &hdlcd_fb_funcs); + if (err) { + dev_err(dev->dev, "failed to initialize framebuffer.\n"); + kfree(fb); + return ERR_PTR(err); + } + + return fb; +} + +static struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *obj; + struct hdlcd_bo *bo; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); + if (!obj) { + dev_err(dev->dev, "failed to lookup GEM object\n"); + return ERR_PTR(-ENXIO); + } + + bo = to_hdlcd_bo_obj(obj); + hdlcd->fb_helper->fb = hdlcd_fb_alloc(dev, mode_cmd); + if (IS_ERR(hdlcd->fb_helper->fb)) { + dev_err(dev->dev, "failed to allocate DRM framebuffer\n"); + } + hdlcd->bo = bo; + + return hdlcd->fb_helper->fb; +} + +int hdlcd_fbdev_init(struct drm_device *dev) +{ + int ret; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + hdlcd->fb_helper = kzalloc(sizeof(*hdlcd->fb_helper), GFP_KERNEL); + if (!hdlcd->fb_helper) + return -ENOMEM; + + hdlcd->fb_helper->funcs = &hdlcd_fb_helper_funcs; + ret = drm_fb_helper_init(dev, hdlcd->fb_helper, dev->mode_config.num_crtc, + dev->mode_config.num_connector); + if (ret < 0) { + dev_err(dev->dev, "Failed to initialize DRM fb helper.\n"); + goto err_free; + } + + ret = drm_fb_helper_single_add_all_connectors(hdlcd->fb_helper); + if (ret < 0) { + dev_err(dev->dev, "Failed to add connectors.\n"); + goto err_helper_fini; + } + + drm_helper_disable_unused_functions(dev); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + ret = drm_fb_helper_initial_config(hdlcd->fb_helper, 32); + if (ret < 0) { + dev_err(dev->dev, "Failed to set initial hw configuration.\n"); + goto err_helper_fini; + } + + return 0; + +err_helper_fini: + drm_fb_helper_fini(hdlcd->fb_helper); + +err_free: + kfree(hdlcd->fb_helper); + hdlcd->fb_helper = NULL; + + return ret; +} + +static void hdlcd_fb_output_poll_changed(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + struct drm_fb_helper *fb_helper = hdlcd->fb_helper; + + drm_fb_helper_hotplug_event(fb_helper); +} + +static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { + .fb_create = hdlcd_fb_create, + .output_poll_changed = hdlcd_fb_output_poll_changed, +}; + + +void hdlcd_drm_mode_config_init(struct drm_device *dev) +{ + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = HDLCD_MAX_XRES; + dev->mode_config.max_height = HDLCD_MAX_YRES; + dev->mode_config.funcs = &hdlcd_mode_config_funcs; +} diff --git a/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c b/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c new file mode 100644 index 000000000000..5f6a4b4ff9f6 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +/* + * Theory of operation: + * + * The DRM framework expects the CRTC -> Encoder -> Connector chain, + * where the CRTC is reading from framebuffer, passes data to the + * encoder and that formats the signals to something usable by the + * attached connector(s). Connectors can use i2c links to talk with + * attached monitors. + * + * The HDMI transmitter is a different beast: it is both and encoder + * and a connector in DRM parlance *and* can only be reached via i2c. + * It implements an i2c pass through mode for the situation where one + * wants to talk with the attached monitor. To complicate things + * even further, the VExpress boards that have the SiI9022 chip share + * the i2c line between the on-board microcontroller and the CoreTiles. + * This leads to a situation where the microcontroller might be able to + * talk with the SiI9022 transmitter, but not the CoreTile. And the + * micro has a very small brain and a list of hardcoded modes that + * it can program into the HDMI transmitter, so only a limited set + * of resolutions will be valid. + * + * This file handles only the case where the i2c connection is available + * to the kernel. For the case where we have to ask the microcontroller + * to do the modesetting for us see the hdlcd_vexpress_encoder.c file. + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <drm/i2c/tda998x.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include "hdlcd_drv.h" + +static inline struct drm_encoder_slave * +hdlcd_get_slave_encoder(struct drm_connector * connector) +{ + return to_encoder_slave(hdlcd_connector_best_encoder(connector)); +} + +static void hdlcd_connector_destroy(struct drm_connector *connector) +{ +} + +static enum drm_connector_status +hdlcd_connector_detect(struct drm_connector *connector, bool force) +{ + struct drm_encoder_slave *slave; + if (!connector->encoder) + connector->encoder = hdlcd_connector_best_encoder(connector); + + slave = hdlcd_get_slave_encoder(connector); + if (!slave || !slave->slave_funcs) + return connector_status_unknown; + + return slave->slave_funcs->detect(connector->encoder, connector); +} + +static const struct drm_connector_funcs hdlcd_connector_funcs = { + .destroy = hdlcd_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = hdlcd_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +struct drm_encoder * +hdlcd_connector_best_encoder(struct drm_connector *connector) +{ + int enc_id = connector->encoder_ids[0]; + struct drm_mode_object *obj; + struct drm_encoder *encoder; + + if (connector->encoder) + return connector->encoder; + + if (enc_id) { + obj = drm_mode_object_find(connector->dev, enc_id, + DRM_MODE_OBJECT_ENCODER); + if (obj) { + encoder = obj_to_encoder(obj); + return encoder; + } + } + return NULL; + +} + +static int hdlcd_hdmi_con_get_modes(struct drm_connector *connector) +{ + struct drm_encoder_slave *slave = hdlcd_get_slave_encoder(connector); + + if (slave && slave->slave_funcs) + return slave->slave_funcs->get_modes(&slave->base, connector); + + return 0; +} + +static int hdlcd_hdmi_con_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_encoder_slave *slave = hdlcd_get_slave_encoder(connector); + + if (slave && slave->slave_funcs) + return slave->slave_funcs->mode_valid(connector->encoder, mode); + + return MODE_ERROR; +} + +static const struct drm_connector_helper_funcs hdlcd_hdmi_con_helper_funcs = { + .get_modes = hdlcd_hdmi_con_get_modes, + .mode_valid = hdlcd_hdmi_con_mode_valid, + .best_encoder = hdlcd_connector_best_encoder, +}; + + +static struct drm_encoder_funcs hdlcd_encoder_funcs = { + .destroy = drm_i2c_encoder_destroy, +}; + +static void hdlcd_hdmi_encoder_disable(struct drm_encoder *encoder) +{ + drm_i2c_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static struct drm_encoder_helper_funcs hdlcd_encoder_helper_funcs = { + .dpms = drm_i2c_encoder_dpms, + .save = drm_i2c_encoder_save, + .restore = drm_i2c_encoder_restore, + .mode_fixup = drm_i2c_encoder_mode_fixup, + .prepare = drm_i2c_encoder_prepare, + .commit = drm_i2c_encoder_commit, + .mode_set = drm_i2c_encoder_mode_set, + .detect = drm_i2c_encoder_detect, + .disable = hdlcd_hdmi_encoder_disable, +}; + +static struct tda998x_encoder_params tda998x_params = { + .swap_a = 2, + .swap_b = 3, + .swap_c = 4, + .swap_d = 5, + .swap_e = 0, + .swap_f = 1, +}; + +int hdlcd_create_digital_connector(struct drm_device *dev, + struct hdlcd_drm_private *hdlcd) +{ + int err; + struct i2c_board_info i2c_info; + struct drm_encoder_slave *slave; + struct drm_connector *connector; + struct device_node *node = hdlcd->slave_node; + + slave = kzalloc(sizeof(*slave), GFP_KERNEL); + if (!slave) + return -ENOMEM; + + slave->base.possible_crtcs = 1; + slave->base.possible_clones = 0; + + err = drm_encoder_init(dev, &slave->base, &hdlcd_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (err) + goto encoder_init_err; + + drm_encoder_helper_add(&slave->base, &hdlcd_encoder_helper_funcs); + + /* get the driver for the i2c slave node */ + i2c_info.of_node = node; + err = of_modalias_node(node, i2c_info.type, sizeof(i2c_info.type)); + if (err < 0) { + dev_err(dev->dev, "failed to get a module alias for node %s\n", + node->full_name); + } + + /* Hack: this needs to be specified in the device tree */ + i2c_info.platform_data = &tda998x_params; + + err = drm_i2c_encoder_init(dev, slave, NULL, &i2c_info); + of_node_put(node); + if (err) + goto connector_alloc_err; + + connector = kzalloc(sizeof(*connector), GFP_KERNEL); + if (!connector) { + err = -ENOMEM; + goto connector_alloc_err; + } + + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + err = drm_connector_init(dev, connector, &hdlcd_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + if (err) + goto connector_init_err; + + drm_connector_helper_add(connector, &hdlcd_hdmi_con_helper_funcs); + + connector->encoder = &slave->base; + err = drm_mode_connector_attach_encoder(connector, &slave->base); + if (err) { + goto connector_attach_err; + } + + drm_sysfs_connector_add(connector); + + slave->base.dev->dev->platform_data = hdlcd; + return err; + +connector_attach_err: + drm_connector_cleanup(connector); +connector_init_err: + kfree(connector); +connector_alloc_err: + drm_encoder_cleanup(&slave->base); +encoder_init_err: + kfree(slave); + + return err; +} diff --git a/drivers/gpu/drm/arm/hdlcd_regs.h b/drivers/gpu/drm/arm/hdlcd_regs.h new file mode 100644 index 000000000000..571dad4a6f37 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_regs.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller register definition + */ + +#ifndef __HDLCD_REGS_H__ +#define __HDLCD_REGS_H__ + +/* register offsets */ +#define HDLCD_REG_VERSION 0x0000 /* ro */ +#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */ +#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */ +#define HDLCD_REG_INT_MASK 0x0018 /* rw */ +#define HDLCD_REG_INT_STATUS 0x001c /* ro */ +#define HDLCD_REG_FB_BASE 0x0100 /* rw */ +#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */ +#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */ +#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */ +#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */ +#define HDLCD_REG_V_SYNC 0x0200 /* rw */ +#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */ +#define HDLCD_REG_V_DATA 0x0208 /* rw */ +#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */ +#define HDLCD_REG_H_SYNC 0x0210 /* rw */ +#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */ +#define HDLCD_REG_H_DATA 0x0218 /* rw */ +#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */ +#define HDLCD_REG_POLARITIES 0x0220 /* rw */ +#define HDLCD_REG_COMMAND 0x0230 /* rw */ +#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */ +#define HDLCD_REG_BLUE_SELECT 0x0244 /* rw */ +#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */ +#define HDLCD_REG_RED_SELECT 0x024c /* rw */ + +/* version */ +#define HDLCD_PRODUCT_ID 0x1CDC0000 +#define HDLCD_PRODUCT_MASK 0xFFFF0000 +#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00 +#define HDLCD_VERSION_MINOR_MASK 0x000000FF + +/* interrupts */ +#define HDLCD_INTERRUPT_DMA_END (1 << 0) +#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1) +#define HDLCD_INTERRUPT_VSYNC (1 << 2) +#define HDLCD_INTERRUPT_UNDERRUN (1 << 3) +#define HDLCD_DEBUG_INT_MASK (HDLCD_INTERRUPT_DMA_END | \ + HDLCD_INTERRUPT_BUS_ERROR | \ + HDLCD_INTERRUPT_UNDERRUN) + +/* polarities */ +#define HDLCD_POLARITY_VSYNC (1 << 0) +#define HDLCD_POLARITY_HSYNC (1 << 1) +#define HDLCD_POLARITY_DATAEN (1 << 2) +#define HDLCD_POLARITY_DATA (1 << 3) +#define HDLCD_POLARITY_PIXELCLK (1 << 4) + +/* commands */ +#define HDLCD_COMMAND_DISABLE (0 << 0) +#define HDLCD_COMMAND_ENABLE (1 << 0) + +/* pixel format */ +#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31) +#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31) +#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3) + +/* bus options */ +#define HDLCD_BUS_BURST_MASK 0x01f +#define HDLCD_BUS_MAX_OUTSTAND 0xf00 +#define HDLCD_BUS_BURST_NONE (0 << 0) +#define HDLCD_BUS_BURST_1 (1 << 0) +#define HDLCD_BUS_BURST_2 (1 << 1) +#define HDLCD_BUS_BURST_4 (1 << 2) +#define HDLCD_BUS_BURST_8 (1 << 3) +#define HDLCD_BUS_BURST_16 (1 << 4) + +/* Max resolution supported is 4096x4096, 32bpp */ +#define HDLCD_MAX_XRES 4096 +#define HDLCD_MAX_YRES 4096 + +#define NR_PALETTE 256 + +#endif /* __HDLCD_REGS_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c new file mode 100644 index 000000000000..e96a62343648 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include <linux/vexpress.h> + +#include "hdlcd_drv.h" + +/* + * use the functionality of vexpress-config to set the output mode + * for HDLCD on Versatile Express boards that lack proper control + * of the DDC i2c chip. + */ + +static struct vexpress_config_func *vconfig_func; + +/* + * Predefined modes that are available through the VExpress micro + */ +static const struct { + int hsize, vsize, vrefresh, dvimode; +} vexpress_dvimodes[] = { + { 640, 480, 60, 0 }, /* VGA */ + { 800, 600, 60, 1 }, /* SVGA */ + { 1024, 768, 60, 2 }, /* XGA */ + { 1280, 1024, 60, 3 }, /* SXGA */ + { 1600, 1200, 60, 4 }, /* UXGA */ + { 1920, 1080, 60, 5 }, /* HD1080 */ +}; + +static void hdlcd_connector_destroy(struct drm_connector *connector) +{ +} + +static enum drm_connector_status +hdlcd_connector_detect(struct drm_connector *connector, bool force) +{ + if (vconfig_func) + return connector_status_connected; + return connector_status_disconnected; +} + +static const struct drm_connector_funcs hdlcd_connector_funcs = { + .destroy = hdlcd_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = hdlcd_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static int hdlcd_vexpress_con_get_modes(struct drm_connector *connector) +{ + int i; + struct drm_display_mode *mode; + + /* Add the predefined modes */ + for (i = 0; i < ARRAY_SIZE(vexpress_dvimodes); i++) { + mode = drm_mode_find_dmt(connector->dev, + vexpress_dvimodes[i].hsize, + vexpress_dvimodes[i].vsize, + vexpress_dvimodes[i].vrefresh, false); + if (!mode) + continue; + /* prefer the 1280x1024 mode */ + if (i == 3) + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + } + + return i; +} + +/* + * mode valid is only called for detected modes and we know that + * the restricted list is correct ;) + */ +static int hdlcd_vexpress_con_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs hdlcd_vexpress_con_helper_funcs = { + .get_modes = hdlcd_vexpress_con_get_modes, + .mode_valid = hdlcd_vexpress_con_mode_valid, + .best_encoder = hdlcd_connector_best_encoder, +}; + + +static void hdlcd_vexpress_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); + if (vconfig_func) + vexpress_config_func_put(vconfig_func); +} + +static const struct drm_encoder_funcs hdlcd_vexpress_encoder_funcs = { + .destroy = hdlcd_vexpress_encoder_destroy, +}; + +static void hdlcd_vexpress_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + /* VExpress micro has no support for DPMS */ +} + +static bool hdlcd_vexpress_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needs to be done here */ + return true; +} + +static void hdlcd_vexpress_encoder_prepare(struct drm_encoder *encoder) +{ + hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_vexpress_encoder_commit(struct drm_encoder *encoder) +{ + hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void hdlcd_vexpress_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int i, vrefresh = drm_mode_vrefresh(mode); + + if (vconfig_func) { + for (i = 0; i < ARRAY_SIZE(vexpress_dvimodes); i++) { + if (vexpress_dvimodes[i].hsize != mode->hdisplay) + continue; + if (vexpress_dvimodes[i].vsize != mode->vdisplay) + continue; + if (vexpress_dvimodes[i].vrefresh != vrefresh) + continue; + + vexpress_config_write(vconfig_func, 0, + vexpress_dvimodes[i].dvimode); + return; + } + } +} + +static const struct drm_encoder_helper_funcs +hdlcd_vexpress_encoder_helper_funcs = { + .dpms = hdlcd_vexpress_encoder_dpms, + .mode_fixup = hdlcd_vexpress_encoder_mode_fixup, + .prepare = hdlcd_vexpress_encoder_prepare, + .commit = hdlcd_vexpress_encoder_commit, + .mode_set = hdlcd_vexpress_encoder_mode_set, +}; + +static const struct of_device_id vexpress_dvi_match[] = { + { .compatible = "arm,vexpress-dvimode" }, + {} +}; + +int hdlcd_create_vexpress_connector(struct drm_device *dev, + struct hdlcd_drm_private *hdlcd) +{ + int err; + struct drm_connector *connector; + struct device_node *node; + struct drm_encoder *encoder; + + node = of_find_matching_node(NULL, vexpress_dvi_match); + if (!node) + return -ENXIO; + + vconfig_func = vexpress_config_func_get_by_node(node); + if (!vconfig_func) { + dev_err(dev->dev, "failed to get an output connector\n"); + return -ENXIO; + } + + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + if (!encoder) { + err = -ENOMEM; + goto encoder_alloc_fail; + } + + encoder->possible_crtcs = 1; + encoder->possible_clones = 0; + err = drm_encoder_init(dev, encoder, &hdlcd_vexpress_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (err) + goto encoder_init_fail; + + drm_encoder_helper_add(encoder, &hdlcd_vexpress_encoder_helper_funcs); + + connector = kzalloc(sizeof(*connector), GFP_KERNEL); + if (!connector) { + err = -ENOMEM; + goto connector_alloc_err; + } + + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + err = drm_connector_init(dev, connector, &hdlcd_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + if (err) + goto connector_init_err; + + drm_connector_helper_add(connector, &hdlcd_vexpress_con_helper_funcs); + + connector->encoder = encoder; + err = drm_mode_connector_attach_encoder(connector, encoder); + if (err) + goto connector_attach_err; + + return 0; + +connector_attach_err: + drm_connector_cleanup(connector); +connector_init_err: + kfree(connector); +connector_alloc_err: + drm_encoder_cleanup(encoder); +encoder_init_fail: + kfree(encoder); +encoder_alloc_fail: + vexpress_config_func_put(vconfig_func); + + return err; +} diff --git a/drivers/gpu/drm/arm/hdlcd_virt_encoder.c b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c new file mode 100644 index 000000000000..d9feb066a311 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include "hdlcd_drv.h" + +struct hdlcd_connector { + struct drm_connector connector; + struct display_timings *timings; +}; + +#define conn_to_hdlcd(x) container_of(x, struct hdlcd_connector, connector) + +static void hdlcd_connector_destroy(struct drm_connector *connector) +{ + struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector); + + drm_connector_cleanup(connector); + kfree(hdlcd); +} + +static enum drm_connector_status hdlcd_connector_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs hdlcd_connector_funcs = { + .destroy = hdlcd_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = hdlcd_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static int hdlcd_connector_get_modes(struct drm_connector *connector) +{ + struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector); + struct display_timings *timings = hdlcd->timings; + int i; + + for (i = 0; i < timings->num_timings; i++) { + struct drm_display_mode *mode = drm_mode_create(connector->dev); + struct videomode vm; + + if (videomode_from_timings(timings, &vm, i)) + break; + + drm_display_mode_from_videomode(&vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER; + if (timings->native_mode == i) + mode->type = DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int hdlcd_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs hdlcd_virt_con_helper_funcs = { + .get_modes = hdlcd_connector_get_modes, + .mode_valid = hdlcd_connector_mode_valid, + .best_encoder = hdlcd_connector_best_encoder, +}; + +static void hdlcd_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_funcs hdlcd_encoder_funcs = { + .destroy = hdlcd_encoder_destroy, +}; + +static void hdlcd_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool hdlcd_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void hdlcd_encoder_prepare(struct drm_encoder *encoder) +{ + hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_encoder_commit(struct drm_encoder *encoder) +{ + hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void hdlcd_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_helper_funcs hdlcd_encoder_helper_funcs = { + .dpms = hdlcd_encoder_dpms, + .mode_fixup = hdlcd_encoder_mode_fixup, + .prepare = hdlcd_encoder_prepare, + .commit = hdlcd_encoder_commit, + .mode_set = hdlcd_encoder_mode_set, +}; + +int hdlcd_create_virtual_connector(struct drm_device *dev) +{ + struct drm_encoder *encoder; + struct hdlcd_connector *hdlcdc; + struct drm_connector *connector; + int ret; + + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + if (!encoder) + return -ENOMEM; + + encoder->possible_crtcs = 1; + encoder->possible_clones = 0; + + ret = drm_encoder_init(dev, encoder, &hdlcd_encoder_funcs, + DRM_MODE_ENCODER_VIRTUAL); + if (ret) + goto encoder_init_err; + + drm_encoder_helper_add(encoder, &hdlcd_encoder_helper_funcs); + + hdlcdc = kzalloc(sizeof(*hdlcdc), GFP_KERNEL); + if (!hdlcdc) { + ret = -ENOMEM; + goto connector_alloc_err; + } + + hdlcdc->timings = of_get_display_timings(dev->platformdev->dev.of_node); + if (!hdlcdc->timings) { + dev_err(dev->dev, "failed to get display panel timings\n"); + ret = -ENXIO; + goto connector_init_err; + } + + connector = &hdlcdc->connector; + + /* bogus values, pretend we're a 24" screen */ + connector->display_info.width_mm = 519; + connector->display_info.height_mm = 324; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + ret = drm_connector_init(dev, connector, &hdlcd_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + + drm_connector_helper_add(connector, &hdlcd_virt_con_helper_funcs); + + connector->encoder = encoder; + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + + return ret; + +attach_err: + drm_connector_cleanup(connector); +connector_init_err: + kfree(hdlcdc); +connector_alloc_err: + drm_encoder_cleanup(encoder); +encoder_init_err: + kfree(encoder); + + return ret; +}; diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 83f0ba5859c0..4184970c1e36 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1597,13 +1597,8 @@ drm_mode_std(struct drm_connector *connector, struct edid *edid, } /* check whether it can be found in default mode table */ - if (drm_monitor_supports_rb(edid)) { - mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate, - true); - if (mode) - return mode; - } - mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate, false); + mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate, + drm_monitor_supports_rb(edid)); if (mode) return mode; @@ -1994,7 +1989,8 @@ do_inferred_modes(struct detailed_timing *timing, void *c) closure->edid, timing); - if (!version_greater(closure->edid, 1, 1)) + if (!version_greater(closure->edid, 1, 1) || + !(closure->edid->features & DRM_EDID_FEATURE_DEFAULT_GTF)) return; /* GTF not defined yet */ switch (range->flags) { @@ -2951,8 +2947,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) num_modes += add_cvt_modes(connector, edid); num_modes += add_standard_modes(connector, edid); num_modes += add_established_modes(connector, edid); - if (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF) - num_modes += add_inferred_modes(connector, edid); + num_modes += add_inferred_modes(connector, edid); num_modes += add_cea_modes(connector, edid); if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75)) diff --git a/drivers/gpu/drm/drm_encoder_slave.c b/drivers/gpu/drm/drm_encoder_slave.c index 0cfb60f54766..36c0aa740bcd 100644 --- a/drivers/gpu/drm/drm_encoder_slave.c +++ b/drivers/gpu/drm/drm_encoder_slave.c @@ -25,6 +25,7 @@ */ #include <linux/module.h> +#include <linux/of_i2c.h> #include <drm/drm_encoder_slave.h> @@ -61,7 +62,10 @@ int drm_i2c_encoder_init(struct drm_device *dev, request_module("%s%s", I2C_MODULE_PREFIX, info->type); - client = i2c_new_device(adap, info); + if (info->of_node) + client = of_find_i2c_device_by_node(info->of_node); + else + client = i2c_new_device(adap, info); if (!client) { err = -ENOMEM; goto fail; diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index 239ef30f4a62..c9d7081acf59 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -640,6 +640,55 @@ void drm_gem_vm_close(struct vm_area_struct *vma) } EXPORT_SYMBOL(drm_gem_vm_close); +/** + * drm_gem_mmap_obj - memory map a GEM object + * @obj: the GEM object to map + * @obj_size: the object size to be mapped, in bytes + * @vma: VMA for the area to be mapped + * + * Set up the VMA to prepare mapping of the GEM object using the gem_vm_ops + * provided by the driver. Depending on their requirements, drivers can either + * provide a fault handler in their gem_vm_ops (in which case any accesses to + * the object will be trapped, to perform migration, GTT binding, surface + * register allocation, or performance monitoring), or mmap the buffer memory + * synchronously after calling drm_gem_mmap_obj. + * + * This function is mainly intended to implement the DMABUF mmap operation, when + * the GEM object is not looked up based on its fake offset. To implement the + * DRM mmap operation, drivers should use the drm_gem_mmap() function. + * + * Return 0 or success or -EINVAL if the object size is smaller than the VMA + * size, or if no gem_vm_ops are provided. + */ +int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size, + struct vm_area_struct *vma) +{ + struct drm_device *dev = obj->dev; + + /* Check for valid size. */ + if (obj_size < vma->vm_end - vma->vm_start) + return -EINVAL; + + if (!dev->driver->gem_vm_ops) + return -EINVAL; + + vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_ops = dev->driver->gem_vm_ops; + vma->vm_private_data = obj; + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + + /* Take a ref for this mapping of the object, so that the fault + * handler can dereference the mmap offset's pointer to the object. + * This reference is cleaned up by the corresponding vm_close + * (which should happen whether the vma was created by this call, or + * by a vm_open due to mremap or partial unmap or whatever). + */ + drm_gem_object_reference(obj); + + drm_vm_open_locked(dev, vma); + return 0; +} +EXPORT_SYMBOL(drm_gem_mmap_obj); /** * drm_gem_mmap - memory map routine for GEM objects @@ -649,11 +698,9 @@ EXPORT_SYMBOL(drm_gem_vm_close); * If a driver supports GEM object mapping, mmap calls on the DRM file * descriptor will end up here. * - * If we find the object based on the offset passed in (vma->vm_pgoff will + * Look up the GEM object based on the offset passed in (vma->vm_pgoff will * contain the fake offset we created when the GTT map ioctl was called on - * the object), we set up the driver fault handler so that any accesses - * to the object can be trapped, to perform migration, GTT binding, surface - * register allocation, or performance monitoring. + * the object) and map it with a call to drm_gem_mmap_obj(). */ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) { @@ -661,7 +708,6 @@ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) struct drm_device *dev = priv->minor->dev; struct drm_gem_mm *mm = dev->mm_private; struct drm_local_map *map = NULL; - struct drm_gem_object *obj; struct drm_hash_item *hash; int ret = 0; @@ -682,32 +728,7 @@ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) goto out_unlock; } - /* Check for valid size. */ - if (map->size < vma->vm_end - vma->vm_start) { - ret = -EINVAL; - goto out_unlock; - } - - obj = map->handle; - if (!obj->dev->driver->gem_vm_ops) { - ret = -EINVAL; - goto out_unlock; - } - - vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; - vma->vm_ops = obj->dev->driver->gem_vm_ops; - vma->vm_private_data = map->handle; - vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); - - /* Take a ref for this mapping of the object, so that the fault - * handler can dereference the mmap offset's pointer to the object. - * This reference is cleaned up by the corresponding vm_close - * (which should happen whether the vma was created by this call, or - * by a vm_open due to mremap or partial unmap or whatever). - */ - drm_gem_object_reference(obj); - - drm_vm_open_locked(dev, vma); + ret = drm_gem_mmap_obj(map->handle, map->size, vma); out_unlock: mutex_unlock(&dev->struct_mutex); diff --git a/drivers/gpu/drm/drm_gem_cma_helper.c b/drivers/gpu/drm/drm_gem_cma_helper.c index c25b7a0eb0e3..23d9f408ef18 100644 --- a/drivers/gpu/drm/drm_gem_cma_helper.c +++ b/drivers/gpu/drm/drm_gem_cma_helper.c @@ -35,8 +35,12 @@ static unsigned int get_gem_mmap_offset(struct drm_gem_object *obj) static void drm_gem_cma_buf_destroy(struct drm_device *drm, struct drm_gem_cma_object *cma_obj) { - dma_free_writecombine(drm->dev, cma_obj->base.size, cma_obj->vaddr, - cma_obj->paddr); + DEFINE_DMA_ATTRS(attrs); + + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + + dma_free_attrs(drm->dev, cma_obj->base.size, + cma_obj->vaddr, cma_obj->paddr, &attrs); } /* @@ -51,6 +55,9 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm, struct drm_gem_cma_object *cma_obj; struct drm_gem_object *gem_obj; int ret; + DEFINE_DMA_ATTRS(attrs); + + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); size = round_up(size, PAGE_SIZE); @@ -58,8 +65,8 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm, if (!cma_obj) return ERR_PTR(-ENOMEM); - cma_obj->vaddr = dma_alloc_writecombine(drm->dev, size, - &cma_obj->paddr, GFP_KERNEL | __GFP_NOWARN); + cma_obj->vaddr = dma_alloc_attrs(drm->dev, size, + &cma_obj->paddr, GFP_KERNEL | __GFP_NOWARN, &attrs); if (!cma_obj->vaddr) { dev_err(drm->dev, "failed to allocate buffer with size %d\n", size); ret = -ENOMEM; @@ -139,7 +146,6 @@ err_handle_create: void drm_gem_cma_free_object(struct drm_gem_object *gem_obj) { struct drm_gem_cma_object *cma_obj; - if (gem_obj->map_list.map) drm_gem_free_mmap_offset(gem_obj); diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 4d341db462a2..508e1e6f3194 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -11,6 +11,13 @@ config DRM_I2C_CH7006 This driver is currently only useful if you're also using the nouveau driver. +config DRM_I2C_SII9022 + tristate "Silicon Image SiI9022 HDMI transmitter" + default m if DRM_HDLCD + help + Support for SiI9022 HDMI transmitter, used on the + ARM Versatile Express motherboards. + config DRM_I2C_SIL164 tristate "Silicon Image sil164 TMDS transmitter" default m if DRM_NOUVEAU diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 43aa33baebed..c1e2ee0004c3 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -3,8 +3,11 @@ ccflags-y := -Iinclude/drm ch7006-y := ch7006_drv.o ch7006_mode.o obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o +sii9022-y := sii9022_drv.o +obj-$(CONFIG_DRM_I2C_SII9022) += sii9022.o + sil164-y := sil164_drv.o obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o -tda998x-y := tda998x_drv.o +tda998x-y := tda998x_drv.o tda998x_codec.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o diff --git a/drivers/gpu/drm/i2c/sii9022_drv.c b/drivers/gpu/drm/i2c/sii9022_drv.c new file mode 100644 index 000000000000..5ba82d0182f5 --- /dev/null +++ b/drivers/gpu/drm/i2c/sii9022_drv.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2013 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Silicon Image SiI9022 driver for DRM I2C encoder slave + */ + +#include <linux/module.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> + +#define SII9022_INFORMAT_REG 0x09 +#define SII9022_OUTFORMAT_REG 0x0a +#define SII9022_SYS_CONTROL_REG 0x1a +# define SII9022_DDC_OUT_MODE (1 << 0) +# define SII9022_DDC_BUS_STAT (1 << 1) +# define SII9022_DDC_BUS_GRANT (1 << 2) +# define SII9022_AV_MUTE (1 << 3) +# define SII9022_OUTPUT_EN (1 << 4) +#define SII9022_ID_REG 0x1b +#define SII9022_POWER_REG 0x1e +#define SII9022_SEC_CTRL_REG 0x2a +#define SII9022_SEC_STATUS_REG 0x29 +#define SII9022_SEC_VERSION_REG 0x30 +#define SII9022_INTR_REG 0x3c +#define SII9022_INTR_STATUS 0x3d +# define SII9022_HOTPLUG_EVENT (1 << 0) +# define SII9022_RECEIVER_EVENT (1 << 1) +# define SII9022_HOTPLUG_STATE (1 << 2) +# define SII9022_RECEIVER_STATE (1 << 3) +#define SII9022_VENDOR_ID 0xb0 +#define SII9022_INTERNAL_PAGE 0xbc +#define SII9022_INTERNAL_INDEX 0xbd +#define SII9022_INTERNAL_REG 0xbe +#define SII9022_CTRL_REG 0xc7 + + +struct sii9022_video_regs { + uint16_t pixel_clock; + uint16_t vrefresh; + uint16_t cols; + uint16_t lines; + uint8_t pixel_data; +}; + +static void sii9022_write(struct i2c_client *client, uint8_t addr, uint8_t val) +{ + uint8_t buf[] = { addr, val }; + int ret; + + ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + if (ret < 0) + dev_err(&client->dev, "Error writing to subaddress 0x%x: %d\n", addr, ret); +} + +static uint8_t sii9022_read(struct i2c_client *client, uint8_t addr) +{ + uint8_t val; + int ret; + + ret = i2c_master_send(client, &addr, sizeof(addr)); + if (ret < 0) + goto fail; + + ret = i2c_master_recv(client, &val, sizeof(val)); + if (ret < 0) + goto fail; + + return val; + +fail: + dev_err(&client->dev, "Error reading from subaddress 0x%x: %d\n", addr, ret); + return 0; +} + +static void sii9022_encoder_set_config(struct drm_encoder *encoder, void *params) +{ +} + +static void sii9022_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + uint8_t val; + + switch (mode) { + case DRM_MODE_DPMS_OFF: + val = sii9022_read(client, SII9022_SYS_CONTROL_REG); + val |= SII9022_OUTPUT_EN; + sii9022_write(client, SII9022_SYS_CONTROL_REG, val); + /* wait for AVI InfoFrames to flush */ + mdelay(128); + /* use D2 for OFF as we cannot control the reset pin */ + sii9022_write(client, SII9022_POWER_REG, 0x2); + break; + case DRM_MODE_DPMS_ON: + val = sii9022_read(client, SII9022_SYS_CONTROL_REG); + val &= ~SII9022_OUTPUT_EN; + sii9022_write(client, SII9022_SYS_CONTROL_REG, val); + /* fall through */ + default: + sii9022_write(client, SII9022_POWER_REG, mode); + break; + } +} + +static enum drm_connector_status +sii9022_encoder_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + enum drm_connector_status con_status = connector_status_unknown; + uint8_t status = sii9022_read(client, SII9022_INTR_STATUS); + + if (status & SII9022_HOTPLUG_STATE) + con_status = connector_status_connected; + else + con_status = connector_status_disconnected; + + /* clear the event status bits */ + sii9022_write(client, SII9022_INTR_STATUS, + 0xff /*SII9022_HOTPLUG_EVENT | SII9022_RECEIVER_EVENT */); + status = sii9022_read(client, SII9022_INTR_STATUS); + + return con_status; +} + +static int sii9022_encoder_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + struct edid *edid = NULL; + uint8_t status; + int ret = 0, timeout = 10; + + /* Disable HDCP link security */ + do { + sii9022_write(client, SII9022_SEC_CTRL_REG, 0); + status = sii9022_read(client, SII9022_SEC_CTRL_REG); + } while (status); + status = sii9022_read(client, SII9022_SEC_STATUS_REG); + + /* first, request the pass-through mode in order to read the edid */ + status = sii9022_read(client, SII9022_SYS_CONTROL_REG); + status |= SII9022_DDC_BUS_GRANT; + sii9022_write(client, SII9022_SYS_CONTROL_REG, status); + do { + /* wait for state change */ + status = sii9022_read(client, SII9022_SYS_CONTROL_REG); + --timeout; + } while (((status & SII9022_DDC_BUS_STAT) != SII9022_DDC_BUS_STAT) && timeout); + + if (!timeout) { + dev_warn(&client->dev, "timeout waiting for DDC bus grant\n"); + goto release_ddc; + } + + /* write back the value read in order to close the i2c switch */ + sii9022_write(client, SII9022_SYS_CONTROL_REG, status); + + edid = drm_get_edid(connector, client->adapter); + if (!edid) { + dev_err(&client->dev, "failed to get EDID data\n"); + ret = -1; + } + +release_ddc: + timeout = 10; + do { + status &= ~(SII9022_DDC_BUS_STAT | SII9022_DDC_BUS_GRANT); + sii9022_write(client, SII9022_SYS_CONTROL_REG, status); + status = sii9022_read(client, SII9022_SYS_CONTROL_REG); + --timeout; + } while ((status & (SII9022_DDC_BUS_STAT | SII9022_DDC_BUS_GRANT)) && timeout); + + if (edid) { + drm_mode_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + if (drm_detect_hdmi_monitor(edid)) + sii9022_write(client, SII9022_SYS_CONTROL_REG, status | 1); + else + sii9022_write(client, SII9022_SYS_CONTROL_REG, status & 0xfe); + kfree(edid); + } + + return ret; +} + +static bool sii9022_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int sii9022_encoder_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void sii9022_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + /* SiI9022 clock is pixclock / 10000 Hz */ + int clk = adjusted_mode->clock / 10; + int i, vrefresh = adjusted_mode->vrefresh * 100; + uint8_t buf[15]; /* start address reg + 14 bytes max */ + + /* Set Video Mode (8 registers block) */ + buf[0] = 0; /* start register */ + buf[1] = clk & 0xff; + buf[2] = (clk & 0xff00) >> 8; + buf[3] = vrefresh & 0xff; + buf[4] = (vrefresh & 0xff00) >> 8; + buf[5] = adjusted_mode->crtc_hdisplay & 0xff; + buf[6] = (adjusted_mode->crtc_hdisplay & 0xff00) >> 8; + buf[7] = adjusted_mode->crtc_vdisplay & 0xff; + buf[8] = (adjusted_mode->crtc_vdisplay & 0xff00) >> 8; + + if (i2c_master_send(client, buf, 9) < 0) { + dev_err(&client->dev, "Could not write video mode data\n"); + return; + } + + /* input is full range RGB */ + sii9022_write(client, SII9022_INFORMAT_REG, 0x04); + /* output is full range digital RGB */ + sii9022_write(client, SII9022_OUTFORMAT_REG, 0x17); + + /* set the AVI InfoFrame (14 registers block */ + buf[0] = 0x0c; /* start register */ + buf[1] = 0x0e; /* AVI_DBYTE0 = checksum */ + buf[2] = 0x10; /* AVI_DBYTE1 */ + buf[3] = 0x50; /* AVI_DBYTE2 (colorimetry) */ + buf[4] = 0; /* AVI_DBYTE3 (scaling) */ + buf[5] = drm_match_cea_mode(adjusted_mode); + buf[6] = 0; /* AVI_DBYTE5 (pixel repetition factor) */ + buf[7] = 0; + buf[8] = 0; + buf[9] = 0; + buf[10] = 0; + buf[11] = 0; + buf[12] = 0; + buf[13] = 0; + buf[14] = 0; + + /* calculate checksum */ + buf[1] = 0x82 + 0x02 + 13; /* Identifier code for AVI InfoFrame, length */ + for (i = 2; i < 15; i++) + buf[1] += buf[i]; + buf[1] = 0x100 - buf[1]; + + if (i2c_master_send(client, buf, ARRAY_SIZE(buf)) < 0) { + dev_err(&client->dev, "Could not write video mode data\n"); + return; + } +} + +static struct drm_encoder_slave_funcs sii9022_encoder_funcs = { + .set_config = sii9022_encoder_set_config, + .dpms = sii9022_encoder_dpms, + .detect = sii9022_encoder_detect, + .get_modes = sii9022_encoder_get_modes, + .mode_fixup = sii9022_encoder_mode_fixup, + .mode_valid = sii9022_encoder_mode_valid, + .mode_set = sii9022_encoder_mode_set, +}; + +static int sii9022_encoder_init(struct i2c_client *client, + struct drm_device *dev, + struct drm_encoder_slave *encoder) +{ + encoder->slave_funcs = &sii9022_encoder_funcs; + + return 0; +} + +static struct i2c_device_id sii9022_ids[] = { + { "sii9022-tpi", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sii9022_ids); + +static int sii9022_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int dev_id, dev_rev; + + /* first step is to enable the TPI mode */ + sii9022_write(client, SII9022_CTRL_REG, 0x00); + dev_id = sii9022_read(client, SII9022_ID_REG); + dev_rev = sii9022_read(client, SII9022_ID_REG+1); + if (dev_id != SII9022_VENDOR_ID) { + printk(KERN_INFO "sii9022 not found\n"); + return -ENODEV; + } + dev_id = sii9022_read(client, SII9022_SEC_VERSION_REG); + dev_info(&client->dev, "found %s chip (rev %01u.%01u)\n", + dev_id ? "SiI9024" : "SiI9022", + (dev_rev >> 4) & 0xf, dev_rev & 0xf); + + /* disable interrupts */ + sii9022_write(client, SII9022_INTR_REG, 0x00); + + return 0; +} + +static int sii9022_remove(struct i2c_client *client) +{ + return 0; +} + +static struct drm_i2c_encoder_driver sii9022_driver = { + .i2c_driver = { + .probe = sii9022_probe, + .remove = sii9022_remove, + .driver = { + .name = "sii9022-tpi", + }, + .id_table = sii9022_ids, + .class = I2C_CLASS_DDC, + }, + .encoder_init = sii9022_encoder_init, +}; + +static int __init sii9022_init(void) +{ + return drm_i2c_encoder_register(THIS_MODULE, &sii9022_driver); +} + +static void __exit sii9022_exit(void) +{ + drm_i2c_encoder_unregister(&sii9022_driver); +} + +module_init(sii9022_init); +module_exit(sii9022_exit); + +MODULE_ALIAS(I2C_MODULE_PREFIX "sii9022-tpi"); +MODULE_AUTHOR("Liviu Dudau <Liviu.Dudau@arm.com>"); +MODULE_DESCRIPTION("Silicon Image SiI9022 HDMI transmitter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/i2c/tda998x_codec.c b/drivers/gpu/drm/i2c/tda998x_codec.c new file mode 100644 index 000000000000..c84d2b58f42d --- /dev/null +++ b/drivers/gpu/drm/i2c/tda998x_codec.c @@ -0,0 +1,248 @@ +/* + * ALSA SoC TDA998X CODEC + * + * Copyright (C) 2014 Jean-Francois Moine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/of.h> +#include <linux/i2c.h> +#include <drm/drm_encoder_slave.h> +#include <drm/i2c/tda998x.h> + +#include "tda998x_drv.h" + +#define TDA998X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int tda_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + u8 *eld = priv->eld; + struct snd_pcm_runtime *runtime = substream->runtime; + u8 *sad; + int sad_count; + unsigned eld_ver, mnl, rate_mask; + unsigned max_channels, fmt; + u64 formats; + struct snd_pcm_hw_constraint_list *rate_constraints = + &priv->rate_constraints; + static const u32 hdmi_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + /* check if streaming is already active */ + if (priv->dai_id != AFMT_NO_AUDIO) + return -EBUSY; + priv->dai_id = dai->id; + + if (!eld) + return 0; + + /* adjust the hw params from the ELD (EDID) */ + eld_ver = eld[0] >> 3; + if (eld_ver != 2 && eld_ver != 31) + return 0; + + mnl = eld[4] & 0x1f; + if (mnl > 16) + return 0; + + sad_count = eld[5] >> 4; + sad = eld + 20 + mnl; + + /* Start from the basic audio settings */ + max_channels = 2; + rate_mask = 0; + fmt = 0; + while (sad_count--) { + switch (sad[0] & 0x78) { + case 0x08: /* PCM */ + max_channels = max(max_channels, (sad[0] & 7) + 1u); + rate_mask |= sad[1]; + fmt |= sad[2] & 0x07; + break; + } + sad += 3; + } + + /* set the constraints */ + rate_constraints->list = hdmi_rates; + rate_constraints->count = ARRAY_SIZE(hdmi_rates); + rate_constraints->mask = rate_mask; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + rate_constraints); + + formats = 0; + if (fmt & 1) + formats |= SNDRV_PCM_FMTBIT_S16_LE; + if (fmt & 2) + formats |= SNDRV_PCM_FMTBIT_S20_3LE; + if (fmt & 4) + formats |= SNDRV_PCM_FMTBIT_S24_LE; + snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + formats); + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, max_channels); + return 0; +} + +static int tda_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + /* Requires an attached display */ + if (!priv->encoder->crtc) + return -ENODEV; + + /* if same input and same parameters, do not do a full switch */ + if (dai->id == priv->params.audio_format && + params_format(params) == priv->audio_sample_format) { + tda998x_audio_start(priv, 0); + return 0; + } + priv->params.audio_sample_rate = params_rate(params); + priv->params.audio_format = dai->id; + priv->audio_sample_format = params_format(params); + priv->params.audio_cfg = + priv->audio_ports[dai->id == AFMT_I2S ? 0 : 1]; + tda998x_audio_start(priv, 1); + return 0; +} + +static void tda_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + tda998x_audio_stop(priv); + priv->dai_id = AFMT_NO_AUDIO; +} + +static const struct snd_soc_dai_ops tda_ops = { + .startup = tda_startup, + .hw_params = tda_hw_params, + .shutdown = tda_shutdown, +}; + +static struct snd_soc_dai_driver tda998x_dai[] = { + { + .name = "i2s-hifi", + .id = AFMT_I2S, + .playback = { + .stream_name = "HDMI I2S Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + }, + .ops = &tda_ops, + }, + { + .name = "spdif-hifi", + .id = AFMT_SPDIF, + .playback = { + .stream_name = "HDMI SPDIF Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 22050, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + }, + .ops = &tda_ops, + }, +}; + +static const struct snd_soc_dapm_widget tda_widgets[] = { + SND_SOC_DAPM_OUTPUT("hdmi-out"), +}; +static const struct snd_soc_dapm_route tda_routes[] = { + { "hdmi-out", NULL, "HDMI I2S Playback" }, + { "hdmi-out", NULL, "HDMI SPDIF Playback" }, +}; + +static int tda_probe(struct snd_soc_codec *codec) +{ + struct i2c_client *i2c_client = to_i2c_client(codec->dev); + struct tda998x_priv *priv = i2c_get_clientdata(i2c_client); + struct device_node *np = codec->dev->of_node; + int i, j, ret; + const char *p; + + if (!priv) + return -ENODEV; + snd_soc_codec_set_drvdata(codec, priv); + + if (!np) + return 0; + + /* get the audio input ports*/ + for (i = 0; i < 2; i++) { + u32 port; + + ret = of_property_read_u32_index(np, "audio-ports", i, &port); + if (ret) { + if (i == 0) + dev_err(codec->dev, + "bad or missing audio-ports\n"); + break; + } + ret = of_property_read_string_index(np, "audio-port-names", + i, &p); + if (ret) { + dev_err(codec->dev, + "missing audio-port-names[%d]\n", i); + break; + } + if (strcmp(p, "i2s") == 0) { + j = 0; + } else if (strcmp(p, "spdif") == 0) { + j = 1; + } else { + dev_err(codec->dev, + "bad audio-port-names '%s'\n", p); + break; + } + priv->audio_ports[j] = port; + } + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_tda998x = { + .probe = tda_probe, + .dapm_widgets = tda_widgets, + .num_dapm_widgets = ARRAY_SIZE(tda_widgets), + .dapm_routes = tda_routes, + .num_dapm_routes = ARRAY_SIZE(tda_routes), +}; + +int tda998x_codec_register(struct device *dev) +{ + return snd_soc_register_codec(dev, + &soc_codec_tda998x, + tda998x_dai, ARRAY_SIZE(tda998x_dai)); +} + +void tda998x_codec_unregister(struct device *dev) +{ + snd_soc_unregister_codec(dev); +} diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index e68b58a1aaf9..63723e8fea87 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -17,23 +17,22 @@ +#include <linux/hdmi.h> #include <linux/module.h> +#include <linux/irq.h> +#include <sound/asoundef.h> +#include <sound/pcm_params.h> #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_encoder_slave.h> #include <drm/drm_edid.h> +#include <drm/i2c/tda998x.h> +#include "tda998x_drv.h" #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) -struct tda998x_priv { - struct i2c_client *cec; - uint16_t rev; - uint8_t current_page; - int dpms; -}; - #define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv) /* The TDA9988 series of devices use a paged register scheme.. to simplify @@ -68,10 +67,13 @@ struct tda998x_priv { # define I2C_MASTER_DIS_MM (1 << 0) # define I2C_MASTER_DIS_FILT (1 << 1) # define I2C_MASTER_APP_STRT_LAT (1 << 2) +#define REG_FEAT_POWERDOWN REG(0x00, 0x0e) /* read/write */ +# define FEAT_POWERDOWN_SPDIF (1 << 3) #define REG_INT_FLAGS_0 REG(0x00, 0x0f) /* read/write */ #define REG_INT_FLAGS_1 REG(0x00, 0x10) /* read/write */ #define REG_INT_FLAGS_2 REG(0x00, 0x11) /* read/write */ # define INT_FLAGS_2_EDID_BLK_RD (1 << 1) +#define REG_ENA_ACLK REG(0x00, 0x16) /* read/write */ #define REG_ENA_VP_0 REG(0x00, 0x18) /* read/write */ #define REG_ENA_VP_1 REG(0x00, 0x19) /* read/write */ #define REG_ENA_VP_2 REG(0x00, 0x1a) /* read/write */ @@ -110,6 +112,10 @@ struct tda998x_priv { #define REG_VIP_CNTRL_5 REG(0x00, 0x25) /* write */ # define VIP_CNTRL_5_CKCASE (1 << 0) # define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1) +#define REG_MUX_AP REG(0x00, 0x26) /* read/write */ +# define MUX_AP_SELECT_I2S 0x64 +# define MUX_AP_SELECT_SPDIF 0x40 +#define REG_MUX_VP_VIP_OUT REG(0x00, 0x27) /* read/write */ #define REG_MAT_CONTRL REG(0x00, 0x80) /* write */ # define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0) # define MAT_CONTRL_MAT_BP (1 << 2) @@ -130,8 +136,12 @@ struct tda998x_priv { #define REG_VS_LINE_END_1_LSB REG(0x00, 0xae) /* write */ #define REG_VS_PIX_END_1_MSB REG(0x00, 0xaf) /* write */ #define REG_VS_PIX_END_1_LSB REG(0x00, 0xb0) /* write */ +#define REG_VS_LINE_STRT_2_MSB REG(0x00, 0xb1) /* write */ +#define REG_VS_LINE_STRT_2_LSB REG(0x00, 0xb2) /* write */ #define REG_VS_PIX_STRT_2_MSB REG(0x00, 0xb3) /* write */ #define REG_VS_PIX_STRT_2_LSB REG(0x00, 0xb4) /* write */ +#define REG_VS_LINE_END_2_MSB REG(0x00, 0xb5) /* write */ +#define REG_VS_LINE_END_2_LSB REG(0x00, 0xb6) /* write */ #define REG_VS_PIX_END_2_MSB REG(0x00, 0xb7) /* write */ #define REG_VS_PIX_END_2_LSB REG(0x00, 0xb8) /* write */ #define REG_HS_PIX_START_MSB REG(0x00, 0xb9) /* write */ @@ -142,21 +152,29 @@ struct tda998x_priv { #define REG_VWIN_START_1_LSB REG(0x00, 0xbe) /* write */ #define REG_VWIN_END_1_MSB REG(0x00, 0xbf) /* write */ #define REG_VWIN_END_1_LSB REG(0x00, 0xc0) /* write */ +#define REG_VWIN_START_2_MSB REG(0x00, 0xc1) /* write */ +#define REG_VWIN_START_2_LSB REG(0x00, 0xc2) /* write */ +#define REG_VWIN_END_2_MSB REG(0x00, 0xc3) /* write */ +#define REG_VWIN_END_2_LSB REG(0x00, 0xc4) /* write */ #define REG_DE_START_MSB REG(0x00, 0xc5) /* write */ #define REG_DE_START_LSB REG(0x00, 0xc6) /* write */ #define REG_DE_STOP_MSB REG(0x00, 0xc7) /* write */ #define REG_DE_STOP_LSB REG(0x00, 0xc8) /* write */ #define REG_TBG_CNTRL_0 REG(0x00, 0xca) /* write */ +# define TBG_CNTRL_0_TOP_TGL (1 << 0) +# define TBG_CNTRL_0_TOP_SEL (1 << 1) +# define TBG_CNTRL_0_DE_EXT (1 << 2) +# define TBG_CNTRL_0_TOP_EXT (1 << 3) # define TBG_CNTRL_0_FRAME_DIS (1 << 5) # define TBG_CNTRL_0_SYNC_MTHD (1 << 6) # define TBG_CNTRL_0_SYNC_ONCE (1 << 7) #define REG_TBG_CNTRL_1 REG(0x00, 0xcb) /* write */ -# define TBG_CNTRL_1_VH_TGL_0 (1 << 0) -# define TBG_CNTRL_1_VH_TGL_1 (1 << 1) -# define TBG_CNTRL_1_VH_TGL_2 (1 << 2) -# define TBG_CNTRL_1_VHX_EXT_DE (1 << 3) -# define TBG_CNTRL_1_VHX_EXT_HS (1 << 4) -# define TBG_CNTRL_1_VHX_EXT_VS (1 << 5) +# define TBG_CNTRL_1_H_TGL (1 << 0) +# define TBG_CNTRL_1_V_TGL (1 << 1) +# define TBG_CNTRL_1_TGL_EN (1 << 2) +# define TBG_CNTRL_1_X_EXT (1 << 3) +# define TBG_CNTRL_1_H_EXT (1 << 4) +# define TBG_CNTRL_1_V_EXT (1 << 5) # define TBG_CNTRL_1_DWIN_DIS (1 << 6) #define REG_ENABLE_SPACE REG(0x00, 0xd6) /* write */ #define REG_HVF_CNTRL_0 REG(0x00, 0xe4) /* write */ @@ -171,7 +189,14 @@ struct tda998x_priv { # define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4) # define HVF_CNTRL_1_SEMI_PLANAR (1 << 6) #define REG_RPT_CNTRL REG(0x00, 0xf0) /* write */ - +#define REG_I2S_FORMAT REG(0x00, 0xfc) /* read/write */ +# define I2S_FORMAT(x) (((x) & 3) << 0) +#define REG_AIP_CLKSEL REG(0x00, 0xfd) /* write */ +# define AIP_CLKSEL_AIP_SPDIF (0 << 3) +# define AIP_CLKSEL_AIP_I2S (1 << 3) +# define AIP_CLKSEL_FS_ACLK (0 << 0) +# define AIP_CLKSEL_FS_MCLK (1 << 0) +# define AIP_CLKSEL_FS_FS64SPDIF (2 << 0) /* Page 02h: PLL settings */ #define REG_PLL_SERIAL_1 REG(0x02, 0x00) /* read/write */ @@ -179,7 +204,7 @@ struct tda998x_priv { # define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1) # define PLL_SERIAL_1_SRL_MAN_IZ (1 << 6) #define REG_PLL_SERIAL_2 REG(0x02, 0x01) /* read/write */ -# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0) +# define PLL_SERIAL_2_SRL_NOSC(x) ((x) << 0) # define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4) #define REG_PLL_SERIAL_3 REG(0x02, 0x02) /* read/write */ # define PLL_SERIAL_3_SRL_CCIR (1 << 0) @@ -194,6 +219,12 @@ struct tda998x_priv { #define REG_PLL_SCGR1 REG(0x02, 0x09) /* read/write */ #define REG_PLL_SCGR2 REG(0x02, 0x0a) /* read/write */ #define REG_AUDIO_DIV REG(0x02, 0x0e) /* read/write */ +# define AUDIO_DIV_SERCLK_1 0 +# define AUDIO_DIV_SERCLK_2 1 +# define AUDIO_DIV_SERCLK_4 2 +# define AUDIO_DIV_SERCLK_8 3 +# define AUDIO_DIV_SERCLK_16 4 +# define AUDIO_DIV_SERCLK_32 5 #define REG_SEL_CLK REG(0x02, 0x11) /* read/write */ # define SEL_CLK_SEL_CLK1 (1 << 0) # define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1) @@ -212,6 +243,11 @@ struct tda998x_priv { /* Page 10h: information frames and packets */ +#define REG_IF1_HB0 REG(0x10, 0x20) /* read/write */ +#define REG_IF2_HB0 REG(0x10, 0x40) /* read/write */ +#define REG_IF3_HB0 REG(0x10, 0x60) /* read/write */ +#define REG_IF4_HB0 REG(0x10, 0x80) /* read/write */ +#define REG_IF5_HB0 REG(0x10, 0xa0) /* read/write */ /* Page 11h: audio settings and content info packets */ @@ -221,14 +257,39 @@ struct tda998x_priv { # define AIP_CNTRL_0_LAYOUT (1 << 2) # define AIP_CNTRL_0_ACR_MAN (1 << 5) # define AIP_CNTRL_0_RST_CTS (1 << 6) +#define REG_CA_I2S REG(0x11, 0x01) /* read/write */ +# define CA_I2S_CA_I2S(x) (((x) & 31) << 0) +# define CA_I2S_HBR_CHSTAT (1 << 6) +#define REG_LATENCY_RD REG(0x11, 0x04) /* read/write */ +#define REG_ACR_CTS_0 REG(0x11, 0x05) /* read/write */ +#define REG_ACR_CTS_1 REG(0x11, 0x06) /* read/write */ +#define REG_ACR_CTS_2 REG(0x11, 0x07) /* read/write */ +#define REG_ACR_N_0 REG(0x11, 0x08) /* read/write */ +#define REG_ACR_N_1 REG(0x11, 0x09) /* read/write */ +#define REG_ACR_N_2 REG(0x11, 0x0a) /* read/write */ +#define REG_CTS_N REG(0x11, 0x0c) /* read/write */ +# define CTS_N_K(x) (((x) & 7) << 0) +# define CTS_N_M(x) (((x) & 3) << 4) #define REG_ENC_CNTRL REG(0x11, 0x0d) /* read/write */ # define ENC_CNTRL_RST_ENC (1 << 0) # define ENC_CNTRL_RST_SEL (1 << 1) # define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2) +#define REG_DIP_FLAGS REG(0x11, 0x0e) /* read/write */ +# define DIP_FLAGS_ACR (1 << 0) +# define DIP_FLAGS_GC (1 << 1) +#define REG_DIP_IF_FLAGS REG(0x11, 0x0f) /* read/write */ +# define DIP_IF_FLAGS_IF1 (1 << 1) +# define DIP_IF_FLAGS_IF2 (1 << 2) +# define DIP_IF_FLAGS_IF3 (1 << 3) +# define DIP_IF_FLAGS_IF4 (1 << 4) +# define DIP_IF_FLAGS_IF5 (1 << 5) +#define REG_CH_STAT_B(x) REG(0x11, 0x14 + (x)) /* read/write */ /* Page 12h: HDCP and OTP */ #define REG_TX3 REG(0x12, 0x9a) /* read/write */ +#define REG_TX4 REG(0x12, 0x9b) /* read/write */ +# define TX4_PD_RAM (1 << 1) #define REG_TX33 REG(0x12, 0xb8) /* read/write */ # define TX33_HDMI (1 << 1) @@ -239,11 +300,16 @@ struct tda998x_priv { /* CEC registers: (not paged) */ +#define REG_CEC_INTSTATUS 0xee /* read */ +# define CEC_INTSTATUS_CEC (1 << 0) +# define CEC_INTSTATUS_HDMI (1 << 1) #define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */ # define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7) # define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6) # define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1) # define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0) +#define REG_CEC_RXSHPDINTENA 0xfc /* read/write */ +#define REG_CEC_RXSHPDINT 0xfd /* read */ #define REG_CEC_RXSHPDLEV 0xfe /* read */ # define CEC_RXSHPDLEV_RXSENS (1 << 0) # define CEC_RXSHPDLEV_HPD (1 << 1) @@ -263,21 +329,21 @@ struct tda998x_priv { #define TDA19988 0x0301 static void -cec_write(struct drm_encoder *encoder, uint16_t addr, uint8_t val) +cec_write(struct tda998x_priv *priv, uint16_t addr, uint8_t val) { - struct i2c_client *client = to_tda998x_priv(encoder)->cec; + struct i2c_client *client = priv->cec; uint8_t buf[] = {addr, val}; int ret; - ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + ret = i2c_master_send(client, buf, sizeof(buf)); if (ret < 0) dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr); } static uint8_t -cec_read(struct drm_encoder *encoder, uint8_t addr) +cec_read(struct tda998x_priv *priv, uint8_t addr) { - struct i2c_client *client = to_tda998x_priv(encoder)->cec; + struct i2c_client *client = priv->cec; uint8_t val; int ret; @@ -296,32 +362,36 @@ fail: return 0; } -static void -set_page(struct drm_encoder *encoder, uint16_t reg) +static int +set_page(struct tda998x_priv *priv, uint16_t reg) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); - if (REG2PAGE(reg) != priv->current_page) { - struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + struct i2c_client *client = priv->hdmi; uint8_t buf[] = { REG_CURPAGE, REG2PAGE(reg) }; int ret = i2c_master_send(client, buf, sizeof(buf)); - if (ret < 0) - dev_err(&client->dev, "Error %d writing to REG_CURPAGE\n", ret); + if (ret < 0) { + dev_err(&client->dev, "setpage %04x err %d\n", + reg, ret); + return ret; + } priv->current_page = REG2PAGE(reg); } + return 0; } static int -reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt) +reg_read_range(struct tda998x_priv *priv, uint16_t reg, char *buf, int cnt) { - struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + struct i2c_client *client = priv->hdmi; uint8_t addr = REG2ADDR(reg); int ret; - set_page(encoder, reg); + ret = set_page(priv, reg); + if (ret < 0) + return ret; ret = i2c_master_send(client, &addr, sizeof(addr)); if (ret < 0) @@ -338,81 +408,359 @@ fail: return ret; } -static uint8_t -reg_read(struct drm_encoder *encoder, uint16_t reg) +static void +reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt) +{ + struct i2c_client *client = priv->hdmi; + uint8_t buf[cnt+1]; + int ret; + + buf[0] = REG2ADDR(reg); + memcpy(&buf[1], p, cnt); + + ret = set_page(priv, reg); + if (ret < 0) + return; + + ret = i2c_master_send(client, buf, cnt + 1); + if (ret < 0) + dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); +} + +static int +reg_read(struct tda998x_priv *priv, uint16_t reg) { uint8_t val = 0; - reg_read_range(encoder, reg, &val, sizeof(val)); + int ret; + + ret = reg_read_range(priv, reg, &val, sizeof(val)); + if (ret < 0) + return ret; return val; } static void -reg_write(struct drm_encoder *encoder, uint16_t reg, uint8_t val) +reg_write(struct tda998x_priv *priv, uint16_t reg, uint8_t val) { - struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + struct i2c_client *client = priv->hdmi; uint8_t buf[] = {REG2ADDR(reg), val}; int ret; - set_page(encoder, reg); + ret = set_page(priv, reg); + if (ret < 0) + return; - ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + ret = i2c_master_send(client, buf, sizeof(buf)); if (ret < 0) dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); } static void -reg_write16(struct drm_encoder *encoder, uint16_t reg, uint16_t val) +reg_write16(struct tda998x_priv *priv, uint16_t reg, uint16_t val) { - struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + struct i2c_client *client = priv->hdmi; uint8_t buf[] = {REG2ADDR(reg), val >> 8, val}; int ret; - set_page(encoder, reg); + ret = set_page(priv, reg); + if (ret < 0) + return; - ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + ret = i2c_master_send(client, buf, sizeof(buf)); if (ret < 0) dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); } static void -reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val) +reg_set(struct tda998x_priv *priv, uint16_t reg, uint8_t val) { - reg_write(encoder, reg, reg_read(encoder, reg) | val); + int old_val; + + old_val = reg_read(priv, reg); + if (old_val >= 0) + reg_write(priv, reg, old_val | val); } static void -reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val) +reg_clear(struct tda998x_priv *priv, uint16_t reg, uint8_t val) { - reg_write(encoder, reg, reg_read(encoder, reg) & ~val); + int old_val; + + old_val = reg_read(priv, reg); + if (old_val >= 0) + reg_write(priv, reg, old_val & ~val); } static void -tda998x_reset(struct drm_encoder *encoder) +tda998x_reset(struct tda998x_priv *priv) { /* reset audio and i2c master: */ - reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER); + reg_write(priv, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER); msleep(50); - reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER); + reg_write(priv, REG_SOFTRESET, 0); msleep(50); /* reset transmitter: */ - reg_set(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); - reg_clear(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); + reg_set(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); + reg_clear(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); /* PLL registers common configuration */ - reg_write(encoder, REG_PLL_SERIAL_1, 0x00); - reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1)); - reg_write(encoder, REG_PLL_SERIAL_3, 0x00); - reg_write(encoder, REG_SERIALIZER, 0x00); - reg_write(encoder, REG_BUFFER_OUT, 0x00); - reg_write(encoder, REG_PLL_SCG1, 0x00); - reg_write(encoder, REG_AUDIO_DIV, 0x03); - reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); - reg_write(encoder, REG_PLL_SCGN1, 0xfa); - reg_write(encoder, REG_PLL_SCGN2, 0x00); - reg_write(encoder, REG_PLL_SCGR1, 0x5b); - reg_write(encoder, REG_PLL_SCGR2, 0x00); - reg_write(encoder, REG_PLL_SCG2, 0x10); + reg_write(priv, REG_PLL_SERIAL_1, 0x00); + reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1)); + reg_write(priv, REG_PLL_SERIAL_3, 0x00); + reg_write(priv, REG_SERIALIZER, 0x00); + reg_write(priv, REG_BUFFER_OUT, 0x00); + reg_write(priv, REG_PLL_SCG1, 0x00); + reg_write(priv, REG_AUDIO_DIV, AUDIO_DIV_SERCLK_8); + reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); + reg_write(priv, REG_PLL_SCGN1, 0xfa); + reg_write(priv, REG_PLL_SCGN2, 0x00); + reg_write(priv, REG_PLL_SCGR1, 0x5b); + reg_write(priv, REG_PLL_SCGR2, 0x00); + reg_write(priv, REG_PLL_SCG2, 0x10); + + /* Write the default value MUX register */ + reg_write(priv, REG_MUX_VP_VIP_OUT, 0x24); +} + +/* + * only 2 interrupts may occur: screen plug/unplug and EDID read + */ +static irqreturn_t tda998x_irq_thread(int irq, void *data) +{ + struct tda998x_priv *priv = data; + u8 sta, cec, lvl, flag0, flag1, flag2; + + if (!priv) + return IRQ_HANDLED; + sta = cec_read(priv, REG_CEC_INTSTATUS); + cec = cec_read(priv, REG_CEC_RXSHPDINT); + lvl = cec_read(priv, REG_CEC_RXSHPDLEV); + flag0 = reg_read(priv, REG_INT_FLAGS_0); + flag1 = reg_read(priv, REG_INT_FLAGS_1); + flag2 = reg_read(priv, REG_INT_FLAGS_2); + DRM_DEBUG_DRIVER( + "tda irq sta %02x cec %02x lvl %02x f0 %02x f1 %02x f2 %02x\n", + sta, cec, lvl, flag0, flag1, flag2); + if ((flag2 & INT_FLAGS_2_EDID_BLK_RD) && priv->wq_edid_wait) { + priv->wq_edid_wait = 0; + wake_up(&priv->wq_edid); + } else if (cec != 0) { /* HPD change */ + if (priv->encoder && priv->encoder->dev) + drm_helper_hpd_irq_event(priv->encoder->dev); + } + return IRQ_HANDLED; +} + +static uint8_t tda998x_cksum(uint8_t *buf, size_t bytes) +{ + uint8_t sum = 0; + + while (bytes--) + sum += *buf++; + return (255 - sum) + 1; +} + +#define HB(x) (x) +#define PB(x) (HB(2) + 1 + (x)) + +static void +tda998x_write_if(struct tda998x_priv *priv, uint8_t bit, uint16_t addr, + uint8_t *buf, size_t size) +{ + buf[PB(0)] = tda998x_cksum(buf, size); + + reg_clear(priv, REG_DIP_IF_FLAGS, bit); + reg_write_range(priv, addr, buf, size); + reg_set(priv, REG_DIP_IF_FLAGS, bit); +} + +static void +tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) +{ + u8 buf[PB(HDMI_AUDIO_INFOFRAME_SIZE) + 1]; + + memset(buf, 0, sizeof(buf)); + buf[HB(0)] = HDMI_INFOFRAME_TYPE_AUDIO; + buf[HB(1)] = 0x01; + buf[HB(2)] = HDMI_AUDIO_INFOFRAME_SIZE; + buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */ + buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */ + buf[PB(4)] = p->audio_frame[4]; + buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */ + + tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, + sizeof(buf)); +} + +static void +tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode) +{ + u8 buf[PB(HDMI_AVI_INFOFRAME_SIZE) + 1]; + + memset(buf, 0, sizeof(buf)); + buf[HB(0)] = HDMI_INFOFRAME_TYPE_AVI; + buf[HB(1)] = 0x02; + buf[HB(2)] = HDMI_AVI_INFOFRAME_SIZE; + buf[PB(1)] = HDMI_SCAN_MODE_UNDERSCAN; + buf[PB(2)] = HDMI_ACTIVE_ASPECT_PICTURE; + buf[PB(3)] = HDMI_QUANTIZATION_RANGE_FULL << 2; + buf[PB(4)] = drm_match_cea_mode(mode); + + tda998x_write_if(priv, DIP_IF_FLAGS_IF2, REG_IF2_HB0, buf, + sizeof(buf)); +} + +static void tda998x_audio_mute(struct tda998x_priv *priv, bool on) +{ + if (on) { + reg_set(priv, REG_SOFTRESET, SOFTRESET_AUDIO); + reg_clear(priv, REG_SOFTRESET, SOFTRESET_AUDIO); + reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO); + } else { + reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO); + } +} + +static void +tda998x_configure_audio(struct tda998x_priv *priv, + struct drm_display_mode *mode, struct tda998x_encoder_params *p) +{ + uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv, aclk; + uint32_t n, cts; + + /* Enable audio ports */ + reg_write(priv, REG_ENA_AP, p->audio_cfg); + + /* Set audio input source */ + switch (p->audio_format) { + case AFMT_SPDIF: + reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF); + clksel_aip = AIP_CLKSEL_AIP_SPDIF; + clksel_fs = AIP_CLKSEL_FS_FS64SPDIF; + cts_n = CTS_N_M(3) | CTS_N_K(3); + aclk = 0; /* no clock */ + break; + + case AFMT_I2S: + reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S); + clksel_aip = AIP_CLKSEL_AIP_I2S; + clksel_fs = AIP_CLKSEL_FS_ACLK; + /* with I2S input, the CTS_N predivider depends on + * the sample width */ + switch (priv->audio_sample_format) { + case SNDRV_PCM_FORMAT_S16_LE: + cts_n = CTS_N_M(3) | CTS_N_K(1); + break; + case SNDRV_PCM_FORMAT_S24_LE: + cts_n = CTS_N_M(3) | CTS_N_K(2); + break; + default: + case SNDRV_PCM_FORMAT_S32_LE: + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + } + aclk = 1; /* clock enable */ + break; + + default: + BUG(); + return; + } + + reg_write(priv, REG_AIP_CLKSEL, clksel_aip); + reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT | + AIP_CNTRL_0_ACR_MAN); /* auto CTS */ + reg_write(priv, REG_CTS_N, cts_n); + reg_write(priv, REG_ENA_ACLK, aclk); + + /* + * Audio input somehow depends on HDMI line rate which is + * related to pixclk. Testing showed that modes with pixclk + * >100MHz need a larger divider while <40MHz need the default. + * There is no detailed info in the datasheet, so we just + * assume 100MHz requires larger divider. + */ + adiv = AUDIO_DIV_SERCLK_8; + if (mode->clock > 100000) + adiv++; /* AUDIO_DIV_SERCLK_16 */ + + /* S/PDIF asks for a larger divider */ + if (p->audio_format == AFMT_SPDIF) + adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */ + + reg_write(priv, REG_AUDIO_DIV, adiv); + + /* + * This is the approximate value of N, which happens to be + * the recommended values for non-coherent clocks. + */ + n = 128 * p->audio_sample_rate / 1000; + + /* Write the CTS and N values */ + if ((n > 0) && (mode->clock > 0)) { + /* + * The average CTS value is calculated as: + * + * fTMDS * n / (128 * fs) + * + * which equates to: + * + * fTMDS / 1000 + * + * for non-coherent clocks. + */ + cts = mode->clock; + } else { + cts = 82500; + } + buf[0] = cts; + buf[1] = cts >> 8; + buf[2] = cts >> 16; + buf[3] = n; + buf[4] = n >> 8; + buf[5] = n >> 16; + reg_write_range(priv, REG_ACR_CTS_0, buf, 6); + + /* Set CTS clock reference */ + reg_write(priv, REG_AIP_CLKSEL, clksel_aip | clksel_fs); + + /* Reset CTS generator */ + reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); + reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); + + /* Write the channel status */ + buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT; + buf[1] = 0x00; + buf[2] = IEC958_AES3_CON_FS_NOTID; + buf[3] = IEC958_AES4_CON_ORIGFS_NOTID | + IEC958_AES4_CON_MAX_WORDLEN_24; + reg_write_range(priv, REG_CH_STAT_B(0), buf, 4); + + tda998x_audio_mute(priv, true); + msleep(20); + tda998x_audio_mute(priv, false); + + /* Write the audio information packet */ + tda998x_write_aif(priv, p); +} + +/* tda998x codec interface */ +void tda998x_audio_start(struct tda998x_priv *priv, + int full) +{ + struct tda998x_encoder_params *p = &priv->params; + + if (!full) { + reg_write(priv, REG_ENA_AP, p->audio_cfg); + return; + } + tda998x_configure_audio(priv, &priv->encoder->crtc->hwmode, p); +} + +void tda998x_audio_stop(struct tda998x_priv *priv) +{ + reg_write(priv, REG_ENA_AP, 0); } /* DRM encoder functions */ @@ -420,6 +768,23 @@ tda998x_reset(struct drm_encoder *encoder) static void tda998x_encoder_set_config(struct drm_encoder *encoder, void *params) { + struct tda998x_priv *priv = to_tda998x_priv(encoder); + struct tda998x_encoder_params *p = params; + + priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) | + (p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) | + VIP_CNTRL_0_SWAP_B(p->swap_b) | + (p->mirr_b ? VIP_CNTRL_0_MIRR_B : 0); + priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(p->swap_c) | + (p->mirr_c ? VIP_CNTRL_1_MIRR_C : 0) | + VIP_CNTRL_1_SWAP_D(p->swap_d) | + (p->mirr_d ? VIP_CNTRL_1_MIRR_D : 0); + priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(p->swap_e) | + (p->mirr_e ? VIP_CNTRL_2_MIRR_E : 0) | + VIP_CNTRL_2_SWAP_F(p->swap_f) | + (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0); + + priv->params = *p; } static void @@ -436,25 +801,20 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode) switch (mode) { case DRM_MODE_DPMS_ON: - /* enable audio and video ports */ - reg_write(encoder, REG_ENA_AP, 0xff); - reg_write(encoder, REG_ENA_VP_0, 0xff); - reg_write(encoder, REG_ENA_VP_1, 0xff); - reg_write(encoder, REG_ENA_VP_2, 0xff); + /* enable video ports, audio will be enabled later */ + reg_write(priv, REG_ENA_VP_0, 0xff); + reg_write(priv, REG_ENA_VP_1, 0xff); + reg_write(priv, REG_ENA_VP_2, 0xff); /* set muxing after enabling ports: */ - reg_write(encoder, REG_VIP_CNTRL_0, - VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3)); - reg_write(encoder, REG_VIP_CNTRL_1, - VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1)); - reg_write(encoder, REG_VIP_CNTRL_2, - VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5)); + reg_write(priv, REG_VIP_CNTRL_0, priv->vip_cntrl_0); + reg_write(priv, REG_VIP_CNTRL_1, priv->vip_cntrl_1); + reg_write(priv, REG_VIP_CNTRL_2, priv->vip_cntrl_2); break; case DRM_MODE_DPMS_OFF: - /* disable audio and video ports */ - reg_write(encoder, REG_ENA_AP, 0x00); - reg_write(encoder, REG_ENA_VP_0, 0x00); - reg_write(encoder, REG_ENA_VP_1, 0x00); - reg_write(encoder, REG_ENA_VP_2, 0x00); + /* disable video ports */ + reg_write(priv, REG_ENA_VP_0, 0x00); + reg_write(priv, REG_ENA_VP_1, 0x00); + reg_write(priv, REG_ENA_VP_2, 0x00); break; } @@ -494,132 +854,199 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *adjusted_mode) { struct tda998x_priv *priv = to_tda998x_priv(encoder); - uint16_t hs_start, hs_end, line_start, line_end; - uint16_t vwin_start, vwin_end, de_start, de_end; - uint16_t ref_pix, ref_line, pix_start2; + uint16_t ref_pix, ref_line, n_pix, n_line; + uint16_t hs_pix_s, hs_pix_e; + uint16_t vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e; + uint16_t vs2_pix_s, vs2_pix_e, vs2_line_s, vs2_line_e; + uint16_t vwin1_line_s, vwin1_line_e; + uint16_t vwin2_line_s, vwin2_line_e; + uint16_t de_pix_s, de_pix_e; uint8_t reg, div, rep; - hs_start = mode->hsync_start - mode->hdisplay; - hs_end = mode->hsync_end - mode->hdisplay; - line_start = 1; - line_end = 1 + mode->vsync_end - mode->vsync_start; - vwin_start = mode->vtotal - mode->vsync_start; - vwin_end = vwin_start + mode->vdisplay; - de_start = mode->htotal - mode->hdisplay; - de_end = mode->htotal; - - pix_start2 = 0; - if (mode->flags & DRM_MODE_FLAG_INTERLACE) - pix_start2 = (mode->htotal / 2) + hs_start; - - /* TODO how is this value calculated? It is 2 for all common - * formats in the tables in out of tree nxp driver (assuming - * I've properly deciphered their byzantine table system) + /* + * Internally TDA998x is using ITU-R BT.656 style sync but + * we get VESA style sync. TDA998x is using a reference pixel + * relative to ITU to sync to the input frame and for output + * sync generation. Currently, we are using reference detection + * from HS/VS, i.e. REFPIX/REFLINE denote frame start sync point + * which is position of rising VS with coincident rising HS. + * + * Now there is some issues to take care of: + * - HDMI data islands require sync-before-active + * - TDA998x register values must be > 0 to be enabled + * - REFLINE needs an additional offset of +1 + * - REFPIX needs an addtional offset of +1 for UYUV and +3 for RGB + * + * So we add +1 to all horizontal and vertical register values, + * plus an additional +3 for REFPIX as we are using RGB input only. */ - ref_line = 2; - - /* this might changes for other color formats from the CRTC: */ - ref_pix = 3 + hs_start; + n_pix = mode->htotal; + n_line = mode->vtotal; + + hs_pix_e = mode->hsync_end - mode->hdisplay; + hs_pix_s = mode->hsync_start - mode->hdisplay; + de_pix_e = mode->htotal; + de_pix_s = mode->htotal - mode->hdisplay; + ref_pix = 3 + hs_pix_s; + + /* + * Attached LCD controllers may generate broken sync. Allow + * those to adjust the position of the rising VS edge by adding + * HSKEW to ref_pix. + */ + if (adjusted_mode->flags & DRM_MODE_FLAG_HSKEW) + ref_pix += adjusted_mode->hskew; + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0) { + ref_line = 1 + mode->vsync_start - mode->vdisplay; + vwin1_line_s = mode->vtotal - mode->vdisplay - 1; + vwin1_line_e = vwin1_line_s + mode->vdisplay; + vs1_pix_s = vs1_pix_e = hs_pix_s; + vs1_line_s = mode->vsync_start - mode->vdisplay; + vs1_line_e = vs1_line_s + + mode->vsync_end - mode->vsync_start; + vwin2_line_s = vwin2_line_e = 0; + vs2_pix_s = vs2_pix_e = 0; + vs2_line_s = vs2_line_e = 0; + } else { + ref_line = 1 + (mode->vsync_start - mode->vdisplay)/2; + vwin1_line_s = (mode->vtotal - mode->vdisplay)/2; + vwin1_line_e = vwin1_line_s + mode->vdisplay/2; + vs1_pix_s = vs1_pix_e = hs_pix_s; + vs1_line_s = (mode->vsync_start - mode->vdisplay)/2; + vs1_line_e = vs1_line_s + + (mode->vsync_end - mode->vsync_start)/2; + vwin2_line_s = vwin1_line_s + mode->vtotal/2; + vwin2_line_e = vwin2_line_s + mode->vdisplay/2; + vs2_pix_s = vs2_pix_e = hs_pix_s + mode->htotal/2; + vs2_line_s = vs1_line_s + mode->vtotal/2 ; + vs2_line_e = vs2_line_s + + (mode->vsync_end - mode->vsync_start)/2; + } div = 148500 / mode->clock; - - DBG("clock=%d, div=%u", mode->clock, div); - DBG("hs_start=%u, hs_end=%u, line_start=%u, line_end=%u", - hs_start, hs_end, line_start, line_end); - DBG("vwin_start=%u, vwin_end=%u, de_start=%u, de_end=%u", - vwin_start, vwin_end, de_start, de_end); - DBG("ref_line=%u, ref_pix=%u, pix_start2=%u", - ref_line, ref_pix, pix_start2); + if (div != 0) { + div--; + if (div > 3) + div = 3; + } /* mute the audio FIFO: */ - reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO); + reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO); /* set HDMI HDCP mode off: */ - reg_set(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS); - reg_clear(encoder, REG_TX33, TX33_HDMI); + reg_write(priv, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS); + reg_clear(priv, REG_TX33, TX33_HDMI); + reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0)); - reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0)); /* no pre-filter or interpolator: */ - reg_write(encoder, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) | + reg_write(priv, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) | HVF_CNTRL_0_INTPOL(0)); - reg_write(encoder, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0)); - reg_write(encoder, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) | + reg_write(priv, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0)); + reg_write(priv, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) | VIP_CNTRL_4_BLC(0)); - reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR); - reg_clear(encoder, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ); - reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE); - reg_write(encoder, REG_SERIALIZER, 0); - reg_write(encoder, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0)); + reg_clear(priv, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ); + reg_clear(priv, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR | + PLL_SERIAL_3_SRL_DE); + reg_write(priv, REG_SERIALIZER, 0); + reg_write(priv, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0)); /* TODO enable pixel repeat for pixel rates less than 25Msamp/s */ rep = 0; - reg_write(encoder, REG_RPT_CNTRL, 0); - reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) | + reg_write(priv, REG_RPT_CNTRL, 0); + reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) | SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); - reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) | + reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) | PLL_SERIAL_2_SRL_PR(rep)); - reg_write16(encoder, REG_VS_PIX_STRT_2_MSB, pix_start2); - reg_write16(encoder, REG_VS_PIX_END_2_MSB, pix_start2); - /* set color matrix bypass flag: */ - reg_set(encoder, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP); + reg_write(priv, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP | + MAT_CONTRL_MAT_SC(1)); /* set BIAS tmds value: */ - reg_write(encoder, REG_ANA_GENERAL, 0x09); - - reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD); + reg_write(priv, REG_ANA_GENERAL, 0x09); - reg_write(encoder, REG_VIP_CNTRL_3, 0); - reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS); - if (mode->flags & DRM_MODE_FLAG_NVSYNC) - reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL); + /* + * Sync on rising HSYNC/VSYNC + */ + reg = VIP_CNTRL_3_SYNC_HS; + /* + * TDA19988 requires high-active sync at input stage, + * so invert low-active sync provided by master encoder here + */ if (mode->flags & DRM_MODE_FLAG_NHSYNC) - reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL); - - reg_write(encoder, REG_VIDFORMAT, 0x00); - reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1); - reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1); - reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start); - reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end); - reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start); - reg_write16(encoder, REG_VS_PIX_END_1_MSB, hs_start); - reg_write16(encoder, REG_HS_PIX_START_MSB, hs_start); - reg_write16(encoder, REG_HS_PIX_STOP_MSB, hs_end); - reg_write16(encoder, REG_VWIN_START_1_MSB, vwin_start); - reg_write16(encoder, REG_VWIN_END_1_MSB, vwin_end); - reg_write16(encoder, REG_DE_START_MSB, de_start); - reg_write16(encoder, REG_DE_STOP_MSB, de_end); + reg |= VIP_CNTRL_3_H_TGL; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + reg |= VIP_CNTRL_3_V_TGL; + reg_write(priv, REG_VIP_CNTRL_3, reg); + + reg_write(priv, REG_VIDFORMAT, 0x00); + reg_write16(priv, REG_REFPIX_MSB, ref_pix); + reg_write16(priv, REG_REFLINE_MSB, ref_line); + reg_write16(priv, REG_NPIX_MSB, n_pix); + reg_write16(priv, REG_NLINE_MSB, n_line); + reg_write16(priv, REG_VS_LINE_STRT_1_MSB, vs1_line_s); + reg_write16(priv, REG_VS_PIX_STRT_1_MSB, vs1_pix_s); + reg_write16(priv, REG_VS_LINE_END_1_MSB, vs1_line_e); + reg_write16(priv, REG_VS_PIX_END_1_MSB, vs1_pix_e); + reg_write16(priv, REG_VS_LINE_STRT_2_MSB, vs2_line_s); + reg_write16(priv, REG_VS_PIX_STRT_2_MSB, vs2_pix_s); + reg_write16(priv, REG_VS_LINE_END_2_MSB, vs2_line_e); + reg_write16(priv, REG_VS_PIX_END_2_MSB, vs2_pix_e); + reg_write16(priv, REG_HS_PIX_START_MSB, hs_pix_s); + reg_write16(priv, REG_HS_PIX_STOP_MSB, hs_pix_e); + reg_write16(priv, REG_VWIN_START_1_MSB, vwin1_line_s); + reg_write16(priv, REG_VWIN_END_1_MSB, vwin1_line_e); + reg_write16(priv, REG_VWIN_START_2_MSB, vwin2_line_s); + reg_write16(priv, REG_VWIN_END_2_MSB, vwin2_line_e); + reg_write16(priv, REG_DE_START_MSB, de_pix_s); + reg_write16(priv, REG_DE_STOP_MSB, de_pix_e); if (priv->rev == TDA19988) { /* let incoming pixels fill the active space (if any) */ - reg_write(encoder, REG_ENABLE_SPACE, 0x01); + reg_write(priv, REG_ENABLE_SPACE, 0x00); } - reg_write16(encoder, REG_REFPIX_MSB, ref_pix); - reg_write16(encoder, REG_REFLINE_MSB, ref_line); - - reg = TBG_CNTRL_1_VHX_EXT_DE | - TBG_CNTRL_1_VHX_EXT_HS | - TBG_CNTRL_1_VHX_EXT_VS | - TBG_CNTRL_1_DWIN_DIS | /* HDCP off */ - TBG_CNTRL_1_VH_TGL_2; - if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC)) - reg |= TBG_CNTRL_1_VH_TGL_0; - reg_set(encoder, REG_TBG_CNTRL_1, reg); + /* + * Always generate sync polarity relative to input sync and + * revert input stage toggled sync at output stage + */ + reg = TBG_CNTRL_1_DWIN_DIS | TBG_CNTRL_1_TGL_EN; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + reg |= TBG_CNTRL_1_H_TGL; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + reg |= TBG_CNTRL_1_V_TGL; + reg_write(priv, REG_TBG_CNTRL_1, reg); /* must be last register set: */ - reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE); + reg_write(priv, REG_TBG_CNTRL_0, 0); + + /* Only setup the info frames if the sink is HDMI */ + if (priv->is_hdmi_sink) { + /* We need to turn HDMI HDCP stuff on to get audio through */ + reg &= ~TBG_CNTRL_1_DWIN_DIS; + reg_write(priv, REG_TBG_CNTRL_1, reg); + reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1)); + reg_set(priv, REG_TX33, TX33_HDMI); + + tda998x_write_avi(priv, adjusted_mode); + + if (priv->params.audio_cfg) + tda998x_configure_audio(priv, adjusted_mode, + &priv->params); + } } static enum drm_connector_status tda998x_encoder_detect(struct drm_encoder *encoder, struct drm_connector *connector) { - uint8_t val = cec_read(encoder, REG_CEC_RXSHPDLEV); + struct tda998x_priv *priv = to_tda998x_priv(encoder); + uint8_t val = cec_read(priv, REG_CEC_RXSHPDLEV); + return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected : connector_status_disconnected; } @@ -627,69 +1054,106 @@ tda998x_encoder_detect(struct drm_encoder *encoder, static int read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk) { + struct tda998x_priv *priv = to_tda998x_priv(encoder); uint8_t offset, segptr; int ret, i; - /* enable EDID read irq: */ - reg_set(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); - offset = (blk & 1) ? 128 : 0; segptr = blk / 2; - reg_write(encoder, REG_DDC_ADDR, 0xa0); - reg_write(encoder, REG_DDC_OFFS, offset); - reg_write(encoder, REG_DDC_SEGM_ADDR, 0x60); - reg_write(encoder, REG_DDC_SEGM, segptr); + reg_write(priv, REG_DDC_ADDR, 0xa0); + reg_write(priv, REG_DDC_OFFS, offset); + reg_write(priv, REG_DDC_SEGM_ADDR, 0x60); + reg_write(priv, REG_DDC_SEGM, segptr); /* enable reading EDID: */ - reg_write(encoder, REG_EDID_CTRL, 0x1); + priv->wq_edid_wait = 1; + reg_write(priv, REG_EDID_CTRL, 0x1); /* flag must be cleared by sw: */ - reg_write(encoder, REG_EDID_CTRL, 0x0); + reg_write(priv, REG_EDID_CTRL, 0x0); /* wait for block read to complete: */ - for (i = 100; i > 0; i--) { - uint8_t val = reg_read(encoder, REG_INT_FLAGS_2); - if (val & INT_FLAGS_2_EDID_BLK_RD) - break; - msleep(1); + if (priv->hdmi->irq) { + i = wait_event_timeout(priv->wq_edid, + !priv->wq_edid_wait, + msecs_to_jiffies(100)); + if (i < 0) { + dev_err(&priv->hdmi->dev, "read edid wait err %d\n", i); + return i; + } + } else { + for (i = 10; i > 0; i--) { + msleep(10); + ret = reg_read(priv, REG_INT_FLAGS_2); + if (ret < 0) + return ret; + if (ret & INT_FLAGS_2_EDID_BLK_RD) + break; + } } - if (i == 0) + if (i == 0) { + dev_err(&priv->hdmi->dev, "read edid timeout\n"); return -ETIMEDOUT; + } - ret = reg_read_range(encoder, REG_EDID_DATA_0, buf, EDID_LENGTH); + ret = reg_read_range(priv, REG_EDID_DATA_0, buf, EDID_LENGTH); if (ret != EDID_LENGTH) { - dev_err(encoder->dev->dev, "failed to read edid block %d: %d", - blk, ret); + dev_err(&priv->hdmi->dev, "failed to read edid block %d: %d\n", + blk, ret); return ret; } - reg_clear(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); - return 0; } +static int +read_validate_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk) +{ + bool print_bad_edid = drm_debug & DRM_UT_KMS; + int ret; + int retries = 1; + + do + { + bool print_bad = print_bad_edid && (retries == 0); + + ret = read_edid_block(encoder, buf, blk); + /* Fail on I2C error */ + if (ret) + break; + + /* But retry checksum errored blocks */ + if (drm_edid_block_valid(buf, blk, print_bad)) + break; + else + ret = -EINVAL; + } while (retries-- > 0); + + return ret; +} + static uint8_t * do_get_edid(struct drm_encoder *encoder) { - int j = 0, valid_extensions = 0; + struct tda998x_priv *priv = to_tda998x_priv(encoder); + int j, valid_extensions = 0; uint8_t *block, *new; - bool print_bad_edid = drm_debug & DRM_UT_KMS; if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL) return NULL; - /* base block fetch */ - if (read_edid_block(encoder, block, 0)) - goto fail; + if (priv->rev == TDA19988) + reg_clear(priv, REG_TX4, TX4_PD_RAM); - if (!drm_edid_block_valid(block, 0, print_bad_edid)) + /* base block fetch */ + if (read_validate_edid_block(encoder, block, 0)) goto fail; /* if there's no extensions, we're done */ if (block[0x7e] == 0) - return block; + goto done; new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL); if (!new) @@ -698,10 +1162,7 @@ do_get_edid(struct drm_encoder *encoder) for (j = 1; j <= block[0x7e]; j++) { uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH; - if (read_edid_block(encoder, ext_block, j)) - goto fail; - - if (!drm_edid_block_valid(ext_block, j, print_bad_edid)) + if (read_validate_edid_block(encoder, ext_block, j)) goto fail; valid_extensions++; @@ -716,10 +1177,16 @@ do_get_edid(struct drm_encoder *encoder) block = new; } +done: + if (priv->rev == TDA19988) + reg_set(priv, REG_TX4, TX4_PD_RAM); + return block; fail: - dev_warn(encoder->dev->dev, "failed to read EDID\n"); + if (priv->rev == TDA19988) + reg_set(priv, REG_TX4, TX4_PD_RAM); + dev_warn(&priv->hdmi->dev, "failed to read EDID\n"); kfree(block); return NULL; } @@ -728,12 +1195,19 @@ static int tda998x_encoder_get_modes(struct drm_encoder *encoder, struct drm_connector *connector) { + struct tda998x_priv *priv = to_tda998x_priv(encoder); struct edid *edid = (struct edid *)do_get_edid(encoder); int n = 0; if (edid) { drm_mode_connector_update_edid_property(connector, edid); n = drm_add_edid_modes(connector, edid); + priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid); + + /* keep the EDID as ELD for the audio subsystem */ + drm_edid_to_eld(connector, edid); + priv->eld = connector->eld; + kfree(edid); } @@ -744,7 +1218,13 @@ static int tda998x_encoder_create_resources(struct drm_encoder *encoder, struct drm_connector *connector) { - DBG(""); + struct tda998x_priv *priv = to_tda998x_priv(encoder); + + if (priv->hdmi->irq) + connector->polled = DRM_CONNECTOR_POLL_HPD; + else + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; return 0; } @@ -763,6 +1243,17 @@ tda998x_encoder_destroy(struct drm_encoder *encoder) { struct tda998x_priv *priv = to_tda998x_priv(encoder); drm_i2c_encoder_destroy(encoder); + + /* disable all IRQs and free the IRQ handler */ + cec_write(priv, REG_CEC_RXSHPDINTENA, 0); + reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + if (priv->hdmi->irq) + free_irq(priv->hdmi->irq, priv); + + tda998x_codec_unregister(&priv->hdmi->dev); + + if (priv->cec) + i2c_unregister_device(priv->cec); kfree(priv); } @@ -800,56 +1291,137 @@ tda998x_encoder_init(struct i2c_client *client, struct drm_device *dev, struct drm_encoder_slave *encoder_slave) { - struct drm_encoder *encoder = &encoder_slave->base; struct tda998x_priv *priv; + struct device_node *np = client->dev.of_node; + u32 video; + int rev_lo, rev_hi, ret; + unsigned short cec_addr; priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->current_page = 0; - priv->cec = i2c_new_dummy(client->adapter, 0x34); + priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3); + priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1); + priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5); + + priv->params.audio_frame[1] = 1; /* channels - 1 */ + priv->params.audio_sample_rate = 48000; /* 48kHz */ + + priv->current_page = 0xff; + priv->hdmi = client; + /* CEC I2C address bound to TDA998x I2C addr by configuration pins */ + cec_addr = 0x34 + (client->addr & 0x03); + priv->cec = i2c_new_dummy(client->adapter, cec_addr); + if (!priv->cec) { + kfree(priv); + return -ENODEV; + } + + priv->encoder = &encoder_slave->base; priv->dpms = DRM_MODE_DPMS_OFF; encoder_slave->slave_priv = priv; encoder_slave->slave_funcs = &tda998x_encoder_funcs; + i2c_set_clientdata(client, priv); /* wake up the device: */ - cec_write(encoder, REG_CEC_ENAMODS, + cec_write(priv, REG_CEC_ENAMODS, CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI); - tda998x_reset(encoder); + tda998x_reset(priv); /* read version: */ - priv->rev = reg_read(encoder, REG_VERSION_LSB) | - reg_read(encoder, REG_VERSION_MSB) << 8; + rev_lo = reg_read(priv, REG_VERSION_LSB); + rev_hi = reg_read(priv, REG_VERSION_MSB); + if (rev_lo < 0 || rev_hi < 0) { + ret = rev_lo < 0 ? rev_lo : rev_hi; + goto fail; + } + + priv->rev = rev_lo | rev_hi << 8; /* mask off feature bits: */ priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */ switch (priv->rev) { - case TDA9989N2: dev_info(dev->dev, "found TDA9989 n2"); break; - case TDA19989: dev_info(dev->dev, "found TDA19989"); break; - case TDA19989N2: dev_info(dev->dev, "found TDA19989 n2"); break; - case TDA19988: dev_info(dev->dev, "found TDA19988"); break; + case TDA9989N2: + dev_info(&client->dev, "found TDA9989 n2"); + break; + case TDA19989: + dev_info(&client->dev, "found TDA19989"); + break; + case TDA19989N2: + dev_info(&client->dev, "found TDA19989 n2"); + break; + case TDA19988: + dev_info(&client->dev, "found TDA19988"); + break; default: - DBG("found unsupported device: %04x", priv->rev); + dev_err(&client->dev, "found unsupported device: %04x\n", + priv->rev); goto fail; } /* after reset, enable DDC: */ - reg_write(encoder, REG_DDC_DISABLE, 0x00); + reg_write(priv, REG_DDC_DISABLE, 0x00); /* set clock on DDC channel: */ - reg_write(encoder, REG_TX3, 39); + reg_write(priv, REG_TX3, 39); /* if necessary, disable multi-master: */ if (priv->rev == TDA19989) - reg_set(encoder, REG_I2C_MASTER, I2C_MASTER_DIS_MM); + reg_set(priv, REG_I2C_MASTER, I2C_MASTER_DIS_MM); - cec_write(encoder, REG_CEC_FRO_IM_CLK_CTRL, + cec_write(priv, REG_CEC_FRO_IM_CLK_CTRL, CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL); + /* initialize the optional IRQ */ + if (client->irq) { + int irqf_trigger; + + /* init read EDID waitqueue */ + init_waitqueue_head(&priv->wq_edid); + + /* clear pending interrupts */ + reg_read(priv, REG_INT_FLAGS_0); + reg_read(priv, REG_INT_FLAGS_1); + reg_read(priv, REG_INT_FLAGS_2); + + irqf_trigger = + irqd_get_trigger_type(irq_get_irq_data(client->irq)); + ret = request_threaded_irq(client->irq, NULL, + tda998x_irq_thread, + irqf_trigger | IRQF_ONESHOT, + "tda998x", priv); + if (ret) { + dev_err(&client->dev, + "failed to request IRQ#%u: %d\n", + client->irq, ret); + goto fail; + } + + /* enable HPD irq */ + cec_write(priv, REG_CEC_RXSHPDINTENA, CEC_RXSHPDLEV_HPD); + } + + /* enable EDID read irq: */ + reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + + /* register the audio CODEC */ + tda998x_codec_register(&client->dev); + + if (!np) + return 0; /* non-DT */ + + /* get the optional video properties */ + ret = of_property_read_u32(np, "video-ports", &video); + if (ret == 0) { + priv->vip_cntrl_0 = video >> 16; + priv->vip_cntrl_1 = video >> 8; + priv->vip_cntrl_2 = video; + } + return 0; fail: @@ -864,6 +1436,14 @@ fail: return -ENXIO; } +#ifdef CONFIG_OF +static const struct of_device_id tda998x_dt_ids[] = { + { .compatible = "nxp,tda998x", }, + { } +}; +MODULE_DEVICE_TABLE(of, tda998x_dt_ids); +#endif + static struct i2c_device_id tda998x_ids[] = { { "tda998x", 0 }, { } @@ -876,6 +1456,7 @@ static struct drm_i2c_encoder_driver tda998x_driver = { .remove = tda998x_remove, .driver = { .name = "tda998x", + .of_match_table = of_match_ptr(tda998x_dt_ids), }, .id_table = tda998x_ids, }, diff --git a/drivers/gpu/drm/i2c/tda998x_drv.h b/drivers/gpu/drm/i2c/tda998x_drv.h new file mode 100644 index 000000000000..e6c8dd5dbe21 --- /dev/null +++ b/drivers/gpu/drm/i2c/tda998x_drv.h @@ -0,0 +1,32 @@ +/* tda998x private data */ + +struct tda998x_priv { + struct i2c_client *cec; + struct i2c_client *hdmi; + uint16_t rev; + uint8_t current_page; + int dpms; + bool is_hdmi_sink; + u8 vip_cntrl_0; + u8 vip_cntrl_1; + u8 vip_cntrl_2; + struct tda998x_encoder_params params; + + wait_queue_head_t wq_edid; + volatile int wq_edid_wait; + struct drm_encoder *encoder; + + u8 audio_ports[2]; + int audio_sample_format; + int dai_id; /* DAI ID when streaming active */ + + u8 *eld; + + struct snd_pcm_hw_constraint_list rate_constraints; +}; + +int tda998x_codec_register(struct device *dev); +void tda998x_codec_unregister(struct device *dev); + +void tda998x_audio_start(struct tda998x_priv *priv, int full); +void tda998x_audio_stop(struct tda998x_priv *priv); diff --git a/drivers/gpu/drm/pl111/Kbuild b/drivers/gpu/drm/pl111/Kbuild new file mode 100755 index 000000000000..f10d58c70dff --- /dev/null +++ b/drivers/gpu/drm/pl111/Kbuild @@ -0,0 +1,28 @@ +# +# (C) COPYRIGHT 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-y += pl111_drm_device.o \ + pl111_drm_connector.o \ + pl111_drm_crtc.o \ + pl111_drm_cursor.o \ + pl111_drm_dma_buf.o \ + pl111_drm_encoder.o \ + pl111_drm_fb.o \ + pl111_drm_gem.o \ + pl111_drm_pl111.o \ + pl111_drm_platform.o \ + pl111_drm_suspend.o \ + pl111_drm_vma.o + +obj-$(CONFIG_DRM_PL111) += pl111_drm.o diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig new file mode 100755 index 000000000000..60b465c56c51 --- /dev/null +++ b/drivers/gpu/drm/pl111/Kconfig @@ -0,0 +1,23 @@ +# +# (C) COPYRIGHT 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. +# +# + +config DRM_PL111 + tristate "DRM Support for PL111 CLCD Controller" + depends on DRM + select DRM_KMS_HELPER + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE + help + Choose this option for DRM support for the PL111 CLCD controller. + If M is selected the module will be called pl111_drm. + diff --git a/drivers/gpu/drm/pl111/Makefile b/drivers/gpu/drm/pl111/Makefile new file mode 100755 index 000000000000..2869f587266b --- /dev/null +++ b/drivers/gpu/drm/pl111/Makefile @@ -0,0 +1,32 @@ +# +# (C) COPYRIGHT 2011-2013 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. +# +# + + +# linux build system bootstrap for out-of-tree module + +# default to building for the host +ARCH ?= $(shell uname -m) + +ifeq ($(KDIR),) +$(error Must specify KDIR to point to the kernel to target)) +endif + +all: pl111_drm + +pl111_drm: + $(MAKE) ARCH=$(ARCH) -C $(KDIR) M=$(CURDIR) EXTRA_CFLAGS="-I$(CURDIR)/../../../include" CONFIG_DMA_SHARED_BUFFER_USES_KDS=y CONFIG_DRM_PL111=m + +clean: + $(MAKE) ARCH=$(ARCH) -C $(KDIR) M=$(CURDIR) clean + diff --git a/drivers/gpu/drm/pl111/pl111_clcd_ext.h b/drivers/gpu/drm/pl111/pl111_clcd_ext.h new file mode 100755 index 000000000000..43f212efac6b --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_clcd_ext.h @@ -0,0 +1,95 @@ +/* + * + * (C) COPYRIGHT 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_clcd_ext.h + * Extended CLCD register definitions + */ + +#ifndef PL111_CLCD_EXT_H_ +#define PL111_CLCD_EXT_H_ + +/* + * PL111 cursor register definitions not defined in the kernel's clcd header. + * + * TODO MIDEGL-1718: move to include/linux/amba/clcd.h + */ + +#define CLCD_CRSR_IMAGE 0x00000800 +#define CLCD_CRSR_IMAGE_MAX_WORDS 256 +#define CLCD_CRSR_IMAGE_WORDS_PER_LINE 4 +#define CLCD_CRSR_IMAGE_PIXELS_PER_WORD 16 + +#define CLCD_CRSR_LBBP_COLOR_MASK 0x00000003 +#define CLCD_CRSR_LBBP_BACKGROUND 0x0 +#define CLCD_CRSR_LBBP_FOREGROUND 0x1 +#define CLCD_CRSR_LBBP_TRANSPARENT 0x2 +#define CLCD_CRSR_LBBP_INVERSE 0x3 + + +#define CLCD_CRSR_CTRL 0x00000c00 +#define CLCD_CRSR_CONFIG 0x00000c04 +#define CLCD_CRSR_PALETTE_0 0x00000c08 +#define CLCD_CRSR_PALETTE_1 0x00000c0c +#define CLCD_CRSR_XY 0x00000c10 +#define CLCD_CRSR_CLIP 0x00000c14 +#define CLCD_CRSR_IMSC 0x00000c20 +#define CLCD_CRSR_ICR 0x00000c24 +#define CLCD_CRSR_RIS 0x00000c28 +#define CLCD_MIS 0x00000c2c + +#define CRSR_CTRL_CRSR_ON (1 << 0) +#define CRSR_CTRL_CRSR_MAX 3 +#define CRSR_CTRL_CRSR_NUM_SHIFT 4 +#define CRSR_CTRL_CRSR_NUM_MASK \ + (CRSR_CTRL_CRSR_MAX << CRSR_CTRL_CRSR_NUM_SHIFT) +#define CRSR_CTRL_CURSOR_0 0 +#define CRSR_CTRL_CURSOR_1 1 +#define CRSR_CTRL_CURSOR_2 2 +#define CRSR_CTRL_CURSOR_3 3 + +#define CRSR_CONFIG_CRSR_SIZE (1 << 0) +#define CRSR_CONFIG_CRSR_FRAME_SYNC (1 << 1) + +#define CRSR_PALETTE_RED_SHIFT 0 +#define CRSR_PALETTE_GREEN_SHIFT 8 +#define CRSR_PALETTE_BLUE_SHIFT 16 + +#define CRSR_PALETTE_RED_MASK 0x000000ff +#define CRSR_PALETTE_GREEN_MASK 0x0000ff00 +#define CRSR_PALETTE_BLUE_MASK 0x00ff0000 +#define CRSR_PALETTE_MASK (~0xff000000) + +#define CRSR_XY_MASK 0x000003ff +#define CRSR_XY_X_SHIFT 0 +#define CRSR_XY_Y_SHIFT 16 + +#define CRSR_XY_X_MASK CRSR_XY_MASK +#define CRSR_XY_Y_MASK (CRSR_XY_MASK << CRSR_XY_Y_SHIFT) + +#define CRSR_CLIP_MASK 0x3f +#define CRSR_CLIP_X_SHIFT 0 +#define CRSR_CLIP_Y_SHIFT 8 + +#define CRSR_CLIP_X_MASK CRSR_CLIP_MASK +#define CRSR_CLIP_Y_MASK (CRSR_CLIP_MASK << CRSR_CLIP_Y_SHIFT) + +#define CRSR_IMSC_CRSR_IM (1<<0) +#define CRSR_ICR_CRSR_IC (1<<0) +#define CRSR_RIS_CRSR_RIS (1<<0) +#define CRSR_MIS_CRSR_MIS (1<<0) + +#endif /* PL111_CLCD_EXT_H_ */ diff --git a/drivers/gpu/drm/pl111/pl111_drm.h b/drivers/gpu/drm/pl111/pl111_drm.h new file mode 100755 index 000000000000..e151e8c02b11 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm.h @@ -0,0 +1,270 @@ +/* + * + * (C) COPYRIGHT 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. + * + */ + + + +#ifndef _PL111_DRM_H_ +#define _PL111_DRM_H_ + +#define DRIVER_AUTHOR "ARM Ltd." +#define DRIVER_NAME "pl111_drm" +#define DRIVER_DESC "DRM module for PL111" +#define DRIVER_LICENCE "GPL" +#define DRIVER_ALIAS "platform:pl111_drm" +#define DRIVER_DATE "20101111" +#define DRIVER_VERSION "0.2" +#define DRIVER_MAJOR 2 +#define DRIVER_MINOR 1 +#define DRIVER_PATCHLEVEL 1 + +/* + * Number of flips allowed in flight at any one time. Any more flips requested + * beyond this value will cause the caller to block until earlier flips have + * completed. + * + * For performance reasons, this must be greater than the number of buffers + * used in the rendering pipeline. Note that the rendering pipeline can contain + * different types of buffer, e.g.: + * - 2 final framebuffers + * - >2 geometry buffers for GPU use-cases + * - >2 vertex buffers for GPU use-cases + * + * For example, a system using 5 geometry buffers could have 5 flips in flight, + * and so NR_FLIPS_IN_FLIGHT_THRESHOLD must be 5 or greater. + * + * Whilst there may be more intermediate buffers (such as vertex/geometry) than + * final framebuffers, KDS is used to ensure that GPU rendering waits for the + * next off-screen buffer, so it doesn't overwrite an on-screen buffer and + * produce tearing. + */ + +/* + * Here, we choose a conservative value. A lower value is most likely + * suitable for GPU use-cases. + */ +#define NR_FLIPS_IN_FLIGHT_THRESHOLD 16 + +#define CLCD_IRQ_NEXTBASE_UPDATE (1u<<2) + +struct pl111_drm_flip_resource; + +struct pl111_gem_bo_dma { + dma_addr_t fb_dev_addr; + void *fb_cpu_addr; +}; + +struct pl111_gem_bo_shm { + struct page **pages; + dma_addr_t *dma_addrs; +}; + +struct pl111_gem_bo { + struct drm_gem_object gem_object; + u32 type; + union { + struct pl111_gem_bo_dma dma; + struct pl111_gem_bo_shm shm; + } backing_data; + struct sg_table *sgt; +}; + +extern struct pl111_drm_dev_private priv; + +struct pl111_drm_framebuffer { + struct drm_framebuffer fb; + struct pl111_gem_bo *bo; +}; + +struct pl111_drm_flip_resource { +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + /* This is the kds set associated to the dma_buf we want to flip */ + struct kds_resource_set *kds_res_set; +#endif + struct drm_framebuffer *fb; + struct drm_crtc *crtc; + struct list_head link; + bool page_flip; + struct drm_pending_vblank_event *event; +}; + +struct pl111_drm_crtc { + struct drm_crtc crtc; + int crtc_index; + +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + /* This protects "old_kds_res_set" and "displaying_fb" */ + spinlock_t current_displaying_lock; + /* + * When a buffer is displayed its associated kds resource + * will be obtained and stored here. Every time a buffer + * flip is completed this old kds set is released and assigned + * the kds set of the new buffer. + */ + struct kds_resource_set *old_kds_res_set; + /* + * Stores which frame buffer is currently being displayed by + * this CRTC or NULL if nothing is being displayed. It is used + * to tell whether we need to obtain a set of kds resources for + * exported buffer objects. + */ + struct drm_framebuffer *displaying_fb; +#endif + struct drm_display_mode *new_mode; + struct drm_display_mode *current_mode; + int last_bpp; + + /* + * This spinlock protects "update_queue", "current_update_res" + * and calls to do_flip_to_res() which updates the CLCD base + * registers. + */ + spinlock_t base_update_lock; + /* + * The resource that caused a base address update. Only one can be + * pending, hence it's != NULL if there's a pending update + */ + struct pl111_drm_flip_resource *current_update_res; + /* Queue of things waiting to update the base address */ + struct list_head update_queue; + + void (*show_framebuffer_cb)(struct pl111_drm_flip_resource *flip_res, + struct drm_framebuffer *fb); +}; + +struct pl111_drm_connector { + struct drm_connector connector; +}; + +struct pl111_drm_encoder { + struct drm_encoder encoder; +}; + +struct pl111_drm_dev_private { + struct pl111_drm_crtc *pl111_crtc; + + struct amba_device *amba_dev; + unsigned long mmio_start; + __u32 mmio_len; + void *regs; + struct clk *clk; +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + struct kds_callback kds_cb; + struct kds_callback kds_obtain_current_cb; +#endif + /* + * Number of flips that were started in show_framebuffer_on_crtc(), + * but haven't completed yet - because we do deferred flipping + */ + atomic_t nr_flips_in_flight; + wait_queue_head_t wait_for_flips; + + /* + * Used to prevent race between pl111_dma_buf_release and + * drm_gem_prime_handle_to_fd + */ + struct mutex export_dma_buf_lock; + + uint32_t number_crtcs; + + /* Cache for flip resources used to avoid kmalloc on each page flip */ + struct kmem_cache *page_flip_slab; +}; + +enum pl111_cursor_size { + CURSOR_32X32, + CURSOR_64X64 +}; + +enum pl111_cursor_sync { + CURSOR_SYNC_NONE, + CURSOR_SYNC_VSYNC +}; + + +/** + * Buffer allocation function which is more flexible than dumb_create(), + * it allows passing driver specific flags to control the kind of buffer + * to be allocated. + */ +int pl111_drm_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + +/****** TODO MIDEGL-1718: this should be moved to uapi/include/drm/pl111_drm.h ********/ + +/* + * Parameters for different buffer objects: + * bit [0]: backing storage + * (0 -> SHM) + * (1 -> DMA) + * bit [2:1]: kind of mapping + * (0x0 -> uncached) + * (0x1 -> write combine) + * (0x2 -> cached) + */ +#define PL111_BOT_MASK (0x7) +#define PL111_BOT_SHM (0x0 << 0) +#define PL111_BOT_DMA (0x1 << 0) +#define PL111_BOT_UNCACHED (0x0 << 1) +#define PL111_BOT_WC (0x1 << 1) +#define PL111_BOT_CACHED (0x2 << 1) + +/** + * User-desired buffer creation information structure. + * + * @size: user-desired memory allocation size. + * - this size value would be page-aligned internally. + * @flags: user request for setting memory type or cache attributes as a bit op + * - PL111_BOT_DMA / PL111_BOT_SHM + * - PL111_BOT_UNCACHED / PL111_BOT_WC / PL111_BOT_CACHED + * @handle: returned a handle to created gem object. + * - this handle will be set by gem module of kernel side. + */ +struct drm_pl111_gem_create { + uint32_t height; + uint32_t width; + uint32_t bpp; + uint32_t flags; + /* handle, pitch, size will be returned */ + uint32_t handle; + uint32_t pitch; + uint64_t size; +}; + +#define DRM_PL111_GEM_CREATE 0x00 + +#define DRM_IOCTL_PL111_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_PL111_GEM_CREATE, struct drm_pl111_gem_create) +/****************************************************************************/ + +#define PL111_FB_FROM_FRAMEBUFFER(drm_fb) \ + (container_of(drm_fb, struct pl111_drm_framebuffer, fb)) + +#define PL111_BO_FROM_FRAMEBUFFER(drm_fb) \ + (container_of(drm_fb, struct pl111_drm_framebuffer, fb)->bo) + +#define PL111_BO_FROM_GEM(gem_obj) \ + container_of(gem_obj, struct pl111_gem_bo, gem_object) + +#define to_pl111_crtc(x) container_of(x, struct pl111_drm_crtc, crtc) + +#define PL111_ENCODER_FROM_ENCODER(x) \ + container_of(x, struct pl111_drm_encoder, encoder) + +#define PL111_CONNECTOR_FROM_CONNECTOR(x) \ + container_of(x, struct pl111_drm_connector, connector) + +#include "pl111_drm_funcs.h" + +#endif /* _PL111_DRM_H_ */ diff --git a/drivers/gpu/drm/pl111/pl111_drm_connector.c b/drivers/gpu/drm/pl111/pl111_drm_connector.c new file mode 100755 index 000000000000..5d9cf80de618 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_connector.c @@ -0,0 +1,170 @@ +/* + * + * (C) COPYRIGHT 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_connector.c + * Implementation of the connector functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "pl111_drm.h" + + +static struct { + int w, h, type; +} pl111_drm_modes[] = { + { 640, 480, DRM_MODE_TYPE_PREFERRED}, + { 800, 600, 0}, + {1024, 768, 0}, + { -1, -1, -1} +}; + +void pl111_connector_destroy(struct drm_connector *connector) +{ + struct pl111_drm_connector *pl111_connector = + PL111_CONNECTOR_FROM_CONNECTOR(connector); + + DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector); + + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(pl111_connector); +} + +enum drm_connector_status pl111_connector_detect(struct drm_connector + *connector, bool force) +{ + DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector); + return connector_status_connected; +} + +void pl111_connector_dpms(struct drm_connector *connector, int mode) +{ + DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector); +} + +struct drm_encoder * +pl111_connector_helper_best_encoder(struct drm_connector *connector) +{ + DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector); + + if (connector->encoder != NULL) { + return connector->encoder; /* Return attached encoder */ + } else { + /* + * If there is no attached encoder we choose the best candidate + * from the list. + * For PL111 there is only one encoder so we return the first + * one we find. + * Other h/w would require a suitable criterion below. + */ + struct drm_encoder *encoder = NULL; + struct drm_device *dev = connector->dev; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, + head) { + if (1) { /* criterion ? */ + break; + } + } + return encoder; /* return best candidate encoder */ + } +} + +int pl111_connector_helper_get_modes(struct drm_connector *connector) +{ + int i = 0; + int count = 0; + + DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector); + + while (pl111_drm_modes[i].w != -1) { + struct drm_display_mode *mode = + drm_mode_find_dmt(connector->dev, + pl111_drm_modes[i].w, + pl111_drm_modes[i].h, + 60 +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) + , false +#endif + ); + + if (mode != NULL) { + mode->type |= pl111_drm_modes[i].type; + drm_mode_probed_add(connector, mode); + count++; + } + + i++; + } + + DRM_DEBUG_KMS("found %d modes\n", count); + + return count; +} + +int pl111_connector_helper_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector); + return MODE_OK; +} + +const struct drm_connector_funcs connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = pl111_connector_destroy, + .detect = pl111_connector_detect, + .dpms = pl111_connector_dpms, +}; + +const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = pl111_connector_helper_get_modes, + .mode_valid = pl111_connector_helper_mode_valid, + .best_encoder = pl111_connector_helper_best_encoder, +}; + +struct pl111_drm_connector *pl111_connector_create(struct drm_device *dev) +{ + struct pl111_drm_connector *pl111_connector; + + pl111_connector = kzalloc(sizeof(struct pl111_drm_connector), + GFP_KERNEL); + + if (pl111_connector == NULL) { + pr_err("Failed to allocated pl111_drm_connector\n"); + return NULL; + } + + drm_connector_init(dev, &pl111_connector->connector, &connector_funcs, + DRM_MODE_CONNECTOR_DVII); + + drm_connector_helper_add(&pl111_connector->connector, + &connector_helper_funcs); + + drm_sysfs_connector_add(&pl111_connector->connector); + + return pl111_connector; +} + diff --git a/drivers/gpu/drm/pl111/pl111_drm_crtc.c b/drivers/gpu/drm/pl111/pl111_drm_crtc.c new file mode 100755 index 000000000000..a06c991dbf23 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_crtc.c @@ -0,0 +1,421 @@ +/* + * + * (C) COPYRIGHT 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_crtc.c + * Implementation of the CRTC functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "pl111_drm.h" + +static int pl111_crtc_num; + +void pl111_common_irq(struct pl111_drm_crtc *pl111_crtc) +{ + struct drm_device *dev = pl111_crtc->crtc.dev; + struct pl111_drm_flip_resource *old_flip_res; + struct pl111_gem_bo *bo; + unsigned long irq_flags; + int flips_in_flight; +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + unsigned long flags; +#endif + + spin_lock_irqsave(&pl111_crtc->base_update_lock, irq_flags); + + /* + * Cache the flip resource that caused the IRQ since it will be + * dispatched later. Early return if the IRQ isn't associated to + * a base register update. + * + * TODO MIDBASE-2790: disable IRQs when a flip is not pending. + */ + old_flip_res = pl111_crtc->current_update_res; + if (!old_flip_res) { + spin_unlock_irqrestore(&pl111_crtc->base_update_lock, irq_flags); + return; + } + pl111_crtc->current_update_res = NULL; + + /* Prepare the next flip (if any) of the queue as soon as possible. */ + if (!list_empty(&pl111_crtc->update_queue)) { + struct pl111_drm_flip_resource *flip_res; + /* Remove the head of the list */ + flip_res = list_first_entry(&pl111_crtc->update_queue, + struct pl111_drm_flip_resource, link); + list_del(&flip_res->link); + do_flip_to_res(flip_res); + /* + * current_update_res will be set, so guarentees that + * another flip_res coming in gets queued instead of + * handled immediately + */ + } + spin_unlock_irqrestore(&pl111_crtc->base_update_lock, irq_flags); + + /* Finalize properly the flip that caused the IRQ */ + DRM_DEBUG_KMS("DRM Finalizing old_flip_res=%p\n", old_flip_res); + + bo = PL111_BO_FROM_FRAMEBUFFER(old_flip_res->fb); +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); + release_kds_resource_and_display(old_flip_res); + spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags); +#endif + /* Release DMA buffer on this flip */ + if (bo->gem_object.export_dma_buf != NULL) + dma_buf_put(bo->gem_object.export_dma_buf); + + drm_handle_vblank(dev, pl111_crtc->crtc_index); + + /* Wake up any processes waiting for page flip event */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)) + if (old_flip_res->event) { + spin_lock_bh(&dev->event_lock); + drm_send_vblank_event(dev, pl111_crtc->crtc_index, + old_flip_res->event); + spin_unlock_bh(&dev->event_lock); + } +#else + if (old_flip_res->event) { + struct drm_pending_vblank_event *e = old_flip_res->event; + struct timeval now; + unsigned int seq; + + DRM_DEBUG_KMS("%s: wake up page flip event (%p)\n", __func__, + old_flip_res->event); + + spin_lock_bh(&dev->event_lock); + seq = drm_vblank_count_and_time(dev, pl111_crtc->crtc_index, + &now); + e->pipe = pl111_crtc->crtc_index; + e->event.sequence = seq; + e->event.tv_sec = now.tv_sec; + e->event.tv_usec = now.tv_usec; + + list_add_tail(&e->base.link, + &e->base.file_priv->event_list); + + wake_up_interruptible(&e->base.file_priv->event_wait); + spin_unlock_bh(&dev->event_lock); + } +#endif + + drm_vblank_put(dev, pl111_crtc->crtc_index); + + /* + * workqueue.c:process_one_work(): + * "It is permissible to free the struct work_struct from + * inside the function that is called from it" + */ + kmem_cache_free(priv.page_flip_slab, old_flip_res); + + flips_in_flight = atomic_dec_return(&priv.nr_flips_in_flight); + if (flips_in_flight == 0 || + flips_in_flight == (NR_FLIPS_IN_FLIGHT_THRESHOLD - 1)) + wake_up(&priv.wait_for_flips); + + DRM_DEBUG_KMS("DRM release flip_res=%p\n", old_flip_res); +} + +void show_framebuffer_on_crtc_cb(void *cb1, void *cb2) +{ + struct pl111_drm_flip_resource *flip_res = cb1; + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc); + + pl111_crtc->show_framebuffer_cb(cb1, cb2); +} + +int show_framebuffer_on_crtc(struct drm_crtc *crtc, + struct drm_framebuffer *fb, bool page_flip, + struct drm_pending_vblank_event *event) +{ + struct pl111_gem_bo *bo; + struct pl111_drm_flip_resource *flip_res; + int flips_in_flight; + int old_flips_in_flight; + + crtc->fb = fb; + + bo = PL111_BO_FROM_FRAMEBUFFER(fb); + if (bo == NULL) { + DRM_DEBUG_KMS("Failed to get pl111_gem_bo object\n"); + return -EINVAL; + } + + /* If this is a full modeset, wait for all outstanding flips to complete + * before continuing. This avoids unnecessary complication from being + * able to queue up multiple modesets and queues of mixed modesets and + * page flips. + * + * Modesets should be uncommon and will not be performant anyway, so + * making them synchronous should have negligible performance impact. + */ + if (!page_flip) { + int ret = wait_event_killable(priv.wait_for_flips, + atomic_read(&priv.nr_flips_in_flight) == 0); + if (ret) + return ret; + } + + /* + * There can be more 'early display' flips in flight than there are + * buffers, and there is (currently) no explicit bound on the number of + * flips. Hence, we need a new allocation for each one. + * + * Note: this could be optimized down if we knew a bound on the flips, + * since an application can only have so many buffers in flight to be + * useful/not hog all the memory + */ + flip_res = kmem_cache_alloc(priv.page_flip_slab, GFP_KERNEL); + if (flip_res == NULL) { + pr_err("kmem_cache_alloc failed to alloc - flip ignored\n"); + return -ENOMEM; + } + + /* + * increment flips in flight, whilst blocking when we reach + * NR_FLIPS_IN_FLIGHT_THRESHOLD + */ + do { + /* + * Note: use of assign-and-then-compare in the condition to set + * flips_in_flight + */ + int ret = wait_event_killable(priv.wait_for_flips, + (flips_in_flight = + atomic_read(&priv.nr_flips_in_flight)) + < NR_FLIPS_IN_FLIGHT_THRESHOLD); + if (ret != 0) { + kmem_cache_free(priv.page_flip_slab, flip_res); + return ret; + } + + old_flips_in_flight = atomic_cmpxchg(&priv.nr_flips_in_flight, + flips_in_flight, flips_in_flight + 1); + } while (old_flips_in_flight != flips_in_flight); + + flip_res->fb = fb; + flip_res->crtc = crtc; + flip_res->page_flip = page_flip; + flip_res->event = event; + INIT_LIST_HEAD(&flip_res->link); + DRM_DEBUG_KMS("DRM alloc flip_res=%p\n", flip_res); +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + if (bo->gem_object.export_dma_buf != NULL) { + struct dma_buf *buf = bo->gem_object.export_dma_buf; + unsigned long shared[1] = { 0 }; + struct kds_resource *resource_list[1] = { + get_dma_buf_kds_resource(buf) }; + int err; + + get_dma_buf(buf); + DRM_DEBUG_KMS("Got dma_buf %p\n", buf); + + /* Wait for the KDS resource associated with this buffer */ + err = kds_async_waitall(&flip_res->kds_res_set, + &priv.kds_cb, flip_res, fb, 1, shared, + resource_list); + BUG_ON(err); + } else { + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + + DRM_DEBUG_KMS("No dma_buf for this flip\n"); + + /* No dma-buf attached so just call the callback directly */ + flip_res->kds_res_set = NULL; + pl111_crtc->show_framebuffer_cb(flip_res, fb); + } +#else + if (bo->gem_object.export_dma_buf != NULL) { + struct dma_buf *buf = bo->gem_object.export_dma_buf; + + get_dma_buf(buf); + DRM_DEBUG_KMS("Got dma_buf %p\n", buf); + } else { + DRM_DEBUG_KMS("No dma_buf for this flip\n"); + } + + /* No dma-buf attached to this so just call the callback directly */ + { + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + pl111_crtc->show_framebuffer_cb(flip_res, fb); + } +#endif + + /* For the same reasons as the wait at the start of this function, + * wait for the modeset to complete before continuing. + */ + if (!page_flip) { + int ret = wait_event_killable(priv.wait_for_flips, + flips_in_flight == 0); + if (ret) + return ret; + } + + return 0; +} + +int pl111_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + DRM_DEBUG_KMS("%s: crtc=%p, fb=%p, event=%p\n", + __func__, crtc, fb, event); + return show_framebuffer_on_crtc(crtc, fb, true, event); +} + +int pl111_crtc_helper_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, struct drm_framebuffer *old_fb) +{ + int ret; + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + struct drm_display_mode *duplicated_mode; + + DRM_DEBUG_KMS("DRM crtc_helper_mode_set, x=%d y=%d bpp=%d\n", + adjusted_mode->hdisplay, adjusted_mode->vdisplay, + crtc->fb->bits_per_pixel); + + duplicated_mode = drm_mode_duplicate(crtc->dev, adjusted_mode); + if (!duplicated_mode) + return -ENOMEM; + + pl111_crtc->new_mode = duplicated_mode; + ret = show_framebuffer_on_crtc(crtc, crtc->fb, false, NULL); + if (ret != 0) { + pl111_crtc->new_mode = pl111_crtc->current_mode; + drm_mode_destroy(crtc->dev, duplicated_mode); + } + + return ret; +} + +void pl111_crtc_helper_prepare(struct drm_crtc *crtc) +{ + DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc); +} + +void pl111_crtc_helper_commit(struct drm_crtc *crtc) +{ + DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc); +} + +bool pl111_crtc_helper_mode_fixup(struct drm_crtc *crtc, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) + const struct drm_display_mode *mode, +#else + struct drm_display_mode *mode, +#endif + struct drm_display_mode *adjusted_mode) +{ + DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc); + +#ifdef CONFIG_ARCH_VEXPRESS + /* + * 1024x768 with more than 16 bits per pixel may not work + * correctly on Versatile Express due to bandwidth issues + */ + if (mode->hdisplay == 1024 && mode->vdisplay == 768 && + crtc->fb->bits_per_pixel > 16) { + DRM_INFO("*WARNING* 1024x768 at > 16 bpp may suffer corruption\n"); + } +#endif + + return true; +} + +void pl111_crtc_helper_disable(struct drm_crtc *crtc) +{ + int ret; + + DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc); + + /* don't disable crtc until no flips in flight as irq will be disabled */ + ret = wait_event_killable(priv.wait_for_flips, atomic_read(&priv.nr_flips_in_flight) == 0); + if(ret) { + pr_err("pl111_crtc_helper_disable failed\n"); + return; + } + clcd_disable(crtc); +} + +void pl111_crtc_destroy(struct drm_crtc *crtc) +{ + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + + DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc); + + drm_crtc_cleanup(crtc); + kfree(pl111_crtc); +} + +const struct drm_crtc_funcs crtc_funcs = { + .cursor_set = pl111_crtc_cursor_set, + .cursor_move = pl111_crtc_cursor_move, + .set_config = drm_crtc_helper_set_config, + .page_flip = pl111_crtc_page_flip, + .destroy = pl111_crtc_destroy +}; + +const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .mode_set = pl111_crtc_helper_mode_set, + .prepare = pl111_crtc_helper_prepare, + .commit = pl111_crtc_helper_commit, + .mode_fixup = pl111_crtc_helper_mode_fixup, + .disable = pl111_crtc_helper_disable, +}; + +struct pl111_drm_crtc *pl111_crtc_create(struct drm_device *dev) +{ + struct pl111_drm_crtc *pl111_crtc; + + pl111_crtc = kzalloc(sizeof(struct pl111_drm_crtc), GFP_KERNEL); + if (pl111_crtc == NULL) { + pr_err("Failed to allocated pl111_drm_crtc\n"); + return NULL; + } + + drm_crtc_init(dev, &pl111_crtc->crtc, &crtc_funcs); + drm_crtc_helper_add(&pl111_crtc->crtc, &crtc_helper_funcs); + + pl111_crtc->crtc_index = pl111_crtc_num; + pl111_crtc_num++; + pl111_crtc->crtc.enabled = 0; + pl111_crtc->last_bpp = 0; + pl111_crtc->current_update_res = NULL; +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + pl111_crtc->displaying_fb = NULL; + pl111_crtc->old_kds_res_set = NULL; + spin_lock_init(&pl111_crtc->current_displaying_lock); +#endif + pl111_crtc->show_framebuffer_cb = show_framebuffer_on_crtc_cb_internal; + INIT_LIST_HEAD(&pl111_crtc->update_queue); + spin_lock_init(&pl111_crtc->base_update_lock); + + return pl111_crtc; +} + diff --git a/drivers/gpu/drm/pl111/pl111_drm_cursor.c b/drivers/gpu/drm/pl111/pl111_drm_cursor.c new file mode 100755 index 000000000000..87f1007d16dd --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_cursor.c @@ -0,0 +1,331 @@ +/* + * + * (C) COPYRIGHT 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_cursor.c + * Implementation of cursor functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include "pl111_clcd_ext.h" +#include "pl111_drm.h" + +#define PL111_MAX_CURSOR_WIDTH (64) +#define PL111_MAX_CURSOR_HEIGHT (64) + +#define ARGB_2_LBBP_BINARY_THRESHOLD (1 << 7) +#define ARGB_ALPHA_SHIFT 24 +#define ARGB_ALPHA_MASK (0xff << ARGB_ALPHA_SHIFT) +#define ARGB_RED_SHIFT 16 +#define ARGB_RED_MASK (0xff << ARGB_RED_SHIFT) +#define ARGB_GREEN_SHIFT 8 +#define ARGB_GREEN_MASK (0xff << ARGB_GREEN_SHIFT) +#define ARGB_BLUE_SHIFT 0 +#define ARGB_BLUE_MASK (0xff << ARGB_BLUE_SHIFT) + + +void pl111_set_cursor_size(enum pl111_cursor_size size) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CONFIG); + + if (size == CURSOR_64X64) + reg_data |= CRSR_CONFIG_CRSR_SIZE; + else + reg_data &= ~CRSR_CONFIG_CRSR_SIZE; + + writel(reg_data, priv.regs + CLCD_CRSR_CONFIG); +} + +void pl111_set_cursor_sync(enum pl111_cursor_sync sync) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CONFIG); + + if (sync == CURSOR_SYNC_VSYNC) + reg_data |= CRSR_CONFIG_CRSR_FRAME_SYNC; + else + reg_data &= ~CRSR_CONFIG_CRSR_FRAME_SYNC; + + writel(reg_data, priv.regs + CLCD_CRSR_CONFIG); +} + +void pl111_set_cursor(u32 cursor) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CTRL); + + reg_data &= ~(CRSR_CTRL_CRSR_MAX << CRSR_CTRL_CRSR_NUM_SHIFT); + reg_data |= (cursor & CRSR_CTRL_CRSR_MAX) << CRSR_CTRL_CRSR_NUM_SHIFT; + + writel(reg_data, priv.regs + CLCD_CRSR_CTRL); +} + +void pl111_set_cursor_enable(bool enable) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CTRL); + + if (enable) + reg_data |= CRSR_CTRL_CRSR_ON; + else + reg_data &= ~CRSR_CTRL_CRSR_ON; + + writel(reg_data, priv.regs + CLCD_CRSR_CTRL); +} + +void pl111_set_cursor_position(u32 x, u32 y) +{ + u32 reg_data = (x & CRSR_XY_MASK) | + ((y & CRSR_XY_MASK) << CRSR_XY_Y_SHIFT); + + writel(reg_data, priv.regs + CLCD_CRSR_XY); +} + +void pl111_set_cursor_clipping(u32 x, u32 y) +{ + u32 reg_data; + + /* + * Do not allow setting clipping values larger than + * the cursor size since the cursor is already fully hidden + * when x,y = PL111_MAX_CURSOR_WIDTH. + */ + if (x > PL111_MAX_CURSOR_WIDTH) + x = PL111_MAX_CURSOR_WIDTH; + if (y > PL111_MAX_CURSOR_WIDTH) + y = PL111_MAX_CURSOR_WIDTH; + + reg_data = (x & CRSR_CLIP_MASK) | + ((y & CRSR_CLIP_MASK) << CRSR_CLIP_Y_SHIFT); + + writel(reg_data, priv.regs + CLCD_CRSR_CLIP); +} + +void pl111_set_cursor_palette(u32 color0, u32 color1) +{ + writel(color0 & CRSR_PALETTE_MASK, priv.regs + CLCD_CRSR_PALETTE_0); + writel(color1 & CRSR_PALETTE_MASK, priv.regs + CLCD_CRSR_PALETTE_1); +} + +void pl111_cursor_enable(void) +{ + pl111_set_cursor_sync(CURSOR_SYNC_VSYNC); + pl111_set_cursor_size(CURSOR_64X64); + pl111_set_cursor_palette(0x0, 0x00ffffff); + pl111_set_cursor_enable(true); +} + +void pl111_cursor_disable(void) +{ + pl111_set_cursor_enable(false); +} + +/* shift required to locate pixel into the correct position in + * a cursor LBBP word, indexed by x mod 16. + */ +static const unsigned char +x_mod_16_to_value_shift[CLCD_CRSR_IMAGE_PIXELS_PER_WORD] = { + 6, 4, 2, 0, 14, 12, 10, 8, 22, 20, 18, 16, 30, 28, 26, 24 +}; + +/* Pack the pixel value into its correct position in the buffer as specified + * for LBBP */ +static inline void +set_lbbp_pixel(uint32_t *buffer, unsigned int x, unsigned int y, + uint32_t value) +{ + u32 *cursor_ram = priv.regs + CLCD_CRSR_IMAGE; + uint32_t shift; + uint32_t data; + + shift = x_mod_16_to_value_shift[x % CLCD_CRSR_IMAGE_PIXELS_PER_WORD]; + + /* Get the word containing this pixel */ + cursor_ram = cursor_ram + (x >> CLCD_CRSR_IMAGE_WORDS_PER_LINE) + (y << 2); + + /* Update pixel in cursor RAM */ + data = readl(cursor_ram); + data &= ~(CLCD_CRSR_LBBP_COLOR_MASK << shift); + data |= value << shift; + writel(data, cursor_ram); +} + +static u32 pl111_argb_to_lbbp(u32 argb_pix) +{ + u32 lbbp_pix = CLCD_CRSR_LBBP_TRANSPARENT; + u32 alpha = (argb_pix & ARGB_ALPHA_MASK) >> ARGB_ALPHA_SHIFT; + u32 red = (argb_pix & ARGB_RED_MASK) >> ARGB_RED_SHIFT; + u32 green = (argb_pix & ARGB_GREEN_MASK) >> ARGB_GREEN_SHIFT; + u32 blue = (argb_pix & ARGB_BLUE_MASK) >> ARGB_BLUE_SHIFT; + + /* + * Converting from 8 pixel transparency to binary transparency + * it's the best we can achieve. + */ + if (alpha & ARGB_2_LBBP_BINARY_THRESHOLD) { + u32 gray, max, min; + + /* + * Convert to gray using the lightness method: + * gray = [max(R,G,B) + min(R,G,B)]/2 + */ + min = min(red, green); + min = min(min, blue); + max = max(red, green); + max = max(max, blue); + gray = (min + max) >> 1; /* divide by 2 */ + /* Apply binary threshold to the gray value calculated */ + if (gray & ARGB_2_LBBP_BINARY_THRESHOLD) + lbbp_pix = CLCD_CRSR_LBBP_FOREGROUND; + else + lbbp_pix = CLCD_CRSR_LBBP_BACKGROUND; + } + + return lbbp_pix; +} + +/* + * The PL111 hardware cursor supports only LBBP which is a 2bpp format but + * the cursor format from userspace is ARGB8888 so we need to convert + * to LBBP here. + */ +static void pl111_set_cursor_image(u32 *data) +{ +#ifdef ARGB_LBBP_CONVERSION_DEBUG + /* Add 1 on width to insert trailing NULL */ + char string_cursor[PL111_MAX_CURSOR_WIDTH + 1]; +#endif /* ARGB_LBBP_CONVERSION_DEBUG */ + unsigned int x; + unsigned int y; + + for (y = 0; y < PL111_MAX_CURSOR_HEIGHT; y++) { + for (x = 0; x < PL111_MAX_CURSOR_WIDTH; x++) { + u32 value = pl111_argb_to_lbbp(*data); + +#ifdef ARGB_LBBP_CONVERSION_DEBUG + if (value == CLCD_CRSR_LBBP_TRANSPARENT) + string_cursor[x] = 'T'; + else if (value == CLCD_CRSR_LBBP_FOREGROUND) + string_cursor[x] = 'F'; + else if (value == CLCD_CRSR_LBBP_INVERSE) + string_cursor[x] = 'I'; + else + string_cursor[x] = 'B'; + +#endif /* ARGB_LBBP_CONVERSION_DEBUG */ + set_lbbp_pixel(data, x, y, value); + ++data; + } +#ifdef ARGB_LBBP_CONVERSION_DEBUG + string_cursor[PL111_MAX_CURSOR_WIDTH] = '\0'; + DRM_INFO("%s\n", string_cursor); +#endif /* ARGB_LBBP_CONVERSION_DEBUG */ + } +} + +int pl111_crtc_cursor_set(struct drm_crtc *crtc, + struct drm_file *file_priv, + uint32_t handle, + uint32_t width, + uint32_t height) +{ + struct drm_gem_object *obj; + struct pl111_gem_bo *bo; + + DRM_DEBUG_KMS("handle = %u, width = %u, height = %u\n", + handle, width, height); + + if (!handle) { + pl111_cursor_disable(); + return 0; + } + + if ((width != PL111_MAX_CURSOR_WIDTH) || + (height != PL111_MAX_CURSOR_HEIGHT)) + return -EINVAL; + + obj = drm_gem_object_lookup(crtc->dev, file_priv, handle); + if (!obj) { + DRM_ERROR("Cannot find cursor object for handle = %d\n", + handle); + return -ENOENT; + } + + /* + * We expect a PL111_MAX_CURSOR_WIDTH x PL111_MAX_CURSOR_HEIGHT + * ARGB888 buffer object in the input. + * + */ + if (obj->size < (PL111_MAX_CURSOR_WIDTH * PL111_MAX_CURSOR_HEIGHT * 4)) { + DRM_ERROR("Cannot set cursor with an obj size = %d\n", + obj->size); + drm_gem_object_unreference_unlocked(obj); + return -EINVAL; + } + + bo = PL111_BO_FROM_GEM(obj); + if (!(bo->type & PL111_BOT_DMA)) { + DRM_ERROR("Tried to set cursor with non DMA backed obj = %p\n", + obj); + drm_gem_object_unreference_unlocked(obj); + return -EINVAL; + } + + pl111_set_cursor_image(bo->backing_data.dma.fb_cpu_addr); + + /* + * Since we copy the contents of the buffer to the HW cursor internal + * memory this GEM object is not needed anymore. + */ + drm_gem_object_unreference_unlocked(obj); + + pl111_cursor_enable(); + + return 0; +} + +int pl111_crtc_cursor_move(struct drm_crtc *crtc, + int x, int y) +{ + int x_clip = 0; + int y_clip = 0; + + DRM_DEBUG("x %d y %d\n", x, y); + + /* + * The cursor image is clipped automatically at the screen limits when + * it extends beyond the screen image to the right or bottom but + * we must clip it using pl111 HW features for negative values. + */ + if (x < 0) { + x_clip = -x; + x = 0; + } + if (y < 0) { + y_clip = -y; + y = 0; + } + + pl111_set_cursor_clipping(x_clip, y_clip); + pl111_set_cursor_position(x, y); + + return 0; +} diff --git a/drivers/gpu/drm/pl111/pl111_drm_device.c b/drivers/gpu/drm/pl111/pl111_drm_device.c new file mode 100755 index 000000000000..b8ea5f6725ec --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_device.c @@ -0,0 +1,326 @@ +/* + * + * (C) COPYRIGHT 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; + + 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; + } + + 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, + .fasync = drm_fasync, +}; + +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_FB_DMA | 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 = DRM_ARRAY_SIZE(pl111_ioctls); + ret = 0; + driver.kdriver.platform_device = dev; + return drm_platform_init(&driver, dev); + +} + +void pl111_drm_exit(struct platform_device *dev) +{ + pr_info("DRM %s\n", __func__); + drm_platform_exit(&driver, dev); +} diff --git a/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c b/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c new file mode 100755 index 000000000000..a0d65f1d5faf --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c @@ -0,0 +1,615 @@ +/* + * + * (C) COPYRIGHT 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_dma_buf.c + * Implementation of the dma_buf functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "pl111_drm.h" + +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS +static void obtain_kds_if_currently_displayed(struct drm_device *dev, + struct pl111_gem_bo *bo, + struct dma_buf *dma_buf) +{ + unsigned long shared[1] = { 0 }; + struct kds_resource *resource_list[1]; + struct kds_resource_set *kds_res_set; + struct drm_crtc *crtc; + bool cb_has_called = false; + unsigned long flags; + int err; + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake); + + DRM_DEBUG_KMS("Obtaining initial KDS res for bo:%p dma_buf:%p\n", + bo, dma_buf); + + resource_list[0] = get_dma_buf_kds_resource(dma_buf); + get_dma_buf(dma_buf); + + /* + * Can't use kds_waitall(), because kbase will be let through due to + * locked ignore' + */ + err = kds_async_waitall(&kds_res_set, + &priv.kds_obtain_current_cb, &wake, + &cb_has_called, 1, shared, resource_list); + BUG_ON(err); + wait_event(wake, cb_has_called == true); + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); + if (pl111_crtc->displaying_fb) { + struct pl111_drm_framebuffer *pl111_fb; + struct drm_framebuffer *fb = pl111_crtc->displaying_fb; + + pl111_fb = PL111_FB_FROM_FRAMEBUFFER(fb); + + if (pl111_fb->bo == bo) { + DRM_DEBUG_KMS("Initial KDS resource for bo %p", bo); + DRM_DEBUG_KMS(" is being displayed, keeping\n"); + /* There shouldn't be a previous buffer to release */ + BUG_ON(pl111_crtc->old_kds_res_set); + + if (kds_res_set == NULL) { + err = kds_async_waitall(&kds_res_set, + &priv.kds_obtain_current_cb, + &wake, &cb_has_called, + 1, shared, resource_list); + BUG_ON(err); + wait_event(wake, cb_has_called == true); + } + + /* Current buffer will need releasing on next flip */ + pl111_crtc->old_kds_res_set = kds_res_set; + + /* + * Clear kds_res_set, so a new kds_res_set is allocated + * for additional CRTCs + */ + kds_res_set = NULL; + } + } + spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags); + } + + /* kds_res_set will be NULL here if any CRTCs are displaying fb */ + if (kds_res_set != NULL) { + DRM_DEBUG_KMS("Initial KDS resource for bo %p", bo); + DRM_DEBUG_KMS(" not being displayed, discarding\n"); + /* They're not being displayed, release them */ + kds_resource_set_release(&kds_res_set); + } + + dma_buf_put(dma_buf); +} +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) + +static int pl111_dma_buf_mmap(struct dma_buf *buffer, + struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = buffer->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + struct drm_device *dev = obj->dev; + int ret; + + DRM_DEBUG_KMS("DRM %s on dma_buf=%p\n", __func__, buffer); + + mutex_lock(&dev->struct_mutex); + ret = drm_gem_mmap_obj(obj, obj->size, vma); + mutex_unlock(&dev->struct_mutex); + if (ret) + return ret; + + return pl111_bo_mmap(obj, bo, vma, buffer->size); +} + +#else + +static int pl111_dma_buf_mmap(struct dma_buf *buffer, + struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = buffer->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + struct drm_device *dev = obj->dev; + + DRM_DEBUG_KMS("DRM %s on dma_buf=%p\n", __func__, buffer); + + mutex_lock(&dev->struct_mutex); + + /* Check for valid size. */ + if (obj->size < vma->vm_end - vma->vm_start) + return -EINVAL; + + BUG_ON(!dev->driver->gem_vm_ops); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) + vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; +#else + vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND; +#endif + + vma->vm_ops = dev->driver->gem_vm_ops; + vma->vm_private_data = obj; + + /* Take a ref for this mapping of the object, so that the fault + * handler can dereference the mmap offset's pointer to the object. + * This reference is cleaned up by the corresponding vm_close + * (which should happen whether the vma was created by this call, or + * by a vm_open due to mremap or partial unmap or whatever). + */ + drm_gem_object_reference(obj); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) + pl111_drm_vm_open_locked(dev, vma); +#else + drm_vm_open_locked(dev, vma); +#endif + + mutex_unlock(&dev->struct_mutex); + + return pl111_bo_mmap(obj, bo, vma, buffer->size); +} + +#endif /* KERNEL_VERSION */ + +static void pl111_dma_buf_release(struct dma_buf *buf) +{ + /* + * Need to release the dma_buf's reference on the gem object it was + * exported from, and also clear the gem object's export_dma_buf + * pointer to this dma_buf as it no longer exists + */ + struct drm_gem_object *obj = (struct drm_gem_object *)buf->priv; + struct pl111_gem_bo *bo; +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + struct drm_crtc *crtc; + unsigned long flags; +#endif + bo = PL111_BO_FROM_GEM(obj); + + DRM_DEBUG_KMS("Releasing dma_buf %p, drm_gem_obj=%p\n", buf, obj); + +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + list_for_each_entry(crtc, &bo->gem_object.dev->mode_config.crtc_list, + head) { + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); + if (pl111_crtc->displaying_fb) { + struct pl111_drm_framebuffer *pl111_fb; + struct drm_framebuffer *fb = pl111_crtc->displaying_fb; + + pl111_fb = PL111_FB_FROM_FRAMEBUFFER(fb); + if (pl111_fb->bo == bo) { + kds_resource_set_release(&pl111_crtc->old_kds_res_set); + pl111_crtc->old_kds_res_set = NULL; + } + } + spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags); + } +#endif + mutex_lock(&priv.export_dma_buf_lock); + + obj->export_dma_buf = NULL; + drm_gem_object_unreference_unlocked(obj); + + mutex_unlock(&priv.export_dma_buf_lock); +} + +static int pl111_dma_buf_attach(struct dma_buf *buf, struct device *dev, + struct dma_buf_attachment *attach) +{ + DRM_DEBUG_KMS("Attaching dma_buf %p to device %p attach=%p\n", buf, + dev, attach); + + attach->priv = dev; + + return 0; +} + +static void pl111_dma_buf_detach(struct dma_buf *buf, + struct dma_buf_attachment *attach) +{ + DRM_DEBUG_KMS("Detaching dma_buf %p attach=%p\n", attach->dmabuf, + attach); +} + +/* Heavily from exynos_drm_dmabuf.c */ +static struct sg_table *pl111_dma_buf_map_dma_buf(struct dma_buf_attachment + *attach, + enum dma_data_direction + direction) +{ + struct drm_gem_object *obj = attach->dmabuf->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + struct drm_device *dev = obj->dev; + int size, n_pages, nents; + struct scatterlist *s, *sg; + struct sg_table *sgt; + int ret, i; + + DRM_DEBUG_KMS("Mapping dma_buf %p from attach=%p (bo=%p)\n", attach->dmabuf, + attach, bo); + + /* + * Nothing to do, if we are trying to map a dmabuf that has been imported. + * Just return the existing sgt. + */ + if (obj->import_attach) { + BUG_ON(!bo->sgt); + return bo->sgt; + } + + size = obj->size; + n_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + + if (bo->type & PL111_BOT_DMA) { + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + DRM_ERROR("Failed to allocate sg_table\n"); + return ERR_PTR(-ENOMEM); + } + + ret = sg_alloc_table(sgt, 1, GFP_KERNEL); + if (ret < 0) { + DRM_ERROR("Failed to allocate page table\n"); + return ERR_PTR(-ENOMEM); + } + sg_dma_len(sgt->sgl) = size; + /* We use DMA coherent mappings for PL111_BOT_DMA so we must + * use the virtual address returned at buffer allocation */ + sg_set_buf(sgt->sgl, bo->backing_data.dma.fb_cpu_addr, size); + sg_dma_address(sgt->sgl) = bo->backing_data.dma.fb_dev_addr; + } else { /* PL111_BOT_SHM */ + struct page **pages; + int pg = 0; + + mutex_lock(&dev->struct_mutex); + pages = get_pages(obj); + if (IS_ERR(pages)) { + dev_err(obj->dev->dev, "could not get pages: %ld\n", + PTR_ERR(pages)); + return ERR_CAST(pages); + } + sgt = drm_prime_pages_to_sg(pages, n_pages); + if (sgt == NULL) + return ERR_PTR(-ENOMEM); + + pl111_gem_sync_to_dma(bo); + + /* + * At this point the pages have been dma-mapped by either + * get_pages() for non cached maps or pl111_gem_sync_to_dma() + * for cached. So the physical addresses can be assigned + * to the sg entries. + * drm_prime_pages_to_sg() may have combined contiguous pages + * into chunks so we assign the physical address of the first + * page of a chunk to the chunk and check that the physical + * addresses of the rest of the pages in that chunk are also + * contiguous. + */ + sg = sgt->sgl; + nents = sgt->nents; + + for_each_sg(sg, s, nents, i) { + int j, n_pages_in_chunk = sg_dma_len(s) >> PAGE_SHIFT; + + sg_dma_address(s) = bo->backing_data.shm.dma_addrs[pg]; + + for (j = pg+1; j < pg+n_pages_in_chunk; j++) { + BUG_ON(bo->backing_data.shm.dma_addrs[j] != + bo->backing_data.shm.dma_addrs[j-1]+PAGE_SIZE); + } + + pg += n_pages_in_chunk; + } + + mutex_unlock(&dev->struct_mutex); + } + bo->sgt = sgt; + return sgt; +} + +static void pl111_dma_buf_unmap_dma_buf(struct dma_buf_attachment *attach, + struct sg_table *sgt, + enum dma_data_direction direction) +{ + struct drm_gem_object *obj = attach->dmabuf->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + + DRM_DEBUG_KMS("Unmapping dma_buf %p from attach=%p (bo=%p)\n", attach->dmabuf, + attach, bo); + + sg_free_table(sgt); + kfree(sgt); + bo->sgt = NULL; +} + +/* + * There isn't any operation here that can sleep or fail so this callback can + * be used for both kmap and kmap_atomic implementations. + */ +static void *pl111_dma_buf_kmap(struct dma_buf *dma_buf, unsigned long pageno) +{ + struct pl111_gem_bo *bo = dma_buf->priv; + void *vaddr = NULL; + + /* Make sure we cannot access outside the memory range */ + if (((pageno + 1) << PAGE_SHIFT) > bo->gem_object.size) + return NULL; + + if (bo->type & PL111_BOT_DMA) { + vaddr = (bo->backing_data.dma.fb_cpu_addr + + (pageno << PAGE_SHIFT)); + } else { + vaddr = page_address(bo->backing_data.shm.pages[pageno]); + } + + return vaddr; +} + +/* + * Find a scatterlist that starts in "start" and has "len" + * or return a NULL dma_handle. + */ +static dma_addr_t pl111_find_matching_sg(struct sg_table *sgt, size_t start, + size_t len) +{ + struct scatterlist *sg; + unsigned int count; + size_t size = 0; + dma_addr_t dma_handle = 0; + + /* Find a scatterlist that starts in "start" and has "len" + * or return error */ + for_each_sg(sgt->sgl, sg, sgt->nents, count) { + if ((size == start) && (len == sg_dma_len(sg))) { + dma_handle = sg_dma_address(sg); + break; + } + size += sg_dma_len(sg); + } + return dma_handle; +} + +static int pl111_dma_buf_begin_cpu(struct dma_buf *dma_buf, + size_t start, size_t len, + enum dma_data_direction dir) +{ + struct pl111_gem_bo *bo = dma_buf->priv; + struct sg_table *sgt = bo->sgt; + dma_addr_t dma_handle; + + if ((start + len) > bo->gem_object.size) + return -EINVAL; + + if (!(bo->type & PL111_BOT_SHM)) { + struct device *dev = bo->gem_object.dev->dev; + + dma_handle = pl111_find_matching_sg(sgt, start, len); + if (!dma_handle) + return -EINVAL; + + dma_sync_single_range_for_cpu(dev, dma_handle, 0, len, dir); + } + /* PL111_BOT_DMA uses coherents mappings, no need to sync */ + return 0; +} + +static void pl111_dma_buf_end_cpu(struct dma_buf *dma_buf, + size_t start, size_t len, + enum dma_data_direction dir) +{ + struct pl111_gem_bo *bo = dma_buf->priv; + struct sg_table *sgt = bo->sgt; + dma_addr_t dma_handle; + + if ((start + len) > bo->gem_object.size) + return; + + if (!(bo->type & PL111_BOT_DMA)) { + struct device *dev = bo->gem_object.dev->dev; + + dma_handle = pl111_find_matching_sg(sgt, start, len); + if (!dma_handle) + return; + + dma_sync_single_range_for_device(dev, dma_handle, 0, len, dir); + } + /* PL111_BOT_DMA uses coherents mappings, no need to sync */ +} + +static struct dma_buf_ops pl111_dma_buf_ops = { + .release = &pl111_dma_buf_release, + .attach = &pl111_dma_buf_attach, + .detach = &pl111_dma_buf_detach, + .map_dma_buf = &pl111_dma_buf_map_dma_buf, + .unmap_dma_buf = &pl111_dma_buf_unmap_dma_buf, + .kmap_atomic = &pl111_dma_buf_kmap, + .kmap = &pl111_dma_buf_kmap, + .begin_cpu_access = &pl111_dma_buf_begin_cpu, + .end_cpu_access = &pl111_dma_buf_end_cpu, + .mmap = &pl111_dma_buf_mmap, +}; + +struct drm_gem_object *pl111_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + struct dma_buf_attachment *attachment; + struct drm_gem_object *obj; + struct pl111_gem_bo *bo; + struct scatterlist *sgl; + struct sg_table *sgt; + dma_addr_t cont_phys; + int ret = 0; + int i; + + DRM_DEBUG_KMS("DRM %s on dev=%p dma_buf=%p\n", __func__, dev, dma_buf); + + /* is this one of own objects? */ + if (dma_buf->ops == &pl111_dma_buf_ops) { + obj = dma_buf->priv; + /* is it from our device? */ + if (obj->dev == dev) { + /* + * Importing dmabuf exported from our own gem increases + * refcount on gem itself instead of f_count of dmabuf. + */ + drm_gem_object_reference(obj); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) + /* before v3.10.0 we assume the caller has taken a ref on the dma_buf + * we don't want it for self-imported buffers so drop it here */ + dma_buf_put(dma_buf); +#endif + + return obj; + } + } + + attachment = dma_buf_attach(dma_buf, dev->dev); + if (IS_ERR(attachment)) + return ERR_CAST(attachment); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) + /* from 3.10.0 we assume the caller has not taken a ref so we take one here */ + get_dma_buf(dma_buf); +#endif + + sgt = dma_buf_map_attachment(attachment, DMA_BIDIRECTIONAL); + if (IS_ERR_OR_NULL(sgt)) { + ret = PTR_ERR(sgt); + goto err_buf_detach; + } + + bo = kzalloc(sizeof(*bo), GFP_KERNEL); + if (!bo) { + DRM_ERROR("%s: failed to allocate buffer object.\n", __func__); + ret = -ENOMEM; + goto err_unmap_attach; + } + + /* Find out whether the buffer is contiguous or not */ + sgl = sgt->sgl; + cont_phys = sg_phys(sgl); + bo->type |= PL111_BOT_DMA; + for_each_sg(sgt->sgl, sgl, sgt->nents, i) { + dma_addr_t real_phys = sg_phys(sgl); + if (real_phys != cont_phys) { + bo->type &= ~PL111_BOT_DMA; + break; + } + cont_phys += (PAGE_SIZE - sgl->offset); + } + + ret = drm_gem_private_object_init(dev, &bo->gem_object, + dma_buf->size); + if (ret != 0) { + DRM_ERROR("DRM could not import DMA GEM obj\n"); + goto err_free_buffer; + } + + if (bo->type & PL111_BOT_DMA) { + bo->backing_data.dma.fb_cpu_addr = sg_virt(sgt->sgl); + bo->backing_data.dma.fb_dev_addr = sg_phys(sgt->sgl); + DRM_DEBUG_KMS("DRM %s pl111_gem_bo=%p, contiguous import\n", __func__, bo); + } else { /* PL111_BOT_SHM */ + DRM_DEBUG_KMS("DRM %s pl111_gem_bo=%p, non contiguous import\n", __func__, bo); + } + + bo->gem_object.import_attach = attachment; + bo->sgt = sgt; + + return &bo->gem_object; + +err_free_buffer: + kfree(bo); + bo = NULL; +err_unmap_attach: + dma_buf_unmap_attachment(attachment, sgt, DMA_BIDIRECTIONAL); +err_buf_detach: + dma_buf_detach(dma_buf, attachment); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) + /* from 3.10.0 we will have taken a ref so drop it here */ + dma_buf_put(dma_buf); +#endif + return ERR_PTR(ret); +} + +struct dma_buf *pl111_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, int flags) +{ + struct dma_buf *new_buf; + struct pl111_gem_bo *bo; + size_t size; + + DRM_DEBUG("DRM %s on dev=%p drm_gem_obj=%p\n", __func__, dev, obj); + size = obj->size; + + new_buf = dma_buf_export(obj /*priv */ , &pl111_dma_buf_ops, size, + flags | O_RDWR); + bo = PL111_BO_FROM_GEM(new_buf->priv); + + /* + * bo->gem_object.export_dma_buf not setup until after gem_prime_export + * finishes + */ + +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + /* + * Ensure that we hold the kds resource if it's the currently + * displayed buffer. + */ + obtain_kds_if_currently_displayed(dev, bo, new_buf); +#endif + + DRM_DEBUG("Created dma_buf %p\n", new_buf); + + return new_buf; +} + +int pl111_prime_handle_to_fd(struct drm_device *dev, struct drm_file *file_priv, + uint32_t handle, uint32_t flags, int *prime_fd) +{ + int result; + /* + * This will re-use any existing exports, and calls + * driver->gem_prime_export to do the first export when needed + */ + DRM_DEBUG_KMS("DRM %s on file_priv=%p, handle=0x%.8x\n", __func__, + file_priv, handle); + + mutex_lock(&priv.export_dma_buf_lock); + result = drm_gem_prime_handle_to_fd(dev, file_priv, handle, flags, + prime_fd); + mutex_unlock(&priv.export_dma_buf_lock); + + return result; +} diff --git a/drivers/gpu/drm/pl111/pl111_drm_encoder.c b/drivers/gpu/drm/pl111/pl111_drm_encoder.c new file mode 100755 index 000000000000..b7dbbf2794ab --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_encoder.c @@ -0,0 +1,107 @@ +/* + * + * (C) COPYRIGHT 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_encoder.c + * Implementation of the encoder functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "pl111_drm.h" + +bool pl111_encoder_helper_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder); + return true; +} + +void pl111_encoder_helper_prepare(struct drm_encoder *encoder) +{ + DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder); +} + +void pl111_encoder_helper_commit(struct drm_encoder *encoder) +{ + DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder); +} + +void pl111_encoder_helper_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder); +} + +void pl111_encoder_helper_disable(struct drm_encoder *encoder) +{ + DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder); +} + +void pl111_encoder_destroy(struct drm_encoder *encoder) +{ + struct pl111_drm_encoder *pl111_encoder = + PL111_ENCODER_FROM_ENCODER(encoder); + + DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder); + + drm_encoder_cleanup(encoder); + kfree(pl111_encoder); +} + +const struct drm_encoder_funcs encoder_funcs = { + .destroy = pl111_encoder_destroy, +}; + +const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .mode_fixup = pl111_encoder_helper_mode_fixup, + .prepare = pl111_encoder_helper_prepare, + .commit = pl111_encoder_helper_commit, + .mode_set = pl111_encoder_helper_mode_set, + .disable = pl111_encoder_helper_disable, +}; + +struct pl111_drm_encoder *pl111_encoder_create(struct drm_device *dev, + int possible_crtcs) +{ + struct pl111_drm_encoder *pl111_encoder; + + pl111_encoder = kzalloc(sizeof(struct pl111_drm_encoder), GFP_KERNEL); + if (pl111_encoder == NULL) { + pr_err("Failed to allocated pl111_drm_encoder\n"); + return NULL; + } + + drm_encoder_init(dev, &pl111_encoder->encoder, &encoder_funcs, + DRM_MODE_ENCODER_DAC); + + drm_encoder_helper_add(&pl111_encoder->encoder, &encoder_helper_funcs); + + pl111_encoder->encoder.possible_crtcs = possible_crtcs; + + return pl111_encoder; +} + diff --git a/drivers/gpu/drm/pl111/pl111_drm_fb.c b/drivers/gpu/drm/pl111/pl111_drm_fb.c new file mode 100755 index 000000000000..546e4d45f4c9 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_fb.c @@ -0,0 +1,202 @@ +/* + * + * (C) COPYRIGHT 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_fb.c + * Implementation of the framebuffer functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_crtc.h> +#include "pl111_drm.h" + +static void pl111_fb_destroy(struct drm_framebuffer *framebuffer) +{ + struct pl111_drm_framebuffer *pl111_fb; +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + struct drm_crtc *crtc; + unsigned long flags; +#endif + DRM_DEBUG_KMS("Destroying framebuffer 0x%p...\n", framebuffer); + + pl111_fb = PL111_FB_FROM_FRAMEBUFFER(framebuffer); + + /* + * Because flips are deferred, wait for all previous flips to complete + */ + wait_event(priv.wait_for_flips, + atomic_read(&priv.nr_flips_in_flight) == 0); +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + /* + * Release KDS resources if it's currently being displayed. Only occurs + * when the last framebuffer is destroyed. + */ + list_for_each_entry(crtc, &framebuffer->dev->mode_config.crtc_list, + head) { + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); + if (pl111_crtc->displaying_fb == framebuffer) { + /* Release the current buffers */ + if (pl111_crtc->old_kds_res_set != NULL) { + DRM_DEBUG_KMS("Releasing KDS resources for "); + DRM_DEBUG_KMS("displayed 0x%p\n", framebuffer); + kds_resource_set_release( + &pl111_crtc->old_kds_res_set); + } + pl111_crtc->old_kds_res_set = NULL; + } + spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, + flags); + } +#endif + drm_framebuffer_cleanup(framebuffer); + + if ((pl111_fb->bo != NULL) && (&pl111_fb->bo->gem_object != NULL)) + drm_gem_object_unreference_unlocked(&pl111_fb->bo->gem_object); + + kfree(pl111_fb); + + DRM_DEBUG_KMS("Destroyed framebuffer 0x%p\n", framebuffer); +} + +static int pl111_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct pl111_gem_bo *bo = PL111_BO_FROM_FRAMEBUFFER(fb); + DRM_DEBUG_KMS("DRM %s on fb=%p\n", __func__, fb); + + if (bo == NULL) + return -EINVAL; + + return drm_gem_handle_create(file_priv, &bo->gem_object, handle); +} + +const struct drm_framebuffer_funcs fb_funcs = { + .destroy = pl111_fb_destroy, + .create_handle = pl111_fb_create_handle, +}; + +struct drm_framebuffer *pl111_fb_create(struct drm_device *dev, + struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct pl111_drm_framebuffer *pl111_fb = NULL; + struct drm_framebuffer *fb = NULL; + struct drm_gem_object *gem_obj; + struct pl111_gem_bo *bo; + int err = 0; + size_t min_size; + int bpp; + int depth; + + pr_info("DRM %s\n", __func__); + gem_obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); + if (gem_obj == NULL) { + DRM_ERROR("Could not get gem obj from handle to create fb\n"); + err = -ENOENT; + goto error; + } + + bo = PL111_BO_FROM_GEM(gem_obj); + drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp); + + if (mode_cmd->pitches[0] < mode_cmd->width * (bpp >> 3)) { + DRM_ERROR("bad pitch %u for plane 0\n", mode_cmd->pitches[0]); + err = -EINVAL; + goto error; + } + + min_size = (mode_cmd->height - 1) * mode_cmd->pitches[0] + + mode_cmd->width * (bpp >> 3); + + if (bo->gem_object.size < min_size) { + DRM_ERROR("gem obj size < min size\n"); + err = -EINVAL; + goto error; + } + + /* We can't scan out SHM so we can't create an fb for it */ + if (!(bo->type & PL111_BOT_DMA)) { + DRM_ERROR("Can't create FB for non-scanout buffer\n"); + err = -EINVAL; + goto error; + } + + switch ((char)(mode_cmd->pixel_format & 0xFF)) { + case 'Y': + case 'U': + case 'V': + case 'N': + case 'T': + DRM_ERROR("YUV formats not supported\n"); + err = -EINVAL; + goto error; + } + + pl111_fb = kzalloc(sizeof(struct pl111_drm_framebuffer), GFP_KERNEL); + if (pl111_fb == NULL) { + DRM_ERROR("Could not allocate pl111_drm_framebuffer\n"); + err = -ENOMEM; + goto error; + } + fb = &pl111_fb->fb; + + err = drm_framebuffer_init(dev, fb, &fb_funcs); + if (err) { + DRM_ERROR("drm_framebuffer_init failed\n"); + kfree(fb); + fb = NULL; + goto error; + } + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + /* The only framebuffer formats supported by pl111 + * are 16 bpp or 32 bpp with 24 bit depth. + * See clcd_enable() + */ + if (!((fb->bits_per_pixel == 16) || + (fb->bits_per_pixel == 32 && fb->depth == 24))) { + DRM_DEBUG_KMS("unsupported pixel format bpp=%d, depth=%d\n", fb->bits_per_pixel, fb->depth); + drm_framebuffer_cleanup(fb); + kfree(fb); + fb = NULL; + err = -EINVAL; + goto error; + } + + pl111_fb->bo = bo; + + DRM_DEBUG_KMS("Created fb 0x%p with gem_obj 0x%p physaddr=0x%.8x\n", + fb, gem_obj, bo->backing_data.dma.fb_dev_addr); + + return fb; + +error: + if (gem_obj != NULL) + drm_gem_object_unreference_unlocked(gem_obj); + + return ERR_PTR(err); +} diff --git a/drivers/gpu/drm/pl111/pl111_drm_funcs.h b/drivers/gpu/drm/pl111/pl111_drm_funcs.h new file mode 100755 index 000000000000..34e0bad994d7 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_funcs.h @@ -0,0 +1,130 @@ +/* + * + * (C) COPYRIGHT 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_funcs.h + * Function prototypes for PL111 DRM + */ + +#ifndef PL111_DRM_FUNCS_H_ +#define PL111_DRM_FUNCS_H_ + +/* Platform Initialisation */ +int pl111_drm_init(struct platform_device *dev); +void pl111_drm_exit(struct platform_device *dev); + +/* KDS Callbacks */ +void show_framebuffer_on_crtc_cb(void *cb1, void *cb2); +void release_kds_resource_and_display(struct pl111_drm_flip_resource *flip_res); + +/* CRTC Functions */ +struct pl111_drm_crtc *pl111_crtc_create(struct drm_device *dev); +struct pl111_drm_crtc *pl111_crtc_dummy_create(struct drm_device *dev); +void pl111_crtc_destroy(struct drm_crtc *crtc); + +bool pl111_crtc_is_fb_currently_displayed(struct drm_device *dev, + struct drm_framebuffer *fb); + +int show_framebuffer_on_crtc(struct drm_crtc *crtc, + struct drm_framebuffer *fb, bool page_flip, + struct drm_pending_vblank_event *event); + +/* Common IRQ handler */ +void pl111_common_irq(struct pl111_drm_crtc *pl111_crtc); + +int pl111_crtc_cursor_set(struct drm_crtc *crtc, + struct drm_file *file_priv, + uint32_t handle, + uint32_t width, + uint32_t height); +int pl111_crtc_cursor_move(struct drm_crtc *crtc, + int x, int y); + +/* Connector Functions */ +struct pl111_drm_connector *pl111_connector_create(struct drm_device *dev); +void pl111_connector_destroy(struct drm_connector *connector); +struct pl111_drm_connector *pl111_connector_dummy_create(struct drm_device + *dev); + +/* Encoder Functions */ +struct pl111_drm_encoder *pl111_encoder_create(struct drm_device *dev, + int possible_crtcs); +struct pl111_drm_encoder *pl111_encoder_dummy_create(struct drm_device *dev, + int possible_crtcs); +void pl111_encoder_destroy(struct drm_encoder *encoder); + +/* Frame Buffer Functions */ +struct drm_framebuffer *pl111_fb_create(struct drm_device *dev, + struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd); + +/* VMA Functions */ +int pl111_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); +int pl111_gem_mmap(struct file *file_priv, struct vm_area_struct *vma); +struct page **get_pages(struct drm_gem_object *obj); +void put_pages(struct drm_gem_object *obj, struct page **pages); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) +void pl111_drm_vm_open_locked(struct drm_device *dev, + struct vm_area_struct *vma); +void pl111_gem_vm_open(struct vm_area_struct *vma); +void pl111_gem_vm_close(struct vm_area_struct *vma); +#endif + +/* Suspend Functions */ +int pl111_drm_resume(struct drm_device *dev); +int pl111_drm_suspend(struct drm_device *dev, pm_message_t state); + +/* GEM Functions */ +int pl111_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, + struct drm_mode_create_dumb *args); +int pl111_dumb_destroy(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle); +int pl111_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset); +void pl111_gem_free_object(struct drm_gem_object *obj); + +int pl111_bo_mmap(struct drm_gem_object *obj, struct pl111_gem_bo *bo, + struct vm_area_struct *vma, size_t size); +void pl111_gem_sync_to_cpu(struct pl111_gem_bo *bo, int pgoff); +void pl111_gem_sync_to_dma(struct pl111_gem_bo *bo); + +/* DMA BUF Functions */ +struct drm_gem_object *pl111_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf); +int pl111_prime_handle_to_fd(struct drm_device *dev, struct drm_file *file_priv, + uint32_t handle, uint32_t flags, int *prime_fd); +struct dma_buf *pl111_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, int flags); + +/* Pl111 Functions */ +void show_framebuffer_on_crtc_cb_internal(struct pl111_drm_flip_resource + *flip_res, struct drm_framebuffer *fb); +int clcd_disable(struct drm_crtc *crtc); +void do_flip_to_res(struct pl111_drm_flip_resource *flip_res); +int pl111_amba_probe(struct amba_device *dev, const struct amba_id *id); +int pl111_amba_remove(struct amba_device *dev); + +int pl111_device_init(struct drm_device *dev); +void pl111_device_fini(struct drm_device *dev); + +void pl111_convert_drm_mode_to_timing(struct drm_display_mode *mode, + struct clcd_regs *timing); +void pl111_convert_timing_to_drm_mode(struct clcd_regs *timing, + struct drm_display_mode *mode); +#endif /* PL111_DRM_FUNCS_H_ */ diff --git a/drivers/gpu/drm/pl111/pl111_drm_gem.c b/drivers/gpu/drm/pl111/pl111_drm_gem.c new file mode 100755 index 000000000000..ed5dcabe0082 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_gem.c @@ -0,0 +1,401 @@ +/* + * + * (C) COPYRIGHT 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_gem.c + * Implementation of the GEM functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <asm/cacheflush.h> +#include <asm/outercache.h> +#include "pl111_drm.h" + +void pl111_gem_free_object(struct drm_gem_object *obj) +{ + struct pl111_gem_bo *bo; + struct drm_device *dev = obj->dev; + DRM_DEBUG_KMS("DRM %s on drm_gem_object=%p\n", __func__, obj); + + bo = PL111_BO_FROM_GEM(obj); + + if (obj->import_attach) + drm_prime_gem_destroy(obj, bo->sgt); + + if (obj->map_list.map != NULL) + drm_gem_free_mmap_offset(obj); + + /* + * Only free the backing memory if the object has not been imported. + * If it has been imported, the exporter is in charge to free that + * once dmabuf's refcount becomes 0. + */ + if (obj->import_attach) + goto imported_out; + + if (bo->type & PL111_BOT_DMA) { + dma_free_writecombine(dev->dev, obj->size, + bo->backing_data.dma.fb_cpu_addr, + bo->backing_data.dma.fb_dev_addr); + } else if (bo->backing_data.shm.pages != NULL) { + put_pages(obj, bo->backing_data.shm.pages); + } + +imported_out: + drm_gem_object_release(obj); + + kfree(bo); + + DRM_DEBUG_KMS("Destroyed dumb_bo handle 0x%p\n", bo); +} + +static int pl111_gem_object_create(struct drm_device *dev, u64 size, + u32 flags, struct drm_file *file_priv, + u32 *handle) +{ + int ret = 0; + struct pl111_gem_bo *bo = NULL; + + bo = kzalloc(sizeof(*bo), GFP_KERNEL); + if (bo == NULL) { + ret = -ENOMEM; + goto finish; + } + + bo->type = flags; + +#ifndef ARCH_HAS_SG_CHAIN + /* + * If the ARCH can't chain we can't have non-contiguous allocs larger + * than a single sg can hold. + * In this case we fall back to using contiguous memory + */ + if (!(bo->type & PL111_BOT_DMA)) { + long unsigned int n_pages = + PAGE_ALIGN(size) >> PAGE_SHIFT; + if (n_pages > SG_MAX_SINGLE_ALLOC) { + bo->type |= PL111_BOT_DMA; + /* + * Non-contiguous allocation request changed to + * contigous + */ + DRM_INFO("non-contig alloc to contig %lu > %lu pages.", + n_pages, SG_MAX_SINGLE_ALLOC); + } + } +#endif + if (bo->type & PL111_BOT_DMA) { + /* scanout compatible - use physically contiguous buffer */ + bo->backing_data.dma.fb_cpu_addr = + dma_alloc_writecombine(dev->dev, size, + &bo->backing_data.dma.fb_dev_addr, + GFP_KERNEL); + if (bo->backing_data.dma.fb_cpu_addr == NULL) { + DRM_ERROR("dma_alloc_writecombine failed\n"); + ret = -ENOMEM; + goto free_bo; + } + + ret = drm_gem_private_object_init(dev, &bo->gem_object, + size); + if (ret != 0) { + DRM_ERROR("DRM could not initialise GEM object\n"); + goto free_dma; + } + } else { /* PL111_BOT_SHM */ + /* not scanout compatible - use SHM backed object */ + ret = drm_gem_object_init(dev, &bo->gem_object, size); + if (ret != 0) { + DRM_ERROR("DRM could not init SHM backed GEM obj\n"); + ret = -ENOMEM; + goto free_bo; + } + DRM_DEBUG_KMS("Num bytes: %d\n", bo->gem_object.size); + } + + DRM_DEBUG("s=%llu, flags=0x%x, %s 0x%.8lx, type=%d\n", + size, flags, + (bo->type & PL111_BOT_DMA) ? "physaddr" : "shared page array", + (bo->type & PL111_BOT_DMA) ? + (unsigned long)bo->backing_data.dma.fb_dev_addr: + (unsigned long)bo->backing_data.shm.pages, + bo->type); + + ret = drm_gem_handle_create(file_priv, &bo->gem_object, handle); + if (ret != 0) { + DRM_ERROR("DRM failed to create GEM handle\n"); + goto obj_release; + } + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(&bo->gem_object); + + return 0; + +obj_release: + drm_gem_object_release(&bo->gem_object); +free_dma: + if (bo->type & PL111_BOT_DMA) + dma_free_writecombine(dev->dev, size, + bo->backing_data.dma.fb_cpu_addr, + bo->backing_data.dma.fb_dev_addr); +free_bo: + kfree(bo); +finish: + return ret; +} + +int pl111_drm_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pl111_gem_create *args = data; + uint32_t bytes_pp; + + /* Round bpp up, to allow for case where bpp<8 */ + bytes_pp = args->bpp >> 3; + if (args->bpp & ((1 << 3) - 1)) + bytes_pp++; + + if (args->flags & ~PL111_BOT_MASK) { + DRM_ERROR("wrong flags: 0x%x\n", args->flags); + return -EINVAL; + } + + args->pitch = ALIGN(args->width * bytes_pp, 64); + args->size = PAGE_ALIGN(args->pitch * args->height); + + DRM_DEBUG_KMS("gem_create w=%d h=%d p=%d bpp=%d b=%d s=%llu f=0x%x\n", + args->width, args->height, args->pitch, args->bpp, + bytes_pp, args->size, args->flags); + + return pl111_gem_object_create(dev, args->size, args->flags, file_priv, + &args->handle); +} + +int pl111_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, struct drm_mode_create_dumb *args) +{ + uint32_t bytes_pp; + + /* Round bpp up, to allow for case where bpp<8 */ + bytes_pp = args->bpp >> 3; + if (args->bpp & ((1 << 3) - 1)) + bytes_pp++; + + if (args->flags) { + DRM_ERROR("flags must be zero: 0x%x\n", args->flags); + return -EINVAL; + } + + args->pitch = ALIGN(args->width * bytes_pp, 64); + args->size = PAGE_ALIGN(args->pitch * args->height); + + DRM_DEBUG_KMS("dumb_create w=%d h=%d p=%d bpp=%d b=%d s=%llu f=0x%x\n", + args->width, args->height, args->pitch, args->bpp, + bytes_pp, args->size, args->flags); + + return pl111_gem_object_create(dev, args->size, + PL111_BOT_DMA | PL111_BOT_UNCACHED, + file_priv, &args->handle); +} + +int pl111_dumb_destroy(struct drm_file *file_priv, struct drm_device *dev, + uint32_t handle) +{ + DRM_DEBUG_KMS("DRM %s on file_priv=%p handle=0x%.8x\n", __func__, + file_priv, handle); + return drm_gem_handle_delete(file_priv, handle); +} + +int pl111_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret = 0; + DRM_DEBUG_KMS("DRM %s on file_priv=%p handle=0x%.8x\n", __func__, + file_priv, handle); + + /* GEM does all our handle to object mapping */ + obj = drm_gem_object_lookup(dev, file_priv, handle); + if (obj == NULL) { + ret = -ENOENT; + goto fail; + } + + if (obj->map_list.map == NULL) { + ret = drm_gem_create_mmap_offset(obj); + if (ret != 0) { + drm_gem_object_unreference_unlocked(obj); + goto fail; + } + } + + *offset = (uint64_t) obj->map_list.hash.key << PAGE_SHIFT; + + drm_gem_object_unreference_unlocked(obj); +fail: + return ret; +} + +/* sync the buffer for DMA access */ +void pl111_gem_sync_to_dma(struct pl111_gem_bo *bo) +{ + struct drm_device *dev = bo->gem_object.dev; + + if (!(bo->type & PL111_BOT_DMA) && (bo->type & PL111_BOT_CACHED)) { + int i, npages = bo->gem_object.size >> PAGE_SHIFT; + struct page **pages = bo->backing_data.shm.pages; + bool dirty = false; + + for (i = 0; i < npages; i++) { + if (!bo->backing_data.shm.dma_addrs[i]) { + DRM_DEBUG("%s: dma map page=%d bo=%p\n", __func__, i, bo); + bo->backing_data.shm.dma_addrs[i] = + dma_map_page(dev->dev, pages[i], 0, + PAGE_SIZE, DMA_BIDIRECTIONAL); + dirty = true; + } + } + + if (dirty) { + DRM_DEBUG("%s: zap ptes (and flush cache) bo=%p\n", __func__, bo); + /* + * TODO MIDEGL-1813 + * + * Use flush_cache_page() and outer_flush_range() to + * flush only the user space mappings of the dirty pages + */ + flush_cache_all(); + outer_flush_all(); + unmap_mapping_range(bo->gem_object.filp->f_mapping, 0, + bo->gem_object.size, 1); + } + } +} + +void pl111_gem_sync_to_cpu(struct pl111_gem_bo *bo, int pgoff) +{ + struct drm_device *dev = bo->gem_object.dev; + + /* + * TODO MIDEGL-1808 + * + * The following check was meant to detect if the CPU is trying to access + * a buffer that is currently mapped for DMA accesses, which is illegal + * as described by the DMA-API. + * + * However, some of our tests are trying to do that, which triggers the message + * below and avoids dma-unmapping the pages not to annoy the DMA device but that + * leads to the test failing because of caches not being properly flushed. + */ + + /* + if (bo->sgt) { + DRM_ERROR("%s: the CPU is trying to access a dma-mapped buffer\n", __func__); + return; + } + */ + + if (!(bo->type & PL111_BOT_DMA) && (bo->type & PL111_BOT_CACHED) && + bo->backing_data.shm.dma_addrs[pgoff]) { + DRM_DEBUG("%s: unmap bo=%p (s=%d), paddr=%08x\n", + __func__, bo, bo->gem_object.size, + bo->backing_data.shm.dma_addrs[pgoff]); + dma_unmap_page(dev->dev, bo->backing_data.shm.dma_addrs[pgoff], + PAGE_SIZE, DMA_BIDIRECTIONAL); + bo->backing_data.shm.dma_addrs[pgoff] = 0; + } +} + +/* Based on omapdrm driver */ +int pl111_bo_mmap(struct drm_gem_object *obj, struct pl111_gem_bo *bo, + struct vm_area_struct *vma, size_t size) +{ + DRM_DEBUG("DRM %s on drm_gem_object=%p, pl111_gem_bo=%p type=%08x\n", + __func__, obj, bo, bo->type); + + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; + + if (bo->type & PL111_BOT_WC) { + vma->vm_page_prot = + pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + } else if (bo->type & PL111_BOT_CACHED) { + /* + * Objects that do not have a filp (DMA backed) can't be + * mapped as cached now. Write-combine should be enough. + */ + if (WARN_ON(!obj->filp)) + return -EINVAL; + + /* + * As explained in Documentation/dma-buf-sharing.txt + * we need this trick so that we can manually zap ptes + * in order to fake coherency. + */ + fput(vma->vm_file); + vma->vm_pgoff = 0; + get_file(obj->filp); + vma->vm_file = obj->filp; + + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + } else { /* PL111_BOT_UNCACHED */ + vma->vm_page_prot = + pgprot_noncached(vm_get_page_prot(vma->vm_flags)); + } + return 0; +} + +int pl111_gem_mmap(struct file *file_priv, struct vm_area_struct *vma) +{ + int ret; + struct drm_file *priv = file_priv->private_data; + struct drm_device *dev = priv->minor->dev; + struct drm_gem_mm *mm = dev->mm_private; + struct drm_local_map *map = NULL; + struct drm_hash_item *hash; + struct drm_gem_object *obj; + struct pl111_gem_bo *bo; + + DRM_DEBUG_KMS("DRM %s\n", __func__); + + drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash); + map = drm_hash_entry(hash, struct drm_map_list, hash)->map; + obj = map->handle; + bo = PL111_BO_FROM_GEM(obj); + + DRM_DEBUG_KMS("DRM %s on pl111_gem_bo %p bo->type 0x%08x\n", __func__, bo, bo->type); + + /* for an imported buffer we let the exporter handle the mmap */ + if (obj->import_attach) + return dma_buf_mmap(obj->import_attach->dmabuf, vma, 0); + + ret = drm_gem_mmap(file_priv, vma); + if (ret < 0) { + DRM_ERROR("failed to mmap\n"); + return ret; + } + + return pl111_bo_mmap(obj, bo, vma, vma->vm_end - vma->vm_start); +} diff --git a/drivers/gpu/drm/pl111/pl111_drm_pl111.c b/drivers/gpu/drm/pl111/pl111_drm_pl111.c new file mode 100755 index 000000000000..0b3c6813b36c --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_pl111.c @@ -0,0 +1,417 @@ +/* + * + * (C) COPYRIGHT 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_pl111.c + * PL111 specific functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "pl111_drm.h" + +/* This can't be called from IRQ context, due to clk_get() and board->enable */ +static int clcd_enable(struct drm_framebuffer *fb) +{ + __u32 cntl; + struct clcd_board *board; + + pr_info("DRM %s\n", __func__); + + clk_prepare_enable(priv.clk); + + /* Enable and Power Up */ + cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDPWR | CNTL_LCDVCOMP(1); + DRM_DEBUG_KMS("fb->bits_per_pixel = %d\n", fb->bits_per_pixel); + if (fb->bits_per_pixel == 16) + cntl |= CNTL_LCDBPP16_565; + else if (fb->bits_per_pixel == 32 && fb->depth == 24) + cntl |= CNTL_LCDBPP24; + else + BUG_ON(1); + + cntl |= CNTL_BGR; + + writel(cntl, priv.regs + CLCD_PL111_CNTL); + + if (priv.amba_dev->dev.platform_data) { + board = priv.amba_dev->dev.platform_data; + + if (board->enable) + board->enable(NULL); + } + + /* Enable Interrupts */ + writel(CLCD_IRQ_NEXTBASE_UPDATE, priv.regs + CLCD_PL111_IENB); + + return 0; +} + +int clcd_disable(struct drm_crtc *crtc) +{ + struct clcd_board *board; + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + unsigned long flags; +#endif + + pr_info("DRM %s\n", __func__); + + /* Disable Interrupts */ + writel(0x00000000, priv.regs + CLCD_PL111_IENB); + + if (priv.amba_dev->dev.platform_data) { + board = priv.amba_dev->dev.platform_data; + + if (board->disable) + board->disable(NULL); + } + + /* Disable and Power Down */ + writel(0, priv.regs + CLCD_PL111_CNTL); + + /* Disable clock */ + clk_disable_unprepare(priv.clk); + + pl111_crtc->last_bpp = 0; +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); + /* Release the previous buffers */ + if (pl111_crtc->old_kds_res_set != NULL) + kds_resource_set_release(&pl111_crtc->old_kds_res_set); + + pl111_crtc->old_kds_res_set = NULL; + spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags); +#endif + return 0; +} + +/* + * To avoid a possible race where "pl111_crtc->current_update_res" has + * been updated (non NULL) but the corresponding scanout buffer has not been + * written to the base registers we must always call this function holding + * the "base_update_lock" spinlock with IRQs disabled (spin_lock_irqsave()). + */ +void do_flip_to_res(struct pl111_drm_flip_resource *flip_res) +{ + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc); + struct drm_framebuffer *fb; + struct pl111_gem_bo *bo; + size_t min_size; + fb = flip_res->fb; + bo = PL111_BO_FROM_FRAMEBUFFER(fb); + + + + min_size = (fb->height - 1) * fb->pitches[0] + + fb->width * (fb->bits_per_pixel >> 3); + + BUG_ON(bo->gem_object.size < min_size); + + /* Don't even attempt PL111_BOT_SHM, it's not contiguous */ + BUG_ON(bo->type != PL111_BOT_DMA); + + /* + * Note the buffer for releasing after IRQ, and don't allow any more + * updates until then. + * + * This clcd controller latches the new address on next vsync. Address + * latching is indicated by CLCD_IRQ_NEXTBASE_UPDATE, and so we must + * wait for that before releasing the previous buffer's kds + * resources. Otherwise, we'll allow writers to write to the old buffer + * whilst it is still being displayed + */ + pl111_crtc->current_update_res = flip_res; + + DRM_DEBUG_KMS("Displaying fb 0x%p, dumb_bo 0x%p, physaddr %.8x\n", + fb, bo, bo->backing_data.dma.fb_dev_addr); + + if (drm_vblank_get(pl111_crtc->crtc.dev, pl111_crtc->crtc_index) < 0) + DRM_ERROR("Could not get vblank reference for crtc %d\n", + pl111_crtc->crtc_index); + + /* Set the scanout buffer */ + writel(bo->backing_data.dma.fb_dev_addr, priv.regs + CLCD_UBAS); + writel(bo->backing_data.dma.fb_dev_addr + + ((fb->height - 1) * fb->pitches[0]), priv.regs + CLCD_LBAS); +} + +void +show_framebuffer_on_crtc_cb_internal(struct pl111_drm_flip_resource *flip_res, + struct drm_framebuffer *fb) +{ + unsigned long irq_flags; + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc); + + spin_lock_irqsave(&pl111_crtc->base_update_lock, irq_flags); + if (list_empty(&pl111_crtc->update_queue) && + !pl111_crtc->current_update_res) { + do_flip_to_res(flip_res); + } else { + /* + * Enqueue the update to occur on a future IRQ + * This only happens on triple-or-greater buffering + */ + DRM_DEBUG_KMS("Deferring 3+ buffered flip to fb %p to IRQ\n", + fb); + list_add_tail(&flip_res->link, &pl111_crtc->update_queue); + } + spin_unlock_irqrestore(&pl111_crtc->base_update_lock, irq_flags); + + if (!flip_res->page_flip && (pl111_crtc->last_bpp == 0 || + pl111_crtc->last_bpp != fb->bits_per_pixel || + !drm_mode_equal(pl111_crtc->new_mode, + pl111_crtc->current_mode))) { + struct clcd_regs timing; + + pl111_convert_drm_mode_to_timing(pl111_crtc->new_mode, &timing); + + DRM_DEBUG_KMS("Set timing: %08X:%08X:%08X:%08X clk=%ldHz\n", + timing.tim0, timing.tim1, timing.tim2, + timing.tim3, timing.pixclock); + + /* This is the actual mode setting part */ + clk_set_rate(priv.clk, timing.pixclock); + + writel(timing.tim0, priv.regs + CLCD_TIM0); + writel(timing.tim1, priv.regs + CLCD_TIM1); + writel(timing.tim2, priv.regs + CLCD_TIM2); + writel(timing.tim3, priv.regs + CLCD_TIM3); + + clcd_enable(fb); + pl111_crtc->last_bpp = fb->bits_per_pixel; + } + + if (!flip_res->page_flip) { + drm_mode_destroy(flip_res->crtc->dev, pl111_crtc->current_mode); + pl111_crtc->current_mode = pl111_crtc->new_mode; + pl111_crtc->new_mode = NULL; + } + + BUG_ON(!pl111_crtc->current_mode); + + /* + * If IRQs weren't enabled before, they are now. This will eventually + * cause flip_res to be released via pl111_common_irq, which updates + * every time the Base Address is latched (i.e. every frame, regardless + * of whether we update the base address or not) + */ +} + +irqreturn_t pl111_irq(int irq, void *data) +{ + u32 irq_stat; + struct pl111_drm_crtc *pl111_crtc = priv.pl111_crtc; + + irq_stat = readl(priv.regs + CLCD_PL111_MIS); + + if (!irq_stat) + return IRQ_NONE; + + if (irq_stat & CLCD_IRQ_NEXTBASE_UPDATE) + pl111_common_irq(pl111_crtc); + + /* Clear the interrupt once done */ + writel(irq_stat, priv.regs + CLCD_PL111_ICR); + + return IRQ_HANDLED; +} + +int pl111_device_init(struct drm_device *dev) +{ + struct pl111_drm_dev_private *priv = dev->dev_private; + int ret; + + if (priv == NULL) { + pr_err("%s no private data\n", __func__); + return -EINVAL; + } + + if (priv->amba_dev == NULL) { + pr_err("%s no amba device found\n", __func__); + return -EINVAL; + } + + /* set up MMIO for register access */ + priv->mmio_start = priv->amba_dev->res.start; + priv->mmio_len = resource_size(&priv->amba_dev->res); + + DRM_DEBUG_KMS("mmio_start=%lu, mmio_len=%u\n", priv->mmio_start, + priv->mmio_len); + + priv->regs = ioremap(priv->mmio_start, priv->mmio_len); + if (priv->regs == NULL) { + pr_err("%s failed mmio\n", __func__); + return -EINVAL; + } + + /* turn off interrupts */ + writel(0, priv->regs + CLCD_PL111_IENB); + + ret = request_irq(priv->amba_dev->irq[0], pl111_irq, 0, + "pl111_irq_handler", NULL); + if (ret != 0) { + pr_err("%s failed %d\n", __func__, ret); + goto out_mmio; + } + + goto finish; + +out_mmio: + iounmap(priv->regs); +finish: + DRM_DEBUG_KMS("pl111_device_init returned %d\n", ret); + return ret; +} + +void pl111_device_fini(struct drm_device *dev) +{ + struct pl111_drm_dev_private *priv = dev->dev_private; + u32 cntl; + + if (priv == NULL || priv->regs == NULL) + return; + + free_irq(priv->amba_dev->irq[0], NULL); + + cntl = readl(priv->regs + CLCD_PL111_CNTL); + + cntl &= ~CNTL_LCDEN; + writel(cntl, priv->regs + CLCD_PL111_CNTL); + + cntl &= ~CNTL_LCDPWR; + writel(cntl, priv->regs + CLCD_PL111_CNTL); + + iounmap(priv->regs); +} + +int pl111_amba_probe(struct amba_device *dev, const struct amba_id *id) +{ + struct clcd_board *board = dev->dev.platform_data; + int ret; + pr_info("DRM %s\n", __func__); + + if (!board) + dev_warn(&dev->dev, "board data not available\n"); + + ret = amba_request_regions(dev, NULL); + if (ret != 0) { + DRM_ERROR("CLCD: unable to reserve regs region\n"); + goto out; + } + + priv.amba_dev = dev; + + priv.clk = clk_get(&priv.amba_dev->dev, NULL); + if (IS_ERR(priv.clk)) { + DRM_ERROR("CLCD: unable to get clk.\n"); + ret = PTR_ERR(priv.clk); + goto clk_err; + } + + return 0; + +clk_err: + amba_release_regions(dev); +out: + return ret; +} + +int pl111_amba_remove(struct amba_device *dev) +{ + DRM_DEBUG_KMS("DRM %s\n", __func__); + + clk_put(priv.clk); + + amba_release_regions(dev); + + priv.amba_dev = NULL; + + return 0; +} + +void pl111_convert_drm_mode_to_timing(struct drm_display_mode *mode, + struct clcd_regs *timing) +{ + unsigned int ppl, hsw, hfp, hbp; + unsigned int lpp, vsw, vfp, vbp; + unsigned int cpl; + + memset(timing, 0, sizeof(struct clcd_regs)); + + ppl = (mode->hdisplay / 16) - 1; + hsw = mode->hsync_end - mode->hsync_start - 1; + hfp = mode->hsync_start - mode->hdisplay - 1; + hbp = mode->htotal - mode->hsync_end - 1; + + lpp = mode->vdisplay - 1; + vsw = mode->vsync_end - mode->vsync_start - 1; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + cpl = mode->hdisplay - 1; + + timing->tim0 = (ppl << 2) | (hsw << 8) | (hfp << 16) | (hbp << 24); + timing->tim1 = lpp | (vsw << 10) | (vfp << 16) | (vbp << 24); + timing->tim2 = TIM2_IVS | TIM2_IHS | TIM2_IPC | TIM2_BCD | (cpl << 16); + timing->tim3 = 0; + + timing->pixclock = mode->clock * 1000; +} + +void pl111_convert_timing_to_drm_mode(struct clcd_regs *timing, + struct drm_display_mode *mode) +{ + unsigned int ppl, hsw, hfp, hbp; + unsigned int lpp, vsw, vfp, vbp; + + ppl = (timing->tim0 >> 2) & 0x3f; + hsw = (timing->tim0 >> 8) & 0xff; + hfp = (timing->tim0 >> 16) & 0xff; + hbp = (timing->tim0 >> 24) & 0xff; + + lpp = timing->tim1 & 0x3ff; + vsw = (timing->tim1 >> 10) & 0x3f; + vfp = (timing->tim1 >> 16) & 0xff; + vbp = (timing->tim1 >> 24) & 0xff; + + mode->hdisplay = (ppl + 1) * 16; + mode->hsync_start = ((ppl + 1) * 16) + hfp + 1; + mode->hsync_end = ((ppl + 1) * 16) + hfp + hsw + 2; + mode->htotal = ((ppl + 1) * 16) + hfp + hsw + hbp + 3; + mode->hskew = 0; + + mode->vdisplay = lpp + 1; + mode->vsync_start = lpp + vfp + 1; + mode->vsync_end = lpp + vfp + vsw + 2; + mode->vtotal = lpp + vfp + vsw + vbp + 2; + + mode->flags = 0; + + mode->width_mm = 0; + mode->height_mm = 0; + + mode->clock = timing->pixclock / 1000; + mode->hsync = timing->pixclock / mode->htotal; + mode->vrefresh = mode->hsync / mode->vtotal; +} diff --git a/drivers/gpu/drm/pl111/pl111_drm_platform.c b/drivers/gpu/drm/pl111/pl111_drm_platform.c new file mode 100755 index 000000000000..fe7db7392792 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_platform.c @@ -0,0 +1,151 @@ +/* + * + * (C) COPYRIGHT 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_platform.c + * Implementation of the Linux platform device 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/init.h> +#include <linux/platform_device.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include "pl111_drm.h" + +static int pl111_platform_drm_suspend(struct platform_device *dev, + pm_message_t state) +{ + pr_info("DRM %s\n", __func__); + return 0; +} + +static int pl111_platform_drm_resume(struct platform_device *dev) +{ + pr_info("DRM %s\n", __func__); + return 0; +} + +int pl111_platform_drm_probe(struct platform_device *dev) +{ + pr_info("DRM %s\n", __func__); + return pl111_drm_init(dev); +} + +static int pl111_platform_drm_remove(struct platform_device *dev) +{ + pr_info("DRM %s\n", __func__); + pl111_drm_exit(dev); + + return 0; +} + +static struct amba_id pl111_id_table[] = { + { + .id = 0x00041110, + .mask = 0x000ffffe, + }, + {0, 0}, +}; + +static struct amba_driver pl111_amba_driver = { + .drv = { + .name = "clcd-pl11x", + }, + .probe = pl111_amba_probe, + .remove = pl111_amba_remove, + .id_table = pl111_id_table, +}; + +static struct platform_driver platform_drm_driver = { + .probe = pl111_platform_drm_probe, + .remove = pl111_platform_drm_remove, + .suspend = pl111_platform_drm_suspend, + .resume = pl111_platform_drm_resume, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +static const struct platform_device_info pl111_drm_pdevinfo = { + .name = DRIVER_NAME, + .id = -1, + .dma_mask = ~0UL +}; + +static struct platform_device *pl111_drm_device; + +static int __init pl111_platform_drm_init(void) +{ + int ret; + + pr_info("DRM %s\n", __func__); + + pl111_drm_device = platform_device_register_full(&pl111_drm_pdevinfo); + if (pl111_drm_device == NULL) { + pr_err("DRM platform_device_register_full() failed\n"); + return -ENOMEM; + } + + ret = amba_driver_register(&pl111_amba_driver); + if (ret != 0) { + pr_err("DRM amba_driver_register() failed %d\n", ret); + goto err_amba_reg; + } + + ret = platform_driver_register(&platform_drm_driver); + if (ret != 0) { + pr_err("DRM platform_driver_register() failed %d\n", ret); + goto err_pdrv_reg; + } + + return 0; + +err_pdrv_reg: + amba_driver_unregister(&pl111_amba_driver); +err_amba_reg: + platform_device_unregister(pl111_drm_device); + + return ret; +} + +static void __exit pl111_platform_drm_exit(void) +{ + pr_info("DRM %s\n", __func__); + + platform_device_unregister(pl111_drm_device); + amba_driver_unregister(&pl111_amba_driver); + platform_driver_unregister(&platform_drm_driver); +} + +#ifdef MODULE +module_init(pl111_platform_drm_init); +#else +late_initcall(pl111_platform_drm_init); +#endif +module_exit(pl111_platform_drm_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE(DRIVER_LICENCE); +MODULE_ALIAS(DRIVER_ALIAS); diff --git a/drivers/gpu/drm/pl111/pl111_drm_suspend.c b/drivers/gpu/drm/pl111/pl111_drm_suspend.c new file mode 100755 index 000000000000..a61636f8424f --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_suspend.c @@ -0,0 +1,43 @@ +/* + * + * (C) COPYRIGHT 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_suspend.c + * Implementation of the suspend/resume functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include "pl111_drm.h" + +int pl111_drm_suspend(struct drm_device *dev, pm_message_t state) +{ + pr_info("DRM %s\n", __func__); + return 0; +} + +int pl111_drm_resume(struct drm_device *dev) +{ + pr_info("DRM %s\n", __func__); + return 0; +} diff --git a/drivers/gpu/drm/pl111/pl111_drm_vma.c b/drivers/gpu/drm/pl111/pl111_drm_vma.c new file mode 100755 index 000000000000..c9918f79e939 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_vma.c @@ -0,0 +1,307 @@ +/* + * + * (C) COPYRIGHT 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_vma.c + * Implementation of the VM functions 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include "pl111_drm.h" + +/* BEGIN drivers/staging/omapdrm/omap_gem_helpers.c */ +/** + * drm_gem_put_pages - helper to free backing pages for a GEM object + * @obj: obj in question + * @pages: pages to free + */ +static void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages, + bool dirty, bool accessed) +{ + int i, npages; + struct pl111_gem_bo *bo; + npages = obj->size >> PAGE_SHIFT; + bo = PL111_BO_FROM_GEM(obj); + for (i = 0; i < npages; i++) { + if (dirty) + set_page_dirty(pages[i]); + if (accessed) + mark_page_accessed(pages[i]); + /* Undo the reference we took when populating the table */ + page_cache_release(pages[i]); + } + drm_free_large(pages); +} + +void put_pages(struct drm_gem_object *obj, struct page **pages) +{ + int i, npages; + struct pl111_gem_bo *bo; + npages = obj->size >> PAGE_SHIFT; + bo = PL111_BO_FROM_GEM(obj); + _drm_gem_put_pages(obj, pages, true, true); + if (bo->backing_data.shm.dma_addrs) { + for (i = 0; i < npages; i++) { + /* Filter pages unmapped because of CPU accesses */ + if (!bo->backing_data.shm.dma_addrs[i]) + continue; + if (!dma_mapping_error(obj->dev->dev, + bo->backing_data.shm.dma_addrs[i])) { + dma_unmap_page(obj->dev->dev, + bo->backing_data.shm.dma_addrs[i], + PAGE_SIZE, + DMA_BIDIRECTIONAL); + } + } + kfree(bo->backing_data.shm.dma_addrs); + bo->backing_data.shm.dma_addrs = NULL; + } +} + +/** + * drm_gem_get_pages - helper to allocate backing pages for a GEM object + * @obj: obj in question + * @gfpmask: gfp mask of requested pages + */ +static struct page **_drm_gem_get_pages(struct drm_gem_object *obj, + gfp_t gfpmask) +{ + struct inode *inode; + struct address_space *mapping; + struct page *p, **pages; + int i, npages; + + /* This is the shared memory object that backs the GEM resource */ + inode = obj->filp->f_path.dentry->d_inode; + mapping = inode->i_mapping; + + npages = obj->size >> PAGE_SHIFT; + + pages = drm_malloc_ab(npages, sizeof(struct page *)); + if (pages == NULL) + return ERR_PTR(-ENOMEM); + + gfpmask |= mapping_gfp_mask(mapping); + + for (i = 0; i < npages; i++) { + p = shmem_read_mapping_page_gfp(mapping, i, gfpmask); + if (IS_ERR(p)) + goto fail; + pages[i] = p; + + /* + * There is a hypothetical issue w/ drivers that require + * buffer memory in the low 4GB.. if the pages are un- + * pinned, and swapped out, they can end up swapped back + * in above 4GB. If pages are already in memory, then + * shmem_read_mapping_page_gfp will ignore the gfpmask, + * even if the already in-memory page disobeys the mask. + * + * It is only a theoretical issue today, because none of + * the devices with this limitation can be populated with + * enough memory to trigger the issue. But this BUG_ON() + * is here as a reminder in case the problem with + * shmem_read_mapping_page_gfp() isn't solved by the time + * it does become a real issue. + * + * See this thread: http://lkml.org/lkml/2011/7/11/238 + */ + BUG_ON((gfpmask & __GFP_DMA32) && + (page_to_pfn(p) >= 0x00100000UL)); + } + + return pages; + +fail: + while (i--) + page_cache_release(pages[i]); + + drm_free_large(pages); + return ERR_PTR(PTR_ERR(p)); +} + +struct page **get_pages(struct drm_gem_object *obj) +{ + struct pl111_gem_bo *bo; + bo = PL111_BO_FROM_GEM(obj); + + if (bo->backing_data.shm.pages == NULL) { + struct page **p; + int npages = obj->size >> PAGE_SHIFT; + int i; + + p = _drm_gem_get_pages(obj, GFP_KERNEL); + if (IS_ERR(p)) + return ERR_PTR(-ENOMEM); + + bo->backing_data.shm.pages = p; + + if (bo->backing_data.shm.dma_addrs == NULL) { + bo->backing_data.shm.dma_addrs = + kzalloc(npages * sizeof(dma_addr_t), + GFP_KERNEL); + if (bo->backing_data.shm.dma_addrs == NULL) + goto error_out; + } + + if (!(bo->type & PL111_BOT_CACHED)) { + for (i = 0; i < npages; ++i) { + bo->backing_data.shm.dma_addrs[i] = + dma_map_page(obj->dev->dev, p[i], 0, PAGE_SIZE, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(obj->dev->dev, + bo->backing_data.shm.dma_addrs[i])) + goto error_out; + } + } + } + + return bo->backing_data.shm.pages; + +error_out: + put_pages(obj, bo->backing_data.shm.pages); + bo->backing_data.shm.pages = NULL; + return ERR_PTR(-ENOMEM); +} + +/* END drivers/staging/omapdrm/omap_gem_helpers.c */ +int pl111_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct page **pages; + pgoff_t pgoff; + unsigned long pfn; + struct drm_gem_object *obj = vma->vm_private_data; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + struct drm_device *dev = obj->dev; + int ret; + + mutex_lock(&dev->struct_mutex); + + /* We don't use vmf->pgoff since that has the fake offset: */ + pgoff = ((unsigned long)vmf->virtual_address - + vma->vm_start) >> PAGE_SHIFT; + + if (bo->type & PL111_BOT_DMA) { + pfn = (bo->backing_data.dma.fb_dev_addr >> PAGE_SHIFT) + pgoff; + } else { /* PL111_BOT_SHM */ + pages = get_pages(obj); + if (IS_ERR(pages)) { + dev_err(obj->dev->dev, + "could not get pages: %ld\n", PTR_ERR(pages)); + ret = PTR_ERR(pages); + goto error; + } + pfn = page_to_pfn(pages[pgoff]); + pl111_gem_sync_to_cpu(bo, pgoff); + } + + DRM_DEBUG("bo=%p physaddr=0x%.8x for offset 0x%x\n", + bo, PFN_PHYS(pfn), PFN_PHYS(pgoff)); + + ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn); + +error: + mutex_unlock(&dev->struct_mutex); + + switch (ret) { + case 0: + case -ERESTARTSYS: + case -EINTR: + case -EBUSY: + return VM_FAULT_NOPAGE; + case -ENOMEM: + return VM_FAULT_OOM; + default: + return VM_FAULT_SIGBUS; + } +} + +/* + * The core drm_vm_ functions in kernel 3.4 are not ready + * to handle dma_buf cases where vma->vm_file->private_data + * cannot be accessed to get the device. + * + * We use these functions from 3.5 instead where the device + * pointer is passed explicitly. + * + * However they aren't exported from the kernel until 3.10 + */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) +void pl111_drm_vm_open_locked(struct drm_device *dev, + struct vm_area_struct *vma) +{ + struct drm_vma_entry *vma_entry; + + DRM_DEBUG("0x%08lx,0x%08lx\n", + vma->vm_start, vma->vm_end - vma->vm_start); + atomic_inc(&dev->vma_count); + + vma_entry = kmalloc(sizeof(*vma_entry), GFP_KERNEL); + if (vma_entry) { + vma_entry->vma = vma; + vma_entry->pid = current->pid; + list_add(&vma_entry->head, &dev->vmalist); + } +} + +void pl111_drm_vm_close_locked(struct drm_device *dev, + struct vm_area_struct *vma) +{ + struct drm_vma_entry *pt, *temp; + + DRM_DEBUG("0x%08lx,0x%08lx\n", + vma->vm_start, vma->vm_end - vma->vm_start); + atomic_dec(&dev->vma_count); + + list_for_each_entry_safe(pt, temp, &dev->vmalist, head) { + if (pt->vma == vma) { + list_del(&pt->head); + kfree(pt); + break; + } + } +} + +void pl111_gem_vm_open(struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = vma->vm_private_data; + + drm_gem_object_reference(obj); + + mutex_lock(&obj->dev->struct_mutex); + pl111_drm_vm_open_locked(obj->dev, vma); + mutex_unlock(&obj->dev->struct_mutex); +} + +void pl111_gem_vm_close(struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct drm_device *dev = obj->dev; + + mutex_lock(&dev->struct_mutex); + pl111_drm_vm_close_locked(obj->dev, vma); + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); +} +#endif |