blob: fc8799a903554f309f241c108656bc018ad8596b [file] [log] [blame]
/*************************************************************************/ /*!
@File
@Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@License Dual MIT/GPLv2
The contents of this file are subject to the MIT license as set out below.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 ("GPL") in which case the provisions
of GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms of
GPL, and not to allow others to use your version of this file under the terms
of the MIT license, indicate your decision by deleting the provisions above
and replace them with the notice and other provisions required by GPL as set
out in the file called "GPL-COPYING" included in this distribution. If you do
not delete the provisions above, a recipient may use your version of this file
under the terms of either the MIT license or GPL.
This License is also included in this distribution in the file called
"MIT-COPYING".
EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ /**************************************************************************/
/* vi: set ts=8: */
#include <linux/version.h>
#include <linux/console.h>
#include <linux/dma-buf.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <drm/drm_fourcc.h>
#include <video/adf.h>
#include <video/adf_fbdev.h>
#include <video/adf_client.h>
#include <adf/adf_ext.h>
/* for sync_fence_put */
#include PVR_ANDROID_SYNC_HEADER
#include "adf_common.h"
#ifndef CONFIG_FB
#error adf_fbdev needs Linux framebuffer support. Enable it in your kernel.
#endif
MODULE_AUTHOR("Imagination Technologies Ltd. <gpl-support@imgtec.com>");
MODULE_LICENSE("Dual MIT/GPL");
/* NOTE: This is just an example of how to use adf. You should NOT use this
* module in a production environment. It is meaningless to layer adf
* on top of fbdev, as adf is more flexible than fbdev and adf itself
* provides fbdev emulation. Do not use this implementation generally!
*/
#define DRVNAME "adf_fbdev"
#define FALLBACK_REFRESH_RATE 60
#define FALLBACK_DPI 160
#if defined(ADF_FBDEV_NUM_PREFERRED_BUFFERS)
#define NUM_PREFERRED_BUFFERS ADF_FBDEV_NUM_PREFERRED_BUFFERS
#else
#define NUM_PREFERRED_BUFFERS 3
#endif
struct adf_fbdev_dmabuf {
struct sg_table sg_table;
unsigned long paddr;
size_t offset;
size_t length;
void *vaddr;
/* Used for cleanup of dmabuf private data */
spinlock_t *alloc_lock;
u8 *alloc_mask;
u8 id;
};
struct adf_fbdev_device {
struct adf_device base;
struct fb_info *fb_info;
atomic_t refcount;
};
struct adf_fbdev_interface {
struct adf_interface base;
struct drm_mode_modeinfo fb_mode;
u16 width_mm, height_mm;
struct fb_info *fb_info;
spinlock_t alloc_lock;
u8 alloc_mask;
};
/* SIMPLE BUFFER MANAGER *****************************************************/
/* Handle alloc/free from the fbdev carveout (fix.smem_start -> fix.smem_size)
* region. This simple allocator sets a bit in the alloc_mask when a buffer is
* owned by dmabuf. When the dmabuf ->release() is called, the alloc_mask bit
* is cleared and the adf_fbdev_dmabuf object is freed.
*
* Since dmabuf relies on sg_table/scatterlists, and hence struct page*, this
* code may have problems if your framebuffer uses memory that is not in the
* kernel's page tables.
*/
static struct adf_fbdev_dmabuf *
adf_fbdev_alloc_buffer(struct adf_fbdev_interface *interface)
{
struct adf_fbdev_dmabuf *fbdev_dmabuf;
struct scatterlist *sg;
size_t unitary_size;
int i, err;
u32 id = 0;
spin_lock(&interface->alloc_lock);
for (id = 0; id < NUM_PREFERRED_BUFFERS; id++) {
if (!(interface->alloc_mask & (1UL << id))) {
interface->alloc_mask |= (1UL << id);
break;
}
}
spin_unlock(&interface->alloc_lock);
if (id == NUM_PREFERRED_BUFFERS)
return ERR_PTR(-ENOMEM);
unitary_size = interface->fb_info->fix.line_length *
interface->fb_info->var.yres;
/* PAGE_SIZE alignment has been checked already, do NOT allow it
* through here. We are about to allocate an sg_list.
*/
BUG_ON((unitary_size % PAGE_SIZE) != 0);
fbdev_dmabuf = kmalloc(sizeof(struct adf_fbdev_dmabuf), GFP_KERNEL);
if (!fbdev_dmabuf)
return ERR_PTR(-ENOMEM);
/* We only need one scatterlist entry per buffer because fbdev memory
* is always physically contiguous.
*/
err = sg_alloc_table(&fbdev_dmabuf->sg_table, 1, GFP_KERNEL);
if (err) {
kfree(fbdev_dmabuf);
return ERR_PTR(err);
}
/* Increment the reference count of this module as long as the
* adb_fbdev_dmabuf object exists. This prevents this module from
* being unloaded if the buffer is passed around by dmabuf.
*/
if (!try_module_get(THIS_MODULE)) {
pr_err("try_module_get(THIS_MODULE) failed");
kfree(fbdev_dmabuf);
return ERR_PTR(-EFAULT);
}
fbdev_dmabuf->offset = id * unitary_size;
fbdev_dmabuf->length = unitary_size;
fbdev_dmabuf->vaddr = interface->fb_info->screen_base +
fbdev_dmabuf->offset;
fbdev_dmabuf->paddr = interface->fb_info->fix.smem_start +
fbdev_dmabuf->offset;
sg_set_page(fbdev_dmabuf->sg_table.sgl,
pfn_to_page(PFN_DOWN(fbdev_dmabuf->paddr)),
fbdev_dmabuf->length, 0);
/* Shadow what ion is doing currently to ensure sg_dma_address() is
* valid. This is not strictly correct as the dma address should
* only be valid after mapping (ownership changed), and we haven't
* mapped the scatter list yet.
*/
for_each_sg(fbdev_dmabuf->sg_table.sgl, sg,
fbdev_dmabuf->sg_table.nents, i) {
sg_dma_address(sg) = sg_phys(sg);
}
fbdev_dmabuf->alloc_mask = &interface->alloc_mask;
fbdev_dmabuf->alloc_lock = &interface->alloc_lock;
fbdev_dmabuf->id = id;
return fbdev_dmabuf;
}
static void adf_fbdev_free_buffer(struct adf_fbdev_dmabuf *fbdev_dmabuf)
{
unsigned long flags;
spin_lock_irqsave(fbdev_dmabuf->alloc_lock, flags);
(*fbdev_dmabuf->alloc_mask) &= ~(1UL << fbdev_dmabuf->id);
spin_unlock_irqrestore(fbdev_dmabuf->alloc_lock, flags);
sg_free_table(&fbdev_dmabuf->sg_table);
kfree(fbdev_dmabuf);
module_put(THIS_MODULE);
}
/* DMA BUF LAYER *************************************************************/
static struct sg_table *
adf_fbdev_d_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct adf_fbdev_dmabuf *fbdev_dmabuf = attachment->dmabuf->priv;
return &fbdev_dmabuf->sg_table;
}
static void adf_fbdev_d_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction direction)
{
/* No-op */
}
static int adf_fbdev_d_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
struct adf_fbdev_dmabuf *fbdev_dmabuf = dmabuf->priv;
return remap_pfn_range(vma, vma->vm_start,
PFN_DOWN(fbdev_dmabuf->paddr),
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
}
static void adf_fbdev_d_release(struct dma_buf *dmabuf)
{
adf_fbdev_free_buffer(dmabuf->priv);
}
static int
adf_fbdev_d_begin_cpu_access(struct dma_buf *dmabuf, size_t start, size_t len,
enum dma_data_direction dir)
{
struct adf_fbdev_dmabuf *fbdev_dmabuf = dmabuf->priv;
if (start + len > fbdev_dmabuf->length)
return -EINVAL;
return 0;
}
static void adf_fbdev_d_end_cpu_access(struct dma_buf *dmabuf, size_t start,
size_t len, enum dma_data_direction dir)
{
/* Framebuffer memory is cache coherent. No-op. */
}
static void *
adf_fbdev_d_kmap(struct dma_buf *dmabuf, unsigned long page_offset)
{
struct adf_fbdev_dmabuf *fbdev_dmabuf = dmabuf->priv;
void *vaddr;
if (page_offset * PAGE_SIZE >= fbdev_dmabuf->length)
return ERR_PTR(-EINVAL);
vaddr = fbdev_dmabuf->vaddr + page_offset * PAGE_SIZE;
return vaddr;
}
static void
adf_fbdev_d_kunmap(struct dma_buf *dmabuf, unsigned long page_offset,
void *ptr)
{
/* No-op */
}
static void *adf_fbdev_d_vmap(struct dma_buf *dmabuf)
{
struct adf_fbdev_dmabuf *fbdev_dmabuf = dmabuf->priv;
return fbdev_dmabuf->vaddr;
}
static void adf_fbdev_d_vunmap(struct dma_buf *dmabuf, void *vaddr)
{
/* No-op */
}
static const struct dma_buf_ops adf_fbdev_dma_buf_ops = {
.map_dma_buf = adf_fbdev_d_map_dma_buf,
.unmap_dma_buf = adf_fbdev_d_unmap_dma_buf,
.mmap = adf_fbdev_d_mmap,
.release = adf_fbdev_d_release,
.begin_cpu_access = adf_fbdev_d_begin_cpu_access,
.end_cpu_access = adf_fbdev_d_end_cpu_access,
.kmap_atomic = adf_fbdev_d_kmap,
.kunmap_atomic = adf_fbdev_d_kunmap,
.kmap = adf_fbdev_d_kmap,
.kunmap = adf_fbdev_d_kunmap,
.vmap = adf_fbdev_d_vmap,
.vunmap = adf_fbdev_d_vunmap,
};
/* ADF LAYER *****************************************************************/
static u32 adf_fbdev_supported_format;
static int adf_fbdev_validate(struct adf_device *dev, struct adf_post *cfg,
void **driver_state)
{
int err = adf_img_validate_simple(dev, cfg, driver_state);
if (cfg->n_bufs == 0 || err != 0)
return err;
/* Everything checked out in the generic validation, but we
* additionally want to check that the dmabuf came from the
* adf_fbdev module, which the generic code can't check.
*/
if (cfg->bufs[0].dma_bufs[0]->ops != &adf_fbdev_dma_buf_ops)
return -EINVAL;
return 0;
}
static void adf_fbdev_post(struct adf_device *dev, struct adf_post *cfg,
void *driver_state)
{
struct adf_fbdev_device *device = (struct adf_fbdev_device *)dev;
struct fb_var_screeninfo new_var = device->fb_info->var;
struct adf_fbdev_dmabuf *fbdev_dmabuf;
struct adf_buffer *buffer;
int err;
/* "Null" flip handling */
if (cfg->n_bufs == 0)
return;
if (!lock_fb_info(device->fb_info)) {
pr_err("Failed to lock fb_info structure.\n");
return;
}
console_lock();
buffer = &cfg->bufs[0];
fbdev_dmabuf = buffer->dma_bufs[0]->priv;
new_var.yoffset = new_var.yres * fbdev_dmabuf->id;
/* If we're supposed to be able to flip, but the yres_virtual has been
* changed to an unsupported (smaller) value, we need to change it back
* (this is a workaround for some Linux fbdev drivers that seem to lose
* any modifications to yres_virtual after a blank.)
*/
if (new_var.yres_virtual < new_var.yres * NUM_PREFERRED_BUFFERS) {
new_var.activate = FB_ACTIVATE_NOW;
new_var.yres_virtual = new_var.yres * NUM_PREFERRED_BUFFERS;
err = fb_set_var(device->fb_info, &new_var);
if (err)
pr_err("fb_set_var failed (err=%d)\n", err);
} else {
err = fb_pan_display(device->fb_info, &new_var);
if (err)
pr_err("fb_pan_display failed (err=%d)\n", err);
}
console_unlock();
unlock_fb_info(device->fb_info);
}
static int
adf_fbdev_open2(struct adf_obj *obj, struct inode *inode, struct file *file)
{
struct adf_fbdev_device *dev =
(struct adf_fbdev_device *)obj->parent;
atomic_inc(&dev->refcount);
return 0;
}
static void
adf_fbdev_release2(struct adf_obj *obj, struct inode *inode, struct file *file)
{
struct adf_fbdev_device *dev =
(struct adf_fbdev_device *)obj->parent;
struct sync_fence *release_fence;
if (atomic_dec_return(&dev->refcount))
return;
/* This special "null" flip works around a problem with ADF
* which leaves buffers pinned by the display engine even
* after all ADF clients have closed.
*
* The "null" flip is pipelined like any other. The user won't
* be able to unload this module until it has been posted.
*/
release_fence = adf_device_post(&dev->base, NULL, 0, NULL, 0, NULL, 0);
if (IS_ERR_OR_NULL(release_fence)) {
pr_err("Failed to queue null flip command (err=%d).\n",
(int)PTR_ERR(release_fence));
return;
}
sync_fence_put(release_fence);
}
static const struct adf_device_ops adf_fbdev_device_ops = {
.owner = THIS_MODULE,
.base = {
.open = adf_fbdev_open2,
.release = adf_fbdev_release2,
.ioctl = adf_img_ioctl,
},
.validate = adf_fbdev_validate,
.post = adf_fbdev_post,
};
static bool
adf_fbdev_supports_event(struct adf_obj *obj, enum adf_event_type type)
{
switch (type) {
case ADF_EVENT_VSYNC:
case ADF_EVENT_HOTPLUG:
return true;
default:
return false;
}
}
static void
adf_fbdev_set_event(struct adf_obj *obj, enum adf_event_type type,
bool enabled)
{
switch (type) {
case ADF_EVENT_VSYNC:
case ADF_EVENT_HOTPLUG:
break;
default:
BUG();
}
}
static int adf_fbdev_blank2(struct adf_interface *intf, u8 state)
{
struct adf_fbdev_interface *interface =
(struct adf_fbdev_interface *)intf;
struct fb_info *fb_info = interface->fb_info;
if (!fb_info->fbops->fb_blank)
return -EOPNOTSUPP;
return fb_info->fbops->fb_blank(state, fb_info);
}
static int
adf_fbdev_alloc_simple_buffer(struct adf_interface *intf, u16 w, u16 h,
u32 format, struct dma_buf **dma_buf,
u32 *offset, u32 *pitch)
{
struct adf_fbdev_interface *interface =
(struct adf_fbdev_interface *)intf;
struct fb_var_screeninfo *var = &interface->fb_info->var;
struct adf_fbdev_dmabuf *fbdev_dmabuf;
if (w != var->xres) {
pr_err("Simple alloc request w=%u does not match w=%u.\n",
w, var->xres);
return -EINVAL;
}
if (h != var->yres) {
pr_err("Simple alloc request h=%u does not match h=%u.\n",
h, var->yres);
return -EINVAL;
}
if (format != adf_fbdev_supported_format) {
pr_err("Simple alloc request f=0x%x does not match f=0x%x.\n",
format, adf_fbdev_supported_format);
return -EINVAL;
}
fbdev_dmabuf = adf_fbdev_alloc_buffer(interface);
if (IS_ERR_OR_NULL(fbdev_dmabuf))
return PTR_ERR(fbdev_dmabuf);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
{
DEFINE_DMA_BUF_EXPORT_INFO(export_info);
export_info.ops = &adf_fbdev_dma_buf_ops;
export_info.size = fbdev_dmabuf->length;
export_info.flags = O_RDWR;
export_info.priv = fbdev_dmabuf;
*dma_buf = dma_buf_export(&export_info);
}
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0))
*dma_buf = dma_buf_export(fbdev_dmabuf, &adf_fbdev_dma_buf_ops,
fbdev_dmabuf->length, O_RDWR, NULL);
#else
*dma_buf = dma_buf_export(fbdev_dmabuf, &adf_fbdev_dma_buf_ops,
fbdev_dmabuf->length, O_RDWR);
#endif
if (IS_ERR(*dma_buf)) {
adf_fbdev_free_buffer(fbdev_dmabuf);
return PTR_ERR(*dma_buf);
}
*pitch = interface->fb_info->fix.line_length;
*offset = 0;
return 0;
}
static int
adf_fbdev_screen_size(struct adf_interface *intf, u16 *width_mm,
u16 *height_mm)
{
struct adf_fbdev_interface *interface =
(struct adf_fbdev_interface *)intf;
*width_mm = interface->width_mm;
*height_mm = interface->height_mm;
return 0;
}
static int adf_fbdev_modeset(struct adf_interface *intf,
struct drm_mode_modeinfo *mode)
{
struct adf_fbdev_interface *interface =
(struct adf_fbdev_interface *)intf;
return mode == &interface->fb_mode ? 0 : -EINVAL;
}
static const struct adf_interface_ops adf_fbdev_interface_ops = {
.base = {
.supports_event = adf_fbdev_supports_event,
.set_event = adf_fbdev_set_event,
},
.blank = adf_fbdev_blank2,
.alloc_simple_buffer = adf_fbdev_alloc_simple_buffer,
.screen_size = adf_fbdev_screen_size,
.modeset = adf_fbdev_modeset,
};
struct adf_overlay_engine_ops adf_fbdev_overlay_engine_ops = {
.supported_formats = &adf_fbdev_supported_format,
.n_supported_formats = 1,
};
/* If we can flip, we need to make sure we have the memory to do so.
*
* We'll assume that the fbdev device provides extra space in
* yres_virtual for panning; xres_virtual is theoretically supported,
* but it involves more work.
*
* If the fbdev device doesn't have yres_virtual > yres, we'll try
* requesting it before bailing. Userspace applications commonly do
* this with an FBIOPUT_VSCREENINFO ioctl().
*
* Another problem is with a limitation in PowerVR services -- it
* needs framebuffers to be page aligned (this is a SW limitation,
* the HW can support non-page-aligned buffers). So we have to
* check that stride * height for a single buffer is page aligned.
*/
static bool adf_fbdev_flip_possible(struct fb_info *fb_info)
{
struct fb_var_screeninfo var = fb_info->var;
int err;
if (!fb_info->fix.xpanstep && !fb_info->fix.ypanstep &&
!fb_info->fix.ywrapstep) {
pr_err("The fbdev device detected does not support ypan/ywrap.\n");
return false;
}
if ((fb_info->fix.line_length * var.yres) % PAGE_SIZE != 0) {
pr_err("Line length (in bytes) x yres is not a multiple of page size.\n");
return false;
}
/* We might already have enough space */
if (var.yres * NUM_PREFERRED_BUFFERS <= var.yres_virtual)
return true;
pr_err("No buffer space for flipping; asking for more.\n");
var.activate = FB_ACTIVATE_NOW;
var.yres_virtual = var.yres * NUM_PREFERRED_BUFFERS;
err = fb_set_var(fb_info, &var);
if (err) {
pr_err("fb_set_var failed (err=%d).\n", err);
return false;
}
if (var.yres * NUM_PREFERRED_BUFFERS > var.yres_virtual) {
pr_err("Failed to obtain additional buffer space.\n");
return false;
}
/* Some fbdev drivers allow the yres_virtual modification through,
* but don't actually update the fix. We need the fix to be updated
* and more memory allocated, so we can actually take advantage of
* the increased yres_virtual.
*/
if (fb_info->fix.smem_len < fb_info->fix.line_length *
var.yres_virtual) {
pr_err("'fix' not re-allocated with sufficient buffer space.\n");
pr_err("Check NUM_PREFERRED_BUFFERS (%u) is as intended.\n",
NUM_PREFERRED_BUFFERS);
return false;
}
return true;
}
/* Could use devres here? */
static struct {
struct adf_fbdev_device device;
struct adf_fbdev_interface interface;
struct adf_overlay_engine engine;
} dev_data;
static int __init init_adf_fbdev(void)
{
struct drm_mode_modeinfo *mode = &dev_data.interface.fb_mode;
char format_str[ADF_FORMAT_STR_SIZE];
struct fb_info *fb_info;
int err = -ENODEV;
fb_info = registered_fb[0];
if (!fb_info) {
pr_err("No Linux framebuffer (fbdev) device is registered!\n");
pr_err("Check you have a framebuffer driver compiled into your kernel\n");
pr_err("and that it is enabled on the cmdline.\n");
goto err_out;
}
if (!lock_fb_info(fb_info))
goto err_out;
console_lock();
/* Filter out broken FB devices */
if (!fb_info->fix.smem_len || !fb_info->fix.line_length) {
pr_err("The fbdev device detected had a zero smem_len or line_length,\n");
pr_err("which suggests it is a broken driver.\n");
goto err_unlock;
}
if (fb_info->fix.type != FB_TYPE_PACKED_PIXELS ||
fb_info->fix.visual != FB_VISUAL_TRUECOLOR) {
pr_err("The fbdev device detected is not truecolor with packed pixels.\n");
goto err_unlock;
}
if (fb_info->var.bits_per_pixel == 32) {
if (fb_info->var.red.length == 8 ||
fb_info->var.green.length == 8 ||
fb_info->var.blue.length == 8 ||
fb_info->var.red.offset == 16 ||
fb_info->var.green.offset == 8 ||
fb_info->var.blue.offset == 0) {
#if defined(ADF_FBDEV_FORCE_XRGB8888)
adf_fbdev_supported_format = DRM_FORMAT_BGRX8888;
#else
adf_fbdev_supported_format = DRM_FORMAT_BGRA8888;
#endif
} else if (fb_info->var.red.length == 8 ||
fb_info->var.green.length == 8 ||
fb_info->var.blue.length == 8 ||
fb_info->var.red.offset == 0 ||
fb_info->var.green.offset == 8 ||
fb_info->var.blue.offset == 16) {
adf_fbdev_supported_format = DRM_FORMAT_RGBA8888;
} else {
pr_err("The fbdev device detected uses an unrecognized 32bit pixel format (%u/%u/%u, %u/%u/%u)\n",
fb_info->var.red.length,
fb_info->var.green.length,
fb_info->var.blue.length,
fb_info->var.red.offset,
fb_info->var.green.offset,
fb_info->var.blue.offset);
goto err_unlock;
}
} else if (fb_info->var.bits_per_pixel == 16) {
if (fb_info->var.red.length != 5 ||
fb_info->var.green.length != 6 ||
fb_info->var.blue.length != 5 ||
fb_info->var.red.offset != 11 ||
fb_info->var.green.offset != 5 ||
fb_info->var.blue.offset != 0) {
pr_err("The fbdev device detected uses an unrecognized 16bit pixel format (%u/%u/%u, %u/%u/%u)\n",
fb_info->var.red.length,
fb_info->var.green.length,
fb_info->var.blue.length,
fb_info->var.red.offset,
fb_info->var.green.offset,
fb_info->var.blue.offset);
goto err_unlock;
}
adf_fbdev_supported_format = DRM_FORMAT_BGR565;
} else {
pr_err("The fbdev device detected uses an unsupported bpp (%u).\n",
fb_info->var.bits_per_pixel);
goto err_unlock;
}
#if defined(CONFIG_ARCH_MT8173)
/* Workaround for broken framebuffer driver. The wrong pixel format
* is reported to this module. It is always really RGBA8888.
*/
adf_fbdev_supported_format = DRM_FORMAT_RGBA8888;
#endif
if (!try_module_get(fb_info->fbops->owner)) {
pr_err("try_module_get() failed");
goto err_unlock;
}
if (fb_info->fbops->fb_open &&
fb_info->fbops->fb_open(fb_info, 0) != 0) {
pr_err("fb_open() failed");
goto err_module_put;
}
if (!adf_fbdev_flip_possible(fb_info)) {
pr_err("Flipping must be supported for ADF. Aborting.\n");
goto err_fb_release;
}
err = adf_device_init(&dev_data.device.base, fb_info->dev,
&adf_fbdev_device_ops, "fbdev");
if (err) {
pr_err("adf_device_init failed (%d)", err);
goto err_fb_release;
}
dev_data.device.fb_info = fb_info;
err = adf_interface_init(&dev_data.interface.base,
&dev_data.device.base,
ADF_INTF_DVI, 0, ADF_INTF_FLAG_PRIMARY,
&adf_fbdev_interface_ops, "fbdev_interface");
if (err) {
pr_err("adf_interface_init failed (%d)", err);
goto err_device_destroy;
}
spin_lock_init(&dev_data.interface.alloc_lock);
dev_data.interface.fb_info = fb_info;
/* If the fbdev mode looks viable, try to inherit from it */
if (fb_info->mode)
adf_modeinfo_from_fb_videomode(fb_info->mode, mode);
/* Framebuffer drivers aren't always very good at filling out their
* mode information, so fake up anything that's missing so we don't
* need to accommodate it in userspace.
*/
if (!mode->hdisplay)
mode->hdisplay = fb_info->var.xres;
if (!mode->vdisplay)
mode->vdisplay = fb_info->var.yres;
if (!mode->vrefresh)
mode->vrefresh = FALLBACK_REFRESH_RATE;
if (fb_info->var.width > 0 && fb_info->var.width < 1000) {
dev_data.interface.width_mm = fb_info->var.width;
} else {
dev_data.interface.width_mm = (fb_info->var.xres * 25400) /
(FALLBACK_DPI * 1000);
}
if (fb_info->var.height > 0 && fb_info->var.height < 1000) {
dev_data.interface.height_mm = fb_info->var.height;
} else {
dev_data.interface.height_mm = (fb_info->var.yres * 25400) /
(FALLBACK_DPI * 1000);
}
err = adf_hotplug_notify_connected(&dev_data.interface.base, mode, 1);
if (err) {
pr_err("adf_hotplug_notify_connected failed (%d)", err);
goto err_interface_destroy;
}
/* This doesn't really set the mode, it just updates current_mode */
err = adf_interface_set_mode(&dev_data.interface.base, mode);
if (err) {
pr_err("adf_interface_set_mode failed (%d)", err);
goto err_interface_destroy;
}
err = adf_overlay_engine_init(&dev_data.engine, &dev_data.device.base,
&adf_fbdev_overlay_engine_ops,
"fbdev_overlay_engine");
if (err) {
pr_err("adf_overlay_engine_init failed (%d)", err);
goto err_interface_destroy;
}
err = adf_attachment_allow(&dev_data.device.base,
&dev_data.engine,
&dev_data.interface.base);
if (err) {
pr_err("adf_attachment_allow failed (%d)", err);
goto err_overlay_engine_destroy;
}
adf_format_str(adf_fbdev_supported_format, format_str);
pr_info("Found usable fbdev device (%s):\n"
"range (physical) = 0x%lx-0x%lx\n"
"range (virtual) = %p-%p\n"
"size (bytes) = 0x%x\n"
"xres x yres = %ux%u\n"
"xres x yres (v) = %ux%u\n"
"physical (mm) = %ux%u\n"
"refresh (Hz) = %u\n"
"drm fourcc = %s (0x%x)\n",
fb_info->fix.id,
fb_info->fix.smem_start,
fb_info->fix.smem_start + fb_info->fix.smem_len,
fb_info->screen_base,
fb_info->screen_base + fb_info->screen_size,
fb_info->fix.smem_len,
mode->hdisplay, mode->vdisplay,
fb_info->var.xres_virtual, fb_info->var.yres_virtual,
dev_data.interface.width_mm, dev_data.interface.height_mm,
mode->vrefresh,
format_str, adf_fbdev_supported_format);
err = 0;
err_unlock:
console_unlock();
unlock_fb_info(fb_info);
err_out:
return err;
err_overlay_engine_destroy:
adf_overlay_engine_destroy(&dev_data.engine);
err_interface_destroy:
adf_interface_destroy(&dev_data.interface.base);
err_device_destroy:
adf_device_destroy(&dev_data.device.base);
err_fb_release:
if (fb_info->fbops->fb_release)
fb_info->fbops->fb_release(fb_info, 0);
err_module_put:
module_put(fb_info->fbops->owner);
goto err_unlock;
}
static void __exit exit_adf_fbdev(void)
{
struct fb_info *fb_info = dev_data.device.fb_info;
if (!lock_fb_info(fb_info)) {
pr_err("Failed to lock fb_info.\n");
return;
}
console_lock();
adf_overlay_engine_destroy(&dev_data.engine);
adf_interface_destroy(&dev_data.interface.base);
adf_device_destroy(&dev_data.device.base);
if (fb_info->fbops->fb_release)
fb_info->fbops->fb_release(fb_info, 0);
module_put(fb_info->fbops->owner);
console_unlock();
unlock_fb_info(fb_info);
}
module_init(init_adf_fbdev);
module_exit(exit_adf_fbdev);