From patchwork Wed Mar 11 15:27:04 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Davor Joja X-Patchwork-Id: 5986771 Return-Path: X-Original-To: patchwork-linux-fbdev@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id A83499F380 for ; Wed, 11 Mar 2015 15:37:00 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 09F9A203AC for ; Wed, 11 Mar 2015 15:36:59 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 59496203AA for ; Wed, 11 Mar 2015 15:36:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753935AbbCKPgc (ORCPT ); Wed, 11 Mar 2015 11:36:32 -0400 Received: from mail.logicbricks.com ([89.201.165.134]:27597 "EHLO mail.logicbricks.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754000AbbCKPdX (ORCPT ); Wed, 11 Mar 2015 11:33:23 -0400 X-Greylist: delayed 334 seconds by postgrey-1.27 at vger.kernel.org; Wed, 11 Mar 2015 11:33:15 EDT From: Davor Joja To: , , , CC: Davor Joja Subject: [PATCH 03/10] fbdev: xylon: Framebuffer driver ioctl Date: Wed, 11 Mar 2015 16:27:04 +0100 X-Mailer: git-send-email 1.9.1 In-Reply-To: <1426087631-9952-1-git-send-email-davorjoja@logicbricks.com> References: <1426087631-9952-1-git-send-email-davorjoja@logicbricks.com> MIME-Version: 1.0 X-Originating-IP: [192.168.0.95] Message-ID: <76e287ed-2887-487e-ad44-57badea28824@mail.xylon.local> Sender: linux-fbdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fbdev@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Driver IOCTL providing advanced video controller functionality used by user space applications. Signed-off-by: Davor Joja --- drivers/video/fbdev/xylon/xylonfb_ioctl.c | 672 ++++++++++++++++++++++++++++++ 1 file changed, 672 insertions(+) create mode 100644 drivers/video/fbdev/xylon/xylonfb_ioctl.c diff --git a/drivers/video/fbdev/xylon/xylonfb_ioctl.c b/drivers/video/fbdev/xylon/xylonfb_ioctl.c new file mode 100644 index 0000000..131bdcf --- /dev/null +++ b/drivers/video/fbdev/xylon/xylonfb_ioctl.c @@ -0,0 +1,672 @@ +/* + * Xylon logiCVC frame buffer driver IOCTL functionality + * + * Copyright (C) 2015 Xylon d.o.o. + * Author: Davor Joja + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include "logicvc.h" +#include "xylonfb_core.h" + +static int xylonfb_get_vblank(struct fb_vblank *vblank, struct fb_info *fbi) +{ + vblank->flags |= FB_VBLANK_HAVE_VSYNC; + + return 0; +} + +static void xylonfb_vsync_ctrl(struct fb_info *fbi, bool enable) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + u32 imr; + + mutex_lock(&data->irq_mutex); + + imr = data->reg_access.get_reg_val(data->dev_base, + LOGICVC_INT_MASK_ROFF, ld); + if (enable) { + imr &= (~LOGICVC_INT_V_SYNC); + writel(LOGICVC_INT_V_SYNC, + data->dev_base + LOGICVC_INT_STAT_ROFF); + } else { + imr |= LOGICVC_INT_V_SYNC; + } + + data->reg_access.set_reg_val(imr, data->dev_base, + LOGICVC_INT_MASK_ROFF, ld); + + mutex_unlock(&data->irq_mutex); +} + +int xylonfb_vsync_wait(u32 crt, struct fb_info *fbi) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + int ret, count; + + mutex_lock(&data->irq_mutex); + + count = data->vsync.count; + + ret = wait_event_interruptible_timeout(data->vsync.wait, + (count != data->vsync.count), + HZ/10); + + mutex_unlock(&data->irq_mutex); + + if (ret < 0) + return ret; + else if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + +static unsigned int alpha_normalized(unsigned int alpha, unsigned int used_bits, + bool set) +{ + if (set) + return alpha / (255 / ((1 << used_bits) - 1)); + else + return (((255 << 16) / ((1 << used_bits) - 1)) * alpha) >> 16; +} + +static int xylonfb_layer_alpha(struct xylonfb_layer_data *ld, u8 *alpha, + bool set) +{ + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + unsigned int used_bits; + u32 val; + + if (fd->transparency != LOGICVC_ALPHA_LAYER) + return -EPERM; + + switch (fd->type) { + case LOGICVC_LAYER_YUV: + used_bits = 8; + break; + case LOGICVC_LAYER_RGB: + switch (fd->bpp) { + case 8: + used_bits = 3; + break; + case 16: + used_bits = 6; + break; + case 32: + used_bits = 8; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + if (!set) { + val = data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_ALPHA_ROFF, + ld); + *alpha = (u8)(val & (0xFF >> (8 - used_bits))); + } + + /* get/set normalized alpha value */ + *alpha = alpha_normalized(*alpha, used_bits, set); + + if (set) + data->reg_access.set_reg_val(*alpha, ld->base, + LOGICVC_LAYER_ALPHA_ROFF, + ld); + + return 0; +} + +static int xylonfb_layer_buff(struct fb_info *fbi, + struct xylonfb_layer_buffer *layer_buff, + bool set) +{ + struct xylonfb_layer_data *ld = fbi->par; + unsigned int layer_id = ld->fd->id; + u32 reg; + + if (set) { + if (layer_buff->id >= LOGICVC_MAX_LAYER_BUFFERS) + return -EINVAL; + + reg = readl(ld->data->dev_base + LOGICVC_VBUFF_SELECT_ROFF); + reg |= (1 << (10 + layer_id)); + reg &= ~(0x03 << (layer_id << 1)); + reg |= (layer_buff->id << (layer_id << 1)); + writel(reg, ld->data->dev_base + LOGICVC_VBUFF_SELECT_ROFF); + + xylonfb_vsync_wait(0, fbi); + } else { + reg = readl(ld->data->dev_base + LOGICVC_VBUFF_SELECT_ROFF); + reg >>= ((layer_id << 1)); + layer_buff->id = reg & 0x03; + } + + return 0; +} + +static void xylonfb_rgb_yuv(u32 c1, u32 c2, u32 c3, u32 *pixel, + struct xylonfb_layer_data *ld, bool rgb2yuv) +{ + struct xylonfb_data *data = ld->data; + u32 r, g, b, y, u, v; + + if (rgb2yuv) { + y = ((data->coeff.cyr * c1) + (data->coeff.cyg * c2) + + (data->coeff.cyb * c3) + data->coeff.cy) / + LOGICVC_YUV_NORM; + u = ((-data->coeff.cur * c1) - (data->coeff.cug * c2) + + (data->coeff.cub * c3) + LOGICVC_COEFF_U) / + LOGICVC_YUV_NORM; + v = ((data->coeff.cvr * c1) - (data->coeff.cvg * c2) - + (data->coeff.cvb * c3) + LOGICVC_COEFF_V) / + LOGICVC_YUV_NORM; + + *pixel = (0xFF << 24) | (y << 16) | (u << 8) | v; + } else { + r = ((c1 * LOGICVC_RGB_NORM) + (LOGICVC_COEFF_R_U * c2) - + LOGICVC_COEFF_R) / LOGICVC_RGB_NORM; + g = ((c1 * LOGICVC_RGB_NORM) - (LOGICVC_COEFF_G_U * c2) - + (LOGICVC_COEFF_G_V * c3) + LOGICVC_COEFF_G) / + LOGICVC_RGB_NORM; + b = ((c1 * LOGICVC_RGB_NORM) - (LOGICVC_COEFF_B_V * c3) - + LOGICVC_COEFF_B) / LOGICVC_RGB_NORM; + + *pixel = (0xFF << 24) | (r << 16) | (g << 8) | b; + } +} + +static int xylonfb_layer_color_rgb(struct xylonfb_layer_data *ld, + struct xylonfb_layer_color *layer_color, + unsigned int reg_offset, bool set) +{ + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + void __iomem *base; + u32 r = 0, g = 0, b = 0; + u32 raw_rgb, y, u, v; + int bpp, transparency; + + if (reg_offset == LOGICVC_LAYER_TRANSP_COLOR_ROFF) { + base = ld->base; + bpp = fd->bpp; + transparency = fd->transparency; + } else /* if (reg_offset == LOGICVC_BACKGROUND_COLOR_ROFF) */ { + base = data->dev_base; + bpp = data->bg_layer_bpp; + transparency = -1; + } + + if (set) { + if (layer_color->use_raw) { + raw_rgb = layer_color->raw_rgb; + } else if (data->flags & XYLONFB_FLAGS_BACKGROUND_LAYER_YUV) { + r = layer_color->r; + g = layer_color->g; + b = layer_color->b; + xylonfb_rgb_yuv(r, g, b, &raw_rgb, ld, true); + } else { + r = layer_color->r; + g = layer_color->g; + b = layer_color->b; +check_bpp_set: + switch (bpp) { + case 8: + switch (transparency) { + case LOGICVC_ALPHA_CLUT_16BPP: + bpp = 16; + goto check_bpp_set; + case LOGICVC_ALPHA_CLUT_32BPP: + bpp = 32; + goto check_bpp_set; + default: + raw_rgb = (r & 0xE0) | + ((g & 0xE0) >> 3) | + ((b & 0xC0) >> 6); + break; + } + break; + case 16: + raw_rgb = ((r & 0xF8) << 8) | + ((g & 0xFC) << 3) | + ((b & 0xF8) >> 3); + break; + case 32: + raw_rgb = (r << 16) | (g << 8) | b; + break; + default: + raw_rgb = 0; + } + } + data->reg_access.set_reg_val(raw_rgb, base, reg_offset, ld); + } else { + raw_rgb = data->reg_access.get_reg_val(base, reg_offset, ld); +check_bpp_get: + if (data->flags & XYLONFB_FLAGS_BACKGROUND_LAYER_YUV) { + y = (raw_rgb >> 16) & 0xFF; + u = (raw_rgb >> 8) & 0xFF; + v = raw_rgb & 0xFF; + xylonfb_rgb_yuv(y, u, v, &raw_rgb, ld, false); + } else { + switch (bpp) { + case 8: + switch (transparency) { + case LOGICVC_ALPHA_CLUT_16BPP: + bpp = 16; + goto check_bpp_get; + case LOGICVC_ALPHA_CLUT_32BPP: + bpp = 32; + goto check_bpp_get; + default: + r = raw_rgb >> 5; + r = (((r << 3) | r) << 2) | (r >> 1); + g = (raw_rgb >> 2) & 0x07; + g = (((g << 3) | g) << 2) | (g >> 1); + b = raw_rgb & 0x03; + b = (b << 6) | (b << 4) | (b << 2) | b; + break; + } + break; + case 16: + r = raw_rgb >> 11; + r = (r << 3) | (r >> 2); + g = (raw_rgb >> 5) & 0x3F; + g = (g << 2) | (g >> 4); + b = raw_rgb & 0x1F; + b = (b << 3) | (b >> 2); + break; + case 32: + r = raw_rgb >> 16; + g = (raw_rgb >> 8) & 0xFF; + b = raw_rgb & 0xFF; + break; + default: + raw_rgb = r = g = b = 0; + } + } + layer_color->raw_rgb = raw_rgb; + layer_color->r = (u8)r; + layer_color->g = (u8)g; + layer_color->b = (u8)b; + } + + return 0; +} + +static int xylonfb_layer_geometry(struct fb_info *fbi, + struct xylonfb_layer_geometry *layer_geometry, + bool set) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + u32 x, y, width, height, xoff, yoff, xres, yres; + + xres = fbi->var.xres; + yres = fbi->var.yres; + + if (set) { + x = layer_geometry->x; + y = layer_geometry->y; + width = layer_geometry->width; + height = layer_geometry->height; + + if ((x > xres) || (y > yres)) + return -EINVAL; + + if ((width == 0) || (height == 0)) + return -EINVAL; + + if ((x + width) > xres) { + width = xres - x; + layer_geometry->width = width; + } + if ((y + height) > yres) { + height = yres - y; + layer_geometry->height = height; + } + /* YUV 4:2:2 layer type can only have even layer width */ + if ((width > 2) && (fd->type == LOGICVC_LAYER_YUV) && + (fd->bpp == 16)) + width &= ~((unsigned long) + 1); + + /* + * logiCVC 3.x registers write sequence: + * offset, size, position with implicit last write to + * LOGICVC_LAYER_VPOS_ROFF + * logiCVC 4.x registers write sequence: + * size, position with implicit last write to + * LOGICVC_LAYER_ADDR_ROFF + */ + if (!(data->flags & XYLONFB_FLAGS_DYNAMIC_LAYER_ADDRESS)) { + data->reg_access.set_reg_val(layer_geometry->x_offset, + ld->base, + LOGICVC_LAYER_HOFF_ROFF, + ld); + data->reg_access.set_reg_val(layer_geometry->y_offset, + ld->base, + LOGICVC_LAYER_VOFF_ROFF, + ld); + } + data->reg_access.set_reg_val((width - 1), ld->base, + LOGICVC_LAYER_HSIZE_ROFF, + ld); + data->reg_access.set_reg_val((height - 1), ld->base, + LOGICVC_LAYER_VSIZE_ROFF, + ld); + data->reg_access.set_reg_val((xres - (x + 1)), ld->base, + LOGICVC_LAYER_HPOS_ROFF, + ld); + data->reg_access.set_reg_val((yres - (y + 1)), ld->base, + LOGICVC_LAYER_VPOS_ROFF, + ld); + if (data->flags & XYLONFB_FLAGS_DYNAMIC_LAYER_ADDRESS) { + xoff = layer_geometry->x_offset * (ld->fd->bpp / 8); + yoff = layer_geometry->y_offset * ld->fd->width * + (ld->fd->bpp / 8); + + ld->fb_pbase_active = ld->fb_pbase + xoff + yoff; + + data->reg_access.set_reg_val(ld->fb_pbase_active, + ld->base, + LOGICVC_LAYER_ADDR_ROFF, + ld); + } + } else { + x = data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_HPOS_ROFF, + ld); + layer_geometry->x = xres - (x + 1); + y = data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_VPOS_ROFF, + ld); + layer_geometry->y = yres - (y + 1); + layer_geometry->width = + data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_HSIZE_ROFF, + ld); + layer_geometry->width += 1; + layer_geometry->height = + data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_VSIZE_ROFF, + ld); + layer_geometry->height += 1; + } + + return 0; +} + +static int xylonfb_layer_reg_access(struct xylonfb_layer_data *ld, + struct xylonfb_hw_access *hw_access, + bool set) +{ + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + u32 offset; + u32 rel_offset; + + if ((hw_access->offset < LOGICVC_LAYER_BASE_OFFSET) || + (hw_access->offset > LOGICVC_LAYER_BASE_END)) + return -EPERM; + + if (data->flags & XYLONFB_FLAGS_READABLE_REGS) { + offset = hw_access->offset; + if (set) + data->reg_access.set_reg_val(hw_access->value, + data->dev_base, + offset, + ld); + else + hw_access->value = + data->reg_access.get_reg_val(data->dev_base, + offset, + ld); + return 0; + } + + rel_offset = hw_access->offset - (fd->id * 0x80) - + LOGICVC_LAYER_BASE_OFFSET; + + if (rel_offset > LOGICVC_LAYER_BASE_END) + return -EINVAL; + + if (set) + data->reg_access.set_reg_val(hw_access->value, ld->base, + rel_offset, ld); + else + hw_access->value = data->reg_access.get_reg_val(ld->base, + rel_offset, ld); + + return 0; +} + +int xylonfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + union { + struct fb_vblank vblank; + struct xylonfb_hw_access hw_access; + struct xylonfb_layer_buffer layer_buff; + struct xylonfb_layer_color layer_color; + struct xylonfb_layer_geometry layer_geometry; + struct xylonfb_layer_transparency layer_transp; + } ioctl; + void __user *argp = (void __user *)arg; + unsigned long val; + u32 var32; + int ret = 0; + bool flag; + + switch (cmd) { + case FBIOGET_VBLANK: + if (copy_from_user(&ioctl.vblank, argp, sizeof(ioctl.vblank))) + return -EFAULT; + + ret = xylonfb_get_vblank(&ioctl.vblank, fbi); + if (!ret && + copy_to_user(argp, &ioctl.vblank, sizeof(ioctl.vblank))) + ret = -EFAULT; + break; + + case FBIO_WAITFORVSYNC: + if (get_user(var32, (u32 __user *)arg)) + return -EFAULT; + + ret = xylonfb_vsync_wait(var32, fbi); + break; + + case XYLONFB_VSYNC_CTRL: + if (get_user(flag, (u8 __user *)arg)) + return -EFAULT; + + xylonfb_vsync_ctrl(fbi, flag); + break; + + case XYLONFB_LAYER_IDX: + var32 = ld->fd->id; + put_user(var32, (u32 __user *)arg); + break; + + case XYLONFB_LAYER_ALPHA: + if (copy_from_user(&ioctl.layer_transp, argp, + sizeof(ioctl.layer_transp))) + return -EFAULT; + + mutex_lock(&ld->mutex); + ret = xylonfb_layer_alpha(ld, &ioctl.layer_transp.alpha, + ioctl.layer_transp.set); + if (!ret && !ioctl.layer_transp.set) + if (copy_to_user(argp, &ioctl.layer_transp, + sizeof(ioctl.layer_transp))) + ret = -EFAULT; + mutex_unlock(&ld->mutex); + break; + + case XYLONFB_LAYER_COLOR_TRANSP_CTRL: + if (get_user(flag, (u8 __user *)arg)) + return -EFAULT; + + mutex_lock(&ld->mutex); + var32 = data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); + if (flag) + var32 |= LOGICVC_LAYER_CTRL_COLOR_TRANSPARENCY_DISABLE; + else + var32 &= ~LOGICVC_LAYER_CTRL_COLOR_TRANSPARENCY_DISABLE; + data->reg_access.set_reg_val(var32, ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); + mutex_unlock(&ld->mutex); + break; + + case XYLONFB_LAYER_COLOR_TRANSP: + if (copy_from_user(&ioctl.layer_color, argp, + sizeof(ioctl.layer_color))) + return -EFAULT; + + mutex_lock(&ld->mutex); + ret = xylonfb_layer_color_rgb(ld, &ioctl.layer_color, + LOGICVC_LAYER_TRANSP_COLOR_ROFF, + ioctl.layer_color.set); + if (!ret && !ioctl.layer_color.set) + if (copy_to_user(argp, &ioctl.layer_color, + sizeof(ioctl.layer_color))) + ret = -EFAULT; + mutex_unlock(&ld->mutex); + break; + + case XYLONFB_LAYER_GEOMETRY: + if (!(data->flags & XYLONFB_FLAGS_SIZE_POSITION)) + return -EINVAL; + + if (copy_from_user(&ioctl.layer_geometry, argp, + sizeof(ioctl.layer_geometry))) + return -EFAULT; + + mutex_lock(&ld->mutex); + ret = xylonfb_layer_geometry(fbi, &ioctl.layer_geometry, + ioctl.layer_geometry.set); + if (!ret && !ioctl.layer_geometry.set) + if (copy_to_user(argp, &ioctl.layer_geometry, + sizeof(ioctl.layer_geometry))) + ret = -EFAULT; + mutex_unlock(&ld->mutex); + break; + + case XYLONFB_LAYER_BUFFER: + if (data->major >= 4) + return -EPERM; + + if (copy_from_user(&ioctl.layer_buff, argp, + sizeof(ioctl.layer_buff))) + return -EFAULT; + + mutex_lock(&ld->mutex); + ret = xylonfb_layer_buff(fbi, &ioctl.layer_buff, + ioctl.layer_buff.set); + if (!ret && !ioctl.layer_buff.set) + if (copy_to_user(argp, &ioctl.layer_buff, + sizeof(ioctl.layer_buff))) + ret = -EFAULT; + mutex_unlock(&ld->mutex); + break; + + case XYLONFB_LAYER_BUFFER_OFFSET: + if (data->major < 4) { + var32 = readl(ld->data->dev_base + + LOGICVC_VBUFF_SELECT_ROFF); + var32 >>= (ld->fd->id << 1); + var32 &= 0x03; + val = ld->fd->buffer_offset; + val *= var32; + } else { + val = ld->fd->buffer_offset; + } + put_user(val, (unsigned long __user *)arg); + break; + + case XYLONFB_BACKGROUND_COLOR: + if (data->bg_layer_bpp == 0) + return -EPERM; + + if (copy_from_user(&ioctl.layer_color, argp, + sizeof(ioctl.layer_color))) + return -EFAULT; + + mutex_lock(&ld->mutex); + ret = xylonfb_layer_color_rgb(ld, &ioctl.layer_color, + LOGICVC_BACKGROUND_COLOR_ROFF, + ioctl.layer_color.set); + if (!ret && !ioctl.layer_color.set) + if (copy_to_user(argp, &ioctl.layer_color, + sizeof(ioctl.layer_color))) + ret = -EFAULT; + mutex_unlock(&ld->mutex); + break; + + case XYLONFB_LAYER_EXT_BUFF_SWITCH: + if (get_user(flag, (u8 __user *)arg)) + return -EFAULT; + + mutex_lock(&ld->mutex); + var32 = data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); + if (flag) + var32 |= LOGICVC_LAYER_CTRL_EXTERNAL_BUFFER_SWITCH; + else + var32 &= ~LOGICVC_LAYER_CTRL_EXTERNAL_BUFFER_SWITCH; + data->reg_access.set_reg_val(var32, ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); + mutex_unlock(&ld->mutex); + break; + + case XYLONFB_HW_ACCESS: + if (copy_from_user(&ioctl.hw_access, argp, + sizeof(ioctl.hw_access))) + return -EFAULT; + + ret = xylonfb_layer_reg_access(ld, &ioctl.hw_access, + ioctl.hw_access.set); + if (!ret && !ioctl.hw_access.set) + if (copy_to_user(argp, &ioctl.hw_access, + sizeof(ioctl.hw_access))) + ret = -EFAULT; + break; + + case XYLONFB_IP_CORE_VERSION: + var32 = (data->major << 16) | (data->minor << 8) | data->patch; + if (copy_to_user(argp, &var32, sizeof(u32))) + ret = -EFAULT; + break; + + default: + dev_err(&data->pdev->dev, "unknown ioctl"); + ret = -EINVAL; + } + + return ret; +}