From patchwork Wed Mar 11 15:27:03 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Davor Joja X-Patchwork-Id: 5986741 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 497FC9F380 for ; Wed, 11 Mar 2015 15:36:32 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id DD82E203AD for ; Wed, 11 Mar 2015 15:36:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 91112203AA for ; Wed, 11 Mar 2015 15:36:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753892AbbCKPf4 (ORCPT ); Wed, 11 Mar 2015 11:35:56 -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 S1754001AbbCKPd0 (ORCPT ); Wed, 11 Mar 2015 11:33:26 -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 02/10] fbdev: xylon: Framebuffer driver core Date: Wed, 11 Mar 2015 16:27:03 +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: 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 core functionality. Signed-off-by: Davor Joja --- drivers/video/fbdev/xylon/xylonfb_core.c | 1691 ++++++++++++++++++++++++++++++ drivers/video/fbdev/xylon/xylonfb_core.h | 252 +++++ 2 files changed, 1943 insertions(+) create mode 100644 drivers/video/fbdev/xylon/xylonfb_core.c create mode 100644 drivers/video/fbdev/xylon/xylonfb_core.h diff --git a/drivers/video/fbdev/xylon/xylonfb_core.c b/drivers/video/fbdev/xylon/xylonfb_core.c new file mode 100644 index 0000000..9970215 --- /dev/null +++ b/drivers/video/fbdev/xylon/xylonfb_core.c @@ -0,0 +1,1691 @@ +/* + * Xylon logiCVC frame buffer driver core functions + * + * 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 +#include +#include +#include +#include + +#include "xylonfb_core.h" +#include "logicvc.h" + +#define LOGICVC_PIX_FMT_AYUV v4l2_fourcc('A', 'Y', 'U', 'V') +#define LOGICVC_PIX_FMT_AVUY v4l2_fourcc('A', 'V', 'U', 'Y') +#define LOGICVC_PIX_FMT_ALPHA v4l2_fourcc('A', '8', ' ', ' ') + +#define XYLONFB_PSEUDO_PALETTE_SIZE 256 +#define XYLONFB_VRES_DEFAULT 1080 + +#define LOGICVC_COLOR_RGB_BLACK 0 +#define LOGICVC_COLOR_RGB_WHITE 0xFFFFFF +#define LOGICVC_COLOR_YUV888_BLACK 0x8080 +#define LOGICVC_COLOR_YUV888_WHITE 0xFF8080 + +char *xylonfb_mode_option; + +static const struct xylonfb_vmode xylonfb_vm = { + .vmode = { + .refresh = 60, + .xres = 1024, + .yres = 768, + .pixclock = KHZ2PICOS(65000), + .left_margin = 160, + .right_margin = 24, + .upper_margin = 29, + .lower_margin = 3, + .hsync_len = 136, + .vsync_len = 6, + .vmode = FB_VMODE_NONINTERLACED + }, + .name = "1024x768" +}; + +static int xylonfb_set_timings(struct fb_info *fbi, int bpp); +static void xylonfb_logicvc_disp_ctrl(struct fb_info *fbi, bool enable); +static void xylonfb_enable_logicvc_output(struct fb_info *fbi); +static void xylonfb_disable_logicvc_output(struct fb_info *fbi); +static void xylonfb_logicvc_layer_enable(struct fb_info *fbi, bool enable); +static void xylonfb_fbi_update(struct fb_info *fbi); + +static u32 xylonfb_get_reg(void __iomem *base, unsigned int offset, + struct xylonfb_layer_data *ld) +{ + return readl(base + offset); +} + +static void xylonfb_set_reg(u32 value, void __iomem *base, unsigned int offset, + struct xylonfb_layer_data *ld) +{ + writel(value, (base + offset)); +} + +static unsigned long xylonfb_get_reg_mem_addr(void __iomem *base, + unsigned int offset, + struct xylonfb_layer_data *ld) +{ + unsigned int ordinal = offset / LOGICVC_REG_STRIDE; + + if ((unsigned long)base - (unsigned long)ld->data->dev_base) { + return (unsigned long)(&ld->regs) + (ordinal * sizeof(u32)); + } else { + ordinal -= (LOGICVC_CTRL_ROFF / LOGICVC_REG_STRIDE); + return (unsigned long)(&ld->data->regs) + + (ordinal * sizeof(u32)); + } +} + +static u32 xylonfb_get_reg_mem(void __iomem *base, unsigned int offset, + struct xylonfb_layer_data *ld) +{ + return *((unsigned long *)xylonfb_get_reg_mem_addr(base, offset, ld)); +} + +static void xylonfb_set_reg_mem(u32 value, void __iomem *base, + unsigned int offset, + struct xylonfb_layer_data *ld) +{ + unsigned long *reg_mem_addr = + (unsigned long *)xylonfb_get_reg_mem_addr(base, offset, ld); + *reg_mem_addr = value; + writel((*reg_mem_addr), (base + offset)); +} + +static irqreturn_t xylonfb_isr(int irq, void *dev_id) +{ + struct fb_info **afbi = dev_get_drvdata(dev_id); + struct fb_info *fbi = afbi[0]; + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + void __iomem *dev_base = data->dev_base; + u32 isr; + + XYLONFB_DBG(CORE, "%s", __func__); + + isr = readl(dev_base + LOGICVC_INT_STAT_ROFF); + if (isr & LOGICVC_INT_V_SYNC) { + writel(LOGICVC_INT_V_SYNC, dev_base + LOGICVC_INT_STAT_ROFF); + + data->vsync.count++; + + if (waitqueue_active(&data->vsync.wait)) + wake_up_interruptible(&data->vsync.wait); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int xylonfb_open(struct fb_info *fbi, int user) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + int ret; + bool enable = false; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (atomic_read(&ld->refcount) == 0) { + if (ld->flags & XYLONFB_FLAGS_ACTIVATE_NEXT_OPEN) { + ld->flags &= ~XYLONFB_FLAGS_ACTIVATE_NEXT_OPEN; + enable = true; + } else { + if (fbi->var.activate == FB_ACTIVATE_NOW) { + enable = true; + } else if (fbi->var.activate == FB_ACTIVATE_NXTOPEN) { + ld->flags |= XYLONFB_FLAGS_ACTIVATE_NEXT_OPEN; + return 0; + } else if (fbi->var.activate == FB_ACTIVATE_VBL) { + ret = xylonfb_vsync_wait(0, fbi); + if (ret > 0) + enable = true; + else + return ret; + } + } + + if (enable) { + xylonfb_logicvc_layer_enable(fbi, true); + atomic_inc(&data->refcount); + } + } + + atomic_inc(&ld->refcount); + + return 0; +} + +static int xylonfb_release(struct fb_info *fbi, int user) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (atomic_read(&ld->refcount) > 0) { + atomic_dec(&ld->refcount); + + if (atomic_read(&ld->refcount) == 0) { + xylonfb_logicvc_layer_enable(fbi, false); + atomic_dec(&data->refcount); + } + } + + return 0; +} + +static int xylonfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_layer_fix_data *fd = ld->fd; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (var->xres < LOGICVC_MIN_XRES) + var->xres = LOGICVC_MIN_XRES; + if (var->xres > LOGICVC_MAX_XRES) + var->xres = LOGICVC_MAX_XRES; + if (var->yres < LOGICVC_MIN_VRES) + var->yres = LOGICVC_MIN_VRES; + if (var->yres > LOGICVC_MAX_VRES) + var->yres = LOGICVC_MAX_VRES; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->xres_virtual > fd->width) + var->xres_virtual = fd->width; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + if (var->yres_virtual > fd->height) + var->yres_virtual = fd->height; + + /* YUV 4:2:2 layer type can only have even layer xoffset */ + if (fd->format == XYLONFB_FORMAT_YUYV) + var->xoffset &= ~((unsigned long) + 1); + + if ((var->xoffset + var->xres) >= var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres - 1; + if ((var->yoffset + var->yres) >= var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres - 1; + + if (var->bits_per_pixel != fbi->var.bits_per_pixel) { + if (var->bits_per_pixel == 24) + var->bits_per_pixel = 32; + else + var->bits_per_pixel = fbi->var.bits_per_pixel; + } + + var->grayscale = fbi->var.grayscale; + + var->transp.offset = fbi->var.transp.offset; + var->transp.length = fbi->var.transp.length; + var->transp.msb_right = fbi->var.transp.msb_right; + var->red.offset = fbi->var.red.offset; + var->red.length = fbi->var.red.length; + var->red.msb_right = fbi->var.red.msb_right; + var->green.offset = fbi->var.green.offset; + var->green.length = fbi->var.green.length; + var->green.msb_right = fbi->var.green.msb_right; + var->blue.offset = fbi->var.blue.offset; + var->blue.length = fbi->var.blue.length; + var->blue.msb_right = fbi->var.blue.msb_right; + var->height = fbi->var.height; + var->width = fbi->var.width; + var->sync = fbi->var.sync; + var->rotate = fbi->var.rotate; + + return 0; +} + +static int xylonfb_set_par(struct fb_info *fbi) +{ + struct device *dev = fbi->dev; + struct fb_info **afbi = NULL; + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + unsigned long f; + int i, bpp; + int ret = 0; + char vmode_opt[VMODE_NAME_SIZE]; + bool resolution_change, layer_on[LOGICVC_MAX_LAYERS]; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (data->flags & XYLONFB_FLAGS_VMODE_SET) + return 0; + + if ((fbi->var.xres == data->vm_active.vmode.xres) || + (fbi->var.yres == data->vm_active.vmode.yres)) + resolution_change = false; + else + resolution_change = true; + + if (resolution_change || (data->flags & XYLONFB_FLAGS_VMODE_INIT)) { + if (!(data->flags & XYLONFB_FLAGS_VMODE_INIT)) { + struct xylonfb_layer_data *ld; + + afbi = dev_get_drvdata(fbi->device); + for (i = 0; i < data->layers; i++) { + ld = afbi[i]->par; + if (ld->flags & XYLONFB_FLAGS_LAYER_ENABLED) + layer_on[i] = true; + else + layer_on[i] = false; + } + } + + xylonfb_disable_logicvc_output(fbi); + xylonfb_logicvc_disp_ctrl(fbi, false); + + if (!(data->flags & XYLONFB_FLAGS_VMODE_INIT)) { + data->vm_active.vmode.refresh = 60; + sprintf(vmode_opt, "%dx%d%s-%d@%d%s", + fbi->var.xres, fbi->var.yres, + data->vm_active.opts_cvt, + fbi->var.bits_per_pixel, + data->vm_active.vmode.refresh, + data->vm_active.opts_ext); + if (!strcmp(data->vm.name, vmode_opt)) { + data->vm_active = data->vm; + } else { + bpp = fbi->var.bits_per_pixel; + xylonfb_mode_option = vmode_opt; + ret = xylonfb_set_timings(fbi, bpp); + xylonfb_mode_option = NULL; + } + } + if (!ret) { + f = PICOS2KHZ(data->vm_active.vmode.pixclock); + if (data->flags & XYLONFB_FLAGS_PIXCLK_VALID) + if (xylonfb_hw_pixclk_set(&data->pdev->dev, + data->pixel_clock, f)) + dev_err(dev, + "failed set pixel clock\n"); + + xylonfb_fbi_update(fbi); + XYLONFB_DBG(INFO, "video mode: %dx%d%s-%d@%d%s\n", + fbi->var.xres, fbi->var.yres, + data->vm_active.opts_cvt, + fbi->var.bits_per_pixel, + data->vm_active.vmode.refresh, + data->vm_active.opts_ext); + } + + xylonfb_enable_logicvc_output(fbi); + xylonfb_logicvc_disp_ctrl(fbi, true); + + if (data->flags & XYLONFB_FLAGS_VMODE_INIT) + data->flags |= XYLONFB_FLAGS_VMODE_SET; + + if (!(data->flags & XYLONFB_FLAGS_VMODE_SET)) { + if (!afbi) { + xylonfb_logicvc_layer_enable(fbi, true); + return ret; + } + + for (i = 0; i < data->layers; i++) { + if (layer_on[i]) + xylonfb_logicvc_layer_enable(afbi[i], + true); + } + } + } + + return ret; +} + +static void xylonfb_set_color_hw_rgb2yuv(u16 t, u16 r, u16 g, u16 b, u32 *yuv, + struct xylonfb_layer_data *ld) +{ + struct xylonfb_data *data = ld->data; + u32 y, u, v; + + XYLONFB_DBG(INFO, "%s", __func__); + + y = ((data->coeff.cyr * (r & 0xFF)) + (data->coeff.cyg * (g & 0xFF)) + + (data->coeff.cyb * (b & 0xFF)) + data->coeff.cy) / + LOGICVC_YUV_NORM; + u = ((-data->coeff.cur * (r & 0xFF)) - (data->coeff.cug * (g & 0xFF)) + + (data->coeff.cub * (b & 0xFF)) + LOGICVC_COEFF_U) / + LOGICVC_YUV_NORM; + v = ((data->coeff.cvr * (r & 0xFF)) - (data->coeff.cvg * (g & 0xFF)) - + (data->coeff.cvb * (b & 0xFF)) + LOGICVC_COEFF_V) / + LOGICVC_YUV_NORM; + + *yuv = ((t & 0xFF) << 24) | (y << 16) | (u << 8) | v; +} + +static int xylonfb_set_color_hw(u16 *t, u16 *r, u16 *g, u16 *b, + int len, int id, struct fb_info *fbi) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_layer_fix_data *fd = ld->fd; + u32 pixel, pixel_clut; + u16 a = 0xFF; + int bpp, to, ro, go, bo; + + XYLONFB_DBG(INFO, "%s", __func__); + + bpp = fd->bpp; + + to = fbi->var.transp.offset; + ro = fbi->var.red.offset; + go = fbi->var.green.offset; + bo = fbi->var.blue.offset; + + switch (fbi->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + if ((id > (LOGICVC_CLUT_SIZE - 1)) || (len > LOGICVC_CLUT_SIZE)) + return -EINVAL; + + switch (fd->format_clut) { + case XYLONFB_FORMAT_CLUT_ARGB6565: + while (len > 0) { + if (t) + a = t[id]; + pixel_clut = ((((a & 0xFC) >> 2) << to) | + (((r[id] & 0xF8) >> 3) << ro) | + (((g[id] & 0xFC) >> 2) << go) | + (((b[id] & 0xF8) >> 3) << bo)); + writel(pixel_clut, ld->clut_base + + (id*LOGICVC_CLUT_REGISTER_SIZE)); + len--; + id++; + } + break; + case XYLONFB_FORMAT_CLUT_ARGB8888: + while (len > 0) { + if (t) + a = t[id]; + pixel_clut = (((a & 0xFF) << to) | + ((r[id] & 0xFF) << ro) | + ((g[id] & 0xFF) << go) | + ((b[id] & 0xFF) << bo)); + writel(pixel_clut, ld->clut_base + + (id*LOGICVC_CLUT_REGISTER_SIZE)); + len--; + id++; + } + break; + case XYLONFB_FORMAT_CLUT_AYUV8888: + while (len > 0) { + if (t) + a = t[id]; + xylonfb_set_color_hw_rgb2yuv(a, r[id], + g[id], b[id], + &pixel_clut, + ld); + writel(pixel_clut, ld->clut_base + + (id*LOGICVC_CLUT_REGISTER_SIZE)); + len--; + id++; + } + break; + } + break; + case FB_VISUAL_TRUECOLOR: + switch (fd->format) { + case XYLONFB_FORMAT_RGB332: + while (len > 0) { + pixel = ((((r[id] & 0xE0) >> 5) << ro) | + (((g[id] & 0xE0) >> 5) << go) | + (((b[id] & 0xC0) >> 6) << bo)); + ((u32 *)(fbi->pseudo_palette))[id] = + (pixel << 24) | (pixel << 16) | + (pixel << 8) | pixel; + len--; + id++; + } + break; + case XYLONFB_FORMAT_RGB565: + while (len > 0) { + pixel = ((((r[id] & 0xF8) >> 3) << ro) | + (((g[id] & 0xFC) >> 2) << go) | + (((b[id] & 0xF8) >> 3) << bo)); + ((u32 *)(fbi->pseudo_palette))[id] = + (pixel << 16) | pixel; + len--; + id++; + } + break; + case XYLONFB_FORMAT_XRGB8888: + while (len > 0) { + ((u32 *)(fbi->pseudo_palette))[id] = + (((r[id] & 0xFF) << ro) | + ((g[id] & 0xFF) << go) | + ((b[id] & 0xFF) << bo)); + len--; + id++; + } + break; + case XYLONFB_FORMAT_ARGB8888: + while (len > 0) { + if (t) + a = t[id]; + ((u32 *)(fbi->pseudo_palette))[id] = + (((a & 0xFF) << to) | + ((r[id] & 0xFF) << ro) | + ((g[id] & 0xFF) << go) | + ((b[id] & 0xFF) << bo)); + len--; + id++; + } + break; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int xylonfb_set_color(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *fbi) +{ + XYLONFB_DBG(INFO, "%s", __func__); + + return xylonfb_set_color_hw((u16 *)&transp, + (u16 *)&red, (u16 *)&green, (u16 *)&blue, + 1, regno, fbi); +} + +static int xylonfb_set_cmap(struct fb_cmap *cmap, struct fb_info *fbi) +{ + XYLONFB_DBG(INFO, "%s", __func__); + + return xylonfb_set_color_hw(cmap->transp, + cmap->red, cmap->green, cmap->blue, + cmap->len, cmap->start, fbi); +} + +static void xylonfb_set_pixels(struct fb_info *fbi, + struct xylonfb_layer_data *ld, + int bpp, unsigned int pix) +{ + u32 *vmem; + u8 *vmem8; + u16 *vmem16; + u32 *vmem32; + int x, y, pixoffset; + + XYLONFB_DBG(INFO, "%s", __func__); + + vmem = ld->fb_base + (fbi->var.xoffset * (fbi->var.bits_per_pixel/4)) + + (fbi->var.yoffset * fbi->var.xres_virtual * + (fbi->var.bits_per_pixel/4)); + + switch (bpp) { + case 8: + vmem8 = (u8 *)vmem; + for (y = fbi->var.yoffset; y < fbi->var.yres; y++) { + pixoffset = (y * fbi->var.xres_virtual); + for (x = fbi->var.xoffset; x < fbi->var.xres; x++) + vmem8[pixoffset + x] = pix; + } + break; + case 16: + vmem16 = (u16 *)vmem; + for (y = fbi->var.yoffset; y < fbi->var.yres; y++) { + pixoffset = (y * fbi->var.xres_virtual); + for (x = fbi->var.xoffset; x < fbi->var.xres; x++) + vmem16[pixoffset + x] = pix; + } + break; + case 32: + vmem32 = (u32 *)vmem; + for (y = fbi->var.yoffset; y < fbi->var.yres; y++) { + pixoffset = (y * fbi->var.xres_virtual); + for (x = fbi->var.xoffset; x < fbi->var.xres; x++) + vmem32[pixoffset + x] = pix; + } + break; + } +} + +static int xylonfb_blank(int blank_mode, struct fb_info *fbi) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + void __iomem *dev_base = data->dev_base; + u32 ctrl = data->reg_access.get_reg_val(dev_base, + LOGICVC_CTRL_ROFF, + ld); + u32 power = readl(dev_base + LOGICVC_POWER_CTRL_ROFF); + + XYLONFB_DBG(INFO, "%s", __func__); + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + XYLONFB_DBG(INFO, "FB_BLANK_UNBLANK"); + ctrl |= (LOGICVC_CTRL_HSYNC | LOGICVC_CTRL_VSYNC | + LOGICVC_CTRL_DATA_ENABLE); + data->reg_access.set_reg_val(ctrl, dev_base, + LOGICVC_CTRL_ROFF, ld); + + power |= LOGICVC_V_EN_MSK; + writel(power, dev_base + LOGICVC_POWER_CTRL_ROFF); + + mdelay(50); + break; + + case FB_BLANK_NORMAL: + XYLONFB_DBG(INFO, "FB_BLANK_NORMAL"); + switch (fd->format) { + case XYLONFB_FORMAT_C8: + xylonfb_set_color(0, 0, 0, 0, 0xFF, fbi); + xylonfb_set_pixels(fbi, ld, 8, 0); + break; + case XYLONFB_FORMAT_RGB332: + xylonfb_set_pixels(fbi, ld, 8, 0x00); + break; + case XYLONFB_FORMAT_RGB565: + xylonfb_set_pixels(fbi, ld, 16, 0x0000); + break; + case XYLONFB_FORMAT_XRGB8888: + case XYLONFB_FORMAT_ARGB8888: + xylonfb_set_pixels(fbi, ld, 32, 0xFF000000); + break; + } + break; + + case FB_BLANK_POWERDOWN: + XYLONFB_DBG(INFO, "FB_BLANK_POWERDOWN"); + ctrl &= ~(LOGICVC_CTRL_HSYNC | LOGICVC_CTRL_VSYNC | + LOGICVC_CTRL_DATA_ENABLE); + data->reg_access.set_reg_val(ctrl, dev_base, + LOGICVC_CTRL_ROFF, ld); + + power &= ~LOGICVC_V_EN_MSK; + writel(power, dev_base + LOGICVC_POWER_CTRL_ROFF); + + mdelay(50); + break; + + case FB_BLANK_VSYNC_SUSPEND: + XYLONFB_DBG(INFO, "FB_BLANK_VSYNC_SUSPEND"); + ctrl &= ~LOGICVC_CTRL_VSYNC; + data->reg_access.set_reg_val(ctrl, dev_base, + LOGICVC_CTRL_ROFF, ld); + break; + + case FB_BLANK_HSYNC_SUSPEND: + XYLONFB_DBG(INFO, "FB_BLANK_HSYNC_SUSPEND"); + ctrl &= ~LOGICVC_CTRL_HSYNC; + data->reg_access.set_reg_val(ctrl, dev_base, + LOGICVC_CTRL_ROFF, ld); + break; + } + + return 0; +} + +static int xylonfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (!(data->flags & XYLONFB_FLAGS_SIZE_POSITION)) + return -EINVAL; + + if ((fbi->var.xoffset == var->xoffset) && + (fbi->var.yoffset == var->yoffset)) + return 0; + + if (fbi->var.vmode & FB_VMODE_YWRAP) + return -EINVAL; + + if (((var->xoffset + fbi->var.xres) > fbi->var.xres_virtual) || + ((var->yoffset + fbi->var.yres) > fbi->var.yres_virtual)) + return -EINVAL; + + if (fd->format == XYLONFB_FORMAT_YUYV) + var->xoffset &= ~((unsigned long) + 1); + + fbi->var.xoffset = var->xoffset; + fbi->var.yoffset = var->yoffset; + + if (!(data->flags & XYLONFB_FLAGS_DYNAMIC_LAYER_ADDRESS)) { + data->reg_access.set_reg_val(var->xoffset, ld->base, + LOGICVC_LAYER_HOFF_ROFF, ld); + data->reg_access.set_reg_val(var->yoffset, ld->base, + LOGICVC_LAYER_VOFF_ROFF, ld); + } + data->reg_access.set_reg_val((fbi->var.xres - 1), ld->base, + LOGICVC_LAYER_HPOS_ROFF, ld); + data->reg_access.set_reg_val((fbi->var.yres - 1), ld->base, + LOGICVC_LAYER_VPOS_ROFF, ld); + if (data->flags & XYLONFB_FLAGS_DYNAMIC_LAYER_ADDRESS) { + ld->fb_pbase_active = ld->fb_pbase + + ((var->xoffset * (fd->bpp / 8)) + + (var->yoffset * fd->width * + (fd->bpp / 8))); + data->reg_access.set_reg_val(ld->fb_pbase_active, ld->base, + LOGICVC_LAYER_ADDR_ROFF, ld); + } + + return 0; +} + +static struct fb_ops xylonfb_ops = { + .owner = THIS_MODULE, + .fb_open = xylonfb_open, + .fb_release = xylonfb_release, + .fb_check_var = xylonfb_check_var, + .fb_set_par = xylonfb_set_par, + .fb_setcolreg = xylonfb_set_color, + .fb_setcmap = xylonfb_set_cmap, + .fb_blank = xylonfb_blank, + .fb_pan_display = xylonfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_ioctl = xylonfb_ioctl, +}; + +static int xylonfb_find_next_layer(struct xylonfb_data *data, int layers, + int id) +{ + dma_addr_t address = data->fd[id]->address; + dma_addr_t temp_address = ((unsigned long) - 1); + dma_addr_t loop_address; + int next = -1; + int i; + + XYLONFB_DBG(INFO, "%s", __func__); + + for (i = 0; i < layers; i++) { + loop_address = data->fd[i]->address; + if ((address < loop_address) && (loop_address < temp_address)) { + next = i; + temp_address = loop_address; + } + } + + return next; +} + +static void xylonfb_get_vmem_height(struct xylonfb_data *data, int layers, + int id) +{ + struct xylonfb_layer_fix_data *fd = data->fd[id]; + dma_addr_t vmem_start = fd->address; + dma_addr_t vmem_end; + int next; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (fd->address_range && (id < (layers - 1))) { + fd->height = fd->address_range / (fd->width * (fd->bpp / 8)); + return; + } + + vmem_start = fd->address; + + next = xylonfb_find_next_layer(data, layers, id); + if (next == -1) { + if (fd->address_range) { + fd->height = fd->address_range / + (fd->width * (fd->bpp / 8)); + } else { + if (fd->buffer_offset) + fd->height = fd->buffer_offset * + LOGICVC_MAX_LAYER_BUFFERS; + else + fd->height = XYLONFB_VRES_DEFAULT; + } + } else { + vmem_end = data->fd[next]->address; + fd->height = (vmem_end - vmem_start) / + (fd->width * (fd->bpp / 8)); + } + + if (fd->height > (LOGICVC_MAX_VRES * LOGICVC_MAX_LAYER_BUFFERS)) + fd->height = LOGICVC_MAX_VRES * LOGICVC_MAX_LAYER_BUFFERS; +} + +static void xylonfb_set_fbi_var_screeninfo(struct fb_var_screeninfo *var, + struct xylonfb_data *data) +{ + XYLONFB_DBG(INFO, "%s", __func__); + + var->xres = data->vm_active.vmode.xres; + var->yres = data->vm_active.vmode.yres; + var->pixclock = data->vm_active.vmode.pixclock; + var->left_margin = data->vm_active.vmode.left_margin; + var->right_margin = data->vm_active.vmode.right_margin; + var->upper_margin = data->vm_active.vmode.upper_margin; + var->lower_margin = data->vm_active.vmode.lower_margin; + var->hsync_len = data->vm_active.vmode.hsync_len; + var->vsync_len = data->vm_active.vmode.vsync_len; + var->sync = data->vm_active.vmode.sync; + var->vmode = data->vm_active.vmode.vmode; +} + +static void xylonfb_fbi_update(struct fb_info *fbi) +{ + struct fb_info **afbi = dev_get_drvdata(fbi->device); + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + int i, layers, id; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (!afbi) + return; + + layers = data->layers; + id = ld->fd->id; + + for (i = 0; i < layers; i++) { + if (i == id) + continue; + + xylonfb_set_fbi_var_screeninfo(&afbi[i]->var, data); + afbi[i]->monspecs = afbi[id]->monspecs; + } +} + +static void xylonfb_set_hw_specifics(struct fb_info *fbi, + struct xylonfb_layer_data *ld, + struct xylonfb_layer_fix_data *fd) +{ + struct xylonfb_data *data = ld->data; + + XYLONFB_DBG(INFO, "%s", __func__); + + fbi->fix.smem_start = ld->fb_pbase; + fbi->fix.smem_len = ld->fb_size; + if (fd->type == LOGICVC_LAYER_RGB) { + fbi->fix.type = FB_TYPE_PACKED_PIXELS; + } else if (fd->type == LOGICVC_LAYER_YUV) { + if (fd->format == XYLONFB_FORMAT_C8) + fbi->fix.type = FB_TYPE_PACKED_PIXELS; + else + fbi->fix.type = FB_TYPE_FOURCC; + } + if ((fd->type == LOGICVC_LAYER_YUV) || + (fd->type == LOGICVC_LAYER_ALPHA)) { + if (fd->format == XYLONFB_FORMAT_C8) + fbi->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + fbi->fix.visual = FB_VISUAL_FOURCC; + } else if (fd->format == XYLONFB_FORMAT_C8) { + fbi->fix.visual = FB_VISUAL_PSEUDOCOLOR; + } else { + /* + * Other logiCVC layer pixel formats: + * - 8 bpp: LAYER or PIXEL alpha + * It is not true color, RGB triplet is stored in 8 bits. + * - 16 bpp: + * LAYER alpha: RGB triplet is stored in 16 bits + * - 32 bpp: LAYER or PIXEL alpha + * True color, RGB triplet or ARGB quadriplet + * is stored in 32 bits. + */ + fbi->fix.visual = FB_VISUAL_TRUECOLOR; + } + + fbi->fix.xpanstep = 1; + fbi->fix.ypanstep = 1; + fbi->fix.ywrapstep = 0; + fbi->fix.line_length = fd->width * (fd->bpp / 8); + fbi->fix.mmio_start = ld->pbase; + fbi->fix.mmio_len = LOGICVC_LAYER_REGISTERS_RANGE; + fbi->fix.accel = FB_ACCEL_NONE; + + fbi->var.xres_virtual = fd->width; + fbi->var.yres_virtual = fd->height; + + fbi->var.bits_per_pixel = fd->bpp; + + switch (fd->type) { + case LOGICVC_LAYER_ALPHA: + fbi->var.grayscale = LOGICVC_PIX_FMT_ALPHA; + break; + case LOGICVC_LAYER_RGB: + fbi->var.grayscale = 0; + break; + case LOGICVC_LAYER_YUV: + if (fd->format == XYLONFB_FORMAT_C8) { + fbi->var.grayscale = LOGICVC_PIX_FMT_AYUV; + } else if (fd->format == XYLONFB_FORMAT_YUYV) { + if (fd->component_swap) + fbi->var.grayscale = V4L2_PIX_FMT_VYUY; + else + fbi->var.grayscale = V4L2_PIX_FMT_YVYU; + } else if (fd->format == XYLONFB_FORMAT_AYUV) { + if (fd->component_swap) + fbi->var.grayscale = LOGICVC_PIX_FMT_AVUY; + else + fbi->var.grayscale = LOGICVC_PIX_FMT_AYUV; + } + break; + } + + /* + * Set values according to logiCVC layer data width configuration: + * layer data width can be 1, 2, 4 bytes + */ + if (fd->transparency == LOGICVC_ALPHA_LAYER) { + fbi->var.transp.offset = 0; + fbi->var.transp.length = 0; + } + + switch (fd->format) { + case XYLONFB_FORMAT_A8: + fbi->var.transp.offset = 0; + fbi->var.transp.length = 8; + break; + case XYLONFB_FORMAT_C8: + switch (fd->format_clut) { + case XYLONFB_FORMAT_CLUT_ARGB6565: + fbi->var.transp.offset = 24; + fbi->var.transp.length = 6; + fbi->var.red.offset = 19; + fbi->var.red.length = 5; + fbi->var.green.offset = 10; + fbi->var.green.length = 6; + fbi->var.blue.offset = 3; + fbi->var.blue.length = 5; + break; + case XYLONFB_FORMAT_CLUT_ARGB8888: + fbi->var.transp.offset = 24; + fbi->var.transp.length = 8; + fbi->var.red.offset = 16; + fbi->var.red.length = 8; + fbi->var.green.offset = 8; + fbi->var.green.length = 8; + fbi->var.blue.offset = 0; + fbi->var.blue.length = 8; + break; + case XYLONFB_FORMAT_CLUT_AYUV8888: + fbi->var.transp.offset = 24; + fbi->var.transp.length = 8; + fbi->var.red.offset = 16; + fbi->var.red.length = 8; + fbi->var.green.offset = 8; + fbi->var.green.length = 8; + fbi->var.blue.offset = 0; + fbi->var.blue.length = 8; + break; + } + break; + case XYLONFB_FORMAT_RGB332: + fbi->var.red.offset = 5; + fbi->var.red.length = 3; + fbi->var.green.offset = 2; + fbi->var.green.length = 3; + fbi->var.blue.offset = 0; + fbi->var.blue.length = 2; + break; + case XYLONFB_FORMAT_RGB565: + fbi->var.red.offset = 11; + fbi->var.red.length = 5; + fbi->var.green.offset = 5; + fbi->var.green.length = 6; + fbi->var.blue.offset = 0; + fbi->var.blue.length = 5; + break; + case XYLONFB_FORMAT_ARGB8888: + case XYLONFB_FORMAT_AYUV: + fbi->var.transp.offset = 24; + fbi->var.transp.length = 8; + case XYLONFB_FORMAT_XRGB8888: + fbi->var.red.offset = 16; + fbi->var.red.length = 8; + fbi->var.green.offset = 8; + fbi->var.green.length = 8; + fbi->var.blue.offset = 0; + fbi->var.blue.length = 8; + break; + } + fbi->var.transp.msb_right = 0; + fbi->var.red.msb_right = 0; + fbi->var.green.msb_right = 0; + fbi->var.blue.msb_right = 0; + fbi->var.activate = FB_ACTIVATE_NOW; + fbi->var.height = 0; + fbi->var.width = 0; + fbi->var.sync = 0; + if (!(data->vm_active.ctrl & LOGICVC_CTRL_HSYNC_INVERT)) + fbi->var.sync |= FB_SYNC_HOR_HIGH_ACT; + if (!(data->vm_active.ctrl & LOGICVC_CTRL_VSYNC_INVERT)) + fbi->var.sync |= FB_SYNC_VERT_HIGH_ACT; + fbi->var.rotate = 0; +} + +static int xylonfb_set_timings(struct fb_info *fbi, int bpp) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + struct fb_var_screeninfo fb_var; + struct fb_videomode *vm; + int rc; + + XYLONFB_DBG(INFO, "%s", __func__); + + if ((data->flags & XYLONFB_FLAGS_VMODE_INIT) && + (data->flags & XYLONFB_FLAGS_VMODE_CUSTOM) && + memchr(data->vm.name, 'x', 10)) { + data->vm_active = data->vm; + vm = &data->vm.vmode; + data->vm_active.vmode.refresh = + DIV_ROUND_CLOSEST((PICOS2KHZ(vm->pixclock) * 1000), + ((vm->xres + vm->left_margin + + vm->right_margin + vm->hsync_len) * + (vm->yres + vm->upper_margin + + vm->lower_margin + vm->vsync_len))); + return 0; + } + + rc = fb_find_mode(&fb_var, fbi, xylonfb_mode_option, NULL, 0, + &xylonfb_vm.vmode, bpp); + + switch (rc) { + case 0: + dev_err(fbi->dev, "failed find video mode\n" + "using driver default mode %dx%dM-%d@%d\n", + xylonfb_vm.vmode.xres, + xylonfb_vm.vmode.yres, + bpp, + xylonfb_vm.vmode.refresh); + break; + case 1: + dev_dbg(fbi->dev, "video mode %s", xylonfb_mode_option); + break; + case 2: + dev_warn(fbi->dev, "video mode %s with ignored refresh rate\n", + xylonfb_mode_option); + break; + case 3: + dev_warn(fbi->dev, "default video mode %dx%dM-%d@%d\n", + xylonfb_vm.vmode.xres, + xylonfb_vm.vmode.yres, + bpp, + xylonfb_vm.vmode.refresh); + break; + case 4: + dev_warn(fbi->dev, "video mode fallback\n"); + break; + default: + break; + } + + data->vm_active.ctrl = data->vm.ctrl; + data->vm_active.vmode.xres = fb_var.xres; + data->vm_active.vmode.yres = fb_var.yres; + data->vm_active.vmode.pixclock = fb_var.pixclock; + data->vm_active.vmode.left_margin = fb_var.left_margin; + data->vm_active.vmode.right_margin = fb_var.right_margin; + data->vm_active.vmode.upper_margin = fb_var.upper_margin; + data->vm_active.vmode.lower_margin = fb_var.lower_margin; + data->vm_active.vmode.hsync_len = fb_var.hsync_len; + data->vm_active.vmode.vsync_len = fb_var.vsync_len; + data->vm_active.vmode.sync = fb_var.sync; + data->vm_active.vmode.vmode = fb_var.vmode; + data->vm_active.vmode.refresh = + DIV_ROUND_CLOSEST((PICOS2KHZ(fb_var.pixclock) * 1000), + ((fb_var.xres + fb_var.left_margin + + fb_var.right_margin + fb_var.hsync_len) * + (fb_var.yres + fb_var.upper_margin + + fb_var.lower_margin + fb_var.vsync_len))); + strcpy(data->vm_active.opts_cvt, data->vm.opts_cvt); + strcpy(data->vm_active.opts_ext, data->vm.opts_ext); + sprintf(data->vm_active.name, "%dx%d%s-%d@%d%s", + fb_var.xres, fb_var.yres, + data->vm_active.opts_cvt, + fb_var.bits_per_pixel, + data->vm_active.vmode.refresh, + data->vm_active.opts_ext); + + if (!memchr(data->vm.name, 'x', 10)) + data->vm = data->vm_active; + + return 0; +} + +static int xylonfb_register_fb(struct fb_info *fbi, + struct xylonfb_layer_data *ld, int id, + int *regfb) +{ + struct device *dev = fbi->dev; + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + int transp; + + XYLONFB_DBG(INFO, "%s", __func__); + + fbi->flags = FBINFO_DEFAULT; + fbi->screen_base = (char __iomem *)ld->fb_base; + fbi->screen_size = ld->fb_size; + fbi->pseudo_palette = kzalloc(sizeof(u32) * XYLONFB_PSEUDO_PALETTE_SIZE, + GFP_KERNEL); + fbi->fbops = &xylonfb_ops; + + sprintf(fbi->fix.id, "Xylon FB%d", id); + xylonfb_set_hw_specifics(fbi, ld, fd); + + if (!(data->flags & XYLONFB_FLAGS_VMODE_DEFAULT)) { + if (!xylonfb_set_timings(fbi, fbi->var.bits_per_pixel)) + data->flags |= XYLONFB_FLAGS_VMODE_DEFAULT; + else + dev_err(dev, "videomode not set\n"); + } + xylonfb_set_fbi_var_screeninfo(&fbi->var, data); + fbi->mode = &data->vm_active.vmode; + fbi->mode->name = data->vm_active.name; + + if (fd->transparency == LOGICVC_ALPHA_LAYER) + transp = 0; + else + transp = 1; + if (fb_alloc_cmap(&fbi->cmap, XYLONFB_PSEUDO_PALETTE_SIZE, transp)) + return -ENOMEM; + + /* + * After fb driver registration, values in struct fb_info + * must not be changed anywhere else in driver except in + * xylonfb_set_par() function + */ + *regfb = register_framebuffer(fbi); + if (*regfb) { + dev_err(dev, "failed register fb %d\n", id); + return -EINVAL; + } + + return 0; +} + +static void xylonfb_layer_initialize(struct xylonfb_layer_data *ld) +{ + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + u32 reg = ld->data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); + + XYLONFB_DBG(INFO, "%s", __func__); + + reg |= LOGICVC_LAYER_CTRL_COLOR_TRANSPARENCY_DISABLE; + if (fd->component_swap) + reg |= LOGICVC_LAYER_CTRL_PIXEL_FORMAT_ABGR; + ld->data->reg_access.set_reg_val(reg, ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); + + if (data->flags & XYLONFB_FLAGS_DYNAMIC_LAYER_ADDRESS) + data->reg_access.set_reg_val(ld->fb_pbase, ld->base, + LOGICVC_LAYER_ADDR_ROFF, + ld); +} + +static int xylonfb_vmem_init(struct xylonfb_layer_data *ld, int id, bool *mmap) +{ + struct xylonfb_data *data = ld->data; + struct xylonfb_layer_fix_data *fd = ld->fd; + struct device *dev = &data->pdev->dev; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (fd->address) { + ld->fb_pbase = fd->address; + + xylonfb_get_vmem_height(data, data->layers, id); + ld->fb_size = fd->width * (fd->bpp / 8) * fd->height; + + if (*mmap) { + ld->fb_base = (__force void *)ioremap_wc(ld->fb_pbase, + ld->fb_size); + if (!ld->fb_base) { + dev_err(dev, "failed map video memory\n"); + return -EINVAL; + } + } + } else { + if (fd->buffer_offset) + fd->height = fd->buffer_offset * + LOGICVC_MAX_LAYER_BUFFERS; + else + fd->height = XYLONFB_VRES_DEFAULT * + LOGICVC_MAX_LAYER_BUFFERS; + ld->fb_size = fd->width * (fd->bpp / 8) * fd->height; + + ld->fb_base = dma_alloc_coherent(&data->pdev->dev, + PAGE_ALIGN(ld->fb_size), + &ld->fb_pbase, GFP_KERNEL); + if (!ld->fb_base) { + dev_err(dev, "failed allocate video buffer ID%d\n", id); + return -ENOMEM; + } + + data->flags |= XYLONFB_FLAGS_DMA_BUFFER; + } + + ld->fb_pbase_active = ld->fb_pbase; + + *mmap = false; + + return 0; +} + +static void xylonfb_logicvc_disp_ctrl(struct fb_info *fbi, bool enable) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + void __iomem *dev_base = data->dev_base; + u32 val; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (enable) { + val = LOGICVC_EN_VDD_MSK; + writel(val, dev_base + LOGICVC_POWER_CTRL_ROFF); + mdelay(data->pwr_delay); + val |= LOGICVC_V_EN_MSK; + writel(val, dev_base + LOGICVC_POWER_CTRL_ROFF); + mdelay(data->sig_delay); + val |= LOGICVC_EN_BLIGHT_MSK; + writel(val, dev_base + LOGICVC_POWER_CTRL_ROFF); + } else { + writel(0, dev_base + LOGICVC_POWER_CTRL_ROFF); + } +} + +static void xylonfb_logicvc_layer_enable(struct fb_info *fbi, bool enable) +{ + struct xylonfb_layer_data *ld = fbi->par; + u32 reg; + + XYLONFB_DBG(INFO, "%s", __func__); + + reg = ld->data->reg_access.get_reg_val(ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); + + if (enable) { + reg |= LOGICVC_LAYER_CTRL_ENABLE; + ld->flags |= XYLONFB_FLAGS_LAYER_ENABLED; + } else { + reg &= ~LOGICVC_LAYER_CTRL_ENABLE; + ld->flags &= ~XYLONFB_FLAGS_LAYER_ENABLED; + } + + ld->data->reg_access.set_reg_val(reg, ld->base, + LOGICVC_LAYER_CTRL_ROFF, + ld); +} + +static void xylonfb_enable_logicvc_output(struct fb_info *fbi) +{ + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + void __iomem *dev_base = data->dev_base; + struct fb_videomode *vm = &data->vm_active.vmode; + + XYLONFB_DBG(INFO, "%s", __func__); + + writel(vm->right_margin - 1, dev_base + LOGICVC_HSYNC_FRONT_PORCH_ROFF); + writel(vm->hsync_len - 1, dev_base + LOGICVC_HSYNC_ROFF); + writel(vm->left_margin - 1, dev_base + LOGICVC_HSYNC_BACK_PORCH_ROFF); + writel(vm->xres - 1, dev_base + LOGICVC_HRES_ROFF); + writel(vm->lower_margin - 1, dev_base + LOGICVC_VSYNC_FRONT_PORCH_ROFF); + writel(vm->vsync_len - 1, dev_base + LOGICVC_VSYNC_ROFF); + writel(vm->upper_margin - 1, dev_base + LOGICVC_VSYNC_BACK_PORCH_ROFF); + writel(vm->yres - 1, dev_base + LOGICVC_VRES_ROFF); + data->reg_access.set_reg_val(data->vm_active.ctrl, dev_base, + LOGICVC_CTRL_ROFF, ld); + + if (data->flags & XYLONFB_FLAGS_BACKGROUND_LAYER_YUV) + data->reg_access.set_reg_val(LOGICVC_COLOR_YUV888_BLACK, + dev_base, + LOGICVC_BACKGROUND_COLOR_ROFF, + ld); + else + data->reg_access.set_reg_val(LOGICVC_COLOR_RGB_BLACK, + dev_base, + LOGICVC_BACKGROUND_COLOR_ROFF, + ld); + + writel(LOGICVC_DTYPE_REG_INIT, dev_base + LOGICVC_DTYPE_ROFF); + + XYLONFB_DBG(INFO, "logiCVC HW parameters:\n" \ + " Horizontal Front Porch: %d pixclks\n" \ + " Horizontal Sync: %d pixclks\n" \ + " Horizontal Back Porch: %d pixclks\n" \ + " Vertical Front Porch: %d pixclks\n" \ + " Vertical Sync: %d pixclks\n" \ + " Vertical Back Porch: %d pixclks\n" \ + " Pixel Clock: %d ps\n" \ + " Horizontal Resolution: %d pixels\n" \ + " Vertical Resolution: %d lines\n", \ + vm->right_margin, vm->hsync_len, vm->left_margin, + vm->lower_margin, vm->vsync_len, vm->upper_margin, + vm->pixclock, vm->xres, vm->yres); +} + +static void xylonfb_disable_logicvc_output(struct fb_info *fbi) +{ + struct fb_info **afbi = dev_get_drvdata(fbi->device); + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + int i; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (afbi) + for (i = 0; i < data->layers; i++) + xylonfb_logicvc_layer_enable(afbi[i], false); +} + +static void xylonfb_start(struct fb_info **afbi, int layers) +{ + struct fb_info *fbi = afbi[0]; + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + int i; + + XYLONFB_DBG(INFO, "%s", __func__); + + for (i = 0; i < layers; i++) { + ld = afbi[i]->par; + if (ld->flags & XYLONFB_FLAGS_LAYER_ENABLED) + continue; + + xylonfb_logicvc_layer_enable(afbi[i], false); + } + + if (data->flags & XYLONFB_FLAGS_VSYNC_IRQ) { + writel(LOGICVC_INT_V_SYNC, + data->dev_base + LOGICVC_INT_STAT_ROFF); + data->reg_access.set_reg_val(~LOGICVC_INT_V_SYNC, + data->dev_base, + LOGICVC_INT_MASK_ROFF, ld); + } + + for (i = 0; i < layers; i++) { + ld = afbi[i]->par; + XYLONFB_DBG(INFO, "logiCVC layer %d\n" \ + " Registers Base Address: 0x%lX\n" \ + " Layer Video Memory Address: 0x%lX\n" \ + " X resolution: %d\n" \ + " Y resolution: %d\n" \ + " X resolution (virtual): %d\n" \ + " Y resolution (virtual): %d\n" \ + " Line length (bytes): %d\n" \ + " Bits per Pixel: %d\n" \ + "\n", \ + i, + (unsigned long)ld->pbase, + (unsigned long)ld->fb_pbase, + afbi[i]->var.xres, + afbi[i]->var.yres, + afbi[i]->var.xres_virtual, + afbi[i]->var.yres_virtual, + afbi[i]->fix.line_length, + afbi[i]->var.bits_per_pixel); + } +} + +static void xylonfb_get_vmode_opts(struct xylonfb_data *data) +{ + char *s, *opt, *ext, *c; + + XYLONFB_DBG(INFO, "%s", __func__); + + s = data->vm.name; + opt = data->vm.opts_cvt; + ext = data->vm.opts_ext; + + data->vm.vmode.vmode = 0; + + c = strchr(s, 'M'); + if (c) + *opt++ = *c; + c = strchr(s, 'R'); + if (c) + *opt = *c; + c = strchr(s, 'i'); + if (c) { + *ext++ = *c; + data->vm.vmode.vmode |= FB_VMODE_INTERLACED; + } + c = strchr(s, 'm'); + if (c) + *ext = *c; +} + +static bool xylonfb_allow_console(struct xylonfb_layer_fix_data *fd) +{ + XYLONFB_DBG(INFO, "%s", __func__); + + switch (fd->format) { + case XYLONFB_FORMAT_C8: + case XYLONFB_FORMAT_RGB332: + case XYLONFB_FORMAT_RGB565: + case XYLONFB_FORMAT_XRGB8888: + case XYLONFB_FORMAT_ARGB8888: + return true; + default: + return false; + } +} + +int xylonfb_init_core(struct xylonfb_data *data) +{ + struct device *dev = &data->pdev->dev; + struct fb_info **afbi, *fbi; + struct xylonfb_layer_data *ld; + void __iomem *dev_base; + u32 ip_ver; + int i, ret, layers, console_layer; + int regfb[LOGICVC_MAX_LAYERS]; + size_t size; + unsigned short layer_base_off[] = { + (LOGICVC_LAYER_BASE_OFFSET + LOGICVC_LAYER_0_OFFSET), + (LOGICVC_LAYER_BASE_OFFSET + LOGICVC_LAYER_1_OFFSET), + (LOGICVC_LAYER_BASE_OFFSET + LOGICVC_LAYER_2_OFFSET), + (LOGICVC_LAYER_BASE_OFFSET + LOGICVC_LAYER_3_OFFSET), + (LOGICVC_LAYER_BASE_OFFSET + LOGICVC_LAYER_4_OFFSET) + }; + unsigned short clut_base_off[] = { + (LOGICVC_CLUT_BASE_OFFSET + LOGICVC_CLUT_L0_CLUT_0_OFFSET), + (LOGICVC_CLUT_BASE_OFFSET + LOGICVC_CLUT_L1_CLUT_0_OFFSET), + (LOGICVC_CLUT_BASE_OFFSET + LOGICVC_CLUT_L2_CLUT_0_OFFSET), + (LOGICVC_CLUT_BASE_OFFSET + LOGICVC_CLUT_L3_CLUT_0_OFFSET), + (LOGICVC_CLUT_BASE_OFFSET + LOGICVC_CLUT_L4_CLUT_0_OFFSET), + }; + bool memmap; + + XYLONFB_DBG(INFO, "%s", __func__); + + dev_base = devm_ioremap_resource(dev, &data->resource_mem); + if (IS_ERR(dev_base)) { + dev_err(dev, "failed ioremap mem resource\n"); + return PTR_ERR(dev_base); + } + data->dev_base = dev_base; + + data->irq = data->resource_irq.start; + ret = devm_request_irq(dev, data->irq, xylonfb_isr, IRQF_TRIGGER_HIGH, + XYLONFB_DEVICE_NAME, dev); + if (ret) + return ret; + + ip_ver = readl(dev_base + LOGICVC_IP_VERSION_ROFF); + data->major = (ip_ver >> LOGICVC_MAJOR_REVISION_SHIFT) & + LOGICVC_MAJOR_REVISION_MASK; + data->minor = (ip_ver >> LOGICVC_MINOR_REVISION_SHIFT) & + LOGICVC_MINOR_REVISION_MASK; + data->patch = ip_ver & LOGICVC_PATCH_LEVEL_MASK; + dev_info(dev, "logiCVC IP core %d.%02d.%c\n", + data->major, data->minor, ('a' + data->patch)); + + if (data->major >= 4) + data->flags |= XYLONFB_FLAGS_DYNAMIC_LAYER_ADDRESS; + + layers = data->layers; + if (layers == 0) { + dev_err(dev, "no available layers\n"); + return -ENODEV; + } + console_layer = data->console_layer; + if (console_layer >= layers) { + dev_err(dev, "invalid console layer ID\n"); + console_layer = 0; + } + + if (data->flags & XYLONFB_FLAGS_CHECK_CONSOLE_LAYER) { + if (!xylonfb_allow_console(data->fd[console_layer])) { + dev_err(dev, "invalid console layer format\n"); + return -EINVAL; + } + data->flags &= ~XYLONFB_FLAGS_CHECK_CONSOLE_LAYER; + } + + size = sizeof(struct fb_info *); + afbi = devm_kzalloc(dev, (size * layers), GFP_KERNEL); + if (!afbi) { + dev_err(dev, "failed allocate internal data\n"); + return -ENOMEM; + } + + if (data->flags & XYLONFB_FLAGS_READABLE_REGS) { + data->reg_access.get_reg_val = xylonfb_get_reg; + data->reg_access.set_reg_val = xylonfb_set_reg; + } else { + size = sizeof(struct xylonfb_registers); + data->reg_access.get_reg_val = xylonfb_get_reg_mem; + data->reg_access.set_reg_val = xylonfb_set_reg_mem; + } + + data->coeff.cyr = LOGICVC_COEFF_Y_R; + data->coeff.cyg = LOGICVC_COEFF_Y_G; + data->coeff.cyb = LOGICVC_COEFF_Y_B; + if (data->flags & XYLONFB_FLAGS_DISPLAY_INTERFACE_ITU656) { + data->coeff.cy = LOGICVC_COEFF_ITU656_Y; + data->coeff.cur = LOGICVC_COEFF_ITU656_U_R; + data->coeff.cug = LOGICVC_COEFF_ITU656_U_G; + data->coeff.cub = LOGICVC_COEFF_ITU656_U_B; + data->coeff.cvr = LOGICVC_COEFF_ITU656_V_R; + data->coeff.cvg = LOGICVC_COEFF_ITU656_V_G; + data->coeff.cvb = LOGICVC_COEFF_ITU656_V_B; + } else { + data->coeff.cy = LOGICVC_COEFF_Y; + data->coeff.cur = LOGICVC_COEFF_U_R; + data->coeff.cug = LOGICVC_COEFF_U_G; + data->coeff.cub = LOGICVC_COEFF_U_B; + data->coeff.cvr = LOGICVC_COEFF_V_R; + data->coeff.cvg = LOGICVC_COEFF_V_G; + data->coeff.cvb = LOGICVC_COEFF_V_B; + } + + atomic_set(&data->refcount, 0); + + data->flags |= XYLONFB_FLAGS_VMODE_INIT; + + sprintf(data->vm.name, "%s-%d@%d", + data->vm.name, data->fd[console_layer]->bpp, + data->vm.vmode.refresh); + if (!(data->flags & XYLONFB_FLAGS_VMODE_CUSTOM)) + xylonfb_mode_option = data->vm.name; + xylonfb_get_vmode_opts(data); + + if (data->pixel_clock) { + if (xylonfb_hw_pixclk_supported(dev, data->pixel_clock)) { + data->flags |= XYLONFB_FLAGS_PIXCLK_VALID; + } else { + dev_warn(dev, "pixel clock not supported\n"); + ret = -EPROBE_DEFER; + goto err_probe; + } + } else { + dev_info(dev, "external pixel clock\n"); + } + + ld = NULL; + + for (i = 0; i < layers; i++) + regfb[i] = -1; + memmap = true; + + /* + * /dev/fb0 will be default console layer, + * no matter how logiCVC layers are sorted in memory + */ + for (i = console_layer; i < layers; i++) { + if (regfb[i] != -1) + continue; + + size = sizeof(struct xylonfb_layer_data); + fbi = framebuffer_alloc(size, dev); + if (!fbi) { + dev_err(dev, "failed allocate fb info\n"); + ret = -ENOMEM; + goto err_probe; + } + fbi->dev = dev; + afbi[i] = fbi; + + ld = fbi->par; + ld->data = data; + ld->fd = data->fd[i]; + + atomic_set(&ld->refcount, 0); + + ld->pbase = data->resource_mem.start + layer_base_off[i]; + ld->base = dev_base + layer_base_off[i]; + ld->clut_base = dev_base + clut_base_off[i]; + + ret = xylonfb_vmem_init(ld, i, &memmap); + if (ret) + goto err_probe; + + xylonfb_layer_initialize(ld); + + ret = xylonfb_register_fb(fbi, ld, i, ®fb[i]); + if (ret) + goto err_probe; + + if (console_layer >= 0) + fbi->monspecs = afbi[console_layer]->monspecs; + + mutex_init(&ld->mutex); + + XYLONFB_DBG(INFO, "Layer parameters\n" \ + " ID %d\n" \ + " Width %d pixels\n" \ + " Height %d lines\n" \ + " Bits per pixel %d\n" \ + " Buffer size %d bytes\n", \ + ld->fd->id, + ld->fd->width, + ld->fd->height, + ld->fd->bpp, + ld->fb_size); + + if (console_layer > 0) { + i = -1; + console_layer = -1; + } + } + + if (ld) { + if (!(data->flags & XYLONFB_FLAGS_READABLE_REGS)) + data->reg_access.set_reg_val(0xFFFF, dev_base, + LOGICVC_INT_MASK_ROFF, + ld); + } else { + dev_warn(dev, "initialization not completed\n"); + } + + if (data->flags & XYLONFB_FLAGS_BACKGROUND_LAYER) + dev_info(dev, "BG layer: %s@%dbpp", + data->flags & XYLONFB_FLAGS_BACKGROUND_LAYER_RGB ? \ + "RGB" : "YUV", data->bg_layer_bpp); + + mutex_init(&data->irq_mutex); + init_waitqueue_head(&data->vsync.wait); + atomic_set(&data->refcount, 0); + + dev_set_drvdata(dev, (void *)afbi); + + data->flags &= ~(XYLONFB_FLAGS_VMODE_INIT | + XYLONFB_FLAGS_VMODE_DEFAULT | XYLONFB_FLAGS_VMODE_SET); + xylonfb_mode_option = NULL; + + xylonfb_start(afbi, layers); + + return 0; + +err_probe: + for (i = layers - 1; i >= 0; i--) { + fbi = afbi[i]; + if (!fbi) + continue; + ld = fbi->par; + if (regfb[i] == 0) + unregister_framebuffer(fbi); + else + regfb[i] = 0; + if (fbi->cmap.red) + fb_dealloc_cmap(&fbi->cmap); + if (ld) { + if (data->flags & XYLONFB_FLAGS_DMA_BUFFER) { + if (ld->fb_base) + dma_free_coherent(dev, + PAGE_ALIGN(ld->fb_size), + ld->fb_base, ld->fb_pbase); + } else { + if (ld->fb_base) + iounmap((void __iomem *)ld->fb_base); + } + kfree(fbi->pseudo_palette); + framebuffer_release(fbi); + } + } + + return ret; +} + +int xylonfb_deinit_core(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fb_info **afbi = dev_get_drvdata(dev); + struct fb_info *fbi = afbi[0]; + struct xylonfb_layer_data *ld = fbi->par; + struct xylonfb_data *data = ld->data; + int i; + + XYLONFB_DBG(INFO, "%s", __func__); + + if (atomic_read(&data->refcount) != 0) { + dev_err(dev, "driver in use\n"); + return -EINVAL; + } + + xylonfb_disable_logicvc_output(fbi); + + xylonfb_hw_pixclk_unload(data->pixel_clock); + + for (i = data->layers - 1; i >= 0; i--) { + fbi = afbi[i]; + ld = fbi->par; + + unregister_framebuffer(fbi); + fb_dealloc_cmap(&fbi->cmap); + if (data->flags & XYLONFB_FLAGS_DMA_BUFFER) { + dma_free_coherent(dev, + PAGE_ALIGN(ld->fb_size), + ld->fb_base, ld->fb_pbase); + } + kfree(fbi->pseudo_palette); + framebuffer_release(fbi); + } + + return 0; +} diff --git a/drivers/video/fbdev/xylon/xylonfb_core.h b/drivers/video/fbdev/xylon/xylonfb_core.h new file mode 100644 index 0000000..1a3dee8 --- /dev/null +++ b/drivers/video/fbdev/xylon/xylonfb_core.h @@ -0,0 +1,252 @@ +/* + * Xylon logiCVC frame buffer driver internal data structures + * + * 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. + */ + +#ifndef __XYLONFB_CORE_H__ +#define __XYLONFB_CORE_H__ + +#include +#include +#include + +#define XYLONFB_DRIVER_NAME "xylonfb" +#define XYLONFB_DEVICE_NAME "logicvc" +#define XYLONFB_DRIVER_DESCRIPTION "Xylon logiCVC frame buffer driver" +#define XYLONFB_DRIVER_VERSION "3.0" + +#define INFO 1 +#define CORE 2 +#define DEBUG_LEVEL CORE + +#ifdef DEBUG +#define XYLONFB_DBG(level, format, ...) \ + do { \ + if (level >= DEBUG_LEVEL) \ + pr_info(format "\n", ## __VA_ARGS__); \ + } while (0) +#else +#define XYLONFB_DBG(format, ...) do { } while (0) +#endif + +#define LOGICVC_MAX_LAYERS 5 + +/* Xylon FB driver flags */ +#define XYLONFB_FLAGS_READABLE_REGS (1 << 0) +#define XYLONFB_FLAGS_SIZE_POSITION (1 << 1) +#define XYLONFB_FLAGS_BACKGROUND_LAYER (1 << 2) +#define XYLONFB_FLAGS_BACKGROUND_LAYER_RGB (1 << 3) +#define XYLONFB_FLAGS_BACKGROUND_LAYER_YUV (1 << 4) +#define XYLONFB_FLAGS_DISPLAY_INTERFACE_ITU656 (1 << 5) +#define XYLONFB_FLAGS_DYNAMIC_LAYER_ADDRESS (1 << 6) +#define XYLONFB_FLAGS_CHECK_CONSOLE_LAYER (1 << 7) +#define XYLONFB_FLAGS_PIXCLK_VALID (1 << 8) +#define XYLONFB_FLAGS_DMA_BUFFER (1 << 9) +#define XYLONFB_FLAGS_VSYNC_IRQ (1 << 10) +#define XYLONFB_FLAGS_VMODE_CUSTOM (1 << 11) +#define XYLONFB_FLAGS_VMODE_INIT (1 << 12) +#define XYLONFB_FLAGS_VMODE_DEFAULT (1 << 13) +#define XYLONFB_FLAGS_VMODE_SET (1 << 14) +#define XYLONFB_FLAGS_LAYER_ENABLED (1 << 15) +#define XYLONFB_FLAGS_ACTIVATE_NEXT_OPEN (1 << 16) + +/* Xylon FB driver color formats */ +enum xylonfb_color_format { + XYLONFB_FORMAT_A8, + XYLONFB_FORMAT_C8, + XYLONFB_FORMAT_RGB332, + XYLONFB_FORMAT_RGB565, + XYLONFB_FORMAT_XRGB8888, + XYLONFB_FORMAT_ARGB8888, + XYLONFB_FORMAT_YUYV, + XYLONFB_FORMAT_AYUV, + XYLONFB_FORMAT_CLUT_ARGB6565, + XYLONFB_FORMAT_CLUT_ARGB8888, + XYLONFB_FORMAT_CLUT_AYUV8888 +}; + +struct xylonfb_layer_data; +struct xylonfb_data; + +#define VMODE_NAME_SIZE 21 +#define VMODE_OPTS_SIZE 3 + +struct xylonfb_vmode { + u32 ctrl; + struct fb_videomode vmode; + char name[VMODE_NAME_SIZE]; + char opts_cvt[VMODE_OPTS_SIZE]; + char opts_ext[VMODE_OPTS_SIZE]; +}; + +struct xylonfb_registers { + u32 ctrl; + u32 dtype; + u32 bg; + u32 unused[3]; + u32 int_mask; +}; + +union xylonfb_layer_reg_0 { + u32 addr; + u32 hoff; +}; + +union xylonfb_layer_reg_1 { + u32 unused; + u32 voff; +}; + +struct xylonfb_layer_registers { + union xylonfb_layer_reg_0 reg_0; + union xylonfb_layer_reg_1 reg_1; + u32 hpos; + u32 vpos; + u32 hsize; + u32 vsize; + u32 alpha; + u32 ctrl; + u32 transp; +}; + +struct xylonfb_register_access { + u32 (*get_reg_val)(void __iomem *dev_base, unsigned int offset, + struct xylonfb_layer_data *layer_data); + void (*set_reg_val)(u32 value, void __iomem *dev_base, + unsigned int offset, + struct xylonfb_layer_data *layer_data); +}; + +struct xylonfb_layer_fix_data { + unsigned int id; + u32 address; + u32 address_range; + u32 buffer_offset; + u32 bpp; + u32 width; + u32 height; + u32 format; + u32 format_clut; + u32 transparency; + u32 type; + bool component_swap; +}; + +struct xylonfb_layer_data { + struct mutex mutex; + + atomic_t refcount; + + struct xylonfb_data *data; + struct xylonfb_layer_fix_data *fd; + struct xylonfb_layer_registers regs; + + dma_addr_t pbase; + void __iomem *base; + void __iomem *clut_base; + + dma_addr_t fb_pbase; + void *fb_base; + u32 fb_size; + + dma_addr_t fb_pbase_active; + + u32 flags; +}; + +struct xylonfb_rgb2yuv_coeff { + s32 cy; + s32 cyr; + s32 cyg; + s32 cyb; + s32 cur; + s32 cug; + s32 cub; + s32 cvr; + s32 cvg; + s32 cvb; +}; + +struct xylonfb_sync { + wait_queue_head_t wait; + unsigned int count; +}; + +struct xylonfb_data { + struct platform_device *pdev; + + struct device_node *device; + struct device_node *pixel_clock; + + struct resource resource_mem; + struct resource resource_irq; + + void __iomem *dev_base; + + struct mutex irq_mutex; + + struct xylonfb_register_access reg_access; + struct xylonfb_sync vsync; + struct xylonfb_vmode vm; + struct xylonfb_vmode vm_active; + struct xylonfb_rgb2yuv_coeff coeff; + + struct xylonfb_layer_fix_data *fd[LOGICVC_MAX_LAYERS]; + struct xylonfb_registers regs; + + u32 bg_layer_bpp; + u32 console_layer; + u32 pixel_stride; + + atomic_t refcount; + + u32 flags; + int irq; + u8 layers; + /* + * Delay after applying display power and + * before applying display signals + */ + u32 pwr_delay; + /* + * Delay after applying display signal and + * before applying display backlight power supply + */ + u32 sig_delay; + /* IP version */ + u8 major; + u8 minor; + u8 patch; +}; + +/* Xylon FB video mode options */ +extern char *xylonfb_mode_option; + +/* Xylon FB core pixel clock interface functions */ +extern bool xylonfb_hw_pixclk_supported(struct device *dev, + struct device_node *dn); +extern void xylonfb_hw_pixclk_unload(struct device_node *dn); +extern int xylonfb_hw_pixclk_set(struct device *dev, struct device_node *dn, + unsigned long pixclk_khz); + +/* Xylon FB core V sync wait function */ +extern int xylonfb_vsync_wait(u32 crt, struct fb_info *fbi); + +/* Xylon FB core interface functions */ +extern int xylonfb_init_core(struct xylonfb_data *data); +extern int xylonfb_deinit_core(struct platform_device *pdev); +extern int xylonfb_ioctl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg); + +#endif /* __XYLONFB_CORE_H__ */