18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Driver for AT91 LCD Controller 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2007 Atmel Corporation 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 78c2ecf20Sopenharmony_ci * License. See the file COPYING in the main directory of this archive for 88c2ecf20Sopenharmony_ci * more details. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 148c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 158c2ecf20Sopenharmony_ci#include <linux/clk.h> 168c2ecf20Sopenharmony_ci#include <linux/fb.h> 178c2ecf20Sopenharmony_ci#include <linux/init.h> 188c2ecf20Sopenharmony_ci#include <linux/delay.h> 198c2ecf20Sopenharmony_ci#include <linux/backlight.h> 208c2ecf20Sopenharmony_ci#include <linux/gfp.h> 218c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 228c2ecf20Sopenharmony_ci#include <linux/module.h> 238c2ecf20Sopenharmony_ci#include <linux/of.h> 248c2ecf20Sopenharmony_ci#include <linux/of_device.h> 258c2ecf20Sopenharmony_ci#include <video/of_videomode.h> 268c2ecf20Sopenharmony_ci#include <video/of_display_timing.h> 278c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 288c2ecf20Sopenharmony_ci#include <video/videomode.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <video/atmel_lcdc.h> 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct atmel_lcdfb_config { 338c2ecf20Sopenharmony_ci bool have_alt_pixclock; 348c2ecf20Sopenharmony_ci bool have_hozval; 358c2ecf20Sopenharmony_ci bool have_intensity_bit; 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci /* LCD Controller info data structure, stored in device platform_data */ 398c2ecf20Sopenharmony_cistruct atmel_lcdfb_info { 408c2ecf20Sopenharmony_ci spinlock_t lock; 418c2ecf20Sopenharmony_ci struct fb_info *info; 428c2ecf20Sopenharmony_ci void __iomem *mmio; 438c2ecf20Sopenharmony_ci int irq_base; 448c2ecf20Sopenharmony_ci struct work_struct task; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci unsigned int smem_len; 478c2ecf20Sopenharmony_ci struct platform_device *pdev; 488c2ecf20Sopenharmony_ci struct clk *bus_clk; 498c2ecf20Sopenharmony_ci struct clk *lcdc_clk; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci struct backlight_device *backlight; 528c2ecf20Sopenharmony_ci u8 bl_power; 538c2ecf20Sopenharmony_ci u8 saved_lcdcon; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci u32 pseudo_palette[16]; 568c2ecf20Sopenharmony_ci bool have_intensity_bit; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata pdata; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci struct atmel_lcdfb_config *config; 618c2ecf20Sopenharmony_ci struct regulator *reg_lcd; 628c2ecf20Sopenharmony_ci}; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistruct atmel_lcdfb_power_ctrl_gpio { 658c2ecf20Sopenharmony_ci struct gpio_desc *gpiod; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci struct list_head list; 688c2ecf20Sopenharmony_ci}; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci#define lcdc_readl(sinfo, reg) __raw_readl((sinfo)->mmio+(reg)) 718c2ecf20Sopenharmony_ci#define lcdc_writel(sinfo, reg, val) __raw_writel((val), (sinfo)->mmio+(reg)) 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci/* configurable parameters */ 748c2ecf20Sopenharmony_ci#define ATMEL_LCDC_CVAL_DEFAULT 0xc8 758c2ecf20Sopenharmony_ci#define ATMEL_LCDC_DMA_BURST_LEN 8 /* words */ 768c2ecf20Sopenharmony_ci#define ATMEL_LCDC_FIFO_SIZE 512 /* words */ 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic struct atmel_lcdfb_config at91sam9261_config = { 798c2ecf20Sopenharmony_ci .have_hozval = true, 808c2ecf20Sopenharmony_ci .have_intensity_bit = true, 818c2ecf20Sopenharmony_ci}; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic struct atmel_lcdfb_config at91sam9263_config = { 848c2ecf20Sopenharmony_ci .have_intensity_bit = true, 858c2ecf20Sopenharmony_ci}; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic struct atmel_lcdfb_config at91sam9g10_config = { 888c2ecf20Sopenharmony_ci .have_hozval = true, 898c2ecf20Sopenharmony_ci}; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic struct atmel_lcdfb_config at91sam9g45_config = { 928c2ecf20Sopenharmony_ci .have_alt_pixclock = true, 938c2ecf20Sopenharmony_ci}; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic struct atmel_lcdfb_config at91sam9g45es_config = { 968c2ecf20Sopenharmony_ci}; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic struct atmel_lcdfb_config at91sam9rl_config = { 998c2ecf20Sopenharmony_ci .have_intensity_bit = true, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic u32 contrast_ctr = ATMEL_LCDC_PS_DIV8 1038c2ecf20Sopenharmony_ci | ATMEL_LCDC_POL_POSITIVE 1048c2ecf20Sopenharmony_ci | ATMEL_LCDC_ENA_PWMENABLE; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci#ifdef CONFIG_BACKLIGHT_ATMEL_LCDC 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci/* some bl->props field just changed */ 1098c2ecf20Sopenharmony_cistatic int atmel_bl_update_status(struct backlight_device *bl) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = bl_get_data(bl); 1128c2ecf20Sopenharmony_ci int power = sinfo->bl_power; 1138c2ecf20Sopenharmony_ci int brightness = bl->props.brightness; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci /* REVISIT there may be a meaningful difference between 1168c2ecf20Sopenharmony_ci * fb_blank and power ... there seem to be some cases 1178c2ecf20Sopenharmony_ci * this doesn't handle correctly. 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_ci if (bl->props.fb_blank != sinfo->bl_power) 1208c2ecf20Sopenharmony_ci power = bl->props.fb_blank; 1218c2ecf20Sopenharmony_ci else if (bl->props.power != sinfo->bl_power) 1228c2ecf20Sopenharmony_ci power = bl->props.power; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (brightness < 0 && power == FB_BLANK_UNBLANK) 1258c2ecf20Sopenharmony_ci brightness = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); 1268c2ecf20Sopenharmony_ci else if (power != FB_BLANK_UNBLANK) 1278c2ecf20Sopenharmony_ci brightness = 0; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, brightness); 1308c2ecf20Sopenharmony_ci if (contrast_ctr & ATMEL_LCDC_POL_POSITIVE) 1318c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, 1328c2ecf20Sopenharmony_ci brightness ? contrast_ctr : 0); 1338c2ecf20Sopenharmony_ci else 1348c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci bl->props.fb_blank = bl->props.power = sinfo->bl_power = power; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci return 0; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic int atmel_bl_get_brightness(struct backlight_device *bl) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = bl_get_data(bl); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci return lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic const struct backlight_ops atmel_lcdc_bl_ops = { 1498c2ecf20Sopenharmony_ci .update_status = atmel_bl_update_status, 1508c2ecf20Sopenharmony_ci .get_brightness = atmel_bl_get_brightness, 1518c2ecf20Sopenharmony_ci}; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic void init_backlight(struct atmel_lcdfb_info *sinfo) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci struct backlight_properties props; 1568c2ecf20Sopenharmony_ci struct backlight_device *bl; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci sinfo->bl_power = FB_BLANK_UNBLANK; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (sinfo->backlight) 1618c2ecf20Sopenharmony_ci return; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci memset(&props, 0, sizeof(struct backlight_properties)); 1648c2ecf20Sopenharmony_ci props.type = BACKLIGHT_RAW; 1658c2ecf20Sopenharmony_ci props.max_brightness = 0xff; 1668c2ecf20Sopenharmony_ci bl = backlight_device_register("backlight", &sinfo->pdev->dev, sinfo, 1678c2ecf20Sopenharmony_ci &atmel_lcdc_bl_ops, &props); 1688c2ecf20Sopenharmony_ci if (IS_ERR(bl)) { 1698c2ecf20Sopenharmony_ci dev_err(&sinfo->pdev->dev, "error %ld on backlight register\n", 1708c2ecf20Sopenharmony_ci PTR_ERR(bl)); 1718c2ecf20Sopenharmony_ci return; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci sinfo->backlight = bl; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci bl->props.power = FB_BLANK_UNBLANK; 1768c2ecf20Sopenharmony_ci bl->props.fb_blank = FB_BLANK_UNBLANK; 1778c2ecf20Sopenharmony_ci bl->props.brightness = atmel_bl_get_brightness(bl); 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic void exit_backlight(struct atmel_lcdfb_info *sinfo) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci if (!sinfo->backlight) 1838c2ecf20Sopenharmony_ci return; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (sinfo->backlight->ops) { 1868c2ecf20Sopenharmony_ci sinfo->backlight->props.power = FB_BLANK_POWERDOWN; 1878c2ecf20Sopenharmony_ci sinfo->backlight->ops->update_status(sinfo->backlight); 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci backlight_device_unregister(sinfo->backlight); 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci#else 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic void init_backlight(struct atmel_lcdfb_info *sinfo) 1958c2ecf20Sopenharmony_ci{ 1968c2ecf20Sopenharmony_ci dev_warn(&sinfo->pdev->dev, "backlight control is not available\n"); 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic void exit_backlight(struct atmel_lcdfb_info *sinfo) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci#endif 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic void init_contrast(struct atmel_lcdfb_info *sinfo) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* contrast pwm can be 'inverted' */ 2108c2ecf20Sopenharmony_ci if (pdata->lcdcon_pol_negative) 2118c2ecf20Sopenharmony_ci contrast_ctr &= ~(ATMEL_LCDC_POL_POSITIVE); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci /* have some default contrast/backlight settings */ 2148c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); 2158c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci if (pdata->lcdcon_is_backlight) 2188c2ecf20Sopenharmony_ci init_backlight(sinfo); 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic inline void atmel_lcdfb_power_control(struct atmel_lcdfb_info *sinfo, int on) 2228c2ecf20Sopenharmony_ci{ 2238c2ecf20Sopenharmony_ci int ret; 2248c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci if (pdata->atmel_lcdfb_power_control) 2278c2ecf20Sopenharmony_ci pdata->atmel_lcdfb_power_control(pdata, on); 2288c2ecf20Sopenharmony_ci else if (sinfo->reg_lcd) { 2298c2ecf20Sopenharmony_ci if (on) { 2308c2ecf20Sopenharmony_ci ret = regulator_enable(sinfo->reg_lcd); 2318c2ecf20Sopenharmony_ci if (ret) 2328c2ecf20Sopenharmony_ci dev_err(&sinfo->pdev->dev, 2338c2ecf20Sopenharmony_ci "lcd regulator enable failed: %d\n", ret); 2348c2ecf20Sopenharmony_ci } else { 2358c2ecf20Sopenharmony_ci ret = regulator_disable(sinfo->reg_lcd); 2368c2ecf20Sopenharmony_ci if (ret) 2378c2ecf20Sopenharmony_ci dev_err(&sinfo->pdev->dev, 2388c2ecf20Sopenharmony_ci "lcd regulator disable failed: %d\n", ret); 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic const struct fb_fix_screeninfo atmel_lcdfb_fix __initconst = { 2448c2ecf20Sopenharmony_ci .type = FB_TYPE_PACKED_PIXELS, 2458c2ecf20Sopenharmony_ci .visual = FB_VISUAL_TRUECOLOR, 2468c2ecf20Sopenharmony_ci .xpanstep = 0, 2478c2ecf20Sopenharmony_ci .ypanstep = 1, 2488c2ecf20Sopenharmony_ci .ywrapstep = 0, 2498c2ecf20Sopenharmony_ci .accel = FB_ACCEL_NONE, 2508c2ecf20Sopenharmony_ci}; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic unsigned long compute_hozval(struct atmel_lcdfb_info *sinfo, 2538c2ecf20Sopenharmony_ci unsigned long xres) 2548c2ecf20Sopenharmony_ci{ 2558c2ecf20Sopenharmony_ci unsigned long lcdcon2; 2568c2ecf20Sopenharmony_ci unsigned long value; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci if (!sinfo->config->have_hozval) 2598c2ecf20Sopenharmony_ci return xres; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci lcdcon2 = lcdc_readl(sinfo, ATMEL_LCDC_LCDCON2); 2628c2ecf20Sopenharmony_ci value = xres; 2638c2ecf20Sopenharmony_ci if ((lcdcon2 & ATMEL_LCDC_DISTYPE) != ATMEL_LCDC_DISTYPE_TFT) { 2648c2ecf20Sopenharmony_ci /* STN display */ 2658c2ecf20Sopenharmony_ci if ((lcdcon2 & ATMEL_LCDC_DISTYPE) == ATMEL_LCDC_DISTYPE_STNCOLOR) { 2668c2ecf20Sopenharmony_ci value *= 3; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci if ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_4 2698c2ecf20Sopenharmony_ci || ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_8 2708c2ecf20Sopenharmony_ci && (lcdcon2 & ATMEL_LCDC_SCANMOD) == ATMEL_LCDC_SCANMOD_DUAL )) 2718c2ecf20Sopenharmony_ci value = DIV_ROUND_UP(value, 4); 2728c2ecf20Sopenharmony_ci else 2738c2ecf20Sopenharmony_ci value = DIV_ROUND_UP(value, 8); 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci return value; 2778c2ecf20Sopenharmony_ci} 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic void atmel_lcdfb_stop_nowait(struct atmel_lcdfb_info *sinfo) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci /* Turn off the LCD controller and the DMA controller */ 2848c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, 2858c2ecf20Sopenharmony_ci pdata->guard_time << ATMEL_LCDC_GUARDT_OFFSET); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci /* Wait for the LCDC core to become idle */ 2888c2ecf20Sopenharmony_ci while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY) 2898c2ecf20Sopenharmony_ci msleep(10); 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0); 2928c2ecf20Sopenharmony_ci} 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cistatic void atmel_lcdfb_stop(struct atmel_lcdfb_info *sinfo) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci atmel_lcdfb_stop_nowait(sinfo); 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci /* Wait for DMA engine to become idle... */ 2998c2ecf20Sopenharmony_ci while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) 3008c2ecf20Sopenharmony_ci msleep(10); 3018c2ecf20Sopenharmony_ci} 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_cistatic void atmel_lcdfb_start(struct atmel_lcdfb_info *sinfo) 3048c2ecf20Sopenharmony_ci{ 3058c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_DMACON, pdata->default_dmacon); 3088c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, 3098c2ecf20Sopenharmony_ci (pdata->guard_time << ATMEL_LCDC_GUARDT_OFFSET) 3108c2ecf20Sopenharmony_ci | ATMEL_LCDC_PWR); 3118c2ecf20Sopenharmony_ci} 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_cistatic void atmel_lcdfb_update_dma(struct fb_info *info, 3148c2ecf20Sopenharmony_ci struct fb_var_screeninfo *var) 3158c2ecf20Sopenharmony_ci{ 3168c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 3178c2ecf20Sopenharmony_ci struct fb_fix_screeninfo *fix = &info->fix; 3188c2ecf20Sopenharmony_ci unsigned long dma_addr; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci dma_addr = (fix->smem_start + var->yoffset * fix->line_length 3218c2ecf20Sopenharmony_ci + var->xoffset * info->var.bits_per_pixel / 8); 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci dma_addr &= ~3UL; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci /* Set framebuffer DMA base address and pixel offset */ 3268c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_DMABADDR1, dma_addr); 3278c2ecf20Sopenharmony_ci} 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_cistatic inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo) 3308c2ecf20Sopenharmony_ci{ 3318c2ecf20Sopenharmony_ci struct fb_info *info = sinfo->info; 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci dma_free_wc(info->device, info->fix.smem_len, info->screen_base, 3348c2ecf20Sopenharmony_ci info->fix.smem_start); 3358c2ecf20Sopenharmony_ci} 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci/** 3388c2ecf20Sopenharmony_ci * atmel_lcdfb_alloc_video_memory - Allocate framebuffer memory 3398c2ecf20Sopenharmony_ci * @sinfo: the frame buffer to allocate memory for 3408c2ecf20Sopenharmony_ci * 3418c2ecf20Sopenharmony_ci * This function is called only from the atmel_lcdfb_probe() 3428c2ecf20Sopenharmony_ci * so no locking by fb_info->mm_lock around smem_len setting is needed. 3438c2ecf20Sopenharmony_ci */ 3448c2ecf20Sopenharmony_cistatic int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo) 3458c2ecf20Sopenharmony_ci{ 3468c2ecf20Sopenharmony_ci struct fb_info *info = sinfo->info; 3478c2ecf20Sopenharmony_ci struct fb_var_screeninfo *var = &info->var; 3488c2ecf20Sopenharmony_ci unsigned int smem_len; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci smem_len = (var->xres_virtual * var->yres_virtual 3518c2ecf20Sopenharmony_ci * ((var->bits_per_pixel + 7) / 8)); 3528c2ecf20Sopenharmony_ci info->fix.smem_len = max(smem_len, sinfo->smem_len); 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci info->screen_base = dma_alloc_wc(info->device, info->fix.smem_len, 3558c2ecf20Sopenharmony_ci (dma_addr_t *)&info->fix.smem_start, 3568c2ecf20Sopenharmony_ci GFP_KERNEL); 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci if (!info->screen_base) { 3598c2ecf20Sopenharmony_ci return -ENOMEM; 3608c2ecf20Sopenharmony_ci } 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci memset(info->screen_base, 0, info->fix.smem_len); 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci return 0; 3658c2ecf20Sopenharmony_ci} 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_cistatic const struct fb_videomode *atmel_lcdfb_choose_mode(struct fb_var_screeninfo *var, 3688c2ecf20Sopenharmony_ci struct fb_info *info) 3698c2ecf20Sopenharmony_ci{ 3708c2ecf20Sopenharmony_ci struct fb_videomode varfbmode; 3718c2ecf20Sopenharmony_ci const struct fb_videomode *fbmode = NULL; 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci fb_var_to_videomode(&varfbmode, var); 3748c2ecf20Sopenharmony_ci fbmode = fb_find_nearest_mode(&varfbmode, &info->modelist); 3758c2ecf20Sopenharmony_ci if (fbmode) 3768c2ecf20Sopenharmony_ci fb_videomode_to_var(var, fbmode); 3778c2ecf20Sopenharmony_ci return fbmode; 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci/** 3828c2ecf20Sopenharmony_ci * atmel_lcdfb_check_var - Validates a var passed in. 3838c2ecf20Sopenharmony_ci * @var: frame buffer variable screen structure 3848c2ecf20Sopenharmony_ci * @info: frame buffer structure that represents a single frame buffer 3858c2ecf20Sopenharmony_ci * 3868c2ecf20Sopenharmony_ci * Checks to see if the hardware supports the state requested by 3878c2ecf20Sopenharmony_ci * var passed in. This function does not alter the hardware 3888c2ecf20Sopenharmony_ci * state!!! This means the data stored in struct fb_info and 3898c2ecf20Sopenharmony_ci * struct atmel_lcdfb_info do not change. This includes the var 3908c2ecf20Sopenharmony_ci * inside of struct fb_info. Do NOT change these. This function 3918c2ecf20Sopenharmony_ci * can be called on its own if we intent to only test a mode and 3928c2ecf20Sopenharmony_ci * not actually set it. The stuff in modedb.c is a example of 3938c2ecf20Sopenharmony_ci * this. If the var passed in is slightly off by what the 3948c2ecf20Sopenharmony_ci * hardware can support then we alter the var PASSED in to what 3958c2ecf20Sopenharmony_ci * we can do. If the hardware doesn't support mode change a 3968c2ecf20Sopenharmony_ci * -EINVAL will be returned by the upper layers. You don't need 3978c2ecf20Sopenharmony_ci * to implement this function then. If you hardware doesn't 3988c2ecf20Sopenharmony_ci * support changing the resolution then this function is not 3998c2ecf20Sopenharmony_ci * needed. In this case the driver would just provide a var that 4008c2ecf20Sopenharmony_ci * represents the static state the screen is in. 4018c2ecf20Sopenharmony_ci * 4028c2ecf20Sopenharmony_ci * Returns negative errno on error, or zero on success. 4038c2ecf20Sopenharmony_ci */ 4048c2ecf20Sopenharmony_cistatic int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, 4058c2ecf20Sopenharmony_ci struct fb_info *info) 4068c2ecf20Sopenharmony_ci{ 4078c2ecf20Sopenharmony_ci struct device *dev = info->device; 4088c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 4098c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 4108c2ecf20Sopenharmony_ci unsigned long clk_value_khz; 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci dev_dbg(dev, "%s:\n", __func__); 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci if (!(var->pixclock && var->bits_per_pixel)) { 4178c2ecf20Sopenharmony_ci /* choose a suitable mode if possible */ 4188c2ecf20Sopenharmony_ci if (!atmel_lcdfb_choose_mode(var, info)) { 4198c2ecf20Sopenharmony_ci dev_err(dev, "needed value not specified\n"); 4208c2ecf20Sopenharmony_ci return -EINVAL; 4218c2ecf20Sopenharmony_ci } 4228c2ecf20Sopenharmony_ci } 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci dev_dbg(dev, " resolution: %ux%u\n", var->xres, var->yres); 4258c2ecf20Sopenharmony_ci dev_dbg(dev, " pixclk: %lu KHz\n", PICOS2KHZ(var->pixclock)); 4268c2ecf20Sopenharmony_ci dev_dbg(dev, " bpp: %u\n", var->bits_per_pixel); 4278c2ecf20Sopenharmony_ci dev_dbg(dev, " clk: %lu KHz\n", clk_value_khz); 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci if (PICOS2KHZ(var->pixclock) > clk_value_khz) { 4308c2ecf20Sopenharmony_ci dev_err(dev, "%lu KHz pixel clock is too fast\n", PICOS2KHZ(var->pixclock)); 4318c2ecf20Sopenharmony_ci return -EINVAL; 4328c2ecf20Sopenharmony_ci } 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci /* Do not allow to have real resoulution larger than virtual */ 4358c2ecf20Sopenharmony_ci if (var->xres > var->xres_virtual) 4368c2ecf20Sopenharmony_ci var->xres_virtual = var->xres; 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci if (var->yres > var->yres_virtual) 4398c2ecf20Sopenharmony_ci var->yres_virtual = var->yres; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci /* Force same alignment for each line */ 4428c2ecf20Sopenharmony_ci var->xres = (var->xres + 3) & ~3UL; 4438c2ecf20Sopenharmony_ci var->xres_virtual = (var->xres_virtual + 3) & ~3UL; 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0; 4468c2ecf20Sopenharmony_ci var->transp.msb_right = 0; 4478c2ecf20Sopenharmony_ci var->transp.offset = var->transp.length = 0; 4488c2ecf20Sopenharmony_ci var->xoffset = var->yoffset = 0; 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci if (info->fix.smem_len) { 4518c2ecf20Sopenharmony_ci unsigned int smem_len = (var->xres_virtual * var->yres_virtual 4528c2ecf20Sopenharmony_ci * ((var->bits_per_pixel + 7) / 8)); 4538c2ecf20Sopenharmony_ci if (smem_len > info->fix.smem_len) { 4548c2ecf20Sopenharmony_ci dev_err(dev, "Frame buffer is too small (%u) for screen size (need at least %u)\n", 4558c2ecf20Sopenharmony_ci info->fix.smem_len, smem_len); 4568c2ecf20Sopenharmony_ci return -EINVAL; 4578c2ecf20Sopenharmony_ci } 4588c2ecf20Sopenharmony_ci } 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci /* Saturate vertical and horizontal timings at maximum values */ 4618c2ecf20Sopenharmony_ci var->vsync_len = min_t(u32, var->vsync_len, 4628c2ecf20Sopenharmony_ci (ATMEL_LCDC_VPW >> ATMEL_LCDC_VPW_OFFSET) + 1); 4638c2ecf20Sopenharmony_ci var->upper_margin = min_t(u32, var->upper_margin, 4648c2ecf20Sopenharmony_ci ATMEL_LCDC_VBP >> ATMEL_LCDC_VBP_OFFSET); 4658c2ecf20Sopenharmony_ci var->lower_margin = min_t(u32, var->lower_margin, 4668c2ecf20Sopenharmony_ci ATMEL_LCDC_VFP); 4678c2ecf20Sopenharmony_ci var->right_margin = min_t(u32, var->right_margin, 4688c2ecf20Sopenharmony_ci (ATMEL_LCDC_HFP >> ATMEL_LCDC_HFP_OFFSET) + 1); 4698c2ecf20Sopenharmony_ci var->hsync_len = min_t(u32, var->hsync_len, 4708c2ecf20Sopenharmony_ci (ATMEL_LCDC_HPW >> ATMEL_LCDC_HPW_OFFSET) + 1); 4718c2ecf20Sopenharmony_ci var->left_margin = min_t(u32, var->left_margin, 4728c2ecf20Sopenharmony_ci ATMEL_LCDC_HBP + 1); 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci /* Some parameters can't be zero */ 4758c2ecf20Sopenharmony_ci var->vsync_len = max_t(u32, var->vsync_len, 1); 4768c2ecf20Sopenharmony_ci var->right_margin = max_t(u32, var->right_margin, 1); 4778c2ecf20Sopenharmony_ci var->hsync_len = max_t(u32, var->hsync_len, 1); 4788c2ecf20Sopenharmony_ci var->left_margin = max_t(u32, var->left_margin, 1); 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci switch (var->bits_per_pixel) { 4818c2ecf20Sopenharmony_ci case 1: 4828c2ecf20Sopenharmony_ci case 2: 4838c2ecf20Sopenharmony_ci case 4: 4848c2ecf20Sopenharmony_ci case 8: 4858c2ecf20Sopenharmony_ci var->red.offset = var->green.offset = var->blue.offset = 0; 4868c2ecf20Sopenharmony_ci var->red.length = var->green.length = var->blue.length 4878c2ecf20Sopenharmony_ci = var->bits_per_pixel; 4888c2ecf20Sopenharmony_ci break; 4898c2ecf20Sopenharmony_ci case 16: 4908c2ecf20Sopenharmony_ci /* Older SOCs use IBGR:555 rather than BGR:565. */ 4918c2ecf20Sopenharmony_ci if (sinfo->config->have_intensity_bit) 4928c2ecf20Sopenharmony_ci var->green.length = 5; 4938c2ecf20Sopenharmony_ci else 4948c2ecf20Sopenharmony_ci var->green.length = 6; 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { 4978c2ecf20Sopenharmony_ci /* RGB:5X5 mode */ 4988c2ecf20Sopenharmony_ci var->red.offset = var->green.length + 5; 4998c2ecf20Sopenharmony_ci var->blue.offset = 0; 5008c2ecf20Sopenharmony_ci } else { 5018c2ecf20Sopenharmony_ci /* BGR:5X5 mode */ 5028c2ecf20Sopenharmony_ci var->red.offset = 0; 5038c2ecf20Sopenharmony_ci var->blue.offset = var->green.length + 5; 5048c2ecf20Sopenharmony_ci } 5058c2ecf20Sopenharmony_ci var->green.offset = 5; 5068c2ecf20Sopenharmony_ci var->red.length = var->blue.length = 5; 5078c2ecf20Sopenharmony_ci break; 5088c2ecf20Sopenharmony_ci case 32: 5098c2ecf20Sopenharmony_ci var->transp.offset = 24; 5108c2ecf20Sopenharmony_ci var->transp.length = 8; 5118c2ecf20Sopenharmony_ci fallthrough; 5128c2ecf20Sopenharmony_ci case 24: 5138c2ecf20Sopenharmony_ci if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { 5148c2ecf20Sopenharmony_ci /* RGB:888 mode */ 5158c2ecf20Sopenharmony_ci var->red.offset = 16; 5168c2ecf20Sopenharmony_ci var->blue.offset = 0; 5178c2ecf20Sopenharmony_ci } else { 5188c2ecf20Sopenharmony_ci /* BGR:888 mode */ 5198c2ecf20Sopenharmony_ci var->red.offset = 0; 5208c2ecf20Sopenharmony_ci var->blue.offset = 16; 5218c2ecf20Sopenharmony_ci } 5228c2ecf20Sopenharmony_ci var->green.offset = 8; 5238c2ecf20Sopenharmony_ci var->red.length = var->green.length = var->blue.length = 8; 5248c2ecf20Sopenharmony_ci break; 5258c2ecf20Sopenharmony_ci default: 5268c2ecf20Sopenharmony_ci dev_err(dev, "color depth %d not supported\n", 5278c2ecf20Sopenharmony_ci var->bits_per_pixel); 5288c2ecf20Sopenharmony_ci return -EINVAL; 5298c2ecf20Sopenharmony_ci } 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci return 0; 5328c2ecf20Sopenharmony_ci} 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci/* 5358c2ecf20Sopenharmony_ci * LCD reset sequence 5368c2ecf20Sopenharmony_ci */ 5378c2ecf20Sopenharmony_cistatic void atmel_lcdfb_reset(struct atmel_lcdfb_info *sinfo) 5388c2ecf20Sopenharmony_ci{ 5398c2ecf20Sopenharmony_ci might_sleep(); 5408c2ecf20Sopenharmony_ci 5418c2ecf20Sopenharmony_ci atmel_lcdfb_stop(sinfo); 5428c2ecf20Sopenharmony_ci atmel_lcdfb_start(sinfo); 5438c2ecf20Sopenharmony_ci} 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci/** 5468c2ecf20Sopenharmony_ci * atmel_lcdfb_set_par - Alters the hardware state. 5478c2ecf20Sopenharmony_ci * @info: frame buffer structure that represents a single frame buffer 5488c2ecf20Sopenharmony_ci * 5498c2ecf20Sopenharmony_ci * Using the fb_var_screeninfo in fb_info we set the resolution 5508c2ecf20Sopenharmony_ci * of the this particular framebuffer. This function alters the 5518c2ecf20Sopenharmony_ci * par AND the fb_fix_screeninfo stored in fb_info. It doesn't 5528c2ecf20Sopenharmony_ci * not alter var in fb_info since we are using that data. This 5538c2ecf20Sopenharmony_ci * means we depend on the data in var inside fb_info to be 5548c2ecf20Sopenharmony_ci * supported by the hardware. atmel_lcdfb_check_var is always called 5558c2ecf20Sopenharmony_ci * before atmel_lcdfb_set_par to ensure this. Again if you can't 5568c2ecf20Sopenharmony_ci * change the resolution you don't need this function. 5578c2ecf20Sopenharmony_ci * 5588c2ecf20Sopenharmony_ci */ 5598c2ecf20Sopenharmony_cistatic int atmel_lcdfb_set_par(struct fb_info *info) 5608c2ecf20Sopenharmony_ci{ 5618c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 5628c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 5638c2ecf20Sopenharmony_ci unsigned long hozval_linesz; 5648c2ecf20Sopenharmony_ci unsigned long value; 5658c2ecf20Sopenharmony_ci unsigned long clk_value_khz; 5668c2ecf20Sopenharmony_ci unsigned long bits_per_line; 5678c2ecf20Sopenharmony_ci unsigned long pix_factor = 2; 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_ci might_sleep(); 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci dev_dbg(info->device, "%s:\n", __func__); 5728c2ecf20Sopenharmony_ci dev_dbg(info->device, " * resolution: %ux%u (%ux%u virtual)\n", 5738c2ecf20Sopenharmony_ci info->var.xres, info->var.yres, 5748c2ecf20Sopenharmony_ci info->var.xres_virtual, info->var.yres_virtual); 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci atmel_lcdfb_stop_nowait(sinfo); 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci if (info->var.bits_per_pixel == 1) 5798c2ecf20Sopenharmony_ci info->fix.visual = FB_VISUAL_MONO01; 5808c2ecf20Sopenharmony_ci else if (info->var.bits_per_pixel <= 8) 5818c2ecf20Sopenharmony_ci info->fix.visual = FB_VISUAL_PSEUDOCOLOR; 5828c2ecf20Sopenharmony_ci else 5838c2ecf20Sopenharmony_ci info->fix.visual = FB_VISUAL_TRUECOLOR; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci bits_per_line = info->var.xres_virtual * info->var.bits_per_pixel; 5868c2ecf20Sopenharmony_ci info->fix.line_length = DIV_ROUND_UP(bits_per_line, 8); 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci /* Re-initialize the DMA engine... */ 5898c2ecf20Sopenharmony_ci dev_dbg(info->device, " * update DMA engine\n"); 5908c2ecf20Sopenharmony_ci atmel_lcdfb_update_dma(info, &info->var); 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci /* ...set frame size and burst length = 8 words (?) */ 5938c2ecf20Sopenharmony_ci value = (info->var.yres * info->var.xres * info->var.bits_per_pixel) / 32; 5948c2ecf20Sopenharmony_ci value |= ((ATMEL_LCDC_DMA_BURST_LEN - 1) << ATMEL_LCDC_BLENGTH_OFFSET); 5958c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_DMAFRMCFG, value); 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ci /* Now, the LCDC core... */ 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_ci /* Set pixel clock */ 6008c2ecf20Sopenharmony_ci if (sinfo->config->have_alt_pixclock) 6018c2ecf20Sopenharmony_ci pix_factor = 1; 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_ci value = DIV_ROUND_UP(clk_value_khz, PICOS2KHZ(info->var.pixclock)); 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci if (value < pix_factor) { 6088c2ecf20Sopenharmony_ci dev_notice(info->device, "Bypassing pixel clock divider\n"); 6098c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, ATMEL_LCDC_BYPASS); 6108c2ecf20Sopenharmony_ci } else { 6118c2ecf20Sopenharmony_ci value = (value / pix_factor) - 1; 6128c2ecf20Sopenharmony_ci dev_dbg(info->device, " * programming CLKVAL = 0x%08lx\n", 6138c2ecf20Sopenharmony_ci value); 6148c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, 6158c2ecf20Sopenharmony_ci value << ATMEL_LCDC_CLKVAL_OFFSET); 6168c2ecf20Sopenharmony_ci info->var.pixclock = 6178c2ecf20Sopenharmony_ci KHZ2PICOS(clk_value_khz / (pix_factor * (value + 1))); 6188c2ecf20Sopenharmony_ci dev_dbg(info->device, " updated pixclk: %lu KHz\n", 6198c2ecf20Sopenharmony_ci PICOS2KHZ(info->var.pixclock)); 6208c2ecf20Sopenharmony_ci } 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci /* Initialize control register 2 */ 6248c2ecf20Sopenharmony_ci value = pdata->default_lcdcon2; 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) 6278c2ecf20Sopenharmony_ci value |= ATMEL_LCDC_INVLINE_INVERTED; 6288c2ecf20Sopenharmony_ci if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) 6298c2ecf20Sopenharmony_ci value |= ATMEL_LCDC_INVFRAME_INVERTED; 6308c2ecf20Sopenharmony_ci 6318c2ecf20Sopenharmony_ci switch (info->var.bits_per_pixel) { 6328c2ecf20Sopenharmony_ci case 1: value |= ATMEL_LCDC_PIXELSIZE_1; break; 6338c2ecf20Sopenharmony_ci case 2: value |= ATMEL_LCDC_PIXELSIZE_2; break; 6348c2ecf20Sopenharmony_ci case 4: value |= ATMEL_LCDC_PIXELSIZE_4; break; 6358c2ecf20Sopenharmony_ci case 8: value |= ATMEL_LCDC_PIXELSIZE_8; break; 6368c2ecf20Sopenharmony_ci case 15: fallthrough; 6378c2ecf20Sopenharmony_ci case 16: value |= ATMEL_LCDC_PIXELSIZE_16; break; 6388c2ecf20Sopenharmony_ci case 24: value |= ATMEL_LCDC_PIXELSIZE_24; break; 6398c2ecf20Sopenharmony_ci case 32: value |= ATMEL_LCDC_PIXELSIZE_32; break; 6408c2ecf20Sopenharmony_ci default: BUG(); break; 6418c2ecf20Sopenharmony_ci } 6428c2ecf20Sopenharmony_ci dev_dbg(info->device, " * LCDCON2 = %08lx\n", value); 6438c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_LCDCON2, value); 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_ci /* Vertical timing */ 6468c2ecf20Sopenharmony_ci value = (info->var.vsync_len - 1) << ATMEL_LCDC_VPW_OFFSET; 6478c2ecf20Sopenharmony_ci value |= info->var.upper_margin << ATMEL_LCDC_VBP_OFFSET; 6488c2ecf20Sopenharmony_ci value |= info->var.lower_margin; 6498c2ecf20Sopenharmony_ci dev_dbg(info->device, " * LCDTIM1 = %08lx\n", value); 6508c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_TIM1, value); 6518c2ecf20Sopenharmony_ci 6528c2ecf20Sopenharmony_ci /* Horizontal timing */ 6538c2ecf20Sopenharmony_ci value = (info->var.right_margin - 1) << ATMEL_LCDC_HFP_OFFSET; 6548c2ecf20Sopenharmony_ci value |= (info->var.hsync_len - 1) << ATMEL_LCDC_HPW_OFFSET; 6558c2ecf20Sopenharmony_ci value |= (info->var.left_margin - 1); 6568c2ecf20Sopenharmony_ci dev_dbg(info->device, " * LCDTIM2 = %08lx\n", value); 6578c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_TIM2, value); 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci /* Horizontal value (aka line size) */ 6608c2ecf20Sopenharmony_ci hozval_linesz = compute_hozval(sinfo, info->var.xres); 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_ci /* Display size */ 6638c2ecf20Sopenharmony_ci value = (hozval_linesz - 1) << ATMEL_LCDC_HOZVAL_OFFSET; 6648c2ecf20Sopenharmony_ci value |= info->var.yres - 1; 6658c2ecf20Sopenharmony_ci dev_dbg(info->device, " * LCDFRMCFG = %08lx\n", value); 6668c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_LCDFRMCFG, value); 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci /* FIFO Threshold: Use formula from data sheet */ 6698c2ecf20Sopenharmony_ci value = ATMEL_LCDC_FIFO_SIZE - (2 * ATMEL_LCDC_DMA_BURST_LEN + 3); 6708c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_FIFO, value); 6718c2ecf20Sopenharmony_ci 6728c2ecf20Sopenharmony_ci /* Toggle LCD_MODE every frame */ 6738c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_MVAL, 0); 6748c2ecf20Sopenharmony_ci 6758c2ecf20Sopenharmony_ci /* Disable all interrupts */ 6768c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0U); 6778c2ecf20Sopenharmony_ci /* Enable FIFO & DMA errors */ 6788c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_ci /* ...wait for DMA engine to become idle... */ 6818c2ecf20Sopenharmony_ci while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) 6828c2ecf20Sopenharmony_ci msleep(10); 6838c2ecf20Sopenharmony_ci 6848c2ecf20Sopenharmony_ci atmel_lcdfb_start(sinfo); 6858c2ecf20Sopenharmony_ci 6868c2ecf20Sopenharmony_ci dev_dbg(info->device, " * DONE\n"); 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_ci return 0; 6898c2ecf20Sopenharmony_ci} 6908c2ecf20Sopenharmony_ci 6918c2ecf20Sopenharmony_cistatic inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf) 6928c2ecf20Sopenharmony_ci{ 6938c2ecf20Sopenharmony_ci chan &= 0xffff; 6948c2ecf20Sopenharmony_ci chan >>= 16 - bf->length; 6958c2ecf20Sopenharmony_ci return chan << bf->offset; 6968c2ecf20Sopenharmony_ci} 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci/** 6998c2ecf20Sopenharmony_ci * atmel_lcdfb_setcolreg - Optional function. Sets a color register. 7008c2ecf20Sopenharmony_ci * @regno: Which register in the CLUT we are programming 7018c2ecf20Sopenharmony_ci * @red: The red value which can be up to 16 bits wide 7028c2ecf20Sopenharmony_ci * @green: The green value which can be up to 16 bits wide 7038c2ecf20Sopenharmony_ci * @blue: The blue value which can be up to 16 bits wide. 7048c2ecf20Sopenharmony_ci * @transp: If supported the alpha value which can be up to 16 bits wide. 7058c2ecf20Sopenharmony_ci * @info: frame buffer info structure 7068c2ecf20Sopenharmony_ci * 7078c2ecf20Sopenharmony_ci * Set a single color register. The values supplied have a 16 bit 7088c2ecf20Sopenharmony_ci * magnitude which needs to be scaled in this function for the hardware. 7098c2ecf20Sopenharmony_ci * Things to take into consideration are how many color registers, if 7108c2ecf20Sopenharmony_ci * any, are supported with the current color visual. With truecolor mode 7118c2ecf20Sopenharmony_ci * no color palettes are supported. Here a pseudo palette is created 7128c2ecf20Sopenharmony_ci * which we store the value in pseudo_palette in struct fb_info. For 7138c2ecf20Sopenharmony_ci * pseudocolor mode we have a limited color palette. To deal with this 7148c2ecf20Sopenharmony_ci * we can program what color is displayed for a particular pixel value. 7158c2ecf20Sopenharmony_ci * DirectColor is similar in that we can program each color field. If 7168c2ecf20Sopenharmony_ci * we have a static colormap we don't need to implement this function. 7178c2ecf20Sopenharmony_ci * 7188c2ecf20Sopenharmony_ci * Returns negative errno on error, or zero on success. In an 7198c2ecf20Sopenharmony_ci * ideal world, this would have been the case, but as it turns 7208c2ecf20Sopenharmony_ci * out, the other drivers return 1 on failure, so that's what 7218c2ecf20Sopenharmony_ci * we're going to do. 7228c2ecf20Sopenharmony_ci */ 7238c2ecf20Sopenharmony_cistatic int atmel_lcdfb_setcolreg(unsigned int regno, unsigned int red, 7248c2ecf20Sopenharmony_ci unsigned int green, unsigned int blue, 7258c2ecf20Sopenharmony_ci unsigned int transp, struct fb_info *info) 7268c2ecf20Sopenharmony_ci{ 7278c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 7288c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 7298c2ecf20Sopenharmony_ci unsigned int val; 7308c2ecf20Sopenharmony_ci u32 *pal; 7318c2ecf20Sopenharmony_ci int ret = 1; 7328c2ecf20Sopenharmony_ci 7338c2ecf20Sopenharmony_ci if (info->var.grayscale) 7348c2ecf20Sopenharmony_ci red = green = blue = (19595 * red + 38470 * green 7358c2ecf20Sopenharmony_ci + 7471 * blue) >> 16; 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_ci switch (info->fix.visual) { 7388c2ecf20Sopenharmony_ci case FB_VISUAL_TRUECOLOR: 7398c2ecf20Sopenharmony_ci if (regno < 16) { 7408c2ecf20Sopenharmony_ci pal = info->pseudo_palette; 7418c2ecf20Sopenharmony_ci 7428c2ecf20Sopenharmony_ci val = chan_to_field(red, &info->var.red); 7438c2ecf20Sopenharmony_ci val |= chan_to_field(green, &info->var.green); 7448c2ecf20Sopenharmony_ci val |= chan_to_field(blue, &info->var.blue); 7458c2ecf20Sopenharmony_ci 7468c2ecf20Sopenharmony_ci pal[regno] = val; 7478c2ecf20Sopenharmony_ci ret = 0; 7488c2ecf20Sopenharmony_ci } 7498c2ecf20Sopenharmony_ci break; 7508c2ecf20Sopenharmony_ci 7518c2ecf20Sopenharmony_ci case FB_VISUAL_PSEUDOCOLOR: 7528c2ecf20Sopenharmony_ci if (regno < 256) { 7538c2ecf20Sopenharmony_ci if (sinfo->config->have_intensity_bit) { 7548c2ecf20Sopenharmony_ci /* old style I+BGR:555 */ 7558c2ecf20Sopenharmony_ci val = ((red >> 11) & 0x001f); 7568c2ecf20Sopenharmony_ci val |= ((green >> 6) & 0x03e0); 7578c2ecf20Sopenharmony_ci val |= ((blue >> 1) & 0x7c00); 7588c2ecf20Sopenharmony_ci 7598c2ecf20Sopenharmony_ci /* 7608c2ecf20Sopenharmony_ci * TODO: intensity bit. Maybe something like 7618c2ecf20Sopenharmony_ci * ~(red[10] ^ green[10] ^ blue[10]) & 1 7628c2ecf20Sopenharmony_ci */ 7638c2ecf20Sopenharmony_ci } else { 7648c2ecf20Sopenharmony_ci /* new style BGR:565 / RGB:565 */ 7658c2ecf20Sopenharmony_ci if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { 7668c2ecf20Sopenharmony_ci val = ((blue >> 11) & 0x001f); 7678c2ecf20Sopenharmony_ci val |= ((red >> 0) & 0xf800); 7688c2ecf20Sopenharmony_ci } else { 7698c2ecf20Sopenharmony_ci val = ((red >> 11) & 0x001f); 7708c2ecf20Sopenharmony_ci val |= ((blue >> 0) & 0xf800); 7718c2ecf20Sopenharmony_ci } 7728c2ecf20Sopenharmony_ci 7738c2ecf20Sopenharmony_ci val |= ((green >> 5) & 0x07e0); 7748c2ecf20Sopenharmony_ci } 7758c2ecf20Sopenharmony_ci 7768c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val); 7778c2ecf20Sopenharmony_ci ret = 0; 7788c2ecf20Sopenharmony_ci } 7798c2ecf20Sopenharmony_ci break; 7808c2ecf20Sopenharmony_ci 7818c2ecf20Sopenharmony_ci case FB_VISUAL_MONO01: 7828c2ecf20Sopenharmony_ci if (regno < 2) { 7838c2ecf20Sopenharmony_ci val = (regno == 0) ? 0x00 : 0x1F; 7848c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val); 7858c2ecf20Sopenharmony_ci ret = 0; 7868c2ecf20Sopenharmony_ci } 7878c2ecf20Sopenharmony_ci break; 7888c2ecf20Sopenharmony_ci 7898c2ecf20Sopenharmony_ci } 7908c2ecf20Sopenharmony_ci 7918c2ecf20Sopenharmony_ci return ret; 7928c2ecf20Sopenharmony_ci} 7938c2ecf20Sopenharmony_ci 7948c2ecf20Sopenharmony_cistatic int atmel_lcdfb_pan_display(struct fb_var_screeninfo *var, 7958c2ecf20Sopenharmony_ci struct fb_info *info) 7968c2ecf20Sopenharmony_ci{ 7978c2ecf20Sopenharmony_ci dev_dbg(info->device, "%s\n", __func__); 7988c2ecf20Sopenharmony_ci 7998c2ecf20Sopenharmony_ci atmel_lcdfb_update_dma(info, var); 8008c2ecf20Sopenharmony_ci 8018c2ecf20Sopenharmony_ci return 0; 8028c2ecf20Sopenharmony_ci} 8038c2ecf20Sopenharmony_ci 8048c2ecf20Sopenharmony_cistatic int atmel_lcdfb_blank(int blank_mode, struct fb_info *info) 8058c2ecf20Sopenharmony_ci{ 8068c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_ci switch (blank_mode) { 8098c2ecf20Sopenharmony_ci case FB_BLANK_UNBLANK: 8108c2ecf20Sopenharmony_ci case FB_BLANK_NORMAL: 8118c2ecf20Sopenharmony_ci atmel_lcdfb_start(sinfo); 8128c2ecf20Sopenharmony_ci break; 8138c2ecf20Sopenharmony_ci case FB_BLANK_VSYNC_SUSPEND: 8148c2ecf20Sopenharmony_ci case FB_BLANK_HSYNC_SUSPEND: 8158c2ecf20Sopenharmony_ci break; 8168c2ecf20Sopenharmony_ci case FB_BLANK_POWERDOWN: 8178c2ecf20Sopenharmony_ci atmel_lcdfb_stop(sinfo); 8188c2ecf20Sopenharmony_ci break; 8198c2ecf20Sopenharmony_ci default: 8208c2ecf20Sopenharmony_ci return -EINVAL; 8218c2ecf20Sopenharmony_ci } 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_ci /* let fbcon do a soft blank for us */ 8248c2ecf20Sopenharmony_ci return ((blank_mode == FB_BLANK_NORMAL) ? 1 : 0); 8258c2ecf20Sopenharmony_ci} 8268c2ecf20Sopenharmony_ci 8278c2ecf20Sopenharmony_cistatic const struct fb_ops atmel_lcdfb_ops = { 8288c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 8298c2ecf20Sopenharmony_ci .fb_check_var = atmel_lcdfb_check_var, 8308c2ecf20Sopenharmony_ci .fb_set_par = atmel_lcdfb_set_par, 8318c2ecf20Sopenharmony_ci .fb_setcolreg = atmel_lcdfb_setcolreg, 8328c2ecf20Sopenharmony_ci .fb_blank = atmel_lcdfb_blank, 8338c2ecf20Sopenharmony_ci .fb_pan_display = atmel_lcdfb_pan_display, 8348c2ecf20Sopenharmony_ci .fb_fillrect = cfb_fillrect, 8358c2ecf20Sopenharmony_ci .fb_copyarea = cfb_copyarea, 8368c2ecf20Sopenharmony_ci .fb_imageblit = cfb_imageblit, 8378c2ecf20Sopenharmony_ci}; 8388c2ecf20Sopenharmony_ci 8398c2ecf20Sopenharmony_cistatic irqreturn_t atmel_lcdfb_interrupt(int irq, void *dev_id) 8408c2ecf20Sopenharmony_ci{ 8418c2ecf20Sopenharmony_ci struct fb_info *info = dev_id; 8428c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 8438c2ecf20Sopenharmony_ci u32 status; 8448c2ecf20Sopenharmony_ci 8458c2ecf20Sopenharmony_ci status = lcdc_readl(sinfo, ATMEL_LCDC_ISR); 8468c2ecf20Sopenharmony_ci if (status & ATMEL_LCDC_UFLWI) { 8478c2ecf20Sopenharmony_ci dev_warn(info->device, "FIFO underflow %#x\n", status); 8488c2ecf20Sopenharmony_ci /* reset DMA and FIFO to avoid screen shifting */ 8498c2ecf20Sopenharmony_ci schedule_work(&sinfo->task); 8508c2ecf20Sopenharmony_ci } 8518c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_ICR, status); 8528c2ecf20Sopenharmony_ci return IRQ_HANDLED; 8538c2ecf20Sopenharmony_ci} 8548c2ecf20Sopenharmony_ci 8558c2ecf20Sopenharmony_ci/* 8568c2ecf20Sopenharmony_ci * LCD controller task (to reset the LCD) 8578c2ecf20Sopenharmony_ci */ 8588c2ecf20Sopenharmony_cistatic void atmel_lcdfb_task(struct work_struct *work) 8598c2ecf20Sopenharmony_ci{ 8608c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = 8618c2ecf20Sopenharmony_ci container_of(work, struct atmel_lcdfb_info, task); 8628c2ecf20Sopenharmony_ci 8638c2ecf20Sopenharmony_ci atmel_lcdfb_reset(sinfo); 8648c2ecf20Sopenharmony_ci} 8658c2ecf20Sopenharmony_ci 8668c2ecf20Sopenharmony_cistatic int __init atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info *sinfo) 8678c2ecf20Sopenharmony_ci{ 8688c2ecf20Sopenharmony_ci struct fb_info *info = sinfo->info; 8698c2ecf20Sopenharmony_ci int ret = 0; 8708c2ecf20Sopenharmony_ci 8718c2ecf20Sopenharmony_ci info->var.activate |= FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW; 8728c2ecf20Sopenharmony_ci 8738c2ecf20Sopenharmony_ci dev_info(info->device, 8748c2ecf20Sopenharmony_ci "%luKiB frame buffer at %08lx (mapped at %p)\n", 8758c2ecf20Sopenharmony_ci (unsigned long)info->fix.smem_len / 1024, 8768c2ecf20Sopenharmony_ci (unsigned long)info->fix.smem_start, 8778c2ecf20Sopenharmony_ci info->screen_base); 8788c2ecf20Sopenharmony_ci 8798c2ecf20Sopenharmony_ci /* Allocate colormap */ 8808c2ecf20Sopenharmony_ci ret = fb_alloc_cmap(&info->cmap, 256, 0); 8818c2ecf20Sopenharmony_ci if (ret < 0) 8828c2ecf20Sopenharmony_ci dev_err(info->device, "Alloc color map failed\n"); 8838c2ecf20Sopenharmony_ci 8848c2ecf20Sopenharmony_ci return ret; 8858c2ecf20Sopenharmony_ci} 8868c2ecf20Sopenharmony_ci 8878c2ecf20Sopenharmony_cistatic void atmel_lcdfb_start_clock(struct atmel_lcdfb_info *sinfo) 8888c2ecf20Sopenharmony_ci{ 8898c2ecf20Sopenharmony_ci clk_prepare_enable(sinfo->bus_clk); 8908c2ecf20Sopenharmony_ci clk_prepare_enable(sinfo->lcdc_clk); 8918c2ecf20Sopenharmony_ci} 8928c2ecf20Sopenharmony_ci 8938c2ecf20Sopenharmony_cistatic void atmel_lcdfb_stop_clock(struct atmel_lcdfb_info *sinfo) 8948c2ecf20Sopenharmony_ci{ 8958c2ecf20Sopenharmony_ci clk_disable_unprepare(sinfo->bus_clk); 8968c2ecf20Sopenharmony_ci clk_disable_unprepare(sinfo->lcdc_clk); 8978c2ecf20Sopenharmony_ci} 8988c2ecf20Sopenharmony_ci 8998c2ecf20Sopenharmony_cistatic const struct of_device_id atmel_lcdfb_dt_ids[] = { 9008c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9261-lcdc" , .data = &at91sam9261_config, }, 9018c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9263-lcdc" , .data = &at91sam9263_config, }, 9028c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9g10-lcdc" , .data = &at91sam9g10_config, }, 9038c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9g45-lcdc" , .data = &at91sam9g45_config, }, 9048c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9g45es-lcdc" , .data = &at91sam9g45es_config, }, 9058c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9rl-lcdc" , .data = &at91sam9rl_config, }, 9068c2ecf20Sopenharmony_ci { /* sentinel */ } 9078c2ecf20Sopenharmony_ci}; 9088c2ecf20Sopenharmony_ci 9098c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, atmel_lcdfb_dt_ids); 9108c2ecf20Sopenharmony_ci 9118c2ecf20Sopenharmony_cistatic const char *atmel_lcdfb_wiring_modes[] = { 9128c2ecf20Sopenharmony_ci [ATMEL_LCDC_WIRING_BGR] = "BRG", 9138c2ecf20Sopenharmony_ci [ATMEL_LCDC_WIRING_RGB] = "RGB", 9148c2ecf20Sopenharmony_ci}; 9158c2ecf20Sopenharmony_ci 9168c2ecf20Sopenharmony_cistatic int atmel_lcdfb_get_of_wiring_modes(struct device_node *np) 9178c2ecf20Sopenharmony_ci{ 9188c2ecf20Sopenharmony_ci const char *mode; 9198c2ecf20Sopenharmony_ci int err, i; 9208c2ecf20Sopenharmony_ci 9218c2ecf20Sopenharmony_ci err = of_property_read_string(np, "atmel,lcd-wiring-mode", &mode); 9228c2ecf20Sopenharmony_ci if (err < 0) 9238c2ecf20Sopenharmony_ci return ATMEL_LCDC_WIRING_BGR; 9248c2ecf20Sopenharmony_ci 9258c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(atmel_lcdfb_wiring_modes); i++) 9268c2ecf20Sopenharmony_ci if (!strcasecmp(mode, atmel_lcdfb_wiring_modes[i])) 9278c2ecf20Sopenharmony_ci return i; 9288c2ecf20Sopenharmony_ci 9298c2ecf20Sopenharmony_ci return -ENODEV; 9308c2ecf20Sopenharmony_ci} 9318c2ecf20Sopenharmony_ci 9328c2ecf20Sopenharmony_cistatic void atmel_lcdfb_power_control_gpio(struct atmel_lcdfb_pdata *pdata, int on) 9338c2ecf20Sopenharmony_ci{ 9348c2ecf20Sopenharmony_ci struct atmel_lcdfb_power_ctrl_gpio *og; 9358c2ecf20Sopenharmony_ci 9368c2ecf20Sopenharmony_ci list_for_each_entry(og, &pdata->pwr_gpios, list) 9378c2ecf20Sopenharmony_ci gpiod_set_value(og->gpiod, on); 9388c2ecf20Sopenharmony_ci} 9398c2ecf20Sopenharmony_ci 9408c2ecf20Sopenharmony_cistatic int atmel_lcdfb_of_init(struct atmel_lcdfb_info *sinfo) 9418c2ecf20Sopenharmony_ci{ 9428c2ecf20Sopenharmony_ci struct fb_info *info = sinfo->info; 9438c2ecf20Sopenharmony_ci struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; 9448c2ecf20Sopenharmony_ci struct fb_var_screeninfo *var = &info->var; 9458c2ecf20Sopenharmony_ci struct device *dev = &sinfo->pdev->dev; 9468c2ecf20Sopenharmony_ci struct device_node *np =dev->of_node; 9478c2ecf20Sopenharmony_ci struct device_node *display_np; 9488c2ecf20Sopenharmony_ci struct atmel_lcdfb_power_ctrl_gpio *og; 9498c2ecf20Sopenharmony_ci bool is_gpio_power = false; 9508c2ecf20Sopenharmony_ci struct fb_videomode fb_vm; 9518c2ecf20Sopenharmony_ci struct gpio_desc *gpiod; 9528c2ecf20Sopenharmony_ci struct videomode vm; 9538c2ecf20Sopenharmony_ci int ret; 9548c2ecf20Sopenharmony_ci int i; 9558c2ecf20Sopenharmony_ci 9568c2ecf20Sopenharmony_ci sinfo->config = (struct atmel_lcdfb_config*) 9578c2ecf20Sopenharmony_ci of_match_device(atmel_lcdfb_dt_ids, dev)->data; 9588c2ecf20Sopenharmony_ci 9598c2ecf20Sopenharmony_ci display_np = of_parse_phandle(np, "display", 0); 9608c2ecf20Sopenharmony_ci if (!display_np) { 9618c2ecf20Sopenharmony_ci dev_err(dev, "failed to find display phandle\n"); 9628c2ecf20Sopenharmony_ci return -ENOENT; 9638c2ecf20Sopenharmony_ci } 9648c2ecf20Sopenharmony_ci 9658c2ecf20Sopenharmony_ci ret = of_property_read_u32(display_np, "bits-per-pixel", &var->bits_per_pixel); 9668c2ecf20Sopenharmony_ci if (ret < 0) { 9678c2ecf20Sopenharmony_ci dev_err(dev, "failed to get property bits-per-pixel\n"); 9688c2ecf20Sopenharmony_ci goto put_display_node; 9698c2ecf20Sopenharmony_ci } 9708c2ecf20Sopenharmony_ci 9718c2ecf20Sopenharmony_ci ret = of_property_read_u32(display_np, "atmel,guard-time", &pdata->guard_time); 9728c2ecf20Sopenharmony_ci if (ret < 0) { 9738c2ecf20Sopenharmony_ci dev_err(dev, "failed to get property atmel,guard-time\n"); 9748c2ecf20Sopenharmony_ci goto put_display_node; 9758c2ecf20Sopenharmony_ci } 9768c2ecf20Sopenharmony_ci 9778c2ecf20Sopenharmony_ci ret = of_property_read_u32(display_np, "atmel,lcdcon2", &pdata->default_lcdcon2); 9788c2ecf20Sopenharmony_ci if (ret < 0) { 9798c2ecf20Sopenharmony_ci dev_err(dev, "failed to get property atmel,lcdcon2\n"); 9808c2ecf20Sopenharmony_ci goto put_display_node; 9818c2ecf20Sopenharmony_ci } 9828c2ecf20Sopenharmony_ci 9838c2ecf20Sopenharmony_ci ret = of_property_read_u32(display_np, "atmel,dmacon", &pdata->default_dmacon); 9848c2ecf20Sopenharmony_ci if (ret < 0) { 9858c2ecf20Sopenharmony_ci dev_err(dev, "failed to get property bits-per-pixel\n"); 9868c2ecf20Sopenharmony_ci goto put_display_node; 9878c2ecf20Sopenharmony_ci } 9888c2ecf20Sopenharmony_ci 9898c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&pdata->pwr_gpios); 9908c2ecf20Sopenharmony_ci for (i = 0; i < gpiod_count(dev, "atmel,power-control"); i++) { 9918c2ecf20Sopenharmony_ci ret = -ENOMEM; 9928c2ecf20Sopenharmony_ci gpiod = devm_gpiod_get_index(dev, "atmel,power-control", 9938c2ecf20Sopenharmony_ci i, GPIOD_ASIS); 9948c2ecf20Sopenharmony_ci if (IS_ERR(gpiod)) 9958c2ecf20Sopenharmony_ci continue; 9968c2ecf20Sopenharmony_ci 9978c2ecf20Sopenharmony_ci og = devm_kzalloc(dev, sizeof(*og), GFP_KERNEL); 9988c2ecf20Sopenharmony_ci if (!og) 9998c2ecf20Sopenharmony_ci goto put_display_node; 10008c2ecf20Sopenharmony_ci 10018c2ecf20Sopenharmony_ci og->gpiod = gpiod; 10028c2ecf20Sopenharmony_ci is_gpio_power = true; 10038c2ecf20Sopenharmony_ci 10048c2ecf20Sopenharmony_ci ret = gpiod_direction_output(gpiod, gpiod_is_active_low(gpiod)); 10058c2ecf20Sopenharmony_ci if (ret) { 10068c2ecf20Sopenharmony_ci dev_err(dev, "set direction output gpio atmel,power-control[%d] failed\n", i); 10078c2ecf20Sopenharmony_ci goto put_display_node; 10088c2ecf20Sopenharmony_ci } 10098c2ecf20Sopenharmony_ci list_add(&og->list, &pdata->pwr_gpios); 10108c2ecf20Sopenharmony_ci } 10118c2ecf20Sopenharmony_ci 10128c2ecf20Sopenharmony_ci if (is_gpio_power) 10138c2ecf20Sopenharmony_ci pdata->atmel_lcdfb_power_control = atmel_lcdfb_power_control_gpio; 10148c2ecf20Sopenharmony_ci 10158c2ecf20Sopenharmony_ci ret = atmel_lcdfb_get_of_wiring_modes(display_np); 10168c2ecf20Sopenharmony_ci if (ret < 0) { 10178c2ecf20Sopenharmony_ci dev_err(dev, "invalid atmel,lcd-wiring-mode\n"); 10188c2ecf20Sopenharmony_ci goto put_display_node; 10198c2ecf20Sopenharmony_ci } 10208c2ecf20Sopenharmony_ci pdata->lcd_wiring_mode = ret; 10218c2ecf20Sopenharmony_ci 10228c2ecf20Sopenharmony_ci pdata->lcdcon_is_backlight = of_property_read_bool(display_np, "atmel,lcdcon-backlight"); 10238c2ecf20Sopenharmony_ci pdata->lcdcon_pol_negative = of_property_read_bool(display_np, "atmel,lcdcon-backlight-inverted"); 10248c2ecf20Sopenharmony_ci 10258c2ecf20Sopenharmony_ci ret = of_get_videomode(display_np, &vm, OF_USE_NATIVE_MODE); 10268c2ecf20Sopenharmony_ci if (ret) { 10278c2ecf20Sopenharmony_ci dev_err(dev, "failed to get videomode from DT\n"); 10288c2ecf20Sopenharmony_ci goto put_display_node; 10298c2ecf20Sopenharmony_ci } 10308c2ecf20Sopenharmony_ci 10318c2ecf20Sopenharmony_ci ret = fb_videomode_from_videomode(&vm, &fb_vm); 10328c2ecf20Sopenharmony_ci if (ret < 0) 10338c2ecf20Sopenharmony_ci goto put_display_node; 10348c2ecf20Sopenharmony_ci 10358c2ecf20Sopenharmony_ci fb_add_videomode(&fb_vm, &info->modelist); 10368c2ecf20Sopenharmony_ci 10378c2ecf20Sopenharmony_ciput_display_node: 10388c2ecf20Sopenharmony_ci of_node_put(display_np); 10398c2ecf20Sopenharmony_ci return ret; 10408c2ecf20Sopenharmony_ci} 10418c2ecf20Sopenharmony_ci 10428c2ecf20Sopenharmony_cistatic int __init atmel_lcdfb_probe(struct platform_device *pdev) 10438c2ecf20Sopenharmony_ci{ 10448c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 10458c2ecf20Sopenharmony_ci struct fb_info *info; 10468c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo; 10478c2ecf20Sopenharmony_ci struct resource *regs = NULL; 10488c2ecf20Sopenharmony_ci struct resource *map = NULL; 10498c2ecf20Sopenharmony_ci struct fb_modelist *modelist; 10508c2ecf20Sopenharmony_ci int ret; 10518c2ecf20Sopenharmony_ci 10528c2ecf20Sopenharmony_ci dev_dbg(dev, "%s BEGIN\n", __func__); 10538c2ecf20Sopenharmony_ci 10548c2ecf20Sopenharmony_ci ret = -ENOMEM; 10558c2ecf20Sopenharmony_ci info = framebuffer_alloc(sizeof(struct atmel_lcdfb_info), dev); 10568c2ecf20Sopenharmony_ci if (!info) 10578c2ecf20Sopenharmony_ci goto out; 10588c2ecf20Sopenharmony_ci 10598c2ecf20Sopenharmony_ci sinfo = info->par; 10608c2ecf20Sopenharmony_ci sinfo->pdev = pdev; 10618c2ecf20Sopenharmony_ci sinfo->info = info; 10628c2ecf20Sopenharmony_ci 10638c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&info->modelist); 10648c2ecf20Sopenharmony_ci 10658c2ecf20Sopenharmony_ci if (!pdev->dev.of_node) { 10668c2ecf20Sopenharmony_ci dev_err(dev, "cannot get default configuration\n"); 10678c2ecf20Sopenharmony_ci goto free_info; 10688c2ecf20Sopenharmony_ci } 10698c2ecf20Sopenharmony_ci 10708c2ecf20Sopenharmony_ci ret = atmel_lcdfb_of_init(sinfo); 10718c2ecf20Sopenharmony_ci if (ret) 10728c2ecf20Sopenharmony_ci goto free_info; 10738c2ecf20Sopenharmony_ci 10748c2ecf20Sopenharmony_ci ret = -ENODEV; 10758c2ecf20Sopenharmony_ci if (!sinfo->config) 10768c2ecf20Sopenharmony_ci goto free_info; 10778c2ecf20Sopenharmony_ci 10788c2ecf20Sopenharmony_ci sinfo->reg_lcd = devm_regulator_get(&pdev->dev, "lcd"); 10798c2ecf20Sopenharmony_ci if (IS_ERR(sinfo->reg_lcd)) 10808c2ecf20Sopenharmony_ci sinfo->reg_lcd = NULL; 10818c2ecf20Sopenharmony_ci 10828c2ecf20Sopenharmony_ci info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | 10838c2ecf20Sopenharmony_ci FBINFO_HWACCEL_YPAN; 10848c2ecf20Sopenharmony_ci info->pseudo_palette = sinfo->pseudo_palette; 10858c2ecf20Sopenharmony_ci info->fbops = &atmel_lcdfb_ops; 10868c2ecf20Sopenharmony_ci 10878c2ecf20Sopenharmony_ci info->fix = atmel_lcdfb_fix; 10888c2ecf20Sopenharmony_ci strcpy(info->fix.id, sinfo->pdev->name); 10898c2ecf20Sopenharmony_ci 10908c2ecf20Sopenharmony_ci /* Enable LCDC Clocks */ 10918c2ecf20Sopenharmony_ci sinfo->bus_clk = clk_get(dev, "hclk"); 10928c2ecf20Sopenharmony_ci if (IS_ERR(sinfo->bus_clk)) { 10938c2ecf20Sopenharmony_ci ret = PTR_ERR(sinfo->bus_clk); 10948c2ecf20Sopenharmony_ci goto free_info; 10958c2ecf20Sopenharmony_ci } 10968c2ecf20Sopenharmony_ci sinfo->lcdc_clk = clk_get(dev, "lcdc_clk"); 10978c2ecf20Sopenharmony_ci if (IS_ERR(sinfo->lcdc_clk)) { 10988c2ecf20Sopenharmony_ci ret = PTR_ERR(sinfo->lcdc_clk); 10998c2ecf20Sopenharmony_ci goto put_bus_clk; 11008c2ecf20Sopenharmony_ci } 11018c2ecf20Sopenharmony_ci atmel_lcdfb_start_clock(sinfo); 11028c2ecf20Sopenharmony_ci 11038c2ecf20Sopenharmony_ci modelist = list_first_entry(&info->modelist, 11048c2ecf20Sopenharmony_ci struct fb_modelist, list); 11058c2ecf20Sopenharmony_ci fb_videomode_to_var(&info->var, &modelist->mode); 11068c2ecf20Sopenharmony_ci 11078c2ecf20Sopenharmony_ci atmel_lcdfb_check_var(&info->var, info); 11088c2ecf20Sopenharmony_ci 11098c2ecf20Sopenharmony_ci regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); 11108c2ecf20Sopenharmony_ci if (!regs) { 11118c2ecf20Sopenharmony_ci dev_err(dev, "resources unusable\n"); 11128c2ecf20Sopenharmony_ci ret = -ENXIO; 11138c2ecf20Sopenharmony_ci goto stop_clk; 11148c2ecf20Sopenharmony_ci } 11158c2ecf20Sopenharmony_ci 11168c2ecf20Sopenharmony_ci sinfo->irq_base = platform_get_irq(pdev, 0); 11178c2ecf20Sopenharmony_ci if (sinfo->irq_base < 0) { 11188c2ecf20Sopenharmony_ci ret = sinfo->irq_base; 11198c2ecf20Sopenharmony_ci goto stop_clk; 11208c2ecf20Sopenharmony_ci } 11218c2ecf20Sopenharmony_ci 11228c2ecf20Sopenharmony_ci /* Initialize video memory */ 11238c2ecf20Sopenharmony_ci map = platform_get_resource(pdev, IORESOURCE_MEM, 1); 11248c2ecf20Sopenharmony_ci if (map) { 11258c2ecf20Sopenharmony_ci /* use a pre-allocated memory buffer */ 11268c2ecf20Sopenharmony_ci info->fix.smem_start = map->start; 11278c2ecf20Sopenharmony_ci info->fix.smem_len = resource_size(map); 11288c2ecf20Sopenharmony_ci if (!request_mem_region(info->fix.smem_start, 11298c2ecf20Sopenharmony_ci info->fix.smem_len, pdev->name)) { 11308c2ecf20Sopenharmony_ci ret = -EBUSY; 11318c2ecf20Sopenharmony_ci goto stop_clk; 11328c2ecf20Sopenharmony_ci } 11338c2ecf20Sopenharmony_ci 11348c2ecf20Sopenharmony_ci info->screen_base = ioremap_wc(info->fix.smem_start, 11358c2ecf20Sopenharmony_ci info->fix.smem_len); 11368c2ecf20Sopenharmony_ci if (!info->screen_base) { 11378c2ecf20Sopenharmony_ci ret = -ENOMEM; 11388c2ecf20Sopenharmony_ci goto release_intmem; 11398c2ecf20Sopenharmony_ci } 11408c2ecf20Sopenharmony_ci 11418c2ecf20Sopenharmony_ci /* 11428c2ecf20Sopenharmony_ci * Don't clear the framebuffer -- someone may have set 11438c2ecf20Sopenharmony_ci * up a splash image. 11448c2ecf20Sopenharmony_ci */ 11458c2ecf20Sopenharmony_ci } else { 11468c2ecf20Sopenharmony_ci /* allocate memory buffer */ 11478c2ecf20Sopenharmony_ci ret = atmel_lcdfb_alloc_video_memory(sinfo); 11488c2ecf20Sopenharmony_ci if (ret < 0) { 11498c2ecf20Sopenharmony_ci dev_err(dev, "cannot allocate framebuffer: %d\n", ret); 11508c2ecf20Sopenharmony_ci goto stop_clk; 11518c2ecf20Sopenharmony_ci } 11528c2ecf20Sopenharmony_ci } 11538c2ecf20Sopenharmony_ci 11548c2ecf20Sopenharmony_ci /* LCDC registers */ 11558c2ecf20Sopenharmony_ci info->fix.mmio_start = regs->start; 11568c2ecf20Sopenharmony_ci info->fix.mmio_len = resource_size(regs); 11578c2ecf20Sopenharmony_ci 11588c2ecf20Sopenharmony_ci if (!request_mem_region(info->fix.mmio_start, 11598c2ecf20Sopenharmony_ci info->fix.mmio_len, pdev->name)) { 11608c2ecf20Sopenharmony_ci ret = -EBUSY; 11618c2ecf20Sopenharmony_ci goto free_fb; 11628c2ecf20Sopenharmony_ci } 11638c2ecf20Sopenharmony_ci 11648c2ecf20Sopenharmony_ci sinfo->mmio = ioremap(info->fix.mmio_start, info->fix.mmio_len); 11658c2ecf20Sopenharmony_ci if (!sinfo->mmio) { 11668c2ecf20Sopenharmony_ci dev_err(dev, "cannot map LCDC registers\n"); 11678c2ecf20Sopenharmony_ci ret = -ENOMEM; 11688c2ecf20Sopenharmony_ci goto release_mem; 11698c2ecf20Sopenharmony_ci } 11708c2ecf20Sopenharmony_ci 11718c2ecf20Sopenharmony_ci /* Initialize PWM for contrast or backlight ("off") */ 11728c2ecf20Sopenharmony_ci init_contrast(sinfo); 11738c2ecf20Sopenharmony_ci 11748c2ecf20Sopenharmony_ci /* interrupt */ 11758c2ecf20Sopenharmony_ci ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info); 11768c2ecf20Sopenharmony_ci if (ret) { 11778c2ecf20Sopenharmony_ci dev_err(dev, "request_irq failed: %d\n", ret); 11788c2ecf20Sopenharmony_ci goto unmap_mmio; 11798c2ecf20Sopenharmony_ci } 11808c2ecf20Sopenharmony_ci 11818c2ecf20Sopenharmony_ci /* Some operations on the LCDC might sleep and 11828c2ecf20Sopenharmony_ci * require a preemptible task context */ 11838c2ecf20Sopenharmony_ci INIT_WORK(&sinfo->task, atmel_lcdfb_task); 11848c2ecf20Sopenharmony_ci 11858c2ecf20Sopenharmony_ci ret = atmel_lcdfb_init_fbinfo(sinfo); 11868c2ecf20Sopenharmony_ci if (ret < 0) { 11878c2ecf20Sopenharmony_ci dev_err(dev, "init fbinfo failed: %d\n", ret); 11888c2ecf20Sopenharmony_ci goto unregister_irqs; 11898c2ecf20Sopenharmony_ci } 11908c2ecf20Sopenharmony_ci 11918c2ecf20Sopenharmony_ci ret = atmel_lcdfb_set_par(info); 11928c2ecf20Sopenharmony_ci if (ret < 0) { 11938c2ecf20Sopenharmony_ci dev_err(dev, "set par failed: %d\n", ret); 11948c2ecf20Sopenharmony_ci goto unregister_irqs; 11958c2ecf20Sopenharmony_ci } 11968c2ecf20Sopenharmony_ci 11978c2ecf20Sopenharmony_ci dev_set_drvdata(dev, info); 11988c2ecf20Sopenharmony_ci 11998c2ecf20Sopenharmony_ci /* 12008c2ecf20Sopenharmony_ci * Tell the world that we're ready to go 12018c2ecf20Sopenharmony_ci */ 12028c2ecf20Sopenharmony_ci ret = register_framebuffer(info); 12038c2ecf20Sopenharmony_ci if (ret < 0) { 12048c2ecf20Sopenharmony_ci dev_err(dev, "failed to register framebuffer device: %d\n", ret); 12058c2ecf20Sopenharmony_ci goto reset_drvdata; 12068c2ecf20Sopenharmony_ci } 12078c2ecf20Sopenharmony_ci 12088c2ecf20Sopenharmony_ci /* Power up the LCDC screen */ 12098c2ecf20Sopenharmony_ci atmel_lcdfb_power_control(sinfo, 1); 12108c2ecf20Sopenharmony_ci 12118c2ecf20Sopenharmony_ci dev_info(dev, "fb%d: Atmel LCDC at 0x%08lx (mapped at %p), irq %d\n", 12128c2ecf20Sopenharmony_ci info->node, info->fix.mmio_start, sinfo->mmio, sinfo->irq_base); 12138c2ecf20Sopenharmony_ci 12148c2ecf20Sopenharmony_ci return 0; 12158c2ecf20Sopenharmony_ci 12168c2ecf20Sopenharmony_cireset_drvdata: 12178c2ecf20Sopenharmony_ci dev_set_drvdata(dev, NULL); 12188c2ecf20Sopenharmony_ci fb_dealloc_cmap(&info->cmap); 12198c2ecf20Sopenharmony_ciunregister_irqs: 12208c2ecf20Sopenharmony_ci cancel_work_sync(&sinfo->task); 12218c2ecf20Sopenharmony_ci free_irq(sinfo->irq_base, info); 12228c2ecf20Sopenharmony_ciunmap_mmio: 12238c2ecf20Sopenharmony_ci exit_backlight(sinfo); 12248c2ecf20Sopenharmony_ci iounmap(sinfo->mmio); 12258c2ecf20Sopenharmony_cirelease_mem: 12268c2ecf20Sopenharmony_ci release_mem_region(info->fix.mmio_start, info->fix.mmio_len); 12278c2ecf20Sopenharmony_cifree_fb: 12288c2ecf20Sopenharmony_ci if (map) 12298c2ecf20Sopenharmony_ci iounmap(info->screen_base); 12308c2ecf20Sopenharmony_ci else 12318c2ecf20Sopenharmony_ci atmel_lcdfb_free_video_memory(sinfo); 12328c2ecf20Sopenharmony_ci 12338c2ecf20Sopenharmony_cirelease_intmem: 12348c2ecf20Sopenharmony_ci if (map) 12358c2ecf20Sopenharmony_ci release_mem_region(info->fix.smem_start, info->fix.smem_len); 12368c2ecf20Sopenharmony_cistop_clk: 12378c2ecf20Sopenharmony_ci atmel_lcdfb_stop_clock(sinfo); 12388c2ecf20Sopenharmony_ci clk_put(sinfo->lcdc_clk); 12398c2ecf20Sopenharmony_ciput_bus_clk: 12408c2ecf20Sopenharmony_ci clk_put(sinfo->bus_clk); 12418c2ecf20Sopenharmony_cifree_info: 12428c2ecf20Sopenharmony_ci framebuffer_release(info); 12438c2ecf20Sopenharmony_ciout: 12448c2ecf20Sopenharmony_ci dev_dbg(dev, "%s FAILED\n", __func__); 12458c2ecf20Sopenharmony_ci return ret; 12468c2ecf20Sopenharmony_ci} 12478c2ecf20Sopenharmony_ci 12488c2ecf20Sopenharmony_cistatic int __exit atmel_lcdfb_remove(struct platform_device *pdev) 12498c2ecf20Sopenharmony_ci{ 12508c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 12518c2ecf20Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 12528c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo; 12538c2ecf20Sopenharmony_ci 12548c2ecf20Sopenharmony_ci if (!info || !info->par) 12558c2ecf20Sopenharmony_ci return 0; 12568c2ecf20Sopenharmony_ci sinfo = info->par; 12578c2ecf20Sopenharmony_ci 12588c2ecf20Sopenharmony_ci cancel_work_sync(&sinfo->task); 12598c2ecf20Sopenharmony_ci exit_backlight(sinfo); 12608c2ecf20Sopenharmony_ci atmel_lcdfb_power_control(sinfo, 0); 12618c2ecf20Sopenharmony_ci unregister_framebuffer(info); 12628c2ecf20Sopenharmony_ci atmel_lcdfb_stop_clock(sinfo); 12638c2ecf20Sopenharmony_ci clk_put(sinfo->lcdc_clk); 12648c2ecf20Sopenharmony_ci clk_put(sinfo->bus_clk); 12658c2ecf20Sopenharmony_ci fb_dealloc_cmap(&info->cmap); 12668c2ecf20Sopenharmony_ci free_irq(sinfo->irq_base, info); 12678c2ecf20Sopenharmony_ci iounmap(sinfo->mmio); 12688c2ecf20Sopenharmony_ci release_mem_region(info->fix.mmio_start, info->fix.mmio_len); 12698c2ecf20Sopenharmony_ci if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) { 12708c2ecf20Sopenharmony_ci iounmap(info->screen_base); 12718c2ecf20Sopenharmony_ci release_mem_region(info->fix.smem_start, info->fix.smem_len); 12728c2ecf20Sopenharmony_ci } else { 12738c2ecf20Sopenharmony_ci atmel_lcdfb_free_video_memory(sinfo); 12748c2ecf20Sopenharmony_ci } 12758c2ecf20Sopenharmony_ci 12768c2ecf20Sopenharmony_ci framebuffer_release(info); 12778c2ecf20Sopenharmony_ci 12788c2ecf20Sopenharmony_ci return 0; 12798c2ecf20Sopenharmony_ci} 12808c2ecf20Sopenharmony_ci 12818c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 12828c2ecf20Sopenharmony_ci 12838c2ecf20Sopenharmony_cistatic int atmel_lcdfb_suspend(struct platform_device *pdev, pm_message_t mesg) 12848c2ecf20Sopenharmony_ci{ 12858c2ecf20Sopenharmony_ci struct fb_info *info = platform_get_drvdata(pdev); 12868c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 12878c2ecf20Sopenharmony_ci 12888c2ecf20Sopenharmony_ci /* 12898c2ecf20Sopenharmony_ci * We don't want to handle interrupts while the clock is 12908c2ecf20Sopenharmony_ci * stopped. It may take forever. 12918c2ecf20Sopenharmony_ci */ 12928c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0U); 12938c2ecf20Sopenharmony_ci 12948c2ecf20Sopenharmony_ci sinfo->saved_lcdcon = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_CTR); 12958c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, 0); 12968c2ecf20Sopenharmony_ci atmel_lcdfb_power_control(sinfo, 0); 12978c2ecf20Sopenharmony_ci atmel_lcdfb_stop(sinfo); 12988c2ecf20Sopenharmony_ci atmel_lcdfb_stop_clock(sinfo); 12998c2ecf20Sopenharmony_ci 13008c2ecf20Sopenharmony_ci return 0; 13018c2ecf20Sopenharmony_ci} 13028c2ecf20Sopenharmony_ci 13038c2ecf20Sopenharmony_cistatic int atmel_lcdfb_resume(struct platform_device *pdev) 13048c2ecf20Sopenharmony_ci{ 13058c2ecf20Sopenharmony_ci struct fb_info *info = platform_get_drvdata(pdev); 13068c2ecf20Sopenharmony_ci struct atmel_lcdfb_info *sinfo = info->par; 13078c2ecf20Sopenharmony_ci 13088c2ecf20Sopenharmony_ci atmel_lcdfb_start_clock(sinfo); 13098c2ecf20Sopenharmony_ci atmel_lcdfb_start(sinfo); 13108c2ecf20Sopenharmony_ci atmel_lcdfb_power_control(sinfo, 1); 13118c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, sinfo->saved_lcdcon); 13128c2ecf20Sopenharmony_ci 13138c2ecf20Sopenharmony_ci /* Enable FIFO & DMA errors */ 13148c2ecf20Sopenharmony_ci lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI 13158c2ecf20Sopenharmony_ci | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); 13168c2ecf20Sopenharmony_ci 13178c2ecf20Sopenharmony_ci return 0; 13188c2ecf20Sopenharmony_ci} 13198c2ecf20Sopenharmony_ci 13208c2ecf20Sopenharmony_ci#else 13218c2ecf20Sopenharmony_ci#define atmel_lcdfb_suspend NULL 13228c2ecf20Sopenharmony_ci#define atmel_lcdfb_resume NULL 13238c2ecf20Sopenharmony_ci#endif 13248c2ecf20Sopenharmony_ci 13258c2ecf20Sopenharmony_cistatic struct platform_driver atmel_lcdfb_driver = { 13268c2ecf20Sopenharmony_ci .remove = __exit_p(atmel_lcdfb_remove), 13278c2ecf20Sopenharmony_ci .suspend = atmel_lcdfb_suspend, 13288c2ecf20Sopenharmony_ci .resume = atmel_lcdfb_resume, 13298c2ecf20Sopenharmony_ci .driver = { 13308c2ecf20Sopenharmony_ci .name = "atmel_lcdfb", 13318c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(atmel_lcdfb_dt_ids), 13328c2ecf20Sopenharmony_ci }, 13338c2ecf20Sopenharmony_ci}; 13348c2ecf20Sopenharmony_ci 13358c2ecf20Sopenharmony_cimodule_platform_driver_probe(atmel_lcdfb_driver, atmel_lcdfb_probe); 13368c2ecf20Sopenharmony_ci 13378c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("AT91 LCD Controller framebuffer driver"); 13388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>"); 13398c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 1340