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