Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Sascha Hauer | 2847 | 57.09% | 16 | 21.62% |
Markus Pargmann | 489 | 9.81% | 1 | 1.35% |
Alexander Shiyan | 473 | 9.48% | 5 | 6.76% |
Juergen Beisert | 313 | 6.28% | 1 | 1.35% |
Dario Binacchi | 284 | 5.69% | 7 | 9.46% |
Uwe Kleine-König | 164 | 3.29% | 6 | 8.11% |
Shawn Guo | 119 | 2.39% | 1 | 1.35% |
Martin Kaiser | 80 | 1.60% | 4 | 5.41% |
Fabio Estevam | 53 | 1.06% | 3 | 4.05% |
Russell King | 39 | 0.78% | 3 | 4.05% |
Eric Bénard | 29 | 0.58% | 2 | 2.70% |
Pavel Pisa | 21 | 0.42% | 2 | 2.70% |
Yangtao Li | 17 | 0.34% | 3 | 4.05% |
Denis Carikli | 12 | 0.24% | 1 | 1.35% |
Lv Ruyi | 10 | 0.20% | 1 | 1.35% |
Yang Yingliang | 9 | 0.18% | 1 | 1.35% |
Luis R. Rodriguez | 3 | 0.06% | 1 | 1.35% |
Linus Torvalds | 3 | 0.06% | 1 | 1.35% |
Peter Senna Tschudin | 3 | 0.06% | 1 | 1.35% |
Bartlomiej Zolnierkiewicz | 3 | 0.06% | 1 | 1.35% |
Krzysztof Kozlowski | 2 | 0.04% | 2 | 2.70% |
Thomas Zimmermann | 2 | 0.04% | 1 | 1.35% |
Gwenhael Goavec-Merou | 2 | 0.04% | 1 | 1.35% |
Kees Cook | 2 | 0.04% | 1 | 1.35% |
Sachin Kamat | 1 | 0.02% | 1 | 1.35% |
Jani Nikula | 1 | 0.02% | 1 | 1.35% |
Wei Yongjun | 1 | 0.02% | 1 | 1.35% |
Antonino A. Daplas | 1 | 0.02% | 1 | 1.35% |
Wolfram Sang | 1 | 0.02% | 1 | 1.35% |
Sanjeev Sharma | 1 | 0.02% | 1 | 1.35% |
Christophe Jaillet | 1 | 0.02% | 1 | 1.35% |
Julia Lawall | 1 | 0.02% | 1 | 1.35% |
Total | 4987 | 74 |
// SPDX-License-Identifier: GPL-2.0-only /* * Freescale i.MX Frame Buffer device driver * * Copyright (C) 2004 Sascha Hauer, Pengutronix * Based on acornfb.c Copyright (C) Russell King. * * Please direct your questions and comments on this driver to the following * email address: * * linux-arm-kernel@lists.arm.linux.org.uk */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/string.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/fb.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/ioport.h> #include <linux/cpufreq.h> #include <linux/clk.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> #include <linux/io.h> #include <linux/lcd.h> #include <linux/math64.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/bitfield.h> #include <linux/regulator/consumer.h> #include <video/of_display_timing.h> #include <video/of_videomode.h> #include <video/videomode.h> struct imx_fb_videomode { struct fb_videomode mode; u32 pcr; bool aus_mode; unsigned char bpp; }; /* * Complain if VAR is out of range. */ #define DEBUG_VAR 1 #define DRIVER_NAME "imx-fb" #define LCDC_SSA 0x00 #define LCDC_SIZE 0x04 #define SIZE_XMAX_MASK GENMASK(25, 20) #define YMAX_MASK_IMX1 GENMASK(8, 0) #define YMAX_MASK_IMX21 GENMASK(9, 0) #define LCDC_VPW 0x08 #define VPW_VPW_MASK GENMASK(9, 0) #define LCDC_CPOS 0x0C #define CPOS_CC1 BIT(31) #define CPOS_CC0 BIT(30) #define CPOS_OP BIT(28) #define CPOS_CXP_MASK GENMASK(25, 16) #define LCDC_LCWHB 0x10 #define LCWHB_BK_EN BIT(31) #define LCWHB_CW_MASK GENMASK(28, 24) #define LCWHB_CH_MASK GENMASK(20, 16) #define LCWHB_BD_MASK GENMASK(7, 0) #define LCDC_LCHCC 0x14 #define LCDC_PCR 0x18 #define PCR_TFT BIT(31) #define PCR_COLOR BIT(30) #define PCR_BPIX_MASK GENMASK(27, 25) #define PCR_BPIX_8 3 #define PCR_BPIX_12 4 #define PCR_BPIX_16 5 #define PCR_BPIX_18 6 #define PCR_PCD_MASK GENMASK(5, 0) #define LCDC_HCR 0x1C #define HCR_H_WIDTH_MASK GENMASK(31, 26) #define HCR_H_WAIT_1_MASK GENMASK(15, 8) #define HCR_H_WAIT_2_MASK GENMASK(7, 0) #define LCDC_VCR 0x20 #define VCR_V_WIDTH_MASK GENMASK(31, 26) #define VCR_V_WAIT_1_MASK GENMASK(15, 8) #define VCR_V_WAIT_2_MASK GENMASK(7, 0) #define LCDC_POS 0x24 #define POS_POS_MASK GENMASK(4, 0) #define LCDC_LSCR1 0x28 /* bit fields in imxfb.h */ #define LCDC_PWMR 0x2C /* bit fields in imxfb.h */ #define LCDC_DMACR 0x30 /* bit fields in imxfb.h */ #define LCDC_RMCR 0x34 #define RMCR_LCDC_EN_MX1 BIT(1) #define RMCR_SELF_REF BIT(0) #define LCDC_LCDICR 0x38 #define LCDICR_INT_SYN BIT(2) #define LCDICR_INT_CON BIT(0) #define LCDC_LCDISR 0x40 #define LCDISR_UDR_ERR BIT(3) #define LCDISR_ERR_RES BIT(2) #define LCDISR_EOF BIT(1) #define LCDISR_BOF BIT(0) #define IMXFB_LSCR1_DEFAULT 0x00120300 #define LCDC_LAUSCR 0x80 #define LAUSCR_AUS_MODE BIT(31) /* Used fb-mode. Can be set on kernel command line, therefore file-static. */ static const char *fb_mode; /* * These are the bitfields for each * display depth that we support. */ struct imxfb_rgb { struct fb_bitfield red; struct fb_bitfield green; struct fb_bitfield blue; struct fb_bitfield transp; }; enum imxfb_type { IMX1_FB, IMX21_FB, }; enum imxfb_panel_type { PANEL_TYPE_MONOCHROME, PANEL_TYPE_CSTN, PANEL_TYPE_TFT, }; struct imxfb_info { struct platform_device *pdev; void __iomem *regs; struct clk *clk_ipg; struct clk *clk_ahb; struct clk *clk_per; enum imxfb_type devtype; enum imxfb_panel_type panel_type; bool enabled; /* * These are the addresses we mapped * the framebuffer memory region to. */ dma_addr_t map_dma; u_int map_size; u_int palette_size; dma_addr_t dbar1; dma_addr_t dbar2; u_int pcr; u_int lauscr; u_int pwmr; u_int lscr1; u_int dmacr; bool cmap_inverse; bool cmap_static; struct imx_fb_videomode *mode; int num_modes; struct regulator *lcd_pwr; int lcd_pwr_enabled; }; static const struct platform_device_id imxfb_devtype[] = { { .name = "imx1-fb", .driver_data = IMX1_FB, }, { .name = "imx21-fb", .driver_data = IMX21_FB, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, imxfb_devtype); static const struct of_device_id imxfb_of_dev_id[] = { { .compatible = "fsl,imx1-fb", .data = &imxfb_devtype[IMX1_FB], }, { .compatible = "fsl,imx21-fb", .data = &imxfb_devtype[IMX21_FB], }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imxfb_of_dev_id); static inline int is_imx1_fb(struct imxfb_info *fbi) { return fbi->devtype == IMX1_FB; } #define IMX_NAME "IMX" /* * Minimum X and Y resolutions */ #define MIN_XRES 64 #define MIN_YRES 64 /* Actually this really is 18bit support, the lowest 2 bits of each colour * are unused in hardware. We claim to have 24bit support to make software * like X work, which does not support 18bit. */ static struct imxfb_rgb def_rgb_18 = { .red = {.offset = 16, .length = 8,}, .green = {.offset = 8, .length = 8,}, .blue = {.offset = 0, .length = 8,}, .transp = {.offset = 0, .length = 0,}, }; static struct imxfb_rgb def_rgb_16_tft = { .red = {.offset = 11, .length = 5,}, .green = {.offset = 5, .length = 6,}, .blue = {.offset = 0, .length = 5,}, .transp = {.offset = 0, .length = 0,}, }; static struct imxfb_rgb def_rgb_16_stn = { .red = {.offset = 8, .length = 4,}, .green = {.offset = 4, .length = 4,}, .blue = {.offset = 0, .length = 4,}, .transp = {.offset = 0, .length = 0,}, }; static struct imxfb_rgb def_rgb_8 = { .red = {.offset = 0, .length = 8,}, .green = {.offset = 0, .length = 8,}, .blue = {.offset = 0, .length = 8,}, .transp = {.offset = 0, .length = 0,}, }; static int imxfb_activate_var(struct fb_var_screeninfo *var, struct fb_info *info); static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) { chan &= 0xffff; chan >>= 16 - bf->length; return chan << bf->offset; } static int imxfb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue, u_int trans, struct fb_info *info) { struct imxfb_info *fbi = info->par; u_int val, ret = 1; #define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) if (regno < fbi->palette_size) { val = (CNVT_TOHW(red, 4) << 8) | (CNVT_TOHW(green, 4) << 4) | CNVT_TOHW(blue, 4); writel(val, fbi->regs + 0x800 + (regno << 2)); ret = 0; } return ret; } static int imxfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, u_int trans, struct fb_info *info) { struct imxfb_info *fbi = info->par; unsigned int val; int ret = 1; /* * If inverse mode was selected, invert all the colours * rather than the register number. The register number * is what you poke into the framebuffer to produce the * colour you requested. */ if (fbi->cmap_inverse) { red = 0xffff - red; green = 0xffff - green; blue = 0xffff - blue; } /* * If greyscale is true, then we convert the RGB value * to greyscale no mater what visual we are using. */ if (info->var.grayscale) red = green = blue = (19595 * red + 38470 * green + 7471 * blue) >> 16; switch (info->fix.visual) { case FB_VISUAL_TRUECOLOR: /* * 12 or 16-bit True Colour. We encode the RGB value * according to the RGB bitfield information. */ if (regno < 16) { u32 *pal = info->pseudo_palette; val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue); pal[regno] = val; ret = 0; } break; case FB_VISUAL_STATIC_PSEUDOCOLOR: case FB_VISUAL_PSEUDOCOLOR: ret = imxfb_setpalettereg(regno, red, green, blue, trans, info); break; } return ret; } static const struct imx_fb_videomode *imxfb_find_mode(struct imxfb_info *fbi) { struct imx_fb_videomode *m; int i; if (!fb_mode) return &fbi->mode[0]; for (i = 0, m = &fbi->mode[0]; i < fbi->num_modes; i++, m++) { if (!strcmp(m->mode.name, fb_mode)) return m; } return NULL; } /* * imxfb_check_var(): * Round up in the following order: bits_per_pixel, xres, * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale, * bitfields, horizontal timing, vertical timing. */ static int imxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct imxfb_info *fbi = info->par; struct imxfb_rgb *rgb; const struct imx_fb_videomode *imxfb_mode; unsigned long lcd_clk; unsigned long long tmp; u32 pcr = 0; if (var->xres < MIN_XRES) var->xres = MIN_XRES; if (var->yres < MIN_YRES) var->yres = MIN_YRES; imxfb_mode = imxfb_find_mode(fbi); if (!imxfb_mode) return -EINVAL; var->xres = imxfb_mode->mode.xres; var->yres = imxfb_mode->mode.yres; var->bits_per_pixel = imxfb_mode->bpp; var->pixclock = imxfb_mode->mode.pixclock; var->hsync_len = imxfb_mode->mode.hsync_len; var->left_margin = imxfb_mode->mode.left_margin; var->right_margin = imxfb_mode->mode.right_margin; var->vsync_len = imxfb_mode->mode.vsync_len; var->upper_margin = imxfb_mode->mode.upper_margin; var->lower_margin = imxfb_mode->mode.lower_margin; var->sync = imxfb_mode->mode.sync; var->xres_virtual = max(var->xres_virtual, var->xres); var->yres_virtual = max(var->yres_virtual, var->yres); pr_debug("var->bits_per_pixel=%d\n", var->bits_per_pixel); lcd_clk = clk_get_rate(fbi->clk_per); tmp = var->pixclock * (unsigned long long)lcd_clk; do_div(tmp, 1000000); if (do_div(tmp, 1000000) > 500000) tmp++; pcr = (unsigned int)tmp; if (--pcr > PCR_PCD_MASK) { pcr = PCR_PCD_MASK; dev_warn(&fbi->pdev->dev, "Must limit pixel clock to %luHz\n", lcd_clk / pcr); } switch (var->bits_per_pixel) { case 32: pcr |= FIELD_PREP(PCR_BPIX_MASK, PCR_BPIX_18); rgb = &def_rgb_18; break; case 16: default: if (is_imx1_fb(fbi)) pcr |= FIELD_PREP(PCR_BPIX_MASK, PCR_BPIX_12); else pcr |= FIELD_PREP(PCR_BPIX_MASK, PCR_BPIX_16); if (imxfb_mode->pcr & PCR_TFT) rgb = &def_rgb_16_tft; else rgb = &def_rgb_16_stn; break; case 8: pcr |= FIELD_PREP(PCR_BPIX_MASK, PCR_BPIX_8); rgb = &def_rgb_8; break; } /* add sync polarities */ pcr |= imxfb_mode->pcr & ~(PCR_PCD_MASK | PCR_BPIX_MASK); fbi->pcr = pcr; /* * The LCDC AUS Mode Control Register does not exist on imx1. */ if (!is_imx1_fb(fbi) && imxfb_mode->aus_mode) fbi->lauscr = LAUSCR_AUS_MODE; if (imxfb_mode->pcr & PCR_TFT) fbi->panel_type = PANEL_TYPE_TFT; else if (imxfb_mode->pcr & PCR_COLOR) fbi->panel_type = PANEL_TYPE_CSTN; else fbi->panel_type = PANEL_TYPE_MONOCHROME; /* * Copy the RGB parameters for this display * from the machine specific parameters. */ var->red = rgb->red; var->green = rgb->green; var->blue = rgb->blue; var->transp = rgb->transp; pr_debug("RGBT length = %d:%d:%d:%d\n", var->red.length, var->green.length, var->blue.length, var->transp.length); pr_debug("RGBT offset = %d:%d:%d:%d\n", var->red.offset, var->green.offset, var->blue.offset, var->transp.offset); return 0; } /* * imxfb_set_par(): * Set the user defined part of the display for the specified console */ static int imxfb_set_par(struct fb_info *info) { struct imxfb_info *fbi = info->par; struct fb_var_screeninfo *var = &info->var; if (var->bits_per_pixel == 16 || var->bits_per_pixel == 32) info->fix.visual = FB_VISUAL_TRUECOLOR; else if (!fbi->cmap_static) info->fix.visual = FB_VISUAL_PSEUDOCOLOR; else { /* * Some people have weird ideas about wanting static * pseudocolor maps. I suspect their user space * applications are broken. */ info->fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR; } info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; fbi->palette_size = var->bits_per_pixel == 8 ? 256 : 16; imxfb_activate_var(var, info); return 0; } static int imxfb_enable_controller(struct imxfb_info *fbi) { int ret; if (fbi->enabled) return 0; pr_debug("Enabling LCD controller\n"); writel(fbi->map_dma, fbi->regs + LCDC_SSA); /* panning offset 0 (0 pixel offset) */ writel(FIELD_PREP(POS_POS_MASK, 0), fbi->regs + LCDC_POS); /* disable hardware cursor */ writel(readl(fbi->regs + LCDC_CPOS) & ~(CPOS_CC0 | CPOS_CC1), fbi->regs + LCDC_CPOS); /* * RMCR_LCDC_EN_MX1 is present on i.MX1 only, but doesn't hurt * on other SoCs */ writel(RMCR_LCDC_EN_MX1, fbi->regs + LCDC_RMCR); ret = clk_prepare_enable(fbi->clk_ipg); if (ret) goto err_enable_ipg; ret = clk_prepare_enable(fbi->clk_ahb); if (ret) goto err_enable_ahb; ret = clk_prepare_enable(fbi->clk_per); if (ret) goto err_enable_per; fbi->enabled = true; return 0; err_enable_per: clk_disable_unprepare(fbi->clk_ahb); err_enable_ahb: clk_disable_unprepare(fbi->clk_ipg); err_enable_ipg: writel(0, fbi->regs + LCDC_RMCR); return ret; } static void imxfb_disable_controller(struct imxfb_info *fbi) { if (!fbi->enabled) return; pr_debug("Disabling LCD controller\n"); clk_disable_unprepare(fbi->clk_per); clk_disable_unprepare(fbi->clk_ahb); clk_disable_unprepare(fbi->clk_ipg); fbi->enabled = false; writel(0, fbi->regs + LCDC_RMCR); } static int imxfb_blank(int blank, struct fb_info *info) { struct imxfb_info *fbi = info->par; pr_debug("%s: blank=%d\n", __func__, blank); switch (blank) { case FB_BLANK_POWERDOWN: case FB_BLANK_VSYNC_SUSPEND: case FB_BLANK_HSYNC_SUSPEND: case FB_BLANK_NORMAL: imxfb_disable_controller(fbi); break; case FB_BLANK_UNBLANK: return imxfb_enable_controller(fbi); } return 0; } static const struct fb_ops imxfb_ops = { .owner = THIS_MODULE, FB_DEFAULT_IOMEM_OPS, .fb_check_var = imxfb_check_var, .fb_set_par = imxfb_set_par, .fb_setcolreg = imxfb_setcolreg, .fb_blank = imxfb_blank, }; /* * imxfb_activate_var(): * Configures LCD Controller based on entries in var parameter. Settings are * only written to the controller if changes were made. */ static int imxfb_activate_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct imxfb_info *fbi = info->par; u32 ymax_mask = is_imx1_fb(fbi) ? YMAX_MASK_IMX1 : YMAX_MASK_IMX21; u8 left_margin_low; pr_debug("var: xres=%d hslen=%d lm=%d rm=%d\n", var->xres, var->hsync_len, var->left_margin, var->right_margin); pr_debug("var: yres=%d vslen=%d um=%d bm=%d\n", var->yres, var->vsync_len, var->upper_margin, var->lower_margin); if (fbi->panel_type == PANEL_TYPE_TFT) left_margin_low = 3; else if (fbi->panel_type == PANEL_TYPE_CSTN) left_margin_low = 2; else left_margin_low = 0; #if DEBUG_VAR if (var->xres < 16 || var->xres > 1024) dev_err(&fbi->pdev->dev, "%s: invalid xres %d\n", info->fix.id, var->xres); if (var->hsync_len < 1 || var->hsync_len > 64) dev_err(&fbi->pdev->dev, "%s: invalid hsync_len %d\n", info->fix.id, var->hsync_len); if (var->left_margin < left_margin_low || var->left_margin > 255) dev_err(&fbi->pdev->dev, "%s: invalid left_margin %d\n", info->fix.id, var->left_margin); if (var->right_margin < 1 || var->right_margin > 255) dev_err(&fbi->pdev->dev, "%s: invalid right_margin %d\n", info->fix.id, var->right_margin); if (var->yres < 1 || var->yres > ymax_mask) dev_err(&fbi->pdev->dev, "%s: invalid yres %d\n", info->fix.id, var->yres); if (var->vsync_len > 100) dev_err(&fbi->pdev->dev, "%s: invalid vsync_len %d\n", info->fix.id, var->vsync_len); if (var->upper_margin > 63) dev_err(&fbi->pdev->dev, "%s: invalid upper_margin %d\n", info->fix.id, var->upper_margin); if (var->lower_margin > 255) dev_err(&fbi->pdev->dev, "%s: invalid lower_margin %d\n", info->fix.id, var->lower_margin); #endif /* physical screen start address */ writel(FIELD_PREP(VPW_VPW_MASK, var->xres * var->bits_per_pixel / 8 / 4), fbi->regs + LCDC_VPW); writel(FIELD_PREP(HCR_H_WIDTH_MASK, var->hsync_len - 1) | FIELD_PREP(HCR_H_WAIT_1_MASK, var->right_margin - 1) | FIELD_PREP(HCR_H_WAIT_2_MASK, var->left_margin - left_margin_low), fbi->regs + LCDC_HCR); writel(FIELD_PREP(VCR_V_WIDTH_MASK, var->vsync_len) | FIELD_PREP(VCR_V_WAIT_1_MASK, var->lower_margin) | FIELD_PREP(VCR_V_WAIT_2_MASK, var->upper_margin), fbi->regs + LCDC_VCR); writel(FIELD_PREP(SIZE_XMAX_MASK, var->xres >> 4) | (var->yres & ymax_mask), fbi->regs + LCDC_SIZE); writel(fbi->pcr, fbi->regs + LCDC_PCR); if (fbi->pwmr) writel(fbi->pwmr, fbi->regs + LCDC_PWMR); writel(fbi->lscr1, fbi->regs + LCDC_LSCR1); /* dmacr = 0 is no valid value, as we need DMA control marks. */ if (fbi->dmacr) writel(fbi->dmacr, fbi->regs + LCDC_DMACR); if (fbi->lauscr) writel(fbi->lauscr, fbi->regs + LCDC_LAUSCR); return 0; } static int imxfb_init_fbinfo(struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct imxfb_info *fbi = info->par; struct device_node *np; info->pseudo_palette = devm_kmalloc_array(&pdev->dev, 16, sizeof(u32), GFP_KERNEL); if (!info->pseudo_palette) return -ENOMEM; memset(fbi, 0, sizeof(struct imxfb_info)); fbi->pdev = pdev; fbi->devtype = pdev->id_entry->driver_data; strscpy(info->fix.id, IMX_NAME, sizeof(info->fix.id)); info->fix.type = FB_TYPE_PACKED_PIXELS; info->fix.type_aux = 0; info->fix.xpanstep = 0; info->fix.ypanstep = 0; info->fix.ywrapstep = 0; info->fix.accel = FB_ACCEL_NONE; info->var.nonstd = 0; info->var.activate = FB_ACTIVATE_NOW; info->var.height = -1; info->var.width = -1; info->var.accel_flags = 0; info->var.vmode = FB_VMODE_NONINTERLACED; info->fbops = &imxfb_ops; info->flags = FBINFO_READS_FAST; np = pdev->dev.of_node; info->var.grayscale = of_property_read_bool(np, "cmap-greyscale"); fbi->cmap_inverse = of_property_read_bool(np, "cmap-inverse"); fbi->cmap_static = of_property_read_bool(np, "cmap-static"); fbi->lscr1 = IMXFB_LSCR1_DEFAULT; of_property_read_u32(np, "fsl,lpccr", &fbi->pwmr); of_property_read_u32(np, "fsl,lscr1", &fbi->lscr1); of_property_read_u32(np, "fsl,dmacr", &fbi->dmacr); return 0; } static int imxfb_of_read_mode(struct device *dev, struct device_node *np, struct imx_fb_videomode *imxfb_mode) { int ret; struct fb_videomode *of_mode = &imxfb_mode->mode; u32 bpp; u32 pcr; ret = of_property_read_string(np, "model", &of_mode->name); if (ret) of_mode->name = NULL; ret = of_get_fb_videomode(np, of_mode, OF_USE_NATIVE_MODE); if (ret) { dev_err(dev, "Failed to get videomode from DT\n"); return ret; } ret = of_property_read_u32(np, "bits-per-pixel", &bpp); ret |= of_property_read_u32(np, "fsl,pcr", &pcr); if (ret) { dev_err(dev, "Failed to read bpp and pcr from DT\n"); return -EINVAL; } if (bpp < 1 || bpp > 255) { dev_err(dev, "Bits per pixel have to be between 1 and 255\n"); return -EINVAL; } imxfb_mode->bpp = bpp; imxfb_mode->pcr = pcr; /* * fsl,aus-mode is optional */ imxfb_mode->aus_mode = of_property_read_bool(np, "fsl,aus-mode"); return 0; } static int imxfb_lcd_check_fb(struct lcd_device *lcddev, struct fb_info *fi) { struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); if (!fi || fi->par == fbi) return 1; return 0; } static int imxfb_lcd_get_contrast(struct lcd_device *lcddev) { struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); return fbi->pwmr & 0xff; } static int imxfb_lcd_set_contrast(struct lcd_device *lcddev, int contrast) { struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); if (fbi->pwmr && fbi->enabled) { if (contrast > 255) contrast = 255; else if (contrast < 0) contrast = 0; fbi->pwmr &= ~0xff; fbi->pwmr |= contrast; writel(fbi->pwmr, fbi->regs + LCDC_PWMR); } return 0; } static int imxfb_lcd_get_power(struct lcd_device *lcddev) { struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); if (!IS_ERR(fbi->lcd_pwr) && !regulator_is_enabled(fbi->lcd_pwr)) return FB_BLANK_POWERDOWN; return FB_BLANK_UNBLANK; } static int imxfb_regulator_set(struct imxfb_info *fbi, int enable) { int ret; if (enable == fbi->lcd_pwr_enabled) return 0; if (enable) ret = regulator_enable(fbi->lcd_pwr); else ret = regulator_disable(fbi->lcd_pwr); if (ret == 0) fbi->lcd_pwr_enabled = enable; return ret; } static int imxfb_lcd_set_power(struct lcd_device *lcddev, int power) { struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); if (!IS_ERR(fbi->lcd_pwr)) return imxfb_regulator_set(fbi, power == FB_BLANK_UNBLANK); return 0; } static const struct lcd_ops imxfb_lcd_ops = { .check_fb = imxfb_lcd_check_fb, .get_contrast = imxfb_lcd_get_contrast, .set_contrast = imxfb_lcd_set_contrast, .get_power = imxfb_lcd_get_power, .set_power = imxfb_lcd_set_power, }; static int imxfb_setup(void) { char *opt, *options = NULL; if (fb_get_options("imxfb", &options)) return -ENODEV; if (!options || !*options) return 0; while ((opt = strsep(&options, ",")) != NULL) { if (!*opt) continue; else fb_mode = opt; } return 0; } static int imxfb_probe(struct platform_device *pdev) { struct imxfb_info *fbi; struct lcd_device *lcd; struct fb_info *info; struct imx_fb_videomode *m; const struct of_device_id *of_id; struct device_node *display_np; int ret, i; int bytes_per_pixel; dev_info(&pdev->dev, "i.MX Framebuffer driver\n"); ret = imxfb_setup(); if (ret < 0) return ret; of_id = of_match_device(imxfb_of_dev_id, &pdev->dev); if (of_id) pdev->id_entry = of_id->data; info = framebuffer_alloc(sizeof(struct imxfb_info), &pdev->dev); if (!info) return -ENOMEM; fbi = info->par; platform_set_drvdata(pdev, info); ret = imxfb_init_fbinfo(pdev); if (ret < 0) goto failed_init; fb_mode = NULL; display_np = of_parse_phandle(pdev->dev.of_node, "display", 0); if (!display_np) { dev_err(&pdev->dev, "No display defined in devicetree\n"); ret = -EINVAL; goto failed_init; } /* * imxfb does not support more modes, we choose only the native * mode. */ fbi->num_modes = 1; fbi->mode = devm_kzalloc(&pdev->dev, sizeof(struct imx_fb_videomode), GFP_KERNEL); if (!fbi->mode) { ret = -ENOMEM; of_node_put(display_np); goto failed_init; } ret = imxfb_of_read_mode(&pdev->dev, display_np, fbi->mode); of_node_put(display_np); if (ret) goto failed_init; /* * Calculate maximum bytes used per pixel. In most cases this should * be the same as m->bpp/8 */ m = &fbi->mode[0]; bytes_per_pixel = (m->bpp + 7) / 8; for (i = 0; i < fbi->num_modes; i++, m++) info->fix.smem_len = max_t(size_t, info->fix.smem_len, m->mode.xres * m->mode.yres * bytes_per_pixel); fbi->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); if (IS_ERR(fbi->clk_ipg)) { ret = PTR_ERR(fbi->clk_ipg); goto failed_init; } /* * The LCDC controller does not have an enable bit. The * controller starts directly when the clocks are enabled. * If the clocks are enabled when the controller is not yet * programmed with proper register values (enabled at the * bootloader, for example) then it just goes into some undefined * state. * To avoid this issue, let's enable and disable LCDC IPG clock * so that we force some kind of 'reset' to the LCDC block. */ ret = clk_prepare_enable(fbi->clk_ipg); if (ret) goto failed_init; clk_disable_unprepare(fbi->clk_ipg); fbi->clk_ahb = devm_clk_get(&pdev->dev, "ahb"); if (IS_ERR(fbi->clk_ahb)) { ret = PTR_ERR(fbi->clk_ahb); goto failed_init; } fbi->clk_per = devm_clk_get(&pdev->dev, "per"); if (IS_ERR(fbi->clk_per)) { ret = PTR_ERR(fbi->clk_per); goto failed_init; } fbi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(fbi->regs)) { ret = PTR_ERR(fbi->regs); goto failed_init; } fbi->map_size = PAGE_ALIGN(info->fix.smem_len); info->screen_buffer = dma_alloc_wc(&pdev->dev, fbi->map_size, &fbi->map_dma, GFP_KERNEL); if (!info->screen_buffer) { dev_err(&pdev->dev, "Failed to allocate video RAM\n"); ret = -ENOMEM; goto failed_init; } info->fix.smem_start = fbi->map_dma; INIT_LIST_HEAD(&info->modelist); for (i = 0; i < fbi->num_modes; i++) fb_add_videomode(&fbi->mode[i].mode, &info->modelist); /* * This makes sure that our colour bitfield * descriptors are correctly initialised. */ imxfb_check_var(&info->var, info); /* * For modes > 8bpp, the color map is bypassed. * Therefore, 256 entries are enough. */ ret = fb_alloc_cmap(&info->cmap, 256, 0); if (ret < 0) goto failed_cmap; imxfb_set_par(info); ret = register_framebuffer(info); if (ret < 0) { dev_err(&pdev->dev, "failed to register framebuffer\n"); goto failed_register; } fbi->lcd_pwr = devm_regulator_get(&pdev->dev, "lcd"); if (PTR_ERR(fbi->lcd_pwr) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; goto failed_lcd; } lcd = devm_lcd_device_register(&pdev->dev, "imxfb-lcd", &pdev->dev, fbi, &imxfb_lcd_ops); if (IS_ERR(lcd)) { ret = PTR_ERR(lcd); goto failed_lcd; } lcd->props.max_contrast = 0xff; imxfb_enable_controller(fbi); return 0; failed_lcd: unregister_framebuffer(info); failed_register: fb_dealloc_cmap(&info->cmap); failed_cmap: dma_free_wc(&pdev->dev, fbi->map_size, info->screen_buffer, fbi->map_dma); failed_init: framebuffer_release(info); return ret; } static void imxfb_remove(struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct imxfb_info *fbi = info->par; imxfb_disable_controller(fbi); unregister_framebuffer(info); fb_dealloc_cmap(&info->cmap); dma_free_wc(&pdev->dev, fbi->map_size, info->screen_buffer, fbi->map_dma); framebuffer_release(info); } static int imxfb_suspend(struct device *dev) { struct fb_info *info = dev_get_drvdata(dev); struct imxfb_info *fbi = info->par; imxfb_disable_controller(fbi); return 0; } static int imxfb_resume(struct device *dev) { struct fb_info *info = dev_get_drvdata(dev); struct imxfb_info *fbi = info->par; imxfb_enable_controller(fbi); return 0; } static DEFINE_SIMPLE_DEV_PM_OPS(imxfb_pm_ops, imxfb_suspend, imxfb_resume); static struct platform_driver imxfb_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = imxfb_of_dev_id, .pm = pm_sleep_ptr(&imxfb_pm_ops), }, .probe = imxfb_probe, .remove_new = imxfb_remove, .id_table = imxfb_devtype, }; module_platform_driver(imxfb_driver); MODULE_DESCRIPTION("Freescale i.MX framebuffer driver"); MODULE_AUTHOR("Sascha Hauer, Pengutronix"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1