/* * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /*! * @defgroup Framebuffer_MX27 Framebuffer Driver for MX27. */ /*! * @file mx2fb.c * * @brief Frame buffer driver for MX27 ADS. * * @ingroup Framebuffer_MX27 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dd12832_oled.h" #ifdef CONFIG_PM static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state); static int mx2fb_resume(struct platform_device *pdev); #else #define mx2fb_suspend 0 #define mx2fb_resume 0 #endif #define MX2FB_TYPE_BG 0 #define MX2FB_TYPE_GW 1 #define floor8(a) (a&(~0x07)) //#define iceil8(a) (((int)((a+7)/8))*8) #define iceil8(a) ((int)((a & ~((int)0x03)) + 8)) extern void gpio_slcdc_active(void); extern void gpio_slcdc_inactive(void); static char *fb_mode; static int fb_enabled; static unsigned long default_bpp = 1; static unsigned char brightness = 255; static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list); static struct clk *slcdc_clk; /*! * @brief Structure containing the MX2 specific framebuffer parameters. */ struct mx2fb_par { int type; char *id; int registered; int blank; /* Tell if driver compiled with rotate option enabled */ int rotate; /* Contains displayed data in 1 byte / column (8 pixels) * fb data are stored as 1 byte / pixel * !! must be allocated with 128k alignment using dma_pool_create */ /* FrameBuffer memory map */ unsigned char* fb_vmem; size_t fb_len; dma_addr_t fb_pmem; /* Oled cgram memory map */ unsigned long cgram_cmd_vaddr; unsigned long cgram_cmd_paddr; unsigned long cgram_cmd_len; struct dma_pool *cgram_cmd_dma_pool; unsigned long cgram_data_vaddr; unsigned long cgram_data_paddr; unsigned long cgram_data_len; struct dma_pool *cgram_data_dma_pool; }; /* Framebuffer APIs */ /*static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); static int mx2fb_set_par(struct fb_info *info); static void _set_fix(struct fb_info *info);*/ /* Internal functions */ static int __init _init_fbinfo(struct fb_info *info, struct platform_device *pdev); static int __init _install_fb(struct fb_info *info, struct platform_device *pdev); static void __exit _uninstall_fb(struct fb_info *info); static int _map_video_memory(struct fb_info *info); static void _unmap_video_memory(struct fb_info *info); /*static void _enable_lcdc(struct fb_info *info); static void _disable_lcdc(struct fb_info *info); static void _update_slcdc(struct fb_info *info);*/ /* Oled display information */ struct fb_videomode mxcfb_modedb[] = { { /* 128x32 */ "Densitron DD12832", /* name */ 0, /* refresh */ 128, /* xres */ 32, /* yres */ 0, /* pixclock */ 0, /* left_margin */ 0, /* right_margin */ 0, /* upper_margin */ 0, /* lower_margin */ 0, /* hsync_len */ 0, /* vsync_len */ 0, /* sync */ 0, /* mode */ 0}, /* flag */ }; int mxcfb_modedb_sz = ARRAY_SIZE(mxcfb_modedb); struct mx2fb_par mx2fbp_bg = { .type = MX2FB_TYPE_BG, .id = "DISP0 BG", .registered = 0, #ifdef CONFIG_FB_MXC_DENSITRON_DD12832_ROTATE .rotate = 1, #else .rotate = 0, #endif }; /*! * @brief Framebuffer information structures. * There are up to 3 framebuffers: background, TVout, and graphic window. * If graphic window is configured, it must be the last framebuffer. */ static struct fb_info mx2fb_info = { .par = &mx2fbp_bg, }; /*! * Do a minimal setup of SLCDC to be able to send command to DD12832 */ static void slcdc_first_init(void) { unsigned long val; int i; unsigned long *pdata; /* Screen start address register */ __raw_writel(mx2fbp_bg.cgram_data_paddr, SLCDC_REG(SLCDC_DATABASEADDR)); __raw_writel(mx2fbp_bg.cgram_data_len, SLCDC_REG(SLCDC_DATABUFSIZE)); /* Copy command array to DMA area */ pdata = (unsigned long *)mx2fbp_bg.cgram_cmd_vaddr; for (i = 0; i < mx2fbp_bg.cgram_cmd_len; i++) { if ((i & 0x01) == 0) { /* If even offset, command must be left-aligned in * 32-bits memory space */ *pdata = (unsigned long)_ssd1305_pagecmd_array[i] << 16; } else { /* If odd offset, command must be right-aligned in * 32-bits memory space */ *pdata |= (unsigned long)_ssd1305_pagecmd_array[i]; /* When "right column" is filled, go to next address */ pdata++; } } /* Set Array of command for page addressing */ __raw_writel(mx2fbp_bg.cgram_cmd_paddr, SLCDC_REG(SLCDC_COMBASEADDR)); __raw_writel(mx2fbp_bg.cgram_cmd_len, SLCDC_REG(SLCDC_COMBUFSIZE)); /* Set number of command (words) that must be send to jump to a * specific page. Size of "word" is define by WORDDEFCOM flag in * LCDTRANSCONFIG register */ __raw_writel(PAGE_COMMAND_PACK_SIZE, SLCDC_REG(SLCDC_COMSTRINGSIZ)); /* Define DMA burst */ __raw_writel(0, SLCDC_REG(SLCDC_FIFOCONFIG)); /* Define number of column/segment in a page */ __raw_writel(OLED_WIDTH, SLCDC_REG(SLCDC_LCDCONFIG)); /* Set transfer configuration */ val = SLCDC_DATA_8BIT | SLCDC_COMMAND_8BIT | SLCDC_PARALLEL | SLCDC_WRITEDATA_8BIT | SLCDC_TRANS_LITLENDIAN_8BIT | SLCDC_CSPOL_LOW; __raw_writel(val, SLCDC_REG(SLCDC_LCDTRANSCONFIG)); /* Set control register */ val = SLCDC_MODE_COMMAND | SLCDC_IRQ_DISABLE | SLCDC_IRQ_FLAGS_MASK; __raw_writel(val, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); /* Set SLCDC clock divider = HCLK_SLCDC * val / 128 */ val = 40; __raw_writel(val, SLCDC_REG(SLCDC_LCDCLOCKCONFIG)); } /* * Send one command to oled via SLCDC */ static int _slcdc_sendcmd_single(unsigned char cmd) { while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK); __raw_writel((u32)cmd | WRITE_LCDCMD, SLCDC_REG(SLCDC_LCDWRITEDATA)); return 0; } /* * Send one data to oled via SLCDC */ static int _slcdc_senddata_single(unsigned char cmd) { while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK); __raw_writel((u32)cmd | WRITE_LCDDATA, SLCDC_REG(SLCDC_LCDWRITEDATA)); return 0; } /* * Set Start line for Pan function */ static void dd12832_set_start_line(unsigned char y) { _slcdc_sendcmd_single(SSD1305_CMD_ROWADDR | (y & 0x3F)); } /* * Set page address */ static void dd12832_set_yaddr(unsigned char y) { _slcdc_sendcmd_single(SSD1305_CMD_PAGEADDR | (y & 0x07)); } /* * Set segment address */ static void dd12832_set_xaddr(unsigned char x) { #ifndef CONFIG_FB_MXC_DENSITRON_DD12832_ROTATE _slcdc_sendcmd_single(SSD1305_CMD_HIGHCOLADDR | (x >> 4)); _slcdc_sendcmd_single(SSD1305_CMD_LOWCOLADDR | (x & 0x0F)); #else x += SSD1305_WIDTH - OLED_WIDTH; _slcdc_sendcmd_single(SSD1305_CMD_HIGHCOLADDR | (x >> 4)); _slcdc_sendcmd_single(SSD1305_CMD_LOWCOLADDR | (x & 0x0F)); #endif } /* * Modify contrast value */ static void dd12832_set_brightness(unsigned char level) { if (level == 0) { /* If level 0 asked, display must be turned off * because Display is still ON when Contrast * is set to 0 */ _slcdc_sendcmd_single(SSD1305_CMD_DISPLAY_POWER_OFF); } else { /* Apply new contrast */ _slcdc_sendcmd_single(SSD1305_CMD_BRIGHTNESS_MODE); _slcdc_sendcmd_single(level); /* Be sure that DISPLAY is ON */ _slcdc_sendcmd_single(SSD1305_CMD_DISPLAY_POWER_ON); } } /* * Clear internal RAM of controller */ static void dd12832_clear_lcd(void) { unsigned long status; /* Clear OLED RAM mirror buffer */ char *pdata = (char *)mx2fbp_bg.cgram_data_vaddr; memset(pdata, 0, mx2fbp_bg.cgram_data_len); /* Clear FrameBuffer mirror buffer */ pdata = (char __force *) mx2fbp_bg.fb_vmem; memset(pdata, 0, mx2fbp_bg.fb_len); dd12832_set_yaddr(0); dd12832_set_xaddr(0); while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK); status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status &= ~(SLCDC_SLCDCCTRLSTAT_AUTOMODE_MASK); status |= SLCDC_MODE_DATA | SLCDC_START_TRANSFERT; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); } /* * Send init sequence to display controller */ static int dd12832_init_controller(void) { /* Use SLCDC to send command block */ unsigned long i; for (i = 0; i < sizeof(_ssd1305_init_array); i++) { _slcdc_sendcmd_single(_ssd1305_init_array[i]); } dd12832_clear_lcd(); return 0; } /*! * @brief Enable LCD controller. * @param info framebuffer information pointer */ static void _enable_slcdc(struct fb_info *info) { if (!fb_enabled) { fb_enabled++; if (fb_mode) { unsigned long mode = 0; if (mode == 0) { dd12832_set_brightness(brightness); } } } } /*! * @brief Disable LCD controller. * @param info framebuffer information pointer */ static void _disable_slcdc(struct fb_info *info) { if (fb_enabled) { dd12832_set_brightness(0); fb_enabled = 0; } } /*! * @brief Update SLCDC registers * @param info framebuffer information pointer */ static void _update_slcdc(struct fb_info *info) { unsigned long base; unsigned long val; struct fb_var_screeninfo *var = &info->var; base = (var->yoffset * var->xres_virtual + var->xoffset); base += (unsigned long)info->screen_base; /* Set number of command (words) that must be send to jump to a * specific page. Size of "word" is define by WORDDEFCOM flag in * LCDTRANSCONFIG register */ __raw_writel(PAGE_COMMAND_PACK_SIZE, SLCDC_REG(SLCDC_COMSTRINGSIZ)); /* Define DMA burst */ __raw_writel(0, SLCDC_REG(SLCDC_FIFOCONFIG)); /* Define number of column/segment in a page */ __raw_writel(OLED_WIDTH, SLCDC_REG(SLCDC_LCDCONFIG)); /* Set transfer configuration */ val = SLCDC_DATA_8BIT | SLCDC_COMMAND_8BIT | SLCDC_PARALLEL | SLCDC_WRITEDATA_8BIT | SLCDC_TRANS_LITLENDIAN_8BIT | SLCDC_CSPOL_LOW; __raw_writel(val, SLCDC_REG(SLCDC_LCDTRANSCONFIG)); /* Set SLCDC clock divider = HCLK_SLCDC * val / 128 */ val = 60; __raw_writel(val, SLCDC_REG(SLCDC_LCDCLOCKCONFIG)); return; } /*! * @brief Blanks the display. * * @param blank_mode The blank mode we want. * @param info Frame buffer structure that represents a single frame buffer * * @return Negative errno on error, or zero on success. * * Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking * succeeded, != 0 if un-/blanking failed. * blank_mode == 2: suspend vsync * blank_mode == 3: suspend hsync * blank_mode == 4: powerdown */ static int mx2fb_blank(int blank_mode, struct fb_info *info) { struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; dev_dbg(info->device, "blank mode = %d\n", blank_mode); mx2fbp->blank = blank_mode; switch (blank_mode) { case FB_BLANK_POWERDOWN: case FB_BLANK_VSYNC_SUSPEND: case FB_BLANK_HSYNC_SUSPEND: case FB_BLANK_NORMAL: _disable_slcdc(info); break; case FB_BLANK_UNBLANK: _enable_slcdc(info); break; } return 0; } /* * here we start the process of spliting out the fb update into * individual blocks of pixels. we end up spliting into 64x64 blocks * and finally down to 64x8 pages. */ static void dd12832_cgram_update(struct fb_info *info, unsigned int dx, unsigned int dy, unsigned int w, unsigned int h) { struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; unsigned int startpage, endpage, startseg, endseg; unsigned int curpage, curseg; unsigned long status; int i; unsigned char myseg; char *cgram; unsigned char *ppixel; /* align the request first */ /* Get first line of the page where starting pixel is located */ startpage = floor8(dy) / 8; /* Get first line of the next page where last pixel to update * is located */ endpage = h + dy - 1; endpage = iceil8(endpage) / 8; /* First segment to update */ startseg = dx; /* Last segment to update */ endseg = dx + (w - 1); for (curpage = startpage; curpage < endpage; curpage++) { cgram = (char*)mx2fbp->cgram_data_vaddr; cgram += curpage * info->fix.line_length; cgram += startseg; for (curseg = startseg; curseg <= endseg; curseg++) { /* Get segment to update in video memory */ myseg = 0; ppixel = (unsigned char __force *) mx2fbp->fb_vmem; /* There is 8 lines per page */ ppixel += curpage * info->fix.line_length * 8; /* Points to upper pixel of the current segment */ ppixel += curseg; for (i = 0; i < 8; i++) { if (*ppixel != 0) { myseg |= 1 << i; } /* Jump to next line to get pixel below */ ppixel += info->fix.line_length; } *cgram = myseg; cgram++; } } dd12832_set_xaddr(0); dd12832_set_yaddr(0); while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK); status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status &= ~(SLCDC_SLCDCCTRLSTAT_AUTOMODE_MASK); status |= SLCDC_MODE_DATA | SLCDC_START_TRANSFERT; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); } static void mx2fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { sys_fillrect(info, rect); /* update the physical lcd */ dd12832_cgram_update(info, rect->dx, rect->dy, rect->width, rect->height); } static void mx2fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) { sys_copyarea(info, area); /* update the physical lcd */ dd12832_cgram_update(info, area->dx, area->dy, area->width, area->height); } static void mx2fb_imageblit(struct fb_info *info, const struct fb_image *image) { struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; char *ppixel; const char *pdata; int j; if (image->depth == 1) { /* Draw image */ pdata = image->data; ppixel = mx2fbp->fb_vmem; ppixel += image->dy * info->fix.line_length; ppixel += image->dx; for (j = 0; j < image->height; j++) { memcpy(ppixel, pdata, image->width); pdata += image->width; ppixel += info->fix.line_length; } /* update the physical lcd */ dd12832_cgram_update(info, image->dx, image->dy, image->width, image->height); } } /*! * @brief Pans the display. * * @param var Frame buffer variable screen structure * @param info Frame buffer structure that represents a single frame buffer * * @return Negative errno on error, or zero on success. * * Pan (or wrap, depending on the `vmode' field) the display using the * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values * don't fit, return -EINVAL. */ static int mx2fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { if (info->var.yoffset == var->yoffset) { return 0; /* No change, do nothing */ } if ((var->vmode & FB_VMODE_YWRAP) && (var->yoffset < OLED_WIDTH) && (info->var.yres <= OLED_WIDTH)) { dd12832_set_start_line(var->yoffset); info->var.yoffset = var->yoffset; return 0; } return -EINVAL; } static int mx2fb_sync(struct fb_info *info) { dd12832_cgram_update(info, 0, 0, info->fix.line_length, info->var.yres_virtual); return 0; } /*! * @brief Ioctl function to support customized ioctl operations. * * @param info Framebuffer structure that represents a single frame buffer * @param cmd The command number * @param arg Argument which depends on cmd * * @return Negative errno on error, or zero on success. */ static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { unsigned char level; switch (cmd) { case MX2FB_SET_BRIGHTNESS: if (copy_from_user((void *)&level, (void *)arg, sizeof(level))) return -EFAULT; brightness = level; dd12832_set_brightness(level); break; case MX2FB_FORCE_SYNC: mx2fb_sync(info); break; default: dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd); return -EINVAL; } return 0; } /*! * @brief Set fixed framebuffer parameters based on variable settings. * * @param info framebuffer information pointer * @return Negative errno on error, or zero on success. */ static void _set_fix(struct fb_info *info) { struct fb_fix_screeninfo *fix = &info->fix; struct fb_var_screeninfo *var = &info->var; struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; strncpy(fix->id, mx2fbp->id, strlen(mx2fbp->id)); if (var->bits_per_pixel < 8) fix->line_length = var->xres_virtual; else fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; fix->type = FB_TYPE_PACKED_PIXELS; fix->accel = FB_ACCEL_NONE; fix->visual = FB_VISUAL_MONO10; fix->xpanstep = 0; fix->ypanstep = 1; fix->ywrapstep = 0; } /*! * @brief Validates a var passed in. * * @param var Frame buffer variable screen structure * @param info Frame buffer structure that represents a single frame buffer * * @return Negative errno on error, or zero on success. * * Checks to see if the hardware supports the state requested by var passed * in. This function does not alter the hardware state! If the var passed in * is slightly off by what the hardware can support then we alter the var * PASSED in to what we can do. If the hardware doesn't support mode change * a -EINVAL will be returned by the upper layers. * */ static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { if (var->xres_virtual < var->xres) var->xres_virtual = var->xres; if (var->yres_virtual < var->yres) var->yres_virtual = var->yres; if (var->xoffset < 0) var->xoffset = 0; if (var->yoffset < 0) var->yoffset = 0; if (var->xoffset + info->var.xres > info->var.xres_virtual) var->xoffset = info->var.xres_virtual - info->var.xres; if (var->yoffset + info->var.yres > info->var.yres_virtual) var->yoffset = info->var.yres_virtual - info->var.yres; if (var->bits_per_pixel != default_bpp) var->bits_per_pixel = default_bpp; if (var->pixclock != 0) var->pixclock = 0; var->red.length = var->green.length = var->blue.length = 1; var->red.offset = var->green.offset = var->blue.offset = 0; var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0; var->transp.length = 0; var->transp.offset = 0; var->transp.msb_right = 0; var->height = -1; var->width = -1; var->grayscale = 0; /* Copy nonstd field to/from sync for fbset usage */ var->sync |= var->nonstd; var->nonstd |= var->sync; return 0; } /*! * @brief Alters the hardware state. * * @param info Frame buffer structure that represents a single frame buffer * * @return Zero on success others on failure * * Using the fb_var_screeninfo in fb_info we set the resolution of this * particular framebuffer. This function alters the fb_fix_screeninfo stored * in fb_info. It doesn't not alter var in fb_info since we are using that * data. This means we depend on the data in var inside fb_info to be * supported by the hardware. mx2fb_check_var is always called before * mx2fb_set_par to ensure this. */ static int mx2fb_set_par(struct fb_info *info) { unsigned long len; struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; _set_fix(info); len = info->var.yres_virtual * info->fix.line_length; if (len > info->fix.smem_len) { if (info->fix.smem_start) _unmap_video_memory(info); /* Memory allocation for framebuffer */ if (_map_video_memory(info)) { dev_err(info->device, "Unable to allocate fb memory\n"); return -ENOMEM; } } _update_slcdc(info); mx2fb_blank(mx2fbp->blank, info); return 0; } /* * this is the access path from userspace. they can seek and write to * the fb. it's inefficient for them to do anything less than 64*8 * writes since we update the lcd in each write() anyway. */ static ssize_t mx2fb_write(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos) { /* modded from epson 1355 */ unsigned long p; int err=-EINVAL; unsigned int fbmemlength, x, y, w, h, startpage, endpage; struct arcfb_par *par; unsigned int xres; p = *ppos; par = info->par; xres = info->var.xres; fbmemlength = info->fix.smem_len; if (p > fbmemlength) return -ENOSPC; err = 0; if ((count + p) > fbmemlength) { count = fbmemlength - p; err = -ENOSPC; } if (count) { char *base_addr; base_addr = (char __force *)info->screen_base; count -= copy_from_user(base_addr + p, buf, count); *ppos += count; err = -EFAULT; } /* Check how many page are affected */ startpage = floor8(p) / 8; endpage = iceil8((p + count)) / 8; x = p % info->fix.line_length; y = p / info->fix.line_length; w = count % info->fix.line_length; h = count / info->fix.line_length; if (startpage != (endpage - 1)) { /* If several page affected, update complete line * of all affected pages */ dd12832_cgram_update(info, 0, y, info->fix.line_length, h); } else { /* One page has been affected, only updates modified segment */ dd12832_cgram_update(info, x, y, w, h); } if (p+count > info->fix.line_length) { /* Several line must be updated */ } if (count) return count; return err; } /* fb_mmap * Map video memory in user space. We don't use the generic fb_mmap method * mainly * to allow the use of the TLB streaming flag (CCA=6) */ int mx2fb_mmap(struct fb_info *info /*fbi*/, struct vm_area_struct *vma) { struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; unsigned int len; unsigned long start=0, off; if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) { return -EINVAL; } start = mx2fbp->fb_pmem & PAGE_MASK; len = PAGE_ALIGN((start & ~PAGE_MASK) + mx2fbp->fb_len); off = vma->vm_pgoff << PAGE_SHIFT; if ((vma->vm_end - vma->vm_start + off) > len) { return -EINVAL; } off += start; vma->vm_pgoff = off >> PAGE_SHIFT; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); pgprot_val(vma->vm_page_prot) |= (6 << 9); vma->vm_flags |= VM_IO; if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) { return -EAGAIN; } return 0; } /*! * @brief Framebuffer file operations */ static struct fb_ops mx2fb_ops = { .owner = THIS_MODULE, //.fb_read = fb_sys_read, .fb_write = mx2fb_write, .fb_check_var = mx2fb_check_var, .fb_set_par = mx2fb_set_par, .fb_blank = mx2fb_blank, .fb_pan_display = mx2fb_pan_display, .fb_fillrect = mx2fb_fillrect, .fb_copyarea = mx2fb_copyarea, .fb_imageblit = mx2fb_imageblit, .fb_ioctl = mx2fb_ioctl, .fb_sync = mx2fb_sync, .fb_mmap = mx2fb_mmap, }; /*! * @brief Initialize framebuffer information structure. * * @param info framebuffer information pointer * @param pdev pointer to struct device * @return Negative errno on error, or zero on success. */ static int __init _init_fbinfo(struct fb_info *info, struct platform_device *pdev) { info->device = &pdev->dev; info->var.activate = FB_ACTIVATE_NOW; info->fbops = &mx2fb_ops; info->flags = FBINFO_FLAG_DEFAULT; info->pseudo_palette = NULL; return 0; } /*! * @brief Install framebuffer into the system. * * @param info framebuffer information pointer * @param pdev pointer to struct device * @return Negative errno on error, or zero on success. */ static int __init _install_fb(struct fb_info *info, struct platform_device *pdev) { struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; if (_init_fbinfo(info, pdev)) return -EINVAL; if (fb_mode == 0) fb_mode = pdev->dev.platform_data; if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb, mxcfb_modedb_sz, NULL, default_bpp)) { return -EBUSY; } /* Default Y virtual size is 2x panel size */ /* info->var.yres_virtual = info->var.yres << 1; */ if (mx2fbp->type == MX2FB_TYPE_GW) mx2fbp->blank = FB_BLANK_NORMAL; else mx2fbp->blank = FB_BLANK_UNBLANK; if (mx2fb_set_par(info)) { return -EINVAL; } if (register_framebuffer(info) < 0) { _unmap_video_memory(info); return -EINVAL; } mx2fbp->registered = 1; dev_info(info->device, "fb%d registered successfully on %s (rotate = %d).\n", info->node, fb_mode, mx2fbp->rotate); return 0; } /*! * @brief Uninstall framebuffer from the system. * * @param info framebuffer information pointer */ static void __exit _uninstall_fb(struct fb_info *info) { struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; if (!mx2fbp->registered) return; unregister_framebuffer(info); _unmap_video_memory(info); if (&info->cmap) fb_dealloc_cmap(&info->cmap); mx2fbp->registered = 0; } /*! * @brief Allocate memory for framebuffer. * * @param info framebuffer information pointer * @return Negative errno on error, or zero on success. */ static int _map_video_memory(struct fb_info *info) { unsigned long page; struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; mx2fbp->fb_len = info->fix.line_length * info->var.yres_virtual; mx2fbp->fb_vmem = dma_alloc_coherent(info->device, PAGE_ALIGN(mx2fbp->fb_len), &mx2fbp->fb_pmem, GFP_KERNEL); if (mx2fbp->fb_vmem == 0) { dev_err(info->device, "fail to allocate frambuffer (size: %dK))", mx2fbp->fb_len / 1024); return -ENOMEM; } info->screen_base = mx2fbp->fb_vmem; info->fix.smem_start = mx2fbp->fb_pmem; info->fix.smem_len = mx2fbp->fb_len; dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n", info->fix.smem_start, info->fix.smem_len); info->screen_size = info->fix.smem_len; /* Clear the screen */ memset((char *)mx2fbp->fb_vmem, 0x00, mx2fbp->fb_len); /* * Set page reserved so that mmap will work. This is necessary * since we'll be remapping normal memory. */ for (page = (unsigned long)mx2fbp->fb_vmem; page < PAGE_ALIGN((unsigned long)mx2fbp->fb_vmem + mx2fbp->fb_len); page += PAGE_SIZE) { #ifdef CONFIG_DMA_NONCOHERENT SetPageReserved(virt_to_page(CAC_ADDR(page))); #else SetPageReserved(virt_to_page(page)); #endif } return 0; } /*! * @brief Release memory for framebuffer. * @param info framebuffer information pointer */ static void _unmap_video_memory(struct fb_info *info) { struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par; if (mx2fbp->fb_vmem) { dma_free_noncoherent(info->device, mx2fbp->fb_len, mx2fbp->fb_vmem, mx2fbp->fb_pmem); } mx2fbp->fb_len = 0; mx2fbp->fb_pmem = 0; mx2fbp->fb_vmem = 0; info->screen_base = 0; info->fix.smem_start = 0; info->fix.smem_len = 0; /* Free DMA of SLCDC transfert */ dma_pool_free(mx2fbp->cgram_data_dma_pool, (void *)mx2fbp->cgram_data_vaddr, (dma_addr_t) mx2fbp->cgram_data_paddr); dma_pool_destroy(mx2fbp->cgram_data_dma_pool); mx2fbp->cgram_data_dma_pool = 0; mx2fbp->cgram_data_vaddr = 0; mx2fbp->cgram_data_paddr = 0; mx2fbp->cgram_data_len = 0; /* Free DMA of SLCDC transfert */ dma_pool_free(mx2fbp->cgram_cmd_dma_pool, (void *)mx2fbp->cgram_cmd_vaddr, (dma_addr_t) mx2fbp->cgram_cmd_paddr); dma_pool_destroy(mx2fbp->cgram_cmd_dma_pool); mx2fbp->cgram_cmd_dma_pool = 0; mx2fbp->cgram_cmd_vaddr = 0; mx2fbp->cgram_cmd_paddr = 0; mx2fbp->cgram_cmd_len = 0; } /* * @brief LCDC interrupt handler */ static irqreturn_t mx2fb_isr(int irq, void *dev_id) { struct fb_event event; unsigned long status; status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status &= SLCDC_IRQ_FLAGS_MASK; if (status & SLCDC_IRQ_FLAG) { printk("Oled xfer done : "); if (status == SLCDC_IRQ_FLAG) { printk("Successful\n"); } if (status & SLCDC_IRQ_TEA_FLAG) { printk("DMA error\n"); event.info = &mx2fb_info; atomic_notifier_call_chain(&mx2fb_notifier_list, FB_EVENT_MXC_DMA_ERROR, &event); } if (status & SLCDC_IRQ_UNDRFLOW_FLAG) { printk("Underflow occurs\n"); event.info = &mx2fb_info; atomic_notifier_call_chain(&mx2fb_notifier_list, FB_EVENT_MXC_UNDERFLOW, &event); } } /* Write 1 to clear the status */ status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status |= SLCDC_IRQ_FLAGS_MASK; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); return IRQ_HANDLED; } /*! * @brief Config and request LCDC interrupt */ static void _request_irq(void) { unsigned long status; unsigned long flags; /* Write 1 to clear the status */ status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status |= SLCDC_IRQ_FLAGS_MASK; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); if (request_irq(INT_SLCDC, mx2fb_isr, 0, "SLCDC", 0)) pr_info("Request LCDC IRQ failed.\n"); else { spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); /* Enable interrupt in case client has registered */ //if (mx2fb_notifier_list.head != NULL) { //unsigned long status; //unsigned long ints = MX2FB_INT_EOF; //ints |= MX2FB_INT_GW_EOF; /* Enable interrupt in case client has registered */ //if (mx2fb_notifier_list.head != NULL) { /* Write 1 to clear the status */ status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status |= SLCDC_IRQ_FLAGS_MASK; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); /* Enable SLCDC interrupt */ status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status |= SLCDC_IRQ_ENABLE; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); //} //} spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); } } /*! * @brief Free LCDC interrupt handler */ static void _free_irq(void) { unsigned long status; status =__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); /* Write 1 to clear the status */ status |= SLCDC_IRQ_FLAGS_MASK; /* Disable all LCDC interrupt */ status &= ~SLCDC_IRQ_ENABLE; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); free_irq(INT_SLCDC, 0); } /*! * @brief Register a client notifier * @param nb notifier block to callback on events */ int mx2fb_register_client(struct notifier_block *nb) { unsigned long flags, status; int ret; ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb); spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); /* Enable interrupt in case client has registered */ if (mx2fb_notifier_list.head != NULL) { /* Write 1 to clear the status */ status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status |= SLCDC_IRQ_FLAGS_MASK; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); /* Enable SLCDC interrupt */ status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status |= SLCDC_IRQ_ENABLE; __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); } spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); return ret; } /*! * @brief Unregister a client notifier * @param nb notifier block to callback on events */ int mx2fb_unregister_client(struct notifier_block *nb) { unsigned long flags, status; int ret; ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb); spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); /* Mask interrupt in case no client registered */ if (mx2fb_notifier_list.head == NULL) { /* Enable SLCDC interrupt */ status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); status &= ~(SLCDC_IRQ_ENABLE); __raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT)); } spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); return ret; } #ifdef CONFIG_PM /* * Power management hooks. Note that we won't be called from IRQ context, * unlike the blank functions above, so we may sleep. */ /*! * @brief Suspends the framebuffer and blanks the screen. * Power management support */ #ifdef CONFIG_FB_MXC_EPSON_L4F0024 static int mx2fb_spi_suspend(struct spi_device *spi, pm_message_t state) { #else static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state) { #endif _disable_slcdc(&mx2fb_info); return 0; } /*! * @brief Resumes the framebuffer and unblanks the screen. * Power management support */ #ifdef CONFIG_FB_MXC_EPSON_L4F0024 static int mx2fb_spi_resume(struct spi_device *spi) #else static int mx2fb_resume(struct platform_device *pdev) #endif { _enable_slcdc(&mx2fb_info); return 0; } #endif /* CONFIG_PM */ /*! * @brief Probe routine for the framebuffer driver. It is called during the * driver binding process. * * @return Appropriate error code to the kernel common code */ static int mx2fb_probe(struct platform_device *pdev) { int ret; slcdc_clk = clk_get(&pdev->dev, "slcdc_clk"); clk_enable(slcdc_clk); gpio_slcdc_active(); /* Memory allocation of display data for DMA transfert */ /* convert 1 byte / pixel length to 1 byte / column (8 pixels) */ mx2fbp_bg.cgram_data_len = OLED_WIDTH*OLED_HEIGHT / 8; /* Allocate memory for CGRAM mirror with 128k boundary * (cf note SLCDC in Ch. 44.2 of i.MX27 RM) aligned on 1 byte */ mx2fbp_bg.cgram_data_dma_pool = dma_pool_create("SLCDC_DMA_DATA", &pdev->dev, mx2fbp_bg.cgram_data_len, 1, 128*1024); if (mx2fbp_bg.cgram_data_dma_pool == NULL) { dev_err(&pdev->dev, "Unable to allocated DMA Pool.\n"); return -ENOMEM; } mx2fbp_bg.cgram_data_vaddr = (unsigned long)dma_pool_alloc(mx2fbp_bg.cgram_data_dma_pool, GFP_DMA | GFP_KERNEL, (dma_addr_t *) &mx2fbp_bg.cgram_data_paddr); if ((void *)mx2fbp_bg.cgram_data_vaddr == NULL) { dev_err(&pdev->dev, "Unable to allocated DMA memory.\n"); return -ENOMEM; } /* Memory allocation of display commands for DMA transfert */ /* a page address on LCD is defined by 3 three commands. * Each command must be joined with a byte containing the state * of RS pin to apply (cf Fig 44-5 from SLCDC chapter in i.MX27 RM). * Array must be defined as unsigned short */ mx2fbp_bg.cgram_cmd_len = sizeof(_ssd1305_pagecmd_array)/sizeof(_ssd1305_pagecmd_array[0]); /* Allocate memory for CGRAM mirror with 128k boundary * (cf note SLCDC in Ch. 44.2 of i.MX27 RM) aligned on 2 bytes (array of shorts) */ mx2fbp_bg.cgram_cmd_dma_pool = dma_pool_create("SLCDC_DMA_CMD", &pdev->dev, mx2fbp_bg.cgram_cmd_len/(sizeof(int)/sizeof(_ssd1305_pagecmd_array[0])), 2, 128*1024); if (mx2fbp_bg.cgram_cmd_dma_pool == NULL) { dev_err(&pdev->dev, "Unable to allocated DMA Pool.\n"); return -ENOMEM; } mx2fbp_bg.cgram_cmd_vaddr = (unsigned long)dma_pool_alloc(mx2fbp_bg.cgram_cmd_dma_pool, GFP_DMA | GFP_KERNEL, (dma_addr_t *) &mx2fbp_bg.cgram_cmd_paddr); if ((void *)mx2fbp_bg.cgram_cmd_vaddr == NULL) { dev_err(&pdev->dev, "Unable to allocated DMA memory.\n"); return -ENOMEM; } slcdc_first_init(); dd12832_init_controller(); ret = _install_fb(&mx2fb_info, pdev); if (ret) { dev_err(&pdev->dev, "Failed to register framebuffer\n"); return ret; } //_request_irq(); return 0; } /*! * @brief This structure contains pointers to the power management * callback functions. */ static struct platform_driver mx2fb_driver = { .driver = { .name = "mxc_sdc_fb", .owner = THIS_MODULE, .bus = &platform_bus_type, }, .probe = mx2fb_probe, .suspend = mx2fb_suspend, .resume = mx2fb_resume, }; /*! * @brief Initialization */ int __init mx2fb_init(void) { int ret = 0; ret = platform_driver_register(&mx2fb_driver); return ret; } /*! * @brief Cleanup */ void __exit mx2fb_exit(void) { _free_irq(); _uninstall_fb(&mx2fb_info); platform_driver_unregister(&mx2fb_driver); } /* Modularization */ module_init(mx2fb_init); module_exit(mx2fb_exit); EXPORT_SYMBOL(mx2_gw_set); EXPORT_SYMBOL(mx2fb_register_client); EXPORT_SYMBOL(mx2fb_unregister_client); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("MX2 framebuffer driver"); MODULE_LICENSE("GPL");